目录
什么是JWTs?
JWT格式
JWT vs JWS vs JWE
什么是JWT attacks?
JWT 攻击的影响是什么?
JWT 攻击漏洞是怎么产生的?
<1> Accepting arbitrary signatures(接受任意签名)
Lab1:通过未经验证的签名绕过 JWT 身份验证
<2> 接受没有签名的令牌(none算法)
Lab2:通过有缺陷的签名验证绕过 JWT 身份验证
<3> 暴力破解密钥
使用 hashcat 暴力破解密钥
Lab3:通过弱签名密钥绕过 JWT 身份验证
一、JWT.io 利用密钥构造JWT
二、JWT editor插件 利用密钥构造JWT
<4> JWT 标头参数注入
(1) 通过 jwk 参数注入自签名 JWT
Lab4:通过 jwk header注入绕过 JWT 身份验证
(2) 通过 jku 参数注入自签名 JWT
Lab5:通过 jku header注入绕过 JWT 身份验证
小结感受:
(3) 通过 Kid 参数注入自签名 JWT
<5> 其他有趣的 JWT 标头参数
<6> JWT算法混乱
<7> 如何防止 JWT 攻击
<8> JWT 处理的其他最佳实践
处理令牌(JWTs)可以在网站上留下容易受到各种各样的严重程度较高的攻击。 作为JWTs是最常用在身份验证、会议管理、 访问控制 机制,这些漏洞可能会损害整个网站及其用户
JSON Web 令牌 (JWT) 是一种标准化格式,用于在系统之间发送加密签名的 JSON 数据。它们理论上可以包含任何类型的数据,但最常用于发送有关用户的信息(“声明”),作为身份验证、会话处理和访问控制机制的一部分。
与经典会话令牌不同,服务器所需的所有数据都存储在 JWT 本身的客户端中。这使得 JWT 成为用户需要与多个后端服务器无缝交互的高度分布式网站的流行选择
JWT由3部分组成:一个 header ,一个 payload ,一个 signature . 这些都是每隔一点,如以下例子:
eyJraWQiOiI5MTM2ZGRiMy1jYjBhLTRhMTktYTA3ZS1lYWRmNWE0NGM4YjUiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJwb3J0c3dpZ2dlciIsImV4cCI6MTY0ODAzNzE2NCwibmFtZSI6IkNhcmxvcyBNb250b3lhIiwic3ViIjoiY2FybG9zIiwicm9sZSI6ImJsb2dfYXV0aG9yIiwiZW1haWwiOiJjYXJsb3NAY2FybG9zLW1vbnRveWEubmV0IiwiaWF0IjoxNTE2MjM5MDIyfQ.SYZBPIBg2CRjXAJ8vCER0LA_ENjII1JakvNQoP-Hw6GG1zfl4JyngsZReIfqRvIAEi5L4HV0q7_9qGhQZvy9ZdxEJbwTxRs_6Lb-fZTDpW6lKYNdMyjw45_alSCZ1fypsMWz_2mTpQzil0lOtps5Ei_z7mM7M8gCwe_AGpI53JxduQOaB5HkT5gVrv9cKu9CsW5MS6ZbqYXpGyOG5ehoxqm8DL5tFYaW3lB50ELxi0KsuTKEbD0t5BCl0aCR2MBJWAbN-xeLwEenaqBiwPVvKixYleeDQiBEIylFdNNIMviKRgXiYuAvMziVPbwSgkZVHeEdF5MQP1Oe2Spac-6IfA
header和部分payload的一个JWT是base64urlencode. 头中包含的元数据有关的令本身,而有效载荷包含实际的"要求"有关用户
#!/usr/bin/env python
function base64url_encode($data) {
return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
}
Signature 部分是对前两部分的签名,防止数据篡改
颁发令牌的服务器通常通过对 header和payload 进行哈希处理来生成签名。在某些情况下,它们还会加密生成的哈希。无论哪种方式,此过程都涉及秘密签名密钥。这种机制为服务器提供了一种方法来验证令牌中的数据自发布以来没有被篡改:
由于签名直接来自令牌的其余部分,因此更改标头或有效负载的单个字节会导致签名不匹配。
在不知道服务器的秘密签名密钥的情况下,不可能为给定的标头或有效负载生成正确的签名
首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用 Header 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名。
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
算出签名以后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用"点"(.)分隔,就可以返回给用户。
JWT 规范实际上非常有限。它只定义了一种将信息(“声明”)表示为可以在两方之间传输的 JSON 对象的格式。在实践中,JWT 并没有真正用作独立实体。JWT 规范由 JSON Web 签名 (JWS) 和 JSON Web 加密 (JWE) 规范扩展,它们定义了实际实现 JWT 的具体方法。
换句话说,JWT 通常是 JWS 或 JWE 令牌。当人们使用术语“JWT”时,他们几乎总是指 JWS 令牌。JWE 非常相似,只是令牌的实际内容是加密的,而不仅仅是编码的
JWT 攻击涉及用户将修改后的 JWT 发送到服务器以实现恶意目标。通常,此目标是通过模拟另一个已通过身份验证的用户 来绕过身份验证和访问控制。
JWT attacks 通常是严重的。 如果攻击者是能够创造自己的token与任意的价值观,他们可能可以提升他们自己的特权或假冒其他用户,充分控制他们的账户
JWT 漏洞通常是由于应用程序本身存在缺陷的 JWT 处理而出现的。与 JWT 相关的各种规范在设计上相对灵活,允许网站开发人员自行决定许多实现细节。即使在使用久经考验的库时,这也可能导致他们意外引入漏洞。
这些实现缺陷通常意味着没有正确验证 JWT 的签名。这使攻击者能够篡改通过令牌的有效负载传递给应用程序的值。即使签名得到了可靠的验证,它是否可以真正被信任在很大程度上取决于服务器的密钥是否仍然是秘密。如果此密钥以某种方式泄露,或者可以被猜测或暴力破解,则攻击者可以为任意令牌生成有效签名,从而破坏整个机制。
JWT 库通常提供一种验证令牌的方法和另一种仅对它们进行解码的方法。例如,Node.js 库jsonwebtoken
具有verify()
和decode()
.
有时,开发人员会混淆这两种方法,只将传入的令牌传递给该decode()
方法。这实际上意味着应用程序根本不验证签名
本实验使用基于 JWT 的机制来处理会话。由于实现缺陷,服务器不会验证它接收到的任何 JWT 的签名。
要解决实验室问题,请修改您的会话令牌以访问管理面板
/admin
,然后删除用户carlos
我们wiener:peter 登录,访问一下/admin
回显:
Admin interface only available if logged in as an administrator
Cookie种是 JWT格式,解密后payload里sub是wiener,
我们更改wiener为administrator 拿构造好的JWT更改repeater包里cookie的session值,发包
访问 /admin/delete?username=carlos 删除carlos即可完成任务
除其他外,JWT 标头包含一个alg
参数。这告诉服务器使用哪种算法对令牌进行签名,因此在验证签名时需要使用哪种算法。
{ "alg": "HS256", "typ": "JWT" }
这本质上是有缺陷的,因为服务器别无选择,只能隐式地信任来自令牌的用户可控输入,此时根本没有验证。换句话说,攻击者可以直接影响服务器检查令牌是否可信的方式。
JWT 可以使用一系列不同的算法进行签名,但也可以不签名。在这种情况下,alg
参数设置为none
,表示所谓的“不安全的 JWT”。由于这种明显的危险,服务器通常会拒绝没有签名的令牌。但是,由于这种过滤依赖于字符串解析,您有时可以使用经典的混淆技术绕过这些过滤器,例如混合大写和意外编码
注:即使令牌未签名,有效负载部分仍必须以尾随点终止
本实验使用基于 JWT 的机制来处理会话。服务器被不安全地配置为接受未签名的 JWT。
要解决实验室问题,请修改您的会话令牌以访问管理面板
/admin
,然后删除用户carlos
首先我们 wiener:peter 登录 burp抓/my-account 包 放到JWT editor插件或者JWT.io里更改sub为administrator,header里的alg为none,然后删去后面的Signature部分(留下第二个 . )。
然后把构造好的JWT放入Cookie种发包,成功得到 administrator身份
再次访问 /admin/delete?username=carlos 删除carlos用户,完成任务
一些签名算法,例如 HS256 (HMAC + SHA-256),使用任意的独立字符串作为密钥。就像密码一样,这个秘密不能被攻击者轻易猜出或暴力破解,这一点至关重要。否则,他们可能能够使用他们喜欢的任何标头和有效负载值创建 JWT,然后使用密钥以有效签名重新签署令牌。
在实现 JWT 应用程序时,开发人员有时会犯错误,例如忘记更改默认或占位符密码。他们甚至可能复制并粘贴他们在网上找到的代码片段,然后忘记更改作为示例提供的硬编码密码。在这种情况下,攻击者使用众所周知的秘密词表暴力破解服务器的秘密可能是可以成功的
你只需要一个来自目标服务器的有效的、签名的 JWT 和一个密码表(前面有链接)。然后,可以运行以下命令,将 JWT 和 wordlist 作为参数传递:
hashcat -a 0 -m 16500
Hashcat 使用 wordlist 中的每个密钥对来自 JWT 的标头和有效负载进行签名,然后将生成的签名与来自服务器的原始签名进行比较。如果任何签名匹配,hashcat 会以以下格式输出识别的秘密,以及各种其他详细信息:
由于 hashcat 在计算机上本地运行并且不依赖于向服务器发送请求,因此即使使用巨大的单词表,此过程也非常快。
确定密钥后,我们可以使用它为您喜欢的任何 JWT 标头和有效负载生成有效签名。有关如何在 Burp Suite 中重新签署修改后的 JWT 的详细信息,可以看 签署 JWT
本实验使用基于 JWT 的机制来处理会话。它使用极弱的密钥来签署和验证令牌。这可以很容易地使用前面下载的密码表来破解。
要解决实验室问题,首先要暴力破解网站的密钥。一旦你得到这个,用它来签署一个修改过的会话令牌,让你可以访问管理面板
/admin
,然后删除用户carlos
我们首先登录 wieter:peter 抓包/my-account 得到cookie中的JWT信息
执行以下命令来爆破密钥:
hashcat -a 0 -m 16500 eyJraWQiOiJhNThmNzNhZC1mZWZlLTQ4MjgtOGM5MC1hMWU1ZGIzMTVlMDMiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJwb3J0c3dpZ2dlciIsInN1YiI6IndpZW5lciIsImV4cCI6MTY2NzU1ODUzM30.kaLa3y79Evwaraw5HrSn1EPISzlgBvAvtY1PPOHUvRg jwt.secrets.list
得到密钥:secret1
之后可以利用JWT editor的插件来利用密钥构造JWT,也可以在jwt.io 这里更改
最终拿构造好的JWT 访问/admin 成功
再次访问 /admin/delete?username=carlos 删除carlos用户,完成任务
转到JWT Editor Keys选项卡,然后单击New Symmetric Key。在对话框中,单击生成以生成 JWK 格式的新密钥。将生成的k
属性值替换为 Base64 编码的密钥 即base64(secret1)
返回GET /admin
Burp Repeater 中的请求并切换到扩展生成的JSON Web Token消息编辑器选项卡
在有效负载中,将sub
声明的值更改为administrator
在选项卡底部,单击Sign
,然后选择您在上一部分中生成的密钥。
确保Don't modify header
已选择该选项,然后单击OK
得到JWT:
根据 JWS 规范,只有alg,
header 参数是强制性的。然而,在实践中,JWT 标头(也称为 JOSE 标头)通常包含几个其他参数。以下是攻击者特别感兴趣的。
jwk
(JSON Web Key) - 提供一个表示密钥的嵌入式 JSON 对象。
jku
(JSON Web 密钥集 URL)- 提供一个 URL,服务器可以从中获取一组包含正确密钥的密钥。
kid
(密钥 ID)- 提供一个 ID,在有多个密钥可供选择的情况下,服务器可以使用该 ID 来识别正确的密钥。根据密钥的格式,这可能有一个匹配的kid
参数。
这些用户可控制的参数每个都告诉接收服务器在验证签名时使用哪个密钥。在本节中,您将学习如何利用这些来注入使用自己的任意密钥而不是服务器的密钥签名的修改后的 JWT。
JSON Web 签名 (JWS) 规范描述了一个可选的jwk
标头参数,服务器可以使用该参数以 JWK 格式将其公钥直接嵌入到令牌本身中。
注:JWK(JSON Web Key)是一种将键表示为 JSON 对象的标准化格式。
您可以在以下 JWT 标头中看到一个示例:
{
"kid": "ed2Nf8sb-sD6ng0-scs5390g-fFD8sfxG",
"typ": "JWT",
"alg": "RS256",
"jwk": {
"kty": "RSA",
"e": "AQAB",
"kid": "ed2Nf8sb-sD6ng0-scs5390g-fFD8sfxG",
"n": "yy1wpYmffgXBxhAUJzHHocCuJolwDqql75ZWuCQ_cb33K2vh9m"
}
}
公钥和私钥
如果不熟悉“公钥”和“私钥”这两个术语,burp靶场已经将其作为算法混淆攻击材料的一部分进行了介绍。有关详细信息,可以参阅对称与非对称算法。
理想情况下,服务器应该只使用有限的公钥白名单来验证 JWT 签名。但是,配置错误的服务器有时会使用嵌入在jwk
参数中的任何密钥。
您可以通过使用自己的 RSA 私钥对修改后的 JWT 进行签名来利用此行为,然后将匹配的公钥嵌入jwk
标头中。
jwk
虽然您可以在 Burp 中手动添加或修改参数
本实验使用基于 JWT 的机制来处理会话。服务器支持JWT jwk 标头中的参数。这有时用于将正确的验证密钥直接嵌入到令牌中。但是,它无法检查提供的密钥是否来自受信任的来源
要解决实验室问题,请修改并签署一个 JWT,让您可以访问 管理面板
/admin
,然后删除用户carlos
首先登录 wiener:peter 账户,抓一个/my-account 的包 解密JWT发现了一个kid alg为RS256
然后我们转到Burp 主选项卡栏中 的JWT Editor Keys选项卡,单击新建 RSA 密钥,ok
然后我们回到repeater模块 请求并切换到JWT editor扩展界面,将payload中的sub值改为administrator
点击 Attack 选择Embedded JWK(嵌入的JWK)
Signing Key选择我们刚才新建的RSA密钥
然后在 JWT 的标头中,观察到jwk
添加了一个包含公钥的参数
再次访问 /admin/delete?username=carlos 完成任务
某些服务器不是直接使用 header 参数嵌入公钥,而是jwk
允许您使用jku
(JWK Set URL) header 参数来引用包含密钥的 JWK Set。验证签名时,服务器从该 URL 获取相关密钥
JWK Set:
JWK set 是一个 JSON 对象,其中包含代表不同键的 JWK 数组。您可以在下面看到一个示例
"keys": [
{
"kty": "RSA",
"e": "AQAB",
"kid": "75d0ef47-af89-47a9-9061-7c02a610d5ab",
"n": "o-yy1wpYmffgXBxhAUJzHHocCuJolwDqql75ZWuCQ_cb33K2vh9mk6GPM9gNN4Y_qTVX67WhsN3JvaFYw-fhvsWQ"
},
{
"kty": "RSA",
"e": "AQAB",
"kid": "d8fDFo-fS9-faS14a9-ASf99sa-7c1Ad5abA",
"n": "fc3f-yy1wpYmffgXBxhAUJzHql79gNNQ_cb33HocCuJolwDqmk6GPM4Y_qTVX67WhsN3JvaFYw-dfg6DH-asAScw"
}
]
像这样的 JWK set 有时会通过标准端点公开公开,例如/.well-known/jwks.json
更安全的网站只会从受信任的域中获取密钥,但有时您可以利用 URL 解析差异来绕过这种过滤.这些在SSRF中会有示例
本实验使用基于 JWT 的机制来处理会话。服务器支持JWT jku标头中的参数。但是,在获取密钥之前,它无法检查提供的 URL 是否属于受信任的域
要解决实验室问题,请伪造一个 JWT,让您可以访问 管理面板
/admin
,然后删除用户carlos
第一部分、上传恶意 JWK 集
首先登录 wiener:peter 然后用burp的repeater模块抓个包
然后我们转到Burp 主选项卡栏中 的JWT Editor Keys选项卡,单击新建 RSA 密钥,ok,然后回到浏览器,在浏览器中,转到漏洞利用服务器
将Body部分 的内容替换为如下的 空的 JWK Set
{
"keys" : [
]
}
再返回JWT Editor Keys选项卡,右键单击刚刚生成的密钥条目,然后选择Copy Public Key as JWK
将 JWK 粘贴到keys
漏洞利用服务器上的数组中,然后存储漏洞利用(store)
第二部分、修改和签署 JWT
返回到 GET /admin repeater模块的请求包中 在JWT editor界面,在 JWT 的标头(header)中,将kid
参数的当前值替换为我们上传到 漏洞利用服务器上的 JWK 的值
将新jku
参数添加到 JWT 的标头(header)。将其值设置为漏洞利用服务器上 JWK 集的 URL(注意逗号)
然后点击Sign 选中我们第一部分生成的 RSA密钥,Dont modify header
将构造好的JWT Copy 然后更改之后发包,成功访问/admin
再次访问 /admin/delete?username=carlos 删除carlos用户 完成任务
与通过 jwk header注入绕过身份验证 不同的是,这个 jku header注入多了一个漏洞利用服务器,因而我们可以通过 把 JWK set放到漏洞利用服务器上,然后通过往header里写入 "jku":"漏洞利用服务器url" 从而实现 jwk 注入到header里,有种远程文件包含的味道。 Lab4 是直接把JWK set嵌入到了header里,而这个 Lab5 是通过嵌入jwu 然后 jwu 的 url 值里放着本该嵌入的JWK set
服务器可以使用多个加密密钥来签署不同类型的数据,而不仅仅是 JWT。出于这个原因,JWT 的头部可能包含一个kid
(Key ID)参数,该参数帮助服务器在验证签名时识别使用哪个密钥。
验证密钥通常存储为 JWK 集。在这种情况下,服务器可以简单地查找与kid
令牌相同的 JWK。但是,JWS 规范没有为此 ID 定义具体的结构 - 它只是开发人员选择的任意字符串。例如,他们可能使用kid
参数来指向数据库中的特定条目,甚至是文件的名称。
如果此参数也容易受到目录遍历的影响,则攻击者可能会强制服务器使用其文件系统中的任意文件作为验证密钥。
{
"kid": "../../path/to/file",
"typ": "JWT", "alg": "HS256",
"k": "asGsADas3421-dfh9DGN-AFDFDbasfd8-anfjkvc"
}
如果服务器还支持使用 对称算法 签名的 JWT,这尤其危险。在这种情况下,攻击者可能会将kid
参数指向一个可预测的静态文件,然后使用与该文件内容匹配的密钥对 JWT 进行签名。
从理论上讲,您可以对任何文件执行此操作,但最简单的方法之一是使用/dev/null
,它存在于大多数 Linux 系统上。由于这是一个空文件,因此获取它会返回 null。因此,使用 Base64 编码的空字节对令牌进行签名将产生有效的签名
以下标头参数也可能对攻击者感兴趣:
cty
(内容类型)- 有时用于声明 JWT 有效负载中内容的媒体类型。这通常从标头中省略,但底层解析库无论如何都可能支持它。如果您找到了绕过签名验证的方法,您可以尝试注入cty
标头以将内容类型更改为text/xml
or application/x-java-serialized-object
,这可能会为XXE和反序列化攻击启用新的向量。
x5c
(X.509 证书链) - 有时用于传递 X.509 公钥证书或用于对 JWT 进行数字签名的密钥的证书链。此标头参数可用于注入自签名证书,类似于上面讨论的jwk标头注入攻击。由于 X.509 格式及其扩展的复杂性,解析这些证书也可能引入漏洞。这些攻击的详细信息超出了这些材料的范围,但有关更多详细信息,请查看CVE-2017-2800和CVE-2018-2633。
即使服务器使用了您无法暴力破解的强大机密,您仍然可以通过使用开发人员未预料到的算法签署令牌来伪造有效的 JWT。这被称为算法混淆攻击。
可以参考:
JWT算法混淆攻击这方面和密码关联很大,学到很浅,后期再回来继续看一下
您可以通过采取以下高级措施来保护您自己的网站免受我们所涵盖的许多攻击:
使用最新的库来处理 JWT,并确保您的开发人员完全了解它的工作原理以及任何安全隐患。现代库使您更难以无意中不安全地实现它们,但这并不是万无一失的,因为相关规范具有固有的灵活性。
确保对收到的任何 JWT 执行稳健的签名验证,并考虑边缘情况,例如使用意外算法签名的 JWT。
对标头实施严格的允许主机白名单jku
。
确保您的 header kid
参数 不会受到路径遍历或 SQL 注入的影响。
尽管避免引入漏洞并不是绝对必要的,但我们建议在您的应用程序中使用 JWT 时遵循以下最佳实践:
始终为您发行的任何令牌设置到期日期。
尽可能避免在 URL 参数中发送令牌。
包括aud
(受众)声明(或类似声明)以指定令牌的预期接收者。这可以防止它在不同的网站上使用。
使发行服务器能够撤销令牌(例如,在注销时)