Testing JSON Web Tokens

JSON Web Tokens (JWT) are increasingly present in various types of platform these days. They are usually provided during authentication and used for session persistence in entities such as Web Applications, Mobile Applications, APIs and more. In this blog, I am going to detail some of the methodology you can use to assess JWTs to ensure they are not vulnerable to several flaws. I must note that this is not a comprehensive list, as JWTs have a history of being vulnerable for various different reasons. But this blog does provide a good idea of the important things to check when you see a JWT!

For reference later, JWT_Tool is great for JWT testing. You may also wish to test it on this vulnerable JWT website: https://jwt-lab.herokuapp.com/challenges

I’m not going to describe what JWTs are or how they are created. However, they are BASE64 encoded and when decoded look like:

{
"alg": "HS256",
"typ": "JWT"
}

{
“sub”: “coolwebsite.com”,
“userID”: “1558899”,
“password”: “Derp123*”,
“iat”: 1516239022
}

Issue 1: JWT uses HS256 signing algorithm

The first thing I usually note with nearly all assessments that I conduct, is that the HS256 algorithm being used. This is set within the “alg” parameter, shown within the JWT above. JWTs can be configured to use certificate based signing (Asymmetric encryption) or key based signing (Symmetric encryption). HS256 is a key based signing algorithm, meaning one key is used for both encryption and decryption. This is not necessarily a bad thing, especially when a long and complex secret key is used. However, I always recommend the use of RS256 or one of the many other certificate based signing algorithms. This is because an attacker would have a much harder time gaining access to the private signing certificate and brute forcing that than simply trying to brute force the secret key with HS256.

Issue 2: JWT contains sensitive data

The next thing I tend to look at is the decoded JWT payload. It’s not often that developers do leave sensitive data within the payload but you may be lucky and find there is personal information, a password or other sensitive data that could lead to compromise. You can see in the JWT above, that the userID and password values are present which may be used to login to a users account. Therefore, if you can find a way to gain access to a users JWT, you may then access their account indefinitely. For example, JWTs could be stored in a browsers cache or many different types of log. The recommendation here is obvious, don’t add sensitive data to JWTs.

Issue 3 JWT does not expire

Sometimes you may come across a JWT that either does not include a “exp” field or find that it is not checked. The “exp” field is the expiry time and if this field does not exist or is not validated by the JWT implementation, then JWTs may be valid indefinitely. As previously stated,  JWTs could be stored in a browsers cache or many different types of log. If an attacker finds a JWT and it does not expire, the web application could be compromised.

Issue 4: JWT permits a NULL signature

JWTs work by validating a signature that is signed using the configured encryption algorithm. In flawed JWT implementations, signatures are never verified, meaning an attacker can create their own JWT and it be valid for authentication/authorisation. A JWT signature is made up of the following “Header.Payload.Signature” (note the three full stops). The last string in a JWT is the signature which needs to be removed for this attack and the “alg” parameter in the Header be change to “none”. We can do this with JWT_TOOL using the command:

python3 jwt_tool.py <JWT> -X a

That command will turn the first JWT below into the second JWT, as described above. You will see that the Header has changed and the Signature was removed:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.

You can then try using this JWT to authenticate to the platform using a NULL signature attack. If it works, you may wish to try changing some payload data. Maybe try incrementing the UserID to gain access to other users accounts? At this point, take a look at the payload and see what you can manipulate.

Issue 5: RS256 to HS256

In issue 1, I presented that the RS256 algorithm is more secure than HS256, because that’s true. However, it is possible to exploit RS256 in some cases by changing the JWT signing algorithm back to HS256. When changing this algorithm from RS256 to HS256 and then using the RS256 public key to sign the JWT, the platform may be tricked into thinking the public key was the private key. Subsequently, the platform may validate this attacker created JWT. This works because HS256 requires a shared key to encrypt and decrypt the signature. So when the algorithm is changed from RS256 to HS256, the implementation may become confused and use the HS256 algorithm to successfully validate the JWT with the RS256 public key. The following commands can provide you with a JWT to try this attack. You will need openssl installed:

openssl s_client -connect coolwebsite.com:443 2>&1 < /dev/null | sed -n '/-----BEGIN/,/-----END/p' > downloadedcert.pem

openssl x509 -pubkey -in downloadedcert.pem -noout > pubkey.pem

python3 jwt_tool.py <JWT> -S hs256 -k pubkey.pem

Issue 6: Bruteforce the JWT signing key

When you see a JWT using a symmetric key such as HS256, have a go at bruteforcing the key. My favourite tool for this is Hashcat and the command to do it is shown below:

hashcat -m 16500 -a 0 jwt.txt .\wordlists\rockyou.txt

If you manage to crack the key, good times. Use the key to sign new valid JWTs.