JSON Web 令牌 (JWT) 提供了一种使用 JSON 对象安全地交换数据的方法。它们通常用于授权,因为它们可以被签名、验证并因此被信任——但只有在正确实施的情况下。这是对 JSON Web Token 攻击和漏洞的技术深入探讨。
JWT 格式
JSON Web Token 由 base64url 编码的标头、有效负载和签名组成,以点分隔,如下所示:
HEADER.PAYLOAD.SIGNATURE
让我们拆开以下真实令牌:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJ1c2VyX25hbWUiOiJqb2huLmRvZSIsImlzX2FkbWluIjpmYWxzZX0.fSppjHFaqlNcpK1Q8VudRD84YIuhqFfA67XkLam0_aY
标头包含有关令牌的元数据,例如用于签名的算法和令牌的类型(简称 JWT)。对于此示例,编码前的标头为:
{
"alg": "HS256",
"typ": "JWT"}
有效负载包含有关将由应用程序验证的实体(用户)的信息(声明)。我们的示例令牌包括以下声明:
{
"name": "John Doe",
"user_name": "john.doe",
"is_admin": false}
最后,要生成签名,我们必须对标头、点和有效负载应用 base64url 编码,然后根据算法使用秘密(用于对称加密)或私钥(用于非对称加密)对整个内容进行签名在标题中指定。我们已经放入HS256
了标头,这是一种对称算法,因此编码和签名操作将是:
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
这为我们提供了以下签名,然后将其附加(在点之后)到 base64url 编码的标头和有效负载:
fSppjHFaqlNcpK1Q8VudRD84YIuhqFfA67XkLam0_aY
常见的 JWT 漏洞
JSON Web Tokens 被设计为灵活且面向未来的设计,为适应各种用例和需求留出了很大的空间——但也为实施和使用中的错误留出了很大的空间。以下是使用 JWT 时可能引入的一些典型漏洞。
无法验证签
许多 JWT 库提供一种方法来解码令牌和另一种方法来验证它:
-
decode()
: 仅从 base64url 编码解码令牌而不验证签名。 -
verify()
:解码令牌并验证签名。
有时开发人员可能会混淆这些方法。在这种情况下,签名永远不会被验证,并且应用程序将接受任何令牌(以有效格式)。开发人员也可能禁用签名验证以进行测试,然后忘记重新启用它。此类错误可能导致任意帐户访问或权限提升。
例如,假设我们有以下从未实际验证过的有效令牌:
{
"alg": "HS256",
"typ": "JWT"}.{
"name": "John Doe",
"user_name": "john.doe",
"is_admin": false}
攻击者可以发送以下带有任意签名的令牌以获得升级权限:
{
"alg": "HS256",
"typ": "JWT"}.{
"name": "John Doe",
"user_name": "john.doe",
"is_admin": true}
允许None
算法
JWT 标准接受许多不同类型的算法来生成签名:
-
RSA
-
HMAC
-
椭圆曲线
-
没有
该None
算法指定令牌未签名。如果允许该算法,我们可以通过将现有算法更改为None
并剥离签名来绕过签名检查。让我们从我们预期的令牌开始:
{
"alg": "HS256",
"typ": "JWT" }.{
"name": "John Doe",
"user_name": "john.doe",
"is_admin": false}.SIGNATURE
编码和签名后,令牌将如下所示(签名以粗体显示):
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJ1c2VyX25hbWUiOiJqb2huLmRvZSIsImlzX2FkbWluIjpmYWxzZX0.fSppjHFaqlNcpK1Q8VudRD84YIuhqFfA67XkLam0_aY
如果None
允许作为算法值,攻击者可以简单地使用它来替换有效算法,然后摆脱签名:
{
"alg": "None",
"typ": "JWT"}.{
"name": "John Doe",
"user_name": "john.doe",
"is_admin": true}.
虽然现在未签名,但修改后的令牌将被应用程序接受:
eyJhbGciOiJOb25lIiwidHlwIjoiSldUIn0.eyJuYW1lIjoiSm9obiBEb2UiLCJ1c2VyX25hbWUiOiJqb2huLmRvZSIsImlzX2FkbWluIjp0cnVlfQ.
这就是为什么不接受标头中带有None
, none
, NONE
,nOnE
或任何其他大小写变体的标记很重要的原因alg
。
算法混乱
JWT 接受对称和非对称加密算法。根据加密类型,您需要使用共享密钥或公私钥对:
当应用程序使用非对称加密时,它可以公开发布其公钥并保持私钥保密。这允许应用程序使用其私钥签署令牌,并且任何人都可以使用其公钥验证此令牌。当应用程序不检查接收到的令牌的算法是否与预期的算法匹配时,就会出现算法混淆漏洞。
在很多 JWT 库中,验证签名的方法是:
-
verify(token, secret)
– 如果令牌使用 HMAC 签名 -
verify(token, publicKey)
– 如果令牌使用 RSA 或类似方式签名
不幸的是,在某些库中,此方法本身不会检查接收到的令牌是否使用应用程序的预期算法进行签名。这就是为什么在 HMAC 的情况下,此方法将第二个参数视为共享机密,而在 RSA 的情况下,将视为公钥。
如果在应用程序中可以访问公钥,攻击者可以通过以下方式伪造恶意令牌:
-
将令牌的算法更改为 HMAC
-
篡改有效载荷以获得期望的结果
-
使用在应用程序中找到的公钥对恶意令牌进行签名
-
将 JWT 发送回应用程序
该应用程序需要 RSA 加密,因此当攻击者提供 HMAC 时,该verify()
方法会将公钥视为 HMAC 共享机密,并使用对称加密而不是非对称加密。这意味着令牌将使用应用程序的非秘密公钥进行签名,然后使用相同的公钥进行验证。
为了避免这个漏洞,应用程序必须在将令牌传递给方法之前检查接收到的令牌的算法是否是预期的算法verify()
。
使用琐碎的秘密
使用对称加密,加密签名的强度与使用的秘密一样强。如果一个应用程序使用了一个弱秘密,攻击者可以通过尝试不同的秘密值来简单地暴力破解它,直到原始签名与伪造的签名匹配。发现秘密后,攻击者可以使用它为恶意令牌生成有效签名。为避免此漏洞,必须始终将强机密与对称加密一起使用。
针对 JSON Web Token 的攻击
kid
参数注入
JWT 标头可以包含 Key Id 参数kid
。它通常用于从数据库或文件系统中检索密钥。应用程序使用通过kid
参数获得的密钥来验证签名。如果该参数是可注入的,则可以打开签名绕过甚至是RCE、SQLi、LFI等攻击的途径。
要查看此操作,让我们从以下有效令牌开始:
{
"alg": "HS256",
"typ": "JWT",
"kid": "key1"}.{
"name": "John Doe",
"user_name": "john.doe",
"is_admin": false}
如果该kid
参数容易受到命令注入的影响,则以下修改可能会导致远程代码执行:
{
"alg": "HS256",
"typ": "JWT",
"kid": "key1|/usr/bin/uname"}.{
"name": "John Doe",
"user_name": "john.doe",
"is_admin": false}
kid
参数注入+目录遍历=签名绕过
如果应用程序使用该kid
参数从文件系统中检索密钥,它可能容易受到目录遍历的影响。然后,攻击者可以强制应用程序使用攻击者可以预测其值的文件作为验证密钥。这可以使用应用程序中的任何静态文件来完成。知道密钥文件的值后,攻击者可以制作恶意令牌并使用已知密钥对其进行签名。
继续前面的 JWT 示例,攻击者可能会尝试插入/dev/null
作为密钥源以强制应用程序使用空密钥:
{
"alg": "HS256",
"typ": "JWT",
"kid": "../../../../../../dev/null"}.{
"name": "John Doe",
"user_name": "john.doe",
"is_admin": true}
如果目录遍历/dev/null
成功,攻击者将能够使用空字符串签署恶意令牌。相同的技术可以用于已知的静态文件,例如 CSS 文件。
kid
参数注入+SQL注入=签名绕过
如果应用程序使用该kid
参数从数据库中检索密钥,则它可能容易受到SQL 注入的攻击。如果成功,攻击者可以控制kid
从 SQL 查询返回到参数的值,并使用它来签署恶意令牌。
再次使用相同的示例令牌,假设应用程序使用以下易受攻击的 SQL 查询通过kid
参数获取其 JWT 密钥:
SELECT key FROM keys WHERE key='key1'
然后攻击者可以在参数中注入一条UNION SELECT
语句kid
来控制键值:
{
"alg": "HS256",
"typ": "JWT",
"kid": "xxxx' UNION SELECT 'aaa"}.{
"name": "John Doe",
"user_name": "john.doe",
"is_admin": true}
如果 SQL 注入成功,应用程序将使用以下查询来检索签名密钥:
SELECT key FROM keys WHERE key='xxxx' UNION SELECT 'aaa'
此查询返回aaa
参数kid
,允许攻击者简单地使用aaa
.
为避免这些和其他注入攻击,应用程序应始终kid
在使用参数值之前对其进行清理。
jku
使用标头攻击
在 JWT 标头中,开发者还可以使用jku
参数指定JSON Web Key Set URL。此参数指示应用程序可以在哪里找到用于验证签名的JSON Web Key (JWK) ——基本上是 JSON 格式的公钥。
为了说明,让我们使用以下使用jku
参数指定公钥的 JWT:
{
"alg": "RS256",
"typ": "JWT",
"jku":"https://example.com/key.json"}.{
"name": "John Doe",
"user_name": "john.doe",
"is_admin": false}
指定的key.json
文件可能类似于:
{
"kty": "RSA",
"n": "-4KIwb83vQMH0YrzE44HppWvyNYmyuznuZPKWFt3e0xmdi-WcgiQZ1TC...RMxYC9lr4ZDp-M0",
"e": "AQAB"}
应用程序使用基于jku
标头值检索的 JSON Web Key 验证签名:
现在进行攻击。攻击者可以更改jku参数值以指向他们自己的 JWK 而不是有效的 JWK。如果被接受,这将允许攻击者使用他们自己的私钥签署恶意令牌。发送恶意令牌后,应用程序将获取攻击者的 JWK 并使用它来验证签名:
为了防止此类攻击,应用程序通常使用 URL 过滤。不幸的是,攻击者有办法绕过此类过滤,包括:
-
使用https://trusted(例如https://[email protected]/key.json),如果应用程序检查以trusted
-
使用带有#字符的 URL 片段
-
使用 DNS 命名层次结构
-
使用开放重定向链接
-
使用标头注入链接
-
使用 SSRF 链接
因此,应用程序将允许的主机列入白名单并进行正确的 URL 过滤非常重要。除此之外,应用程序不得存在攻击者可能链接以绕过 URL 过滤的其他漏洞。
概括
JSON Web 令牌正在成为现代 Web 应用程序开发中身份验证过程的重要组成部分,尤其是在实施单点登录 (SSO) 时。为了防止 JWT 漏洞,开发人员应遵循最佳实践并使用受信任的 JWT 库,而不是滚动自己的实现。为了最大限度地降低攻击者将 JWT 攻击与其他漏洞链接起来的风险,您还应该使用高质量的漏洞扫描解决方案来发现弱点,然后再被网络犯罪分子利用。像Invicti 这样的现代 DAST 工具可以识别 JWT 漏洞以及数千个其他问题,使其成为任何应用程序安全工具箱的重要组成部分。
原文始发于微信公众号(Ots安全):JSON Web Token 攻击和漏洞
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论