- 全程带阻:记一次授权网络攻防演练(上) : 一次攻击示例
JWT 攻击是什么?
JWT攻击涉及用户向服务器发送修改过的JWT,以达到恶意的目的。通常情况下,这个目的是通过冒充已经通过认证的用户,绕过认证和访问控制
JWT 攻击影响
JWT攻击的影响通常很严重。如果攻击者能够用任意值创建自己的有效令牌,他们可能会提升自己的权限或者冒充其他用户,完全控制其他用户的账户
利用
:::info 主流的 JWT 攻击方案为:
- 信息泄露
- 使用有缺陷的 JWT 签名验证
- 接受没有签名的令牌
- 非对称加密向下降级为对称加密
- 暴力破解密钥
- 篡改 jwt header,kid指定攻击
:::
敏感信息泄漏
通过 JWT 数据结构的分析,显然可知:header 与 payload 是以明文经 Base64Url 编码传输的,因此,如果 payload 中存在敏感信息的话,就会发生信息泄露。使用有缺陷的 JWT 签名验证
服务器通常不存储有关它们颁发的JWT的任何信息。相反,每个令牌是一个完全独立的实体。这样会有几个优点,但也引入了一个基本问题——服务器实际上并不知道令牌的原始内容,甚至不知道原始签名是什么。因此,如果服务器没有正确地验证签名
,就没有什么可以阻止攻击者对令牌的其余部分进行任意更改
例如,考虑一个包含以下声明的JWT:
{
"username": "carlos",
"isAdmin": false
}
如果服务器根据这个username来识别会话,那么修改其值可能使攻击者能够冒充其他登录用户。同样,如果isAdmin值被用于访问控制,这可能为权限提升提供了一个简单的载体
接受没有签名的令牌
在其他方面,JWT标头包含一个alg参数。这用来告诉服务器使用哪种算法来对令牌进行签名,因此在验证签名时需要使用哪种算法。
{
"alg": "HS256",
"typ": "JWT"
}
这在本质上是有缺陷的,因为服务器没有选择,只能隐含地信任来自令牌的用户可控输入,此时令牌根本没有被验证过。换句话说,攻击者可以直接影响服务器如何检查令牌是否值得信任。
JWT可以使用一系列不同的算法进行签名,但也可以不签名。在这种情况下,alg参数被设置为none,表示所谓的“不安全的JWT”。由于这种情况有明显的危险性,服务器通常会拒绝没有签名的令牌。然而,由于这种过滤依赖于字符串解析,有时可以使用经典的混淆技术绕过这些过滤器,例如混合大写和意外编码。
非对称加密向下降级为对称加密
现在大多数应用使用的算法方案都采用 RSA 非对称加密,server 端保存私钥,用来签发 jwt,对传回来的 jwt 使用公钥解密验证。
碰到这种情况,我们可以修改 alg 为 HS256 对称加密算法,然后使用我们可以获取到的公钥作为 key 进行签名加密,这样一来,当我们将 jwt 传给 server 端的时候,server 端因为默认使用的是公钥解密,而算法为修改后的 HS256 对称加密算法, 所以肯定可以正常解密解析,从而绕过了算法限制。
当 server 端严格指定只允许使用 HMAC 或者 RSA 算法其中一种时候,那这种攻击手段是没有效果的。
暴力破解密钥
一些签名算法,如HS256(HMAC + SHA-256),使用一个任意的、独立的字符串作为密钥。就像密码一样,这个密钥不能被攻击者轻易猜到或暴力破解,这一点至关重要。否则,他们可能会用他们喜欢的任何标头和有效负载值创建JWT,然后使用密钥以有效的签名重新签名令牌。
在实现JWT应用时,开发人员有时会犯一些错误,比如忘记更改默认或占位符密码。他们甚至可能复制和粘贴在网上找到的代码片段,然后忘记更改作为示例提供的硬编码密码。在这种情况下,攻击者使用众所周知的密钥字典来暴力破解服务器的密码是很容易的。
hashcat -a 0 -m 16500 <jwt> <wordlist>
例如 CISCN2019 华北赛区 Day1 Web2 ikun 一题中就有利用爆破 JWT 密钥进行伪造 token。
当然我们也可以使用一些工具进行破解: c-jwt-cracker、jwt_tool或JWTPyCrack
在线 JWT 加解密网站:https://jwt.io/ # 可选头部参数 :::info 根据JWS规范,只有alg标头参数是强制性的。然而在实践中,JWT标头(也被称为JOSE标头)通常包含几个其他参数。以下是攻击者特别感兴趣的。 + jwk(JSON Web Key) —— 提供一个表示密钥的嵌入式 JSON 对象。 + jku(JSON Web Key Set URL) —— 提供一个URL,服务器可以从中获取包含正确密钥的密钥集。 + kid(Key ID) —— 提供一个 ID,在有多个密钥可供选择的情况下,服务器可以使用该ID来识别正确的密钥。根据密钥的格式,这可能有一个匹配的kid参数。 ::: ## kid 参数 服务器可能会使用多个加密密钥来签名不同类型的数据,而不仅仅只是JWT。出于这个原因,JWT的标头部分可能包含一个kid(Key ID)参数,该参数帮助服务器在验证签名时确定使用哪个密钥 验证密钥通常被存储为JWK Set。在这种情况下,服务器可以简单地查找与kid令牌相同的JWK。然而,JWS规范并没有为此ID定义具体的结构,它只是开发者选择的一个任意字符串。例如,他们可能使用kid参数来指向数据库中的一个特定条目,甚至是一个文件的名称。 如果这个参数也易受到目录遍历的影响,攻击者就有可能迫使服务器使用其文件系统中的任意文件作为验证密钥shell
{
"kid": "../../path/to/file",
"typ": "JWT",
"alg": "HS256",
"k": "asGsADas3421-dfh9DGN-AFDFDbasfd8-anfjkvc"
}
如果服务器还支持使用对称算法签名的JWT,这就尤其危险。在这种情况下,攻击者可能会将kid参数指向一个可预测的静态文件,然后用一个与该文件内容相匹配的密钥对JWT进行签名。
从理论上讲,你可以对任何文件执行此操作,但最简单的方法之一是利用/dev/null,它存在于大多数Linux系统上。由于这是一个空文件,获取它会返回null。因此,使用一个Base64编码的空字节对令牌进行签名将产生一个有效的签名。
:::info
实验
通过kid标头路径遍历绕过JWT认证
:::
### 目录遍历
由于 KID 通常用于从文件系统中检索密钥文件,因此,如果在使用前未对其进行清理,则可能导致目录遍历攻击。在这种情况下,攻击者将能够在文件系统中指定任何文件作为用于验证令牌的密钥。
例如,攻击者可以迫使应用程序使用公开可用的文件作为密钥,并使用该文件对 HMAC 令牌进行签名
"kid": "../../public/css/main.css"
//使用公共文件 main.css 验证 token
SQL 注入
KID 还可以用于从数据库检索密钥。在这种情况下,可能可以利用 SQL 注入来绕过 JWT 签名。如果可以在 KID 参数上进行 SQL 注入,则攻击者可以使用该注入返回她想要的任何值例如,上面的注入将使应用程序返回字符串”key”(因为数据库中不存在名为”aaaaaaa”的键)。然后将使用字符串”key”作为密钥来验证令牌。
"kid":"aaaaaaa' UNION SELECT 'key';--"
//使用字符串"key"验证 token
命令注入
有时,当 KID 参数直接传递到不安全的文件读取操作中时,可以将命令注入代码流中。 可能允许这种类型的攻击的函数之一是 Ruby open() 函数。此功能使攻击者只需在 KID 文件名之后将命令添加到输入即可,即可执行系统命令:这只是一个例子。从理论上讲,每当应用程序将未经过滤审查的任何头文件参数传递给类似于 system() , exec() 等的任何函数时,就会发生此类漏洞。
"key_file" | whoami;
JKU 参数
某些服务器不直接使用jwk标头参数来嵌入公钥,而是让你使用jku(JWK Set URL)标头参数来引用一个包含密钥的JWK Set。当验证签名时,服务器会从该URL获取相关密钥。。如果允许该字段,并且没有适当地限制此字段,则攻击者可以托管自己的密钥文件,并指定应用程序使用它来验证 token。
jku URL->包含 JWK 集的文件->用于验证 token 的 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解析的差异来绕过这种过滤
:::info 实验
:::
JWK 参数
JSON Web Signature(JWS)规范描述了一个可选的jwk标头参数,服务器可以使用该参数将其公钥直接嵌入JWK格式的令牌本身中。
你可以在以下JWT标头中看到一个示例:
{
"kid": "ed2Nf8sb-sD6ng0-scs5390g-fFD8sfxG",
"typ": "JWT",
"alg": "RS256",
"jwk": {
"kty": "RSA",
"e": "AQAB",
"kid": "ed2Nf8sb-sD6ng0-scs5390g-fFD8sfxG",
"n": "yy1wpYmffgXBxhAUJzHHocCuJolwDqql75ZWuCQ_cb33K2vh9m"
}
}
理想情况下,服务器应该只使用有限的公钥白名单来验证JWT签名。但是,配置不当的服务器有时会使用jwk参数中嵌入的任何密钥。
你可以利用这种行为,用自己的RSA私钥签署一个修改过的JWT,然后将匹配的公钥嵌入jwk标头中。
虽然可以在Burp中手动添加或修改jwk参数,但JWT Editor扩展提供了一个有用的功能,帮助你测试此漏洞:
- 1.加载扩展后,在Burp的主选项卡栏中,转到JWT Editor Keys选项卡。
- 2.生成新的RSA密钥。
- 3.发送包含JWT的请求到Burp Repeater。
- 4.在消息编辑器中,切换到扩展生成的JSON Web Token选项卡,然后根据需要修改令牌的有效负载。
- 5.点击Attack,然后选择Embedded JWK。出现提示时,选择你新生成的RSA密钥。
- 6.发送请求,测试服务器的响应情况。
也可以通过自己添加jwk标头来手动执行此攻击。然而,你可能还需要更新JWT的kid标头参数,以匹配嵌入密钥的kid。扩展程序的内置攻击为你解决了这个步骤
:::info 实验
:::
其他参数
以下标头参数也可能是攻击者感兴趣的:
- cty(Content Type)—— 有时用于声明JWT有效负载中内容的媒体类型。这通常从标头中省略,但底层解析库可能还是支持它。如果你已经找到了绕过签名验证的方法,可以尝试注入cty标头以将内容类型更改为text/xml或application/x-java-serialized-object,这可能会为XXE和反序列化攻击提供新的载体。
- x5c(X.509证书链) —— 有时用于传递对JWT进行数字签名的X.509公钥证书或证书链。此标头参数可用于注入自签名证书,类似于上面讨论的jwk标头注入攻击。由于X.509格式及其扩展的复杂性,解析这些证书也可能引入漏洞。这些攻击的细节超出了本材料的范围,但要了解更多细节,请查看CVE-2017-2800和CVE-2018-2633。
算法混淆攻击
即使服务器使用了无法暴力破解的强密钥,你仍然可以通过使用开发人员未预料到的算法签名令牌来伪造有效的JWT。这被称为算法混淆攻击。
阅读更多
防御
- 使用最新的库来处理JWT,并确保开发人员完全了解它的工作原理以及任何安全隐患。新式的库使你很难在无意中不安全地实现它们,但由于相关规范具有固有的灵活性,这并不是万无一失。
- 确保对收到的任何JWT都进行可靠的签名验证,并考虑边缘案例,如使用意外算法签名的JWT。
- 对jku标头实施严格的主机允许白名单。
- 确保不会受到通过kid标头参数进行的路径遍历或SQL注入的影响