Exploiting JWT - Lack of Signature Verification

Exploiting JWT - Lack of Signature Verification

TL;DR
Here goes the short PoC -

  • WebApp using JWT for authentication.
  • Removed the signature - Signature is not being verified - Token still works.
  • Modified and Re-encoded payload to get an Account takeover.
    PS: Header was untouched - "alg": "HS256"

A small yet descriptive Introduction

JSON Web Tokens are one of the most in-trend authentication mechanisms that are currently being used in modern applications to protect their data and maintain a tight access control over their apps and API endpoints.

The basic structure of a JSON Web Token can be explained really well by https://jwt.io/

JWT

As it can be seen in the above image, the left side consist of an encoded JWT i.e.,

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

These tokens follow a format, that is, all of the token can be divided into three parts separated by a . (dot) and each part is encoded separately using Base64.

  1. The first part is called the Header - eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
  2. The middle part is the Payload or the actual data that is encrypted/signed- eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ
  3. and the last part is the most important part which verifies the token, i.e., the Signature - SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

The signature of a JSON Web token is calculated by

  • Base64Url encoding the Header
  • Base64Url encoding the Payload
  • Concatenating them using . and
  • Signing it using the alg specified in the Header using a Secret

In the above-mentioned token, this can be achieved using -

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)

The Base64-decoded data can be seen in the above image. It should however be noted that this is a basic JWT architecture and a JWT supports loads of other parameters as well but we'll talk about them in upcoming articles.

They define a secure way of transmission of data which, if implemented correctly, verifies that the data that is being passed in the JWT is tamper-free because the developers can sign the token using their public/private key pairs or using a Secret with HMAC. This approves the integrity of the data.


The actual Bug (PoC)

JWT has been exploited multiple times in numerous articles wherein a vulnerable library is being used that allows attackers to use "alg": "None" and an empty Signature to forge their own Payloads and tokens but this PoC exploits a JWT misconfiguration that occured due to missing Signature verification.
Also take a look at this tool, it's pretty helpful in exploiting those "None" algorithm attacks and has a ton of other features - https://github.com/ticarpi/jwt_tool

Let's call the vulnerable site redacted.com

redacted.com has a subdomain called training.redacted.com that hosts an application which is authenticating users using JWT and signing all their API requests with that JWT.
The JWT is contained in a Cookie value  RedactedAuthCookie= and the token looks something like this -

cookie

My first thought was obviously to test the "None" attack so I used jwt_tool (link above) and a plugin in Burp called JSON Web Tokens to test all kind of attacks.

From what I've learned, to make it work, we have to change the "alg" to "None" and remove the Signature part but even after trying all variations of None - none, None, NONE, and nOnE, none of them worked.

Then I kept the algorithm in the Header as it was in the original request, i.e., HS256 and removed the Signature completely which gave a success response.
This proved that there is in-fact a bug here, probably the signature is not being verified at all, a disaster from the developers side, which made me think of the possibilities of forging my own tokens.

Then I had to manually try each of the three parameters to find which one of them might lead to access into another account - userid, clientid, and userauthenticationid.
It was found that the parameter clientid stores session information.

And we have ourselves a complete Account takeover bug PoC

poc