文章源自-投稿
作者-挽梦雪舞
0x00 JWT含义
JSON Web Tokens(缩写JWTs,读作 [/dʒɒts/]),是一种基于JSON格式,用于在网络上声明某种标准(广泛使用于商业应用程序中)的访问令牌,其包含令牌签名以确保令牌的完整性,令牌使用私钥或公钥/私钥进行签名验证。
JWT通常由三部分组成: 头信息(header), 消息体(payload)和签名(signature)。
我们今天讨论攻击者如何利用它们绕过访问控制,即伪造令牌并以其他人身份登录。
0x01 JWT的工作原理
JWT的头信息部分标识用于生成签名的算法
代码语言:javascript复制{
“ alg”:“ HS256”,
“ typ”:“ JWT”
}
使用的典型加密算法是HMAC和RSA。其中HS256 表明此令牌是使用HMAC-SHA256签名的,它是通过base64url编码的字符串,即eyBhbGcgOiBIUzI1NiwgdHlwIDogSldUIH0K
(Base64url编码是针对URL格式的base64的修改版本。它类似于base64,但使用不同的非字母数字字符并省略了填充符号。其中有关Base64url编码的知识详见
https://zh.wikipedia.org/wiki/Base64这里不做赘述。)
JWT的消息体部分包含实际用于访问控制的信息。此部分在应用于令牌之前也已经进行过base64url编码:
eyB1c2VyX25hbWUgOiBhZG1pbiB9Cg
代码语言:javascript复制{
"loggedInAs" : "admin",
"iat" : 1422779638
}
JWT的签名是用于验证令牌未被篡改的部分。它通过将头信息与消息体串联在一起,然后使用头信息中指定的算法进行签名来计算。
代码语言:javascript复制HMAC-SHA256(
base64urlEncoding(header) '.'
base64urlEncoding(payload),
secret_key
)
签名函数返回值:
4Hb/6ibbViPOzq9SJflsNGPWSk6B8F6EqVrkNjpXh7M
对于此特定令牌,字符串
“eyBhbGcgOiBIUzI1NiwgdHlwIDogSldUIH0K.eyB1c2VyX25hbWUgOiBhZG1pbiB9Cg”已用得到的密钥“secret_key”的HS256算法签名。
接着生成的字符串是:
4Hb/6ibbViPOzq9SJflsNGPWSk6B8F6EqVrkNjpXh7M
最后获取完整的令牌:
将上文提到的三个部分即头信息(header), 消息体(payload)和签名(signature)与“.”串联在一起,便可以获得完整的令牌,如下所示:
eyBhbGcgOiBIUzI1NiwgdHlwIDogSldUIH0K.eyB1c2VyX25hbWUgOiBhZG1pbiB9Cg.4Hb/6ibbViPOzq9SJflsNGPWSk6B8F6EqVrkNjpXh7M
0x02 绕过JWT控件的方法
如果上述操作能够正确实现,那么JWT将提供一种安全的方式来标识用户,此时消息体部分中所包含的数据无法被篡改。(而且由于用户无权访问密钥,因此也不能自己对令牌进行签名。)
但是,如果操作失败或者不正确,攻击者就可以通过多种方式绕过安全机制并伪造任意令牌以其他人身份登录,接下来具体讲述几种绕过方式。
0x03 修改算法类型
攻击者可以伪造自己的令牌,其中一种方法是篡改头信息中的alg字段。如果应用程序没有限制JWT中使用的算法类型,则攻击者可以指定要使用的算法,很明显,这可能会对令牌的安全性造成影响。
1.none 算法
JWT支持“none”算法。如果将alg字段设置为“none”,则任何令牌都将被视为有效。例如,以下令牌将被视为有效:
eyAiYWxnIiA6ICJOb25lIiwgInR5cCIgOiAiSldUIiB9Cg.eyB1c2VyX25hbWUgOiBhZG1pbiB9Cg.
那么它只是这两块的base64url编码版本,并且没有签名。
代码语言:javascript复制{
"alg" : "none",
"typ" : "JWT"
}
{
"user" : "admin"
}
我们不难猜出,此功能最初用于调试目的。但是如果未在发行环境中将其关闭,则攻击者可以利用此安全隐患,通过将alg字段设置为“ none” 来伪造他们想要的任何令牌。然后他们可以使用伪造的令牌模拟网站上的任何人。
2. HMAC算法
上文提到,用于JWT的两种最常见的算法类型是HMAC和RSA。使用HMAC,将使用密钥对令牌进行签名,然后使用相同的密钥进行验证。对于RSA,将首先使用私钥创建令牌,然后使用相应的公钥进行验证,概括如下:
HMAC -> 用密钥签名,并用相同的密钥验证
RSA -> 用私钥签名,并用相应的公钥验证
毋庸置疑,我们需要将HMAC令牌的密钥和RSA令牌的私钥妥善保管,因为它们用于签名令牌。
举个场景说明一下:
我们假设有一个最初设计为使用RSA令牌的应用程序。令牌用私钥A签名,私钥A 不公开。然后使用任何人都可以使用公钥B验证令牌,只要此令牌始终被视为RSA令牌。
使用密钥A签名的令牌->使用密钥B验证的令牌(RSA方案)
如果攻击者改变的alg到HMAC,那么或许可以通过与RSA公钥B 签订伪造的标记来创建有效的令牌,这是因为最初使用RSA对令牌进行签名时,程序会使用RSA公钥B对其进行验证。当将签名算法切换为HMAC时,仍使用RSA公钥B来验证令牌,但是这次是使用令牌时,可以使用相同的公钥B进行签名。
使用密钥B签名的令牌->使用密钥B验证的令牌(HMAC方案)
0x04 提供无效的签名
令牌的无效签名在运用到应用程序后也可能永远不会被验证,攻击者则可以通过提供无效签名来简单地绕过安全机制。
0x05 暴力破解密钥
因为长度有限,也可能暴力破解用于签署JWT的密钥。
攻击者从一开始就知道很多(固定的)信息,比如知道用于对令牌进行签名的算法类型,已签名的消息体以及生成的签名。如果用于对令牌进行签名的密钥不够复杂,则攻击者可能可以轻松地对其进行暴力破解。
0x06 泄漏密钥
如果攻击者无法暴力破解密钥,则可以尝试(旁路攻击)猜解密钥。此时如果存在另一个允许攻击者读取存储密钥值的文件漏洞(如目录遍历,XXE,SSRF),则攻击者可以窃取密钥并签署任意令牌。
(注:如果破解密码学系统使用的信息是通过与其使用人的合法交流获取的,这通常不被认为是旁路攻击/测信道攻击,而是社会工程学攻击。XXE漏洞文章补充链接:https://xz.aliyun.com/t/3357)
0x07 KID操作
KID代表“KEY ID”。它是JWT中的可选头信息字段,它使开发人员可以指定用于验证令牌的密钥。KID参数的正确用法如下所示:
代码语言:javascript复制{
"alg" : "HS256",
"typ" : "JWT",
"kid" : "1" // use key number 1 to verify the token
}
由于此字段是由用户控制的,因此攻击者可能会控制它造成安全问题。
1.目录遍历
由于KID通常用于从文件系统中检索密钥文件,因此,如果在使用前未对其进行清理,则可能导致目录遍历攻击。在这种情况下,攻击者将能够在文件系统中指定任何文件作为用于验证令牌的密钥。
“kid”: “../../public/css/main.css”
例如,攻击者可以强制应用程序使用公开可用的文件作为密钥,并使用该文件对HMAC令牌进行签名。
2. SQL注入
KID还可以用于从数据库检索密钥。在这种情况下,可以利用SQL注入来绕过JWT签名。
如果可以在KID参数上进行SQL注入,则攻击者可以使用该注入返回攻击者想要的任何值。
“kid”: "aaaaaaa' UNION SELECT 'key';--"
例如,上面的SQL注入将使应用程序返回字符串“key”(因为数据库中不存在名为“aaaaaaa”的密钥),然后将使用字符串“key”作为密钥来验证令牌。
0x08 头信息参数处理
除密钥ID外,JWT标准还允许开发人员能够通过URL指定密钥。
1.JKU头信息参数
JKU即“ JWK设置URL”。它是一个可选的头信息字段,用于指定指向一组用于验证令牌的密钥的URL。如果允许该字段,又没有对该字段进行适当的限制,则攻击者可以调用自己的密钥文件,并指定应用程序使用它来验证令牌。
jku URL -> file containing JWK set -> JWK used to verify the token
2. JWK头信息参数
可选的JWK(JSON Web Key)头信息参数允许攻击者将用于验证令牌的密钥直接嵌入到令牌中。
3. X5U,X5C URL操作
和JKU和JWK头信息类似,X5U和X5C头信息参数允许攻击者指定用于验证令牌的公钥证书或证书链。其中,X5U以URI形式指定信息,而X5C则允许将证书值嵌入令牌中。
0x09 其他JWT安全问题
如果没有正确应用JWT,还会产生其他安全问题。这些虽然不是很常见,但是也绝对需要注意:
1.信息泄漏
由于JWT用于访问控制,因此它们通常包含有关用户的信息。
如果令牌未加密,则任何人都可以通过base64解码令牌并读取令牌的消息体。因此,如果令牌中包含敏感信息,则它可能成为信息泄漏的来源。因为JWT正确运用的签名部分可以保证提供数据的完整性,而不是其保密性。
2.命令注入
有时当KID参数直接传递到不安全的文件读取操作中时,可能会将命令注入代码流中。
可能允许这种类型的攻击函数之一是Ruby open()函数。此函数使攻击者只需在KID文件名之后将命令添加到输入,即可执行系统命令:
“key_file” | whoami;
这只是一个例子,从理论上讲,每当应用程序将任何未清理过的头信息参数传递到类似system()、exec()等的任何函数中时,都会出现这样的漏洞。
总而言之,JWT只是用户输入的另一种形式。我们应该始终对它们保持怀疑,并严格地清理它们。
References:
https://tools.ietf.org/html/draft-ietf-oauth-jwt-bcp-07
https://en.wikipedia.org/wiki/JSON_Web_Token
https://medium.com/swlh/hacking-json-web-tokens-jwts-9122efe91e4a