JWT (JSON Web Tokens)

什么是 JWT?

JSON Web Tokens (JWT) 是一种用于在系统之间发送加密签名 JSON 数据的标准化格式。理论上,它们可以包含任何类型的数据,但最常用于发送有关用户的信息("声明"),作为身份验证、会话处理和访问控制机制的一部分。

与传统的会话令牌不同,服务器所需的所有数据都存储在客户端的 JWT 本身中。这使得 JWT 成为高度分布式网站的热门选择,因为用户需要与多个后端服务器无缝交互。

JWT格式

JWT 由三部分组成:头部、有效载荷和签名。这三部分之间用点号分隔,如下例所示: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 的机制的安全性都高度依赖于加密签名。

JWT签名

颁发令牌的服务器通常通过对令牌头部和有效载荷进行哈希运算来生成签名。在某些情况下,他们还会对生成的哈希值进行加密。无论哪种方式,此过程都涉及一个秘密签名密钥。这种机制使服务器能够验证令牌中的数据自颁发以来是否被篡改过:

  • 由于签名直接源自令牌的其余部分,因此更改标头或有效负载中的单个字节会导致签名不匹配。
  • 如果不知道服务器的秘密签名密钥,就不可能为给定的标头或有效负载生成正确的签名。
提示

JWT vs JWS vs JWE

JWT 规范实际上非常有限。它仅定义了一种将信息("声明")表示为 JSON 对象的格式,以便在双方之间传输。实际上,JWT 很少作为独立实体使用。JWT 规范由 JSON Web Signature (JWS) 和 JSON Web Encryption (JWE) 规范扩展,后者定义了 JWT 的具体实现方法。

换句话说,JWT 通常是 JWS 或 JWE 令牌。人们提到"JWT"时,几乎总是指 JWS 令牌。JWE 与 JWS 非常相似,区别在于 JWE 令牌的实际内容是加密的,而不仅仅是编码的。

笔记

为简便起见,本文中"JWT"主要指JWS令牌,尽管所描述的一些漏洞也可能适用于JWE令牌。

什么是JWT攻击?

JWT攻击是指用户向服务器发送篡改后的JWT以达到恶意目的。通常,此目的是通过冒充已通过身份验证的用户来绕过身份验证和访问控制。

JWT攻击会造成哪些影响?

JWT攻击的影响通常十分严重。如果攻击者能够创建具有任意值的有效令牌,他们就可能提升自身权限或冒充其他用户,从而完全控制这些用户的帐户。

JWT攻击的漏洞是如何产生的?

JWT漏洞通常是由于应用程序自身对JWT的处理存在缺陷而导致的。JWT相关的各种规范在设计上相对灵活,允许网站开发人员自行决定许多实现细节。即使使用久经考验的库,这也可能导致他们意外引入漏洞。

这些实现缺陷通常意味着 JWT 的签名未得到正确验证。这使得攻击者能够篡改通过令牌有效负载传递给应用程序的值。即使签名经过了严格的验证,其可信度也很大程度上取决于服务器密钥的保密性。如果该密钥以某种方式泄露,或者可以被猜测或暴力破解,攻击者就可以为任何任意令牌生成有效的签名,从而破坏整个机制。

利用 JWT 签名验证的缺陷

根据设计,服务器通常不会存储任何关于其颁发的 JWT 的信息。相反,每个令牌都是一个完全独立的实体。这有诸多优势,但也引入了一个根本性问题------服务器实际上并不了解令牌的原始内容,甚至不知道原始签名是什么。因此,如果服务器未能正确验证签名,攻击者就可以随意修改令牌的其余部分。

例如,考虑一个包含以下声明的 JWT:

​{ "username": "carlos", "isAdmin": false }​如果服务器基于此值识别会话username​,那么修改此值可能使攻击者能够冒充其他已登录用户。同样,如果此isAdmin​值用于访问控制,则这可能提供一个简单的权限提升途径。

在前几个实验中,您将看到一些示例,了解这些漏洞在实际应用中可能出现的情况。

接受任意签名

JWT 库通常提供一种验证令牌的方法和另一种解码令牌的方法。例如,Node.js 库jsonwebtoken​就提供了verify()​这两种方法decode()​。

有时,开发者会将这两种方法混淆,只将传入的令牌传递给其中一种decode()​方法。这实际上意味着应用程序根本没有验证签名。

实验室学徒通过未经验证的签名绕过 JWT 身份验证

  1. 在实验室里,登录你自己的账号。
  2. 在 Burp 中,转到"代理">"HTTP 历史记录"选项卡,查看登录后的GET /my-account请求。注意您的会话 cookie 是一个 JWT。
  3. 双击令牌的有效负载部分,即可在检查器面板中查看其解码后的 JSON 格式。请注意,sub声明中包含您的用户名。将此请求发送到 Burp Repeater。
  4. 在 Burp Repeater 中,更改路径/admin并发送请求。请注意,只有以该administrator用户身份登录后才能访问管理面板。
  5. 再次选择 JWT 的有效负载。在检查器面板中,将声明的值sub从wiener更改为administrator,然后单击"应用更改"。
  6. 再次发送请求。请查看是否已成功访问管理面板。
  7. 在响应中找到用于删除的 URL carlos(/admin/delete?username=carlos)。向此端点发送请求以完成实验。

接受无签名令牌

JWT 标头包含一个参数,alg​该参数会告知服务器用于签署令牌的算法,从而确定服务器在验证签名时需要使用的算法。

​{ "alg": "HS256", "typ": "JWT" }​这种方法本身就存在缺陷,因为服务器别无选择,只能默认信任用户通过令牌输入的信息,而这些信息此时尚未经过任何验证。换句话说,攻击者可以直接影响服务器检查令牌可信度的方式。

JWT 可以使用多种不同的算法进行签名,也可以不签名。在这种情况下,参数alg​设置为 false​ none​,表示所谓的"未安全 JWT"。由于这种做法存在明显的危险,服务器通常会拒绝未签名的令牌。然而,由于这种过滤依赖于字符串解析,因此有时可以使用经典的混淆技术(例如混合大小写和非标准编码)来绕过这些过滤器。

即使令牌未签名,有效载荷部分仍然必须以尾随点号结尾。

实验室学徒通过有缺陷的签名验证绕过 JWT 身份验证

  1. 在实验室里,登录你自己的账号。
  2. 在 Burp 中,转到"代理">"HTTP 历史记录"选项卡,查看登录后的GET /my-account请求。注意您的会话 cookie 是一个 JWT。
  3. 双击令牌的有效负载部分,即可在检查器面板中查看其解码后的 JSON 格式。请注意,sub声明中包含您的用户名。将此请求发送到 Burp Repeater。
  4. 在 Burp Repeater 中,更改路径/admin并发送请求。请注意,只有以该administrator用户身份登录后才能访问管理面板。
  5. 再次选择 JWT 的有效负载。在"检查器"sub面板中,将声明的值更改为administrator,然后单击"应用更改"。
  6. 选择 JWT 的标头,然后使用检查器将参数值更改alg为none。单击"应用更改"。
  7. 在消息编辑器中,删除 JWT 的签名,但请记住在有效负载后保留尾部的点。
  8. 发送请求后,请确认您已成功访问管理面板。
  9. 在响应中找到用于删除的 URL carlos(/admin/delete?username=carlos)。向此端点发送请求以完成实验。

暴力破解密钥

某些签名算法,例如 HS256(HMAC + SHA-256),使用任意的独立字符串作为密钥。就像密码一样,至关重要的是,这个密钥不能轻易被攻击者猜到或暴力破解。否则,攻击者就可以创建任意头部和有效载荷值的 JWT,然后使用该密钥重新签名,从而获得有效的签名。

在实现 JWT 应用时,开发人员有时会犯一些错误,例如忘记更改默认密钥或占位符密钥。他们甚至可能直接复制粘贴从网上找到的代码片段,然后忘记更改示例中提供的硬编码密钥。在这种情况下,攻击者很容易使用包含已知密钥的字典暴力破解服务器密钥。

使用 hashcat 暴力破解密钥

我们建议使用 hashcat 来暴力破解密钥。您可以手动安装 hashcat,但它也预装在 Kali Linux 系统中,可以直接使用。

您只需要来自目标服务器的有效且已签名的 JWT 和一个包含已知密钥的字典。然后,您可以运行以下命令,并将 JWT 和字典作为参数传递给它:

​hashcat -a 0 -m 16500 <jwt> <wordlist>​Hashcat 使用字典中的每个密钥对 JWT 的头部和有效载荷进行签名,然后将生成的签名与服务器返回的原始签名进行比较。如果任何签名匹配,Hashcat 将以以下格式输出已识别的密钥以及其他各种详细信息:

​<jwt>:<identified-secret>​#### 笔记

如果多次运行该命令,则需要添加--show​标志以输出结果。

由于 hashcat 在您的机器上本地运行,并且不依赖于向服务器发送请求,因此即使使用庞大的字典,此过程也非常快。

一旦您确定了密钥,就可以使用它为任何您想要的 JWT 标头和有效负载生成有效的签名。有关如何在 Burp Suite 中重新签名修改后的 JWT 的详细信息,请参阅"编辑 JWT"

实验室从业者利用弱签名密钥绕过 JWT 身份验证

第一部分 - 暴力破解密钥
  1. 在 Burp 中,从 BApp 商店加载 JWT Editor 扩展。
  2. 在实验室中,登录您自己的帐户,并将登录后GET /my-account请求发送到 Burp Repeater。
  3. 在 Burp Repeater 中,更改路径/admin并发送请求。请注意,只有以该administrator用户身份登录后才能访问管理面板。
  4. 复制 JWT 并暴力破解密钥。您可以使用 hashcat 来完成此操作,如下所示:
    hashcat -a 0 -m 16500 <YOUR-JWT> /path/to/jwt.secrets.list如果你使用 hashcat,它会输出 JWT,然后是密钥。如果一切正常,这应该会显示弱密钥是secret1。
第二部分 - 生成伪造的签名密钥
  1. 使用 Burp Decoder,对你在上一节中暴力破解的密钥进行 Base64 编码。
  2. 在 Burp 中,转到JWT 编辑器的"密钥"选项卡,然后单击"新建对称密钥"。在对话框中,单击"生成"以生成 JWK 格式的新密钥。请注意,您无需选择密钥长度,稍后会自动更新。
  3. 将生成的属性值替换k为 Base64 编码的密钥。
  4. 点击"确定"保存密钥。
第三部分 - 修改并签署 JWT
  1. 返回GET /adminBurp Repeater 中的请求,切换到扩展生成的JSON Web Token消息编辑器选项卡。
  2. sub在有效载荷中,将声明 的值更改为administrator
  3. 在标签页底部,单击Sign,然后选择您在上一节中生成的密钥。
  4. 请确保Don't modify header已选中该选项,然后单击OK。修改后的令牌现在已使用正确的签名进行签名。
  5. 发送请求后,请确认您已成功访问管理面板。
  6. 在响应中找到用于删除的 URL carlos(/admin/delete?username=carlos)。向此端点发送请求以完成实验。

JWT头部参数注入

根据 JWS 规范,只有alg​头部参数是必需的。然而,在实践中,JWT 头部(也称为 JOSE 头部)通常包含其他几个参数。以下这些参数尤其值得攻击者关注。

  • jwk(JSON Web Key)- 提供一个表示密钥的嵌入式 JSON 对象。
  • jku(JSON Web 密钥集 URL) - 提供一个 URL,服务器可以从中获取包含正确密钥的密钥集。
  • kid(密钥 ID)- 提供一个 ID,服务器可以使用该 ID 在有多个密钥可供选择的情况下识别正确的密钥。根据密钥的格式,此 ID 可能具有匹配的kid参数。

通过 jwk 参数注入自签名 JWT

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" } }​#### 公钥和私钥

如果您不熟悉"公钥"和"私钥"这两个术语,我们在关于算法混淆攻击的资料中已经介绍过。更多信息,请参阅"对称算法与非对称算法"

理想情况下,服务器应该只使用有限的公钥白名单来验证 JWT 签名。然而,配置错误的服务器有时会使用jwk​参数中嵌入的任何密钥。

你可以利用这种行为,用你自己的 RSA 私钥对修改后的 JWT 进行签名,然后将匹配的公钥嵌入到jwk​标头中。

​jwk​虽然您可以在 Burp 中 手动添加或修改参数,但JWT Editor 扩展程序提供了一个有用的功能来帮助您测试此漏洞:

  1. 加载扩展程序后,在 Burp 的主标签栏中,转到JWT 编辑器密钥标签。
  2. 生成新的RSA密钥。
  3. 向 Burp Repeater 发送包含 JWT 的请求。
  4. 在消息编辑器中,切换到扩展生成的JSON Web Token选项卡,并根据需要 修改令牌的有效负载。
  5. 点击"攻击",然后选择"嵌入式 JWK"。出现提示时,选择您新生成的 RSA 密钥。
  6. 发送请求以测试服务器的响应情况。

您也可以手动添加jwk​请求头来执行此攻击。但是,您可能还需要更新 JWT 的kid​请求头参数,使其与嵌入的密钥匹配kid​。扩展程序的内置攻击功能会自动处理此步骤。

实验室从业者通过 JWT 头部注入绕过 JWT 身份验证

  1. 在 Burp 中,从 BApp 商店加载 JWT Editor 扩展。
  2. 在实验室中,登录您自己的帐户,并将登录后GET /my-account请求发送到 Burp Repeater。
  3. 在 Burp Repeater 中,更改路径/admin并发送请求。请注意,只有以该administrator用户身份登录后才能访问管理面板。
  4. 转到Burp 主标签栏中的 JWT 编辑器密钥选项卡。
  5. 点击"新建RSA密钥"。
  6. 在对话框中,单击"生成"以自动生成新的密钥对,然后单击"确定"保存密钥。请注意,您无需选择密钥长度,因为稍后会自动更新。
  7. 返回GET /adminBurp Repeater 中的请求,切换到扩展生成的JSON Web Token选项卡。
  8. sub在有效载荷中,将声明 的值更改为administrator。
  9. 在JSON Web Token选项卡 的底部,单击"攻击",然后选择"嵌入式 JWK"。出现提示时,选择您新生成的 RSA 密钥,然后单击"确定"。
  10. 在 JWT 的头部,可以看到jwk添加了一个包含您的公钥的参数。
  11. 发送请求。请查看是否已成功访问管理面板。
  12. 在响应中找到用于删除的 URL carlos(/admin/delete?username=carlos)。向此端点发送请求以完成实验。

通过 jku 参数注入自签名 JWT

​jwk​有些服务器 不直接在请求头参数中嵌入公钥,而是允许您使用jku​(JWK Set URL)请求头参数来引用包含密钥的 JWK Set。验证签名时,服务器会从该 URL 获取相关密钥。

实验室从业者通过注入 jku 标头绕过 JWT 身份验证

第一部分 - 上传恶意 JWK 集
  1. 在 Burp 中,从 BApp 商店加载 JWT Editor 扩展。

  2. 在实验室中,登录您自己的帐户,并将登录后GET /my-account​请求发送到 Burp Repeater。

  3. 在 Burp Repeater 中,更改路径/admin​并发送请求。请注意,只有以该administrator​用户身份登录后才能访问管理面板。

  4. 转到Burp 主标签栏中的 JWT 编辑器密钥选项卡。

  5. 点击​"新建RSA密钥"​。

  6. 在对话框中,单击"生成"以自动生成新的密钥对,然后单击"确定"保存密钥。请注意,您无需选择密钥长度,因为稍后会自动更新。

  7. 在浏览器中,访问漏洞利用服务器。

  8. 将Body部分 的内容替换为一个空的 JWK Set,如下所示:

    <span data-type="code">{

    "keys": [{

    "kty": "RSA",

    "e": "AQAB",

    "kid": "a33a117f-70fa-445e-bf2f-8d38f293bb45",

    "n": "5Iivk1KekMuc1JVU_bxO4WOw11E_oPHzO8qkt53F9Ap1aG9UzKLbO4kbZTJWQcjg_IdStLm41jbX8UcEfugnYQTDrcSmfxrgDIwPjV5v_tgW8gHK8dBEuFRZf8DRNW-ZO7aI1MFyxx2NXiE1izD3zSs4czEntVt5W4JS2byOZ0jpAsze7sOZlXHzmJHE3hPQsxP26Ys-NpBUPqlUMTDZdnsUFBicjPSClOp2eY_TtuVza0-197AC5-YVm-w9Leb0kGyXxM4qS5gByrkxciDl9zsFf1dfKKc5lHLOacG6KwYD-JWBEGh8aIDurGDQdf2UrHrIhxJOrDUnvYGjHHuYVw"

    }

    ]

    }</span>

  9. 返回JWT 编辑器密钥选项卡,右键单击刚刚生成的密钥条目,然后选择​"复制公钥为 JWK"​。

  10. 将 JWK 粘贴到keys​漏洞利用服务器上的数组中,然后存储漏洞利用程序。结果应该类似于这样:

    ​{ "keys": { "kty": "RSA", "e": "AQAB", "kid": "893d8f0b-061f-42c2-a4aa-5056e12b8ae7", "n": "yy1wpYmffgXBxhAUJzHHocCuJolwDqql75ZWuCQ_cb33K2vh9mk6GPM9gNN4Y_qTVX67WhsN3JvaFYw" } }​

第二部分 - 修改并签署 JWT
  1. 返回GET /adminBurp Repeater 中的请求,切换到扩展生成的JSON Web Token消息编辑器选项卡。
  2. 在 JWT 的标头中,将kid参数的当前值替换为kid您上传到漏洞利用服务器的 JWK 的值。
  3. jku在 JWT 的头部 添加一个新参数。将其值设置为漏洞利用服务器上 JWK 集的 URL。
  4. sub在有效载荷中,将声明 的值更改为administrator。
  5. 在选项卡的底部,单击"签名",然后选择您在上一节中生成的 RSA 密钥。
  6. 请确保选中"不修改标头"选项,然后单击"确定"。修改后的令牌现在已使用正确的签名进行签名。
  7. 发送请求。请查看是否已成功访问管理面板。
  8. 在响应中找到用于删除的 URL carlos(/admin/delete?username=carlos)。向此端点发送请求以完成实验。

通过 kid 参数注入自签名 JWT

服务器可能使用多个加密密钥来签署不同类型的数据,而不仅仅是 JWT。因此,JWT 的头部可能包含一个kid​(密钥 ID)参数,该参数有助于服务器在验证签名时确定要使用的密钥。

验证密钥通常以 JWK 集的形式存储。在这种情况下,服务器可以直接查找与kid​令牌相同的 JWK。然而,JWS 规范并未定义此 ID 的具体结构------它只是开发者选择的任意字符串。例如,他们可以使用该kid​参数指向数据库中的特定条目,甚至是文件名。

如果此参数也容易受到目录遍历攻击,攻击者可能会强制服务器使用其文件系统中的任意文件作为验证密钥。

​{ "kid": "../../path/to/file", "typ": "JWT", "alg": "HS256", "k": "asGsADas3421-dfh9DGN-AFDFDbasfd8-anfjkvc" }​如果服务器也支持使用对称算法签名的 JWT,这种情况尤其危险。攻击者可以将kid​参数指向一个可预测的静态文件,然后使用与该文件内容匹配的密钥对 JWT 进行签名。

理论上,你可以对任何文件执行此操作,但最简单的方法之一是使用 .ts​ /dev/null​,大多数 Linux 系统都自带此工具。由于这是一个空文件,读取它返回一个空字符串。因此,使用空字符串对令牌进行签名将得到有效的签名。

实验室从业者通过 KID 标头路径遍历绕过 JWT 身份验证

生成合适的签名密钥
  1. 在 Burp 中,从 BApp 商店加载 JWT Editor 扩展。
  2. 在实验室中,登录您自己的帐户,并将登录后GET /my-account请求发送到 Burp Repeater。
  3. 在 Burp Repeater 中,更改路径/admin并发送请求。请注意,只有以该administrator用户身份登录后才能访问管理面板。
  4. 转到Burp 主标签栏中的 JWT 编辑器密钥选项卡。
  5. 点击新建对称键。
  6. 在对话框中,单击"生成"以生成 JWK 格式的新密钥。请注意,您无需选择密钥长度,稍后系统会自动更新密钥长度。
  7. 将属性的生成值替换k为 Base64 编码的空字节 ( AA==)。请注意,这只是一个权宜之计,因为 JWT 编辑器扩展不允许您使用空字符串对令牌进行签名。
  8. 点击"确定"保存密钥。
修改并签署 JWT
  1. 返回GET /adminBurp Repeater 中的请求,切换到扩展生成的JSON Web Token消息编辑器选项卡。
  2. 在 JWT 的头部,将参数值更改kid为指向该/dev/null文件的路径遍历序列:
    ../../../../../../../dev/null
  3. sub在 JWT 有效负载中,将声明 的值更改为administrator。
  4. 在选项卡的底部,单击"签名",然后选择您在上一节中生成的对称密钥。
  5. 请确保选中"不修改标头"选项,然后单击"确定"。修改后的令牌现在使用空字节作为密钥进行签名。
  6. 发送请求后,请确认您已成功访问管理面板。
  7. 在响应中找到用于删除的 URL carlos(/admin/delete?username=carlos)。向此端点发送请求以完成实验。

算法混淆漏洞是如何产生的?

算法混淆漏洞通常是由于 JWT 库的实现存在缺陷而导致的。尽管实际的验证过程会因所使用的算法而异,但许多库都提供了一种与算法无关的单一签名验证方法。这些方法依赖于alg​令牌头部中的参数来确定其应执行的验证类型。

以下伪代码展示了verify()​JWT 库中此通用方法的声明的简化示例:

​function verify(token, secretOrPublicKey){ algorithm = token.getAlgHeader(); if(algorithm == "RS256"){ // Use the provided key as an RSA public key } else if (algorithm == "HS256"){ // Use the provided key as an HMAC secret key } }​当网站开发者随后使用此方法时,如果假设它只会处理使用非对称算法(例如 RS256)签名的 JWT,就会出现问题。由于这种错误的假设,他们可能会始终向该方法传递一个固定的公钥,如下所示:

​publicKey = <public-key-of-server>; token = request.getCookie("session"); verify(token, publicKey);​在这种情况下,如果服务器收到一个使用对称算法(例如 HS256)签名的令牌,库的通用verify()​方法会将公钥视为 HMAC 密钥。这意味着攻击者可以使用 HS256 算法和该公钥对令牌进行签名,而服务器将使用同一个公钥来验证签名。

笔记

用于签署令牌的公钥必须与服务器上存储的公钥完全相同。这包括使用相同的格式(例如 X.509 PEM)并保留所有非打印字符,例如换行符。实际上,您可能需要尝试不同的格式才能使这种攻击奏效。

执行算法混淆攻击

算法混淆攻击通常包括以下几个主要步骤:

  1. 获取服务器的公钥
  2. 将公钥转换为合适的格式
  3. 创建一个恶意 JWT,其有效负载已修改,并且alg标头设置为HS256。
  4. 使用公钥作为密钥, 通过 HS256 对令牌进行签名。

在本节中,我们将更详细地介绍这个过程,演示如何使用 Burp Suite 执行这种类型的攻击。

步骤 1 - 获取服务器的公钥

​/jwks.json​服务器有时会通过映射到 http://localhost:8000​或http://localhost:8000​ 等 标准端点,以 JSON Web Key (JWK) 对象的形式公开其公钥/.well-known/jwks.json​。这些公钥可能存储在一个名为 JWKSet​ 的 JWK 数组中keys​。这被称为 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" } }​即使密钥没有公开,您也可以从一对现有的 JWT 中提取它

步骤 2 - 将公钥转换为合适的格式

尽管服务器可能以 JWK 格式公开其公钥,但在验证令牌签名时,它会使用存储在本地文件系统或数据库中的密钥副本。该密钥副本的存储格式可能与服务器公开的公钥不同。

为了使攻击奏效,用于签署 JWT 的密钥版本必须与服务器本地副本完全相同。除了格式相同之外,每个字节都必须匹配,包括所有非打印字符。

在本例中,我们假设需要 X.509 PEM 格式的密钥。您可以使用Burp 中的JWT Editor扩展将 JWK 转换为 PEM,方法如下:

  1. 加载扩展程序后,在 Burp 的主标签栏中,转到JWT 编辑器密钥标签。
  2. 点击"新建RSA密钥"。在对话框中,粘贴您之前获取的JWK密钥。
  3. 选择PEM单选按钮并复制生成的 PEM 密钥。
  4. 转到解码器选项卡,对 PEM 文件进行 Base64 编码。
  5. 返回JWT 编辑器密钥选项卡,然后单击"新建对称密钥"。
  6. 在对话框中,单击"生成"以生成 JWK 格式的新密钥。
  7. 将生成的参数值替换k为您刚刚复制的 Base64 编码的 PEM 密钥。
  8. 保存好密钥。

步骤 3 - 修改您的 JWT

一旦您获得了格式合适的公钥,就可以随意修改 JWT。只需确保alg​标头设置为HS256​.

步骤 4 - 使用公钥对 JWT 进行签名

使用 HS256 算法和 RSA 公钥作为密钥对 令牌进行签名。

实验室专家通过算法混淆绕过 JWT 身份验证

第一部分 - 获取服务器的公钥
  1. 在 Burp 中,从 BApp 商店加载 JWT Editor 扩展。
  2. 在实验室中,登录您自己的帐户,并将登录后GET /my-account请求发送到 Burp Repeater。
  3. 在 Burp Repeater 中,更改路径/admin并发送请求。请注意,只有以该administrator用户身份登录后才能访问管理面板。
  4. 在浏览器中,访问标准端点/jwks.json,观察服务器公开的 JWK 集,其中包含一个公钥。
  5. 从数组内部复制 JWK 对象keys。请确保不要意外复制周围数组中的任何字符。
第二部分 - 生成恶意签名密钥
  1. 在 Burp 中,转到Burp 主标签栏中的 JWT 编辑器密钥选项卡。
  2. 点击"新建RSA密钥"。
  3. 在对话框中,确保选中JWK选项,然后粘贴您刚刚复制的 JWK 代码。单击"确定"保存密钥。
  4. 右键单击刚刚创建的密钥条目,然后选择"复制公钥为 PEM"。
  5. 使用解码器选项卡对该 PEM 密钥进行 Base64 编码,然后复制生成的字符串。
  6. 返回Burp 主标签栏中的 JWT 编辑器密钥标签。
  7. 单击"新建对称密钥"。在对话框中,单击"生成"以生成 JWK 格式的新密钥。请注意,您无需选择密钥长度,稍后系统会自动更新密钥长度。
  8. 将 k 属性的生成值替换为您刚刚创建的 Base64 编码的 PEM 文件。
  9. 保存好密钥。
第三部分 - 修改并签署令牌
  1. 返回GET /adminBurp Repeater 中的请求,切换到扩展生成的JSON Web Token选项卡。
  2. 在 JWT 的头部,将参数的值更改alg为HS256。
  3. sub在有效载荷中,将声明 的值更改为administrator。
  4. 在选项卡的底部,单击"签名",然后选择您在上一节中生成的对称密钥。
  5. 请确保选中"不修改标头"选项,然后单击"确定"。修改后的令牌现在使用服务器的公钥作为私钥进行签名。
  6. 发送请求后,请确认您已成功访问管理面板。
  7. 在响应中找到用于删除的 URL carlos(/admin/delete?username=carlos)。向此端点发送请求以完成实验。

现有令牌派生公钥

如果公钥不易获取,您仍然可以通过从一对现有的 JWT 中派生密钥来测试算法混淆。使用诸如 之类的工具,这个过程相对简单。您可以在GitHub 代码库jwt_forgery.py​中找到该工具以及其他一些有用的脚本。 rsa_sign2n

我们还创建了该工具的简化版本,您只需运行一个命令即可:

​docker run --rm -it portswigger/sig2n <token1> <token2>​#### 笔记

您需要 Docker CLI 才能运行该工具的任一版本。首次运行此命令时,它会自动从 Docker Hub 拉取镜像,这可能需要几分钟时间。

这段代码使用您提供的 JWT 来计算一个或多个可能的 <value>​ 值n​。不必过于担心具体含义------您只需知道这些值中只有一个n​与服务器密钥使用的 <value>​ 值匹配即可。对于每个可能的 <value>​ 值,我们的脚本会输出:

  • 采用 Base64 编码的 PEM 密钥,同时支持 X.509 和 PKCS1 格式。
  • 使用上述每个密钥签名的伪造 JWT。

为了找到正确的密钥,可以使用 Burp Repeater 发送包含每个伪造 JWT 的请求。服务器只会接受其中一个。然后,您可以使用匹配的密钥来构建算法混淆攻击。

实验室专家利用算法混淆绕过 JWT 身份验证,且密钥未暴露。

第一部分 - 获取服务器生成的两个 JWT
  1. 在 Burp 中,从 BApp 商店加载 JWT Editor 扩展。
  2. 在实验室中,登录您自己的帐户,并将登录后GET /my-account请求发送到 Burp Repeater。
  3. 在 Burp Repeater 中,更改路径/admin并发送请求。请注意,只有以该administrator用户身份登录后才能访问管理面板。
  4. 复制您的 JWT 会话 cookie 并保存以备后用。
  5. 退出登录,然后重新登录。
  6. 复制新的 JWT 会话 cookie 并保存。现在您已拥有服务器生成的两个有效 JWT。
第二部分 - 暴力破解服务器公钥
  1. 在终端中运行以下命令,并将两个 JWT 作为参数传入。

    ​docker run --rm -it portswigger/sig2n <token1> <token2>​请注意,首次运行此程序时,可能需要几分钟时间才能从 Docker Hub 拉取镜像。

  2. 请注意,输出结果包含一个或多个计算值n​。这些值在数学上都是可能的,但只有其中一个与服务器使用的值匹配。在每种情况下,输出结果还会提供以下信息:

    • 以 X.509 和 PKCS1 格式提供的 Base64 编码的公钥。
    • 用这些密钥分别签名的篡改 JWT。
  3. 从第一个 X.509 条目中复制篡改后的 JWT(您可能只有一个)。

  4. 返回 Burp Repeater 中的请求,并将路径改回/my-account​.

  5. 将会话 cookie 替换为这个新的 JWT,然后发送请求。

    • 如果您收到 200 响应并成功访问您的帐户页面,那么这就是正确的 X.509 密钥。
    • 如果您收到 302 响应,该响应会将您重定向到其他页面/login并移除您的会话 cookie,则说明您使用的 X.509 密钥有误。在这种情况下,请使用篡改后的 JWT 对脚本输出的每个 X.509 密钥重复此步骤。
第三部分 - 生成恶意签名密钥
  1. 从终端窗口中,复制你在上一节中确认正确的 Base64 编码的 X.509 密钥。请注意,你需要选择的是密钥本身,而不是你在上一节中使用的被篡改过的 JWT。
  2. 在 Burp 中,转到JWT 编辑器键选项卡,然后单击"新建对称键"。
  3. 在对话框中,单击"生成"以生成 JWK 格式的新密钥。
  4. 将属性的生成值替换为k您刚刚复制的 Base64 编码的密钥。请注意,这应该是实际的密钥,而不是您在上一节中使用的已篡改的 JWT。
  5. 保存好密钥。
第四部分 - 修改并签署令牌
  1. 返回 Burp Repeater 中的请求,并将路径更改为/admin.
  2. 切换到扩展程序生成的JSON Web Token选项卡。
  3. 在 JWT 的标头中,确保该alg参数设置为HS256。
  4. sub在 JWT 有效负载中,将声明 的值更改为administrator。
  5. 在选项卡的底部,单击"签名",然后选择您在上一节中生成的对称密钥。
  6. 请确保选中"不修改标头"选项,然后单击"确定"。修改后的令牌现在使用服务器的公钥作为私钥进行签名。
  7. 发送请求后,请确认您已成功访问管理面板。
  8. 在响应中找到用于删除的 URL carlos(/admin/delete?username=carlos)。向此端点发送请求以完成实验。