How to exploit JSON web tokens
How to discover and exploit weak implementation of Json web tokens.
André Eichhofer
Knowing at least the foundation on how to exploit weak Json web tokens is key to every auditor when assessing the security of cloud applications. For example, the BSI-C5 catalogue requires a robust session management, similar to Fisma 800 - 53 or FedRAMP.
Before diving into the topic make yourself familiar on how Json web tokens work
A JSON web token (jwt) is generated server side to authenticate users. After it is generated, the server passes the token to the user in the response header.
Example response header
Set cookie: AuthToken=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Then the browser safes the token as a cookie and passes the token each time in the GET request to the server when the user accesses a resource.
You can manipulate the token or try to brute-force the key to get unauthorised access. Most JWT attacks are privilege escalations as you already must have a valid JWT. The goal is to manipulate the token to get elevated access.
Discover Jwts
Shodan searches the headers of services and this way you can discover JWTs.
Search for terms in HTTP response headers (examples):
jwt
"jwt="
,jwt_cookie=
token
session_token
Search for base64 encoded JWT headers
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
: search for “HS256” encrypted tokenseyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9
: search for “RS256” encrypted tokens
Jwt attacks
All attacks aim to manipulate the payload of the JWT in order to get elevated access. To manipulate the payload you must break the signing key. Also, you must have a valid JWT that gives you (unprivileged) access and information about the payload. So, the steps would be as follows:
- Have an unprivileged JWT
- Break signing key
- brute force signing key
- flawed signatures
- header parameter injections
- Manipulate the payload
Weak signing key
The attack path to exploit a weak signing key is:
get a valid JWT
-> brute force signing key
-> change the payload
-> resign the JWT
At first, obtain a valid JWT and inspect it in any decoder, for example:
Set cookie: AuthToken=eyJraWQiOiIyOWFmMzJmMi1mODA2LTQ0ZGYtOTAxYi0zZWYwZTRkMjUxZjQiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJwb3J0c3dpZ2dlciIsInN1YiI6IndpZW5lciIsImV4cCI6MTcwNTA2ODQ2OX0.ZrhSie1csmYWGKLX8Mt4iGbp0bRWW9MuGvM1B6KLPA0
Example output
HEADER:
{
"kid": "29af32f2-f806-44df-901b-3ef0e4d251f4",
"alg": "HS256"
}
PAYLOAD:
{
"iss": "portswigger",
"sub": "wiener",
"exp": 1705068469
}
SIGNATURE
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
[your-256-bit-secret]
)
The goal is to change the user "wiener"
to "admin"
to gain elevated privileges. However, as the JWT is signed with a secret key you cannot just change the payload (the JWT would be invalid and rejected by the server). Therefore, you need to crack the secret key.
You can use several tools to crack the signature with a wordlist:
hashcat -a 0 -m 16500 <jwt> <wordlist>
: cracking with hashcatpython3 jwt_tool.py <jwt> -C -d <wordlist>
: cracking with jwt_tool
After you have cracked the key you can generate a new token with any tool, such as
python3 jwt_tool <jwt> -S <algorithm> -p <secret_key> -T
: tamper token and generate new signed token- Burpsuite JWT extensions
- http://jwt.io
After you have generated a new token you can insert the tampered token in the GET request using Burpsuite or insert the token in you browser to gain access to the resource.
Flawed JWT signature verification
Accepting arbitrary signatures
JWT libraries typically provide one method for verifying tokens and another to decode them. For example, Node.js library jsonwebtoken has verify()
and decode()
. The source code first verifies the signature of the token and then decodes the payload.
Sometimes, developers confuse these methods and only pass tokens to the decode()
method. This means that the application does not verify the signature at all.
In this case you can just tamper the payload without resigning the token. The attack path is simply:
get valid JWT
-> tamper payload
-> privilege escalation
Accepting tokens with no signature
Usaually, JWT tokens are signed with a special signing algorithm. The signing algorithm is defined in the header of the token:
{
"alg": "HS256",
"typ": "JWT"
}
The user could tamper the "alg"
parameter to "alg": "none"
which means that the token is not signed at all. Therefore, servers are programmed to reject unsigned tokens. However, the filtering relies on string parsing and you can sometimes bypass these filters using classic obfuscation techniques, such as mixed capitalization and unexpected encodings.
Example obfuscation techniques:
none
None
nOne
- etc.
If the server accepts a non-signed JWT you can tampler the token to perform privilege escalation, for example
"user": "administrator"
How to test a token for alg:none vulnerability with jwt_tool:
- Remove the signature from the original, unprivileged token using the alg:none exploit (
-X a
) - Test whether the server accepts the unsigned, unprivileged token
- Tamper the original, unprivileged and signed token and change the user payload (
-T
) - Remove the signature from the tampered token using the alg:none exploit
JWT header parameter injections
A JWT header can contain additional parameters, like
jwk
: (JSON Web Key) - embedded JSON object representing the keyjku
: (JSON Web Key Set URL) - Provides a URL from which servers can fetch a set of keys containing the correct keykid
: (Key ID) - Provides an ID that servers can use to identify the correct key in cases where there are multiple keys to choose from
JWK header injection
The jwk
header is used to store a public key within the header itself. This public key is used to verify the token which was signed with a private key.
{
"kid": "ed2Nf8sb-sD6ng0-scs5390g-fFD8sfxG",
"typ": "JWT",
"alg": "RS256",
"jwk": {
"kty": "RSA",
"e": "AQAB",
"kid": "ed2Nf8sb-sD6ng0-scs5390g-fFD8sfxG",
"n": "yy1wpYmffgXBxhAUJzHHocCuJolwDqql75ZWuCQ_cb33K2vh9m"
}
}
Servers should only use a limited list of public keys to verify JWT signatures. However, misconfigured servers sometimes use any key that’s embedded in the jwk parameter. An attacker can therefore create his own public-private key pair, tamper the token, and sign the token with hos own private key.
Attack path:
create a public private key pair
-> tamper token
-> embed own public key in jwk parameter
-> sign token with own private key
Test jwk header injection with jwt_tool (Example):
- Use “inject inline jwks” exploit and tamper token (
-X i -T
) - Change “kid” parameter to “jwt_tool” (otherwise server may not accept token, because of different “kid’s”)
- Change username to “administrator”
Jwt_tool will tamper the token, generate a public-private key pair, embed the public key in the jwk
header and sign the token with the private key.
JKU header injection
The jku header is used to store a url containing the public key in the token. To verify the token the server fetches the public key from the remote url.
Header with url to remote host
{
"kid": "9ab0f248-9d4c-4505-88f0-50a19d21326b",
"alg": "RS256",
"jku": "https://exploit-server.com/public_key"
}
Header with public key on the remote host
{
"keys":[
{
"kty":"RSA",
"kid":"jwt_tool",
"use":"sig",
"e":"AQAB",
"n":"jK4wC4AO9dX77XTwaL6fWmO1U0AFxc4sd1cgEcCL_WQ2LYMWrVytxXPzvjvO4c2gmhgNXmnXWuNoTiwYfPWy0H2pqe8iXIGyHQht9MyTdXmXYugFna_Se41ivbGImjiRStU4i7eO2z28pTvEfMRL3tC_cbaiiIj3pcyyEHSd6ua99XFcE9vyM4GJBfATTVckGerimiRkGgXzgEWl22MpJVoK5oFhJShzP84dY6QEjDYr0M3ZBgoZRv7fB7hqDt6rgD-Akf2YzlgJ_ZqLTPvlyx0ROPxn85WWTxdu0wNe_ObQv5CBSuD51SQaatXG5AEhHDfG7vp5rGAFF6an7KFq8w"
}
]
}
Servers should only fetch keys from trusted domains, but sometimes there are misconfigured and allow untrusted domains.
Attack path:
- Create a public private key pair
- Setup a remote server
- Put the header containing the public key on the server
- Tamper the token
Test jku header injection with jwt_tool (Example):
- Use “spoof jwks” exploit and tamper token (
-X s -ju <url> -T
) - Change “kid” parameter to “jwt_tool” (otherwise server may not accept token, because of different “kid’s”)
- Change username to “administrator”
- Copy the public key header from jwttool_custom_jwks.json to the remote server
Jwt_tool will tamper the token, generate a public-private key pair, output the public key header to a file and sign the token with the private key.
KID header injection
The kid
parameter usually stores information, that tells the server which key to use to verify the token (e.g. database entry, any ID that points to the key). The key can also be the content of a file on the server
If the kid
parameter is vulnerable to path traversal you can generate an arbitrary signature, by
- pointing the path to a static file on the remote server and generating your own key if you know the content of the file
- pointing the path to
/dev/null
and generating an unsigned token (dev/null
always points to zero).
{
"kid": "../../path/to/file",
"typ": "JWT",
"alg": "HS256",
"k": "asGsADas3421-dfh9DGN-AFDFDbasfd8-anfjkvc"
}
Note: You cannot use the attack for a classic path traversal as the “kid” parameter only points to the key, but does not reflect any content.
Attack path:
- The
kid
parameter is vulnerable to path traversal - Algorithm is symmetric
- Tamper the token
- Set
kid
parameter to “/dev/null”
Test kid header injection with jwt_tool (Example):
- Change “kid:” to “../../../dev/null”
- Change username to “administrator”
- Sign the token with HS256 and set an empty password (
-S hs256 -P ''
)
book.hacktricks.xyz/pentesting-web/hacking-jwt-json-web-tokens