JWT攻击
翻译
原文:https://portswigger.net/web-security/jwt
- name: 翻译
desc: 原文:https://portswigger.net/web-security/jwt
bgColor: '#F0DFB1'
textColor: 'green'
2
3
4
# 0JWT攻击
在本节中,我们将探讨 JSON Web Tokens(JWTs)的设计问题,以及有 “缺陷的处理” 是如何让网站暴露于各种高严重性攻击之下的。由于 JWTs 最常用于身份验证、会话管理和访问控制机制,因此这些漏洞可能会危及整个网站及其用户。
如果你不熟悉 JWTs 及其工作原理,请不要担心 - 我们将介绍所有相关细节。我们还提供了一些故意易受攻击的实验室,以便你可以练习如何利用这些漏洞,并安全地攻击真实目标。
实验室
如果你已经熟悉 JWT 攻击背后的基本概念,并且只想在一些实际的、易受攻击的目标上练习和利用它们,那么你可以从下面的链接访问本主题中的所有实验室。
提示
从 Burp Suite Professional 2022.5.1 (opens new window) 开始,Burp Scanner (opens new window) 可以帮助你自动检测 JWT 机制中的许多漏洞。有关详细信息,请参阅 “Target > Issued definitions” 选项卡上的相关问题定义。
# 1JWTs是什么?
JSON Web Tokens(JWTs)是一种标准化格式,用于在系统之间发送经过加密签名的 JSON 数据。但从理论上讲,它们可以包含任何类型的数据,该格式最常用于发送有关用户信息的(“声明”),作为身份验证、会话处理和访问控制机制的一部分。
与传统的会话令牌不同,服务器所需的一切数据都存储在客户端本身的 JWT 中。这使得 JWTs 成为大型分布式网站的热门选择,在这些网站中,用户需要与多个后端服务器无缝交互。
# 1.1JWT格式
JWT 由 3 部分组成:标头、有效信息和签名。它们分别由一个点号分隔,如以下示例所示:
eyJraWQiOiI5MTM2ZGRiMy1jYjBhLTRhMTktYTA3ZS1lYWRmNWE0NGM4YjUiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJwb3J0c3dpZ2dlciIsImV4cCI6MTY0ODAzNzE2NCwibmFtZSI6IkNhcmxvcyBNb250b3lhIiwic3ViIjoiY2FybG9zIiwicm9sZSI6ImJsb2dfYXV0aG9yIiwiZW1haWwiOiJjYXJsb3NAY2FybG9zLW1vbnRveWEubmV0IiwiaWF0IjoxNTE2MjM5MDIyfQ.SYZBPIBg2CRjXAJ8vCER0LA_ENjII1JakvNQoP-Hw6GG1zfl4JyngsZReIfqRvIAEi5L4HV0q7_9qGhQZvy9ZdxEJbwTxRs_6Lb-fZTDpW6lKYNdMyjw45_alSCZ1fypsMWz_2mTpQzil0lOtps5Ei_z7mM7M8gCwe_AGpI53JxduQOaB5HkT5gVrv9cKu9CsW5MS6ZbqYXpGyOG5ehoxqm8DL5tFYaW3lB50ELxi0KsuTKEbD0t5BCl0aCR2MBJWAbN-xeLwEenaqBiwPVvKixYleeDQiBEIylFdNNIMviKRgXiYuAvMziVPbwSgkZVHeEdF5MQP1Oe2Spac-6IfA
JWT 的标头和有效信息部分只是 base64url 编码的 JSON 对象。标头包含关于令牌本身的元数据,而有效信息包含关于用户的实际 “声明”。例如,你可以从上面的令牌中解码有效信息,从而得到以下声明:
{
"iss": "portswigger",
"exp": 1648037164,
"name": "Carlos Montoya",
"sub": "carlos",
"role": "blog_author",
"email": "carlos@carlos-montoya.net",
"iat": 1516239022
}
在大多数情况下,任何有权访问令牌的人,都可以轻松读取或修改这些数据。因此,任何基于 JWT 机制的安全性都严重依赖于加密签名。
# 1.2JWT签名
通常,颁发令牌的服务器会对 标头和有效信息 进行哈希处理,从而生成签名。在某些情况下,它们还会对生成的哈希值进行加密。无论是哪种生成方式,签名的过程都需要用到私有签名密钥。这种机制为服务器提供了一种方法,可用于验证令牌中的数据是否从未被篡改:
- 由于签名源自令牌的前两个部分,因此更改标头或有效信息中的单个字节时,会导致签名不匹配。
- 如果不知道服务器的私有签名密钥,则无法为给定标头或有效信息生成正确的签名。
提示
如果你想更好地了解 JWTs 的构造方式,可以使用jwt.io (opens new window)上的调试器来测试任意令牌。
# 1.3JWT vs JWS vs JWE
JWT 规范实际上非常有限。它只定义了一种格式,用于将信息(“声明”)表示为 JSON 对象,并在两方之间传输。在实践中,JWTs 指的并不是一个单独的个体,应该说是各种规范的集体实现。JWT 规范由 JSON Web Signature(JWS)和 JSON Web Encryption(JWE) 规范进行扩展,这些规范定义了实现 JWTs 的具体方式。
换句话说,JWT 通常是指 JWS 或 JWE 令牌。当人们使用术语 “JWT” 时,他们几乎总是指向 JWS 令牌。JWEs 与其非常相似,只不过令牌的实际内容是加密的,而不仅仅是编码。
笔记
为简单起见,在以下这些材料中,“JWT” 主要是指 JWS 令牌,尽管所描述的某些漏洞也可能适用于 JWE 令牌。
# 2什么是JWT攻击?
JWT 攻击会将修改过后的 JWTs 发送到服务器,以实现恶意目的。通常,攻击者会冒充另一个已通过身份验证的用户,从而绕过身份验证和访问控制 (opens new window)。
# 3JWT攻击的影响是什么?
JWT 攻击的影响通常很严重。如果攻击者能够使用任意值 来创建自己的有效令牌,则他们可以提升自己的权限或冒充其他用户,从而完全控制受害帐户。
# 4JWT攻击这种漏洞是如何产生的?
在应用程序本身中,JWT 漏洞通常是由存在缺陷的 JWT 处理引起的。与 JWTs 相关的各种规范 (opens new window)在设计上相对灵活,允许网站开发人员自定义许多实现细节。这可能导致他们意外地引入漏洞,就算使用久经沙场的库也是如此。
- 这些实现缺陷通常是 JWT 签名没有得到正确验证。这使得攻击者可以操纵令牌中的有效信息,篡改传递给应用程序的值。
- 即使签名得到了正确的验证,但该签名是否真的可以被信任?这在很大程度上取决于服务器的密钥是否仍然保持着机密性。如果此密钥以某种方式泄露,或者可以被猜测以及暴力破解,则攻击者可以为任何令牌生成有效签名,从而危害整个机制。
# 5如何在Burp Suite中使用JWTs
如果你以前没有使用过 JWTs,我们建议你在尝试本主题的实验之前,先熟悉 Burp Suite 的相关功能。
# 6利用有缺陷的JWT签名验证
根据设计,服务器通常不会存储关于 JWTs 的任何信息。相反,每个令牌都是一个完全独立的实体。这有几个优点,但也引入了一个基本问题——服务器对令牌的原始内容一无所知,甚至不知道原始签名是什么。因此,如果服务器未能正确验证签名,则无法阻止攻击者对令牌其余部分的篡改。
例如,考虑包含以下声明的 JWT:
{
"username": "carlos",
"isAdmin": false
}
2
3
4
如果服务器基于username
来标识会话,则修改其值之后,攻击者可能冒充其他登录用户。类似地,如果isAdmin
的值用于访问控制,则可以将其视为 权限提升 的一个简单向量。
在前几个实验中,你将看到一些示例,展示这些漏洞在实际应用程序中的样子。
# 6.1接受任意签名
JWT 库通常会提供两种方法,一种用于验证令牌,另一种用于解码令牌。例如 Node.js 中的jsonwebtoken
库具有verify()
和decode()
方法。
有时,开发人员会搞混这两个方法,只将传入的令牌传递给decode()
方法。这意味着,应用程序根本不会验证签名(只解码 不验证)。
- name: 实验室-学徒
desc: 通过未经验证的签名绕过JWT身份验证 >>
avatar: https://fastly.statically.io/gh/clincat/blog-imgs@main/vuepress/static/imgs/docs/burpsuite-learn/public/lab-logo.png
link: https://portswigger.net/web-security/jwt/lab-jwt-authentication-bypass-via-unverified-signature
bgColor: '#001350'
textColor: '#39d50c'
2
3
4
5
6
# 6.2接受没有签名的令牌
除此之外,JWT 标头包含一个alg
参数。这会告诉服务器使用哪种 算法 对令牌进行签名。相应的,在验证签名时也需要使用同一种算法。
{
"alg": "HS256",
"typ": "JWT"
}
2
3
4
这本身就存在缺陷,因为服务器别无选择,只能隐式信任来自令牌中的用户可控输入,而此时,令牌还没有经过验证。换句话说,攻击者可以直接影响服务器检查令牌的方式。
JWTs 可以使用一系列不同的算法进行签名,但也可以不签名。在这种情况下,alg
参数会设置为none
,表示所谓的 “不安全 JWT”。出于这种明显的危险性,服务器通常会拒绝没有签名的令牌。但是,由于这种过滤方式依赖于字符串解析,所以有时可以使用经典的混淆技术(例如混合大写和意外编码)来绕过这些过滤器。
笔记
即使令牌未得到签名(空签名),有效信息部分仍然以点号结尾。
- name: 实验室-学徒
desc: 通过有缺陷的签名验证绕过JWT身份验证 >>
avatar: https://fastly.statically.io/gh/clincat/blog-imgs@main/vuepress/static/imgs/docs/burpsuite-learn/public/lab-logo.png
link: https://portswigger.net/web-security/jwt/lab-jwt-authentication-bypass-via-flawed-signature-verification
bgColor: '#001350'
textColor: '#39d50c'
2
3
4
5
6
# 7密钥暴力破解
某些签名算法,例如 HS256(HMAC + SHA-256)使用任意的独立字符串作为密钥。就像密码一样,这个密钥不能被攻击者轻易猜到或强行破解,这一点至关重要。否则,他们可以使用他们喜欢的任何标头和有效信息值来创建 JWTs,然后使用密钥生成有效签名,并对令牌进行重新签名。
在实现 JWT 应用程序时,开发人员有时会犯一些错误,例如忘记更改默认值 或 密钥占位符。他们甚至可能 复制并粘贴 他们在网上找到的代码片段,然后忘记更改其中用作示例的硬编码密钥。在这种情况下,攻击者可以使用已知的密钥单词列表 (opens new window),从而简单地暴力破解服务器上的密钥。
# 7.1使用hashcat暴力破解密钥
我们建议使用 hashcat 来暴力破解密钥。你可以手动安装 hashcat (opens new window),也可以在 Kali Linux 上直接使用它。
笔记
如果你使用的是 Kali 的 VirtualBox 预构建镜像,而不是裸机安装版本,则可能没有足够的内存来运行 hashcat。
你只需要一个来自目标服务器的有效的、已签名的 JWT 和一个已知的密钥单词列表 (opens new window)。然后,你可以运行以下命令,将 JWT 和单词列表作为参数传入:
hashcat -a 0 -m 16500 <jwt> <wordlist>
Hashcat 使用单词列表中的每个密钥来对 JWT 的标头和有效信息进行签名,然后将生成的签名 与 来自服务器的原始签名进行比较。如果签名匹配,hashcat 将按以下格式输出已识别的密钥,以及各种其他详细信息:
<jwt>:<identified-secret>
笔记
如果多次运行该命令,则需要包含--show
标志以输出结果。
由于 hashcat 在你的机器上本地运行,并且不会向服务器发送请求,所以就算使用大体量的单词列表,这个过程也非常快。
确定密钥之后,你可以使用该密钥为你喜欢的任何 JWT 标头和信息生成有效签名。有关如何在 Burp Suite 中对修改后的 JWT 重新签名,请参阅编辑 JWTs (opens new window)。
- name: 实验室-从业者
desc: 通过弱签名密钥绕过JWT身份验证 >>
avatar: https://fastly.statically.io/gh/clincat/blog-imgs@main/vuepress/static/imgs/docs/burpsuite-learn/public/lab-logo.png
link: https://portswigger.net/web-security/jwt/lab-jwt-authentication-bypass-via-weak-signing-key
bgColor: '#001350'
textColor: '#4cc1ff'
2
3
4
5
6
如果服务器使用一个极其脆弱的密钥,你甚至可以逐个字符进行暴力破解,而不是使用单词列表。
# 8JWT标头参数注入
根据 JWS 规范,只有alg
标头参数是强制性的。但是在实践中,JWT 标头(也称为 JOSE 标头)通常包含其他几个参数。攻击者对以下内容特别感兴趣。
jwk
(JSON Web Key)- 嵌入一个 JSON 对象,用于表示密钥。jku
(JSON Web Key Set URL)- 提供一个 URL,服务器可以从中获取包含正确密钥的一组密钥。kid
(Key ID)- 提供一个 ID,在有多个密钥可供选择的情况下,服务器可以使用该 ID 来标识正确的密钥。根据密钥格式的不同,应该具有一个相匹配的kid
参数。
如你所见,这些用户可控参数 分别告诉接收方服务器 在验证签名时要使用哪个密钥。在本节中,你将学习如何利用这些特性,来注入你自己的任意密钥(而不是服务器的密钥)以及经过签名的篡改后的 JWTs。
# 8.1通过jwk参数注入自签名JWTs
JSON Web Signature(JWS)规范描述了一个可选的jwk
标头参数,服务器可以通过该参数将 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"
}
}
2
3
4
5
6
7
8
9
10
11
公钥和私钥
如果你不熟悉 “公钥” 和 “私钥” 这两个术语,我们已将其作为 “算法混淆攻击” 章节的一部分进行了介绍。有关详细信息,请参阅对称算法 vs 非对称算法 (opens new window)。
理想情况下,服务器应该使用有限的公钥白名单来验证 JWT 签名。但有时,配置错误的服务器会使用jwk
参数中嵌入的任何密钥。
你可以使用自己的 RSA 私钥对修改后的 JWT 进行签名,然后在jwk
标头中嵌入匹配的公钥来利用此行为。
尽管你可以在 Burp 中手动添加或修改jwk
参数,但 JWT Editor 扩展 (opens new window)提供了一个有用的功能,可以帮助你测试此漏洞:
- 加载扩展程序后,从 Burp 的主选项卡转到 “JWT Editor Keys” 选项卡。
- 生成新的 RSA 密钥 (opens new window)。
- 将包含 JWT 的请求发送到 Burp Repeater。
- 在消息编辑器中,切换到扩展提供的 “JSON Web Token” 选项卡,并根据需要修改 (opens new window)令牌的有效信息。
- 单击 “Attack”,然后选择 “Embedded JWK”。出现提示时,选择新生成的 RSA 密钥。
- 发送请求,测试目标服务器的响应情况。
你也可以自己添加jwk
标头来手动执行此攻击。但是,你同时还需要更新 JWT 的kid
标头参数,以匹配嵌入密钥的kid
。使用扩展的内置攻击项时,将会为你自动处理这一步。
- name: 实验室-从业者
desc: 通过jwk标头注入绕过JWT身份验证 >>
avatar: https://fastly.statically.io/gh/clincat/blog-imgs@main/vuepress/static/imgs/docs/burpsuite-learn/public/lab-logo.png
link: https://portswigger.net/web-security/jwt/lab-jwt-authentication-bypass-via-jwk-header-injection
bgColor: '#001350'
textColor: '#4cc1ff'
2
3
4
5
6
# 8.2通过jku参数注入自签名JWTs
一些服务器允许你使用jku
(JWK Set URL)标头参数来引用包含密钥的 JWK 集合(JWK Set),而无需单独使用jwk
标头参数来嵌入公钥。在验证签名时,服务器会从此 URL 加载相关密钥。
JWK Set
JWK Set(JWK 集合)是一个 JSON 数组,存储着不同的 JWKs 密钥对象。你可以在下面看到一个示例。
{
"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"
}
]
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
像这样的 JWK 集合有时会通过一个标准端点来公开,例如/.well-known/jwks.json
。
较安全的网站只会从受信任的域获取密钥,但有时你可以利用 URL 解析的差异来绕过这种过滤。我们在 SSRF (opens new window) 主题中介绍了一些类似的示例 (opens new window)。
- name: 实验室-从业者
desc: 通过jku标头注入绕过JWT身份验证 >>
avatar: https://fastly.statically.io/gh/clincat/blog-imgs@main/vuepress/static/imgs/docs/burpsuite-learn/public/lab-logo.png
link: https://portswigger.net/web-security/jwt/lab-jwt-authentication-bypass-via-jku-header-injection
bgColor: '#001350'
textColor: '#4cc1ff'
2
3
4
5
6
# 8.3通过kid参数注入自签名JWTs
服务器可以使用多个加密密钥,来对不同类型的数据进行签名,而不仅仅是使用 JWTs。出于此目的,JWT 的标头中可能包含一个kid
(密钥 ID)参数,在验证签名时,该参数告诉服务器要使用哪一个密钥。
用于验证的密钥通常存储为一个 JWK 集。在这种情况下,服务器可能会根据令牌来查找相同子项的 JWK。但是,JWS 规范并没有为这种 ID 定义具体结构 - 它可以是开发人员选择的一个任意字符串。例如,他们可能使用kid
参数来直接指向数据库中的特定条目,甚至是文件名。
如果此参数同时容易受到目录遍历 (opens new window)的攻击,则攻击者可以强制服务器 使用其文件系统上的任意文件 作为验证密钥。
{
"kid": "../../path/to/file",
"typ": "JWT",
"alg": "HS256",
"k": "asGsADas3421-dfh9DGN-AFDFDbasfd8-anfjkvc"
}
2
3
4
5
6
如果服务器支持使用对称算法 (opens new window)进行 JWTs 签名,则这尤其危险。在这种情况下,攻击者可以将kid
参数指向一个可预测的静态文件,然后使用与此文件内容相匹配的密钥,来对 JWT 进行签名。
理论上,你可以使用任何文件来执行此操作,但最简单的方法之一是使用/dev/null
,这在大多数 Linux 系统上都存在。这是一个空文件,所以读取它时将返回一个空字符串。因此,你可以使用空字符串对令牌进行签名,从而生成有效的签名。
笔记
请注意,如果使用的是 JWT Editor 扩展,该扩展不允许你使用空字符串对令牌进行签名。但是,由于扩展中的一个 Bug,你可以使用经过 Base64 编码的空字节来解决这个问题。
- name: 实验室-从业者
desc: 通过kid标头路径遍历绕过JWT身份验证 >>
avatar: https://fastly.statically.io/gh/clincat/blog-imgs@main/vuepress/static/imgs/docs/burpsuite-learn/public/lab-logo.png
link: https://portswigger.net/web-security/jwt/lab-jwt-authentication-bypass-via-kid-header-path-traversal
bgColor: '#001350'
textColor: '#4cc1ff'
2
3
4
5
6
如果服务器将其验证密钥存储在数据库中,则kid
标头参数也是 SQL 注入 (opens new window)攻击的潜在向量。
# 8.4其他有趣的JWT标头参数
攻击者也可能对以下标头参数感兴趣:
cty
(Content Type,内容类型)- 有时用于声明 JWT 有效信息内容的媒体类型。这通常在标头中被省略,但底层解析库可能仍然支持它。如果你找到了绕过签名验证的方法,你可以尝试注入cty
标头,以将内容类型更改为text/xml
或application/x-java-serialized-object
,这可能会为 XXE (opens new window) 和反序列化 (opens new window)开辟新的攻击向量。x5c
(X.509 Certificate Chain,X.509 证书链)- 用于传递对 JWT 进行数字签名的密钥所属的 X.509 公钥证书或证书链。此标头参数可用于注入自签名证书,类似于上面讨论的jwk
标头注入 (opens new window)攻击。由于 X.509 格式及其扩展的复杂性,解析这些证书还可能引入其他漏洞。这些攻击的细节超出了本材料的范围,有关更多详细信息,请参阅 CVE-2017-2800 (opens new window) 和 CVE-2018-2633 (opens new window)。
# 9JWT算法混淆
即使服务器使用了可靠的密钥,致使你无法暴力破解,你仍然可以使用开发人员未预料到的算法,来对令牌进行签名并伪造有效的 JWTs。这被称为算法混淆攻击。 阅读更多
# 10如何防范JWT攻击
你可以采取以下高级措施来保护自己的网站,使其免受我们介绍的许多攻击:
- 使用最新的库来处理 JWTs,并确保开发人员完全了解其工作原理 以及 任何安全隐患。现代库能使你更安全地实现功能,但由于相关规范固有的灵活性,这并不是万无一失的。
- 确保对收到的任何 JWTs 执行可靠的签名验证,并考虑罕见情况,例如使用意外算法签名的 JWTs。
- 对于
jku
标头所允许的主机域,实施严格的白名单。 - 保护
kid
标头参数,使其不易受到路径遍历或 SQL 注入攻击。
# 10.1处理JWT的其他最佳实践
虽然避免漏洞并非绝对必要,但我们建议在应用程序中使用 JWTs 时遵循以下最佳实践:
- 始终为你颁发的任何令牌设置过期时间。
- 尽可能避免在 URL 参数中发送令牌。
- 同步发送
aud
(受众)声明以指定令牌的预期接收者(也可以是类似的声明)。这样可以阻止它在不同的网站上被使用。 - 颁发服务器能够吊销令牌(例如,在登出时让令牌过期)。