JWT漏洞学习
JWT漏洞学习
Sherlock借鉴
JWT(JSON Web Token)是一种无状态认证机制,通过将用户身份和权限信息存储在令牌中,实现安全地在网络应用间传递信息。它具有跨域支持、扩展性和灵活性、安全性以及可扩展的验证方式等特点,成为现代应用开发中重要的认证和授权解决方案。
JWT的组成
JWT的结构由三部分组成,分别是Header、Payload和Signature,下面是每一部分的详细介绍和示例:
Header 部分
在 JWT 中 Header 部分存储的是 Token 类型和加密算法,通常使用JSON对象表示并使用Base64编码,其中包含两个字段:alg和typ
- alg(algorithm):指定了使用的加密算法,常见的有HMAC、RSA和ECDSA等算法
- typ(type):指定了JWT的类型,通常为JWT
下面是一个示例Header:
1 | { |
Payload 部分
Payload包含了JWT的主要信息,通常使用JSON对象表示并使用Base64编码,Payload中包含三个类型的字段:注册声明、公共声明和私有声明
- 公共声明:是自定义的字段,用于传递非敏感信息,例如:用户ID、角色等
- 私有声明:是自定义的字段,用于传递敏感信息,例如密码、信用卡号等
- 注册声明:预定义的标准字段,包含了一些JWT的元数据信息,例如:发行者、过期时间等
下面是一个示例Payload:
1 | { |
其中sub表示主题,name表示名称,iat表示JWT的签发时间
Signature 部分
Signature是使用指定算法对Header和Payload进行签名生成的,用于验证JWT的完整性和真实性
- Signature的生成方式通常是将Header和Payload连接起来然后使用指定算法对其进行签名,最终将签名结果与Header和Payload一起组成JWT
- Signature的生成和验证需要使用相同的密钥
下面是一个示例Signature
1 | HMACSHA256(base64UrlEncode(header) + "." +base64UrlEncode(payload),secret) |
其中HMACSHA256是使用HMAC SHA256算法进行签名,header和payload是经过Base64编码的Header和Payload,secret是用于签名和验证的密钥,最终将Header、Payload和Signature连接起来用句点(.)分隔就形成了一个完整的JWT
完整的JWT
第一部分是Header,第二部分是Payload,第三部分是Signature,它们之间由三个 .
分隔,注意JWT 中的每一部分都是经过Base64编码的,但并不是加密的,因此JWT中的信息是可以被解密的
下面是一个示例JWT
1 | eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c |
JWT名词
- JWS(Signed JWT):JWS是指已签名的JWT。它由JWT的Header、Payload和Signature组成,其中Signature是使用密钥对Header和Payload进行数字签名得到的。通过验证签名,可以确保JWT的完整性和真实性。
- JWK(JSON Web Key):JWK是指用于JWT的密钥。它可以是对称加密密钥(例如密码),也可以是非对称加密密钥(例如公钥/私钥对)。JWK用于生成和验证JWT的签名,确保只有拥有正确密钥的一方能够对JWT进行操作。
- JWE(Encrypted JWT):JWE是指经过加密的JWT。它是在JWS基础上进行了进一步的加密,将JWT的Payload部分加密后得到的结果。JWE可用于保护敏感信息,确保只有授权的接收方能够解密和读取JWT的内容。
- JKU(JSON Web Key Set URL):JKU是JWT Header中的一个字段,该字段包含一个URI,用于指定用于验证令牌密钥的服务器。当需要获取公钥或密钥集合时,可以使用JKU字段指定的URI来获取相关的JWK信息。
- X5U:X5U是JWT Header中的一个字段,它是一个URL,指向一组X.509公钥证书。类似于JKU,X5U字段用于指定可用于验证JWT的公钥证书的位置。
- X.509标准:X.509是一种密码学标准,定义了公共密钥基础设施(PKI)中的数字证书格式。这些证书包含有关实体(例如个人、组织或设备)的信息,以及相关的公钥和数字签名。X.509证书在许多互联网协议中广泛使用,如TLS/SSL等。
解密平台
下面是一个JWT在线构造和解构的平台:
工作原理
JWT的工作流程如下:
- 用户在客户端登录并将登录信息发送给服务器
- 服务器使用私钥对用户信息进行加密生成JWT并将其发送给客户端
- 客户端将JWT存储在本地,每次向服务器发送请求时携带JWT进行认证
- 服务器使用公钥对JWT进行解密和验证,根据JWT中的信息进行身份验证和授权
- 服务器处理请求并返回响应,客户端根据响应进行相应的操作
JWT 基础安全问题(bp靶场)
1、未对签名进行验证
JWT库会通常提供一种验证令牌的方法和一种解码令牌的方法,比如:Node.js库jsonwebtoken有verify()和decode(),有时开发人员会混淆这两种方法,只将传入的令牌传递给decode()方法,这意味着应用程序根本不验证签名
下边我们通过portswigger靶场来演示一下这个漏洞案例:
要求:此实验室使用基于 JWT 的机制来处理会话。由于实现缺陷,服务器不会验证它收到的任何 JWT 的签名。
要解决该实验,请修改您的会话令牌以访问 /admin
上的管理面板,然后删除用户 carlos
。
您可以使用以下凭证登录自己的帐户:wiener:peter
首先我们先登录题目提供的账号wiener,然后去bp的HTTP历史记录里面查看相关条目
可以发现在我们登陆的时候响应包设置了一个seesion值,观察其结构可以发现是JWT形式的,其payload部分选中即可查看
在该条目下可以发现我们登录成功的凭证是session值,根据题目提示可以知道该题只有解码,没有验证,所以我们可以直接修改payload部分实现身份变更
访问/admin,回显提示需要administrator身份才可以访问
将条目/my-account?id=wiener发送至repeater,在请求面板中,转到 JSON Web Token 选项卡(注意该选项卡需要提前安装插件JWT Editor)
将其中的sub值改为administrator,复制JWT值,在session处进行替换,便可以成功访问/admin,删除用户carlos,题目解决
2、未对加密算法进行强验证
在JWT的Header中alg的值用于告诉服务器使用哪种算法对令牌进行签名,从而告诉服务器在验证签名时需要使用哪种算法,JWT同时也支持将算法设定为”None”,如果”alg”字段设为”None”,则标识不签名(后面签名部分可以直接删去),这样一来任何token都是有效的,设定该功能的最初目的是为了方便调试,但是若不在生产环境中关闭该功能,攻击者可以通过将alg字段设置为”None”来伪造他们想要的任何token,接着便可以使用伪造的token冒充任意用户登陆网站
下面为portswigger靶场中的一个漏洞案例:
此实验室使用基于 JWT 的机制来处理会话。服务器被不安全地配置为接受未签名的 JWT。
要解决该实验,请修改您的会话令牌以访问 /admin
上的管理面板,然后删除用户 carlos
。
您可以使用以下凭证登录自己的帐户:wiener:peter
开启题目环境,登录账号后按照上一题进行操作,结果会失败,回显表明该题有开启验证方法
将header部分alg的值改为none后,同时将签名部分删去,要保留payload部分的点
更改session值并访问/admin,成功如下
然后便可以删去用户carlos,解决题目
3、弱密钥
如果使用了不安全的算法(如弱 HMAC 哈希算法),或者算法参数(如密钥长度)不足够强,攻击者可以通过暴力破解、碰撞攻击或其他密码学攻击来伪造有效的 JWT
下面为portswigger靶场中的一个漏洞案例:
要解决实验室问题,请先暴力破解网站的密钥。获得此令牌后,使用它对修改后的会话令牌进行签名,该令牌允许您访问 /admin
的管理面板,然后删除用户 carlos
。
您可以使用以下凭证登录自己的帐户:wiener:peter
前面两种方法都不起作用,所以开始尝试爆破密钥
- JWT字典:https://github.com/wallarm/jwt-secrets
- 我们使用
jwt_tool
来爆破JWT:https://github.com/ticarpi/jwt_tool - 使用教程:https://www.cnblogs.com/xiaozi/p/12005929.html
首先登录拿到账号的jwt,然后将该jwt拿去爆破,结果如下:
可以看到结果中表明secret1就是该jwt的密钥
拿到解密平台中去伪造一个我们所需的jwt
要记得sub值要为管理员的身份
然后拿着伪造的jwt去访问/admin,成功访问,删除要求用户便可以,题目解决
JWT标头注入
1、通过jwk参数注入自签名的JWT
通过 JWK(JSON Web Key)参数注入自签名 JWT 的漏洞是一种利用不安全的 JWT 处理方式来伪造有效身份的攻击手法。这个漏洞主要是由于不安全地处理和解析 JWT 的 公钥,特别是当服务器允许用户通过 JWT 提供自定义的 JWK 时,攻击者可以利用该漏洞来伪造合法的签名,从而冒充其他用户或提升权限。
漏洞工作原理
JWT 通常用于在客户端和服务器之间安全地传输身份信息。JWT 的安全性依赖于正确的签名和验证过程。以下是漏洞的关键点:
- JWT 签名与验证机制:
- JWT 可以使用对称算法(如
HS256
)或非对称算法(如RS256
)来进行签名。 - 在非对称算法中(如
RS256
),签名是通过 私钥 生成的,而验证则使用 公钥。
- JWT 可以使用对称算法(如
- **JWK (JSON Web Key)**:
- JWK 是一种用于表示密钥的标准化格式。JWT 头部中可以包含一个
jwk
字段,表示公钥的相关信息。 - 当服务器在验证 JWT 时,如果发现 JWT 中包含
jwk
参数,它会尝试从中解析出公钥,并用该公钥来验证 JWT 的签名。
- JWK 是一种用于表示密钥的标准化格式。JWT 头部中可以包含一个
- JWK 参数注入的攻击思路:
- 服务器如果允许用户通过 JWT 的头部传递 自定义的 JWK,并没有严格验证这个公钥的来源或合法性,那么攻击者可以利用这一点,注入自己的公钥来生成和验证伪造的签名。
- 攻击者首先生成一对 私钥/公钥,使用自己的私钥对 JWT 进行签名,然后在 JWT 的头部包含攻击者的公钥信息。由于服务器错误地信任了 JWT 中的 JWK 参数,它会用攻击者提供的公钥验证攻击者的伪造签名,从而认为 JWT 是合法的。
攻击过程
生成攻击者的密钥对: 攻击者首先生成一对非对称密钥对(私钥和公钥),用于伪造 JWT。
伪造 JWT:
- 攻击者用自己的 私钥 签名一个自定义的 JWT(可以修改载荷中的敏感数据,例如提升权限)。
- 在 JWT 头部的
jwk
参数中,攻击者包含自己的 公钥,用于让服务器在验证时错误地接受这个伪造的公钥。
JWT 头部示例:
1
2
3
4
5
6
7
8{
"alg": "RS256",
"jwk": {
"kty": "RSA",
"n": "<attacker's public key modulus>",
"e": "AQAB"
}
}发送伪造的 JWT: 攻击者将伪造的 JWT 发送给服务器,服务器错误地使用 JWT 中的攻击者公钥来验证签名。
绕过签名验证: 由于服务器使用攻击者的公钥,验证通过,攻击者成功伪造了一个合法的 JWT,从而绕过身份验证或权限控制。
题目
了解了原理之后,靶场中的题目便可以轻松解决了
要求:此实验室使用基于 JWT 的机制来处理会话。服务器支持 JWT 标头中的 jwk
参数。这有时用于将正确的验证密钥直接嵌入到令牌中。但是,它无法检查提供的密钥是否来自受信任的来源。
要解决实验问题,请修改并签署一个 JWT,该 JWT 允许您访问 /admin
的管理面板,然后删除用户 carlos
。
您可以使用以下凭证登录自己的帐户:wiener:peter
首先登录账号,利用bp获取到jwt
利用bp中的插件JWT Editor生成一个新的RSA密钥
将我们所需的条目(只要有JWT值得都可以)放入重放器中,选项选择JSON Web Token
kid值要改为刚生成的密钥id,sub值要改为管理员
首先点击攻击,选择第一个后将我们刚刚生成的RSA密钥导入进去,最后将新生成的JWT复制即可
回到谷歌,利用插件Cookie Editor将我们的session值进行更改,保存
访问/admin,删去用户carlos,题目解决
2、通过jku参数注入自签名的JWT
漏洞成因
- 信任外部
jku
源:在一些情况下,服务器允许客户端通过jku
参数提供一个 URL,服务器会从这个 URL 获取公钥来验证 JWT 的签名。如果服务器没有对jku
参数中的 URL 进行严格限制或校验,攻击者可以将该 URL 指向他们控制的服务器,从而提供一个伪造的公钥。 - 自签名 JWT:攻击者可以生成一个合法格式的 JWT,并使用自己的私钥进行签名。随后,他们将
jku
指向自己控制的服务器,该服务器会返回与其私钥对应的公钥。由于服务器信任jku
提供的公钥,因此会错误地验证攻击者签名的 JWT。 - 绕过签名验证:通过操控
jku
参数,攻击者可以伪造一个有效的 JWT,服务器由于使用了错误的公钥验证签名,认为该 JWT 是合法的。这可能导致攻击者获得提升的权限或未授权的访问。
题目
要求:此实验室使用基于 JWT 的机制来处理会话。服务器支持 JWT 标头中的 jku
参数。但是,在获取密钥之前,它无法检查提供的 URL 是否属于受信任的域。
要解决实验问题,请伪造一个 JWT,让您能够访问 /admin
的管理面板,然后删除用户 carlos
。
您可以使用以下凭证登录自己的帐户:wiener:peter
首先登陆账号,获取到我们所需的jwt
因为接受jku参数,所以我们就需要伪造一个
首先复制公钥,将其拿到题目提供的服务器中去
要记住再粘贴之前需要加上key头
1 | { |
如下:
复制漏洞服务器的地址
回到bp中,我们需要修改kid和sub值,添加jku键值对
更改结束后,点击Sign,要选对模式
然后i将新生成的jwt复制后拿去替换掉原来的jwt值,进入/admin,删去用户carlos,题目解决
3、通过 kid 注入 JWT,与目录遍历攻击相结合
在 JWT (JSON Web Token) 验证机制中,kid
(Key ID)是 JWT 头部字段的一部分,通常用于指示服务器应使用哪个密钥来验证 JWT 的签名。然而,如果服务器处理 kid
时没有适当的输入验证,攻击者可以利用 kid
注入与目录遍历攻击相结合,绕过 JWT 的签名验证或滥用服务器的密钥管理逻辑,导致未授权访问或权限提升。
当 JWT 使用的是对称加密算法的时候,极有可能存在目录遍历的漏洞,我们能够强制服务器使用其文件系统中的任意文件作为验证密钥
攻击原理
JWT 的头部示例:
1 | { |
kid
的作用:- 在 JWT 头部中,
kid
参数用于指示服务器应使用哪个密钥(或密钥文件)来验证签名。例如,服务器从预定义的路径中加载kid
对应的密钥(如keys/<kid>.key
)。 - 如果攻击者可以操控
kid
的值,并且服务器在读取密钥时存在文件路径拼接问题,攻击者就可以通过目录遍历访问到敏感文件。
- 在 JWT 头部中,
目录遍历攻击:
如果服务器使用以下代码来加载密钥(没有过滤或处理路径输入):
1
2$kid = $_JWT['header']['kid'];
$key = file_get_contents("/var/keys/" . $kid . ".key");当攻击者将
kid
设置为../../etc/passwd
时,拼接后的路径变为/var/keys/../../etc/passwd.key
,经过目录遍历后,实际读取到的文件路径是/etc/passwd
。这就导致服务器读取了不应访问的系统文件,并将该内容作为密钥进行 JWT 验证。
结合签名绕过:
- 在某些情况下,服务器会将文件的内容(如
/etc/passwd
)当作 HMAC 的密钥来验证签名。攻击者可以提前生成一个以/etc/passwd
内容作为密钥的 JWT 签名,并通过这种方式欺骗服务器完成签名验证,从而绕过认证。
- 在某些情况下,服务器会将文件的内容(如
攻击流程
- 攻击者生成一个 JWT,伪造头部中的
kid
字段为../../etc/passwd
。 - 使用
/etc/passwd
内容作为 HMAC 密钥生成 JWT 签名。 - 将伪造的 JWT 发送给服务器。
- 服务器根据
kid
的值读取/etc/passwd
,并使用其内容验证签名。 - 签名验证通过,攻击者成功伪造 JWT,获得未授权的访问
题目
接下来我们来看靶场中的一道题
要求:此实验室使用基于 JWT 的机制来处理会话。为了验证签名,服务器使用 JWT 标头中的 kid
参数从其文件系统中获取相关密钥。
要解决实验问题,请伪造一个 JWT,让您能够访问 /admin
的管理面板,然后删除用户 carlos
。
您可以使用以下凭证登录自己的帐户:wiener:peter
从获取到的JWT中我们可以看到该题使用的是对称密码加密
所以我们来到插件JWT Editor,生成一个 Symmetric Key,也就是对称密钥,并将 k 的值修改为AA==
(base64编码的null字节)
接着我们去修改JWT,将kid的值修改为../../../../../dev/null
I(该文件默认为空文件)
修改完后,点击Sign,密钥要选择刚刚生成的对称密钥,以及选择第一个选项
将生成的JWT复制下来,拿去更改我们的session值,后访问/admin,删去用户carlos,解决题目
JWT算法混淆
1、对称加密与非对称加密
可以使用一系列不同的算法对 JWT 进行签名。其中一些,如HS256(HMAC + SHA-256)使用“对称”密钥。这意味着服务器使用单个密钥对 Token 进行签名和验证。显然,这需要保密,就像密码一样。
其他算法,例如 RS256 (RSA + SHA-256) 使用“非对称”密钥对。它由服务器用来签署令牌的私钥和可用于验证签名的数学相关的公钥组成。
顾名思义,私钥必须保密,但公钥通常是共享的,以便任何人都可以验证服务器颁发的令牌的签名。
2、算法混淆漏洞产生的原因
算法混乱漏洞通常是由于 JWT 库的实现有缺陷而引起
的。尽管实际的验证过程因所使用的算法而异,但许多库提供了一种与算法无关的单一方法来验证签名。这些方法依赖于alg
令牌标头中的参数来确定它们应执行的验证类型。
以下伪代码显示了此泛型verify()
方法的声明在 JWT 库中的简化示例:
1 | function verify(token, secretOrPublicKey){ |
当随后使用此方法的网站开发人员假设它将专门处理使用 RS256 等非对称算法签名的 JWT 时,就会出现问题。由于这个有缺陷的假设,他们可能总是将固定的公钥传递给该方法,如下所示:
1 | publicKey = <public-key-of-server>; |
在这种情况下,如果服务器收到使用 HS256 等对称算法签名的令牌,则库的通用verify()
方法会将公钥视为 HMAC 密钥。这意味着攻击者可以使用 HS256 和公钥对令牌进行签名,并且服务器将使用相同的公钥来验证签名。
上边是抄官方的话,下边我们用大白话来解释一下:
- 假设开发使用的是
RS256
这非对称加密算法生成的jwt。 - 由于信息泄露等原因攻击者可以拿到这个
公钥
,因为上边说过公钥通常是共享的 - 攻击者使用
HS256
算法伪造一个jwt,用这个公钥
作为签名的密钥。
程序会使用verify()
这个方法来验证jwt有没有被篡改。但是这个库设计的有问题(问题:他是通过你jwt头中alg
来判断是使用那种算法来进行签名的。所以我们可以篡改他的算法),这块就会使用RS256
生成的公钥作为HS256
的秘钥来验证攻击者伪造的jwt。这个公钥攻击者可控,所以伪造的jwt就会通过验证。
3、执行算法混淆攻击的步骤
- 获取服务器的公钥
- 将公钥转换为合适的格式
- 创建一个恶意 JWT,其负载经过修改,
alg
标头设置为HS256
. - 使用公钥作为秘密,使用 HS256 对令牌进行签名。
4、通过算法混淆(泄露了密钥)绕过 JWT 身份验证
来看看靶场中的题目
要求:此实验室使用基于 JWT 的机制来处理会话。它使用强大的 RSA 密钥对对令牌进行签名和验证。但是,由于实现缺陷,此机制容易受到算法混淆攻击。
要解决该实验问题,请首先获取服务器的公钥。这是通过标准端点公开的。使用此密钥对修改后的会话令牌进行签名,该令牌允许您访问 /admin
的管理面板,然后删除用户 carlos
。
您可以使用以下凭证登录自己的帐户:wiener:peter
提示:可以假设服务器将其公钥存储为 X.509 PEM 文件
服务器有时会通过映射到 /jwks.json
或 /.well-known/jwks.json
的标准端点将其公有密钥作为 JSON Web 密钥 (JWK) 对象公开。这些可以存储在称为 keys
的 JWK 数组中。这称为 JWK 集。
1 | { |
所以我们尝试访问/jwks.json,获取到如下内容
1 | {"keys":[{"kty":"RSA","e":"AQAB","use":"sig","kid":"1c5caebf-7b98-464a-a532-f29489724191","alg":"RS256","n":"9cl7xLTPv-2lil8QK7FPE4yZR0evPLStVq9QgBHQyqQBsKBC1j6n3Av361kRrVyS3aZGycr0OzVdOgNNQalgHOevwgLEOY_t9BhTJdNe5x7hWiV4BKuI_RPupVTZetDHdtSBamY6fg5MHXbDQMF82dUYE_MJncIMWzUuz7UjIcGuf9fInDcaRZ8cvEEW_Q6em-xxIOTTHE1BK7fO_nNDHcggENAsR0CZCXLVzwZA8KvgPDYinhPDIZoN8AKL8kcJUhzJdERfT1TcQM7uajeItu55mHLE4V16lwzOxhXyLgAnBMPPjMUcXD_GCFilhT7fseceS8H__9EX7zEJgTT7lw"}]} |
将其中的jwt部分复制下来,转到bp的JWT Editor中新生成一个RSA密钥,将复制的内容粘贴进去,点击生成后,右键选中”Copy Public Key as PEM”,最后一行空行不要删掉
1 | -----BEGIN PUBLIC KEY----- |
将该部分内容拿去base64编码后把便编码结果复制起来,如下
1 | LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUE5Y2w3eExUUHYrMmxpbDhRSzdGUApFNHlaUjBldlBMU3RWcTlRZ0JIUXlxUUJzS0JDMWo2bjNBdjM2MWtSclZ5UzNhWkd5Y3IwT3pWZE9nTk5RYWxnCkhPZXZ3Z0xFT1kvdDlCaFRKZE5lNXg3aFdpVjRCS3VJL1JQdXBWVFpldERIZHRTQmFtWTZmZzVNSFhiRFFNRjgKMmRVWUUvTUpuY0lNV3pVdXo3VWpJY0d1ZjlmSW5EY2FSWjhjdkVFVy9RNmVtK3h4SU9UVEhFMUJLN2ZPL25ORApIY2dnRU5Bc1IwQ1pDWExWendaQThLdmdQRFlpbmhQRElab044QUtMOGtjSlVoekpkRVJmVDFUY1FNN3VhamVJCnR1NTVtSExFNFYxNmx3ek94aFh5TGdBbkJNUFBqTVVjWEQvR0NGaWxoVDdmc2VjZVM4SC8vOUVYN3pFSmdUVDcKbHdJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg== |
生成一个对称加密的key,把k替换成我们刚修改完格式的公钥
接着去修改我们的jwt值,修改alg值,sub值,签名选择我们刚刚生成的对称密钥
将新生成的jwt拿去替换,成功访问/admin,题目解决