JWT 攻击
JWT 攻击
JWT (JSON Web Token) 是目前最流行的跨域认证解决方案,是一种基于 Token 的认证授权机制。 从 JWT 的全称可以看出,JWT 本身也是 Token,一种规范化之后的 JSON 结构的 Token。
JWT 自身包含了身份验证所需要的所有信息,因此,我们的服务器不需要存储 Session 信息。这显然增加了系统的可用性和伸缩性,大大减轻了服务端的压力。
JWT组成
JWT 由三部分组成: 头部 、 有效载荷和签名 。这三部分之间用点号分隔,如下例所示:
1 | eyJraWQiOiI5MTM2ZGRiMy1jYjBhLTRhMTktYTA3ZS1lYWRmNWE0NGM4YjUiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJwb3J0c3dpZ2dlciIsImV4cCI6MTY0ODAzNzE2NCwibmFtZSI6IkNhcmxvcyBNb250b3lhIiwic3ViIjoiY2FybG9zIiwicm9sZSI6ImJsb2dfYXV0aG9yIiwiZW1haWwiOiJjYXJsb3NAY2FybG9zLW1vbnRveWEubmV0IiwiaWF0IjoxNTE2MjM5MDIyfQ.SYZBPIBg2CRjXAJ8vCER0LA_ENjII1JakvNQoP-Hw6GG1zfl4JyngsZReIfqRvIAEi5L4HV0q7_9qGhQZvy9ZdxEJbwTxRs_6Lb-fZTDpW6lKYNdMyjw45_alSCZ1fypsMWz_2mTpQzil0lOtps5Ei_z7mM7M8gCwe_AGpI53JxduQOaB5HkT5gVrv9cKu9CsW5MS6ZbqYXpGyOG5ehoxqm8DL5tFYaW3lB50ELxi0KsuTKEbD0t5BCl0aCR2MBJWAbN-xeLwEenaqBiwPVvKixYleeDQiBEIylFdNNIMviKRgXiYuAvMziVPbwSgkZVHeEdF5MQP1Oe2Spac-6IfA |
- Header(头部) : 描述 JWT 的元数据,定义了生成签名的算法(alg)以及
Token的类型。Header 被 Base64Url 编码后成为 JWT 的第一部分。 - Payload(载荷) : 用来存放实际需要传递的数据,包含声明(Claims),如
sub、jti。Payload 被 Base64Url 编码后成为 JWT 的第二部分。 - Signature(签名):服务器通过 Payload、Header 和一个密钥(Secret)使用 Header 里面指定的签名算法(默认是 HMAC SHA256)生成。生成的签名会成为 JWT 的第三部分。
例如:
1 | HMACSHA256(base64UrlEncode(header) + "." +base64UrlEncode(payload),secret) |
其中HMACSHA256是使用HMAC SHA256算法进行签名,header和payload是经过Base64编码的Header和Payload,secret是用于签名和验证的密钥,最终将Header、Payload和Signature连接起来用句点(.)分隔就形成了一个完整的JWT
- JWK(JSON Web Key):JWK是指用于JWT的密钥。它可以是对称加密密钥(例如密码),也可以是非对称加密密钥(例如公钥/私钥对)。JWK用于生成和验证JWT的签名,确保只有拥有正确密钥的一方能够对JWT进行操作。
- JKU(JSON Web Key Set URL):JKU是JWT Header中的一个字段,该字段包含一个URI,用于指定用于验证令牌密钥的服务器。当需要获取公钥或密钥集合时,可以使用JKU字段指定的URI来获取相关的JWK信息。
JWT加密解密网站: jwt.io
JWT工作过程
在基于 JWT 进行身份验证的应用程序中,服务器通过 Payload、Header 和 Secret(密钥)创建 JWT 并将 JWT 发送给客户端。客户端接收到 JWT 之后,会将其保存在 Cookie 或者 localStorage 里面,以后客户端发出的所有请求都会携带这个令牌。
简化后的步骤如下:
- 用户向服务器发送用户名、密码以及验证码用于登陆系统;
- 如果用户用户名、密码以及验证码校验正确的话,服务端会返回已经签名的 Token,也就是 JWT;
- 客户端收到 Token 后自己保存起来(比如浏览器的
localStorage); - 用户以后每次向后端发请求都在 Header 中带上这个 JWT ;
- 服务端检查 JWT 并从中获取用户相关信息。
burpsuite JWT labs
未验证签名
原因:开发者只将传入的令牌传递给 decode() 方法,并没有验证令牌。可随意改动有效载荷部分。
decode() 可以帮助你看到:
header 声称使用了什么算法sub` 指向哪个用户或服务exp` 是否已经过去有哪些 scope 或 role
Verify() 验证会检查:
签名是否匹配可信 secret 或 public key。算法是否在应用允许列表中。iss` 是否是可信签发方。aud` 是否匹配当前服务。exp`、`nbf` 和时钟偏差规则是否满足。
对cookie进行base64解密。然后将sub改成administrator,加密
替换cookie访问/admin页面:
成功授权访问后,找到删除carlos的链接。利用伪造的cookie进行删除:
有缺陷的签名验证
服务器配置不安全,会接受未签名的 JWT。
将alg改为none
sub改为administrator
使用未签名的JWT成功访问/admin
弱签名密钥
该lab使用HMAC SHA256(对称加密)算法进行签名,使用jwt-tool对密钥进行爆破
1 | D:\TOOLS>jwt-brute-force-tool-windows-amd64.exe crack -token "eyJraWQiOiJjNTQxN2ZiOS1hMjRiLTQxZWYtYjRhMi0zNjJhZGM0NTA4ZDciLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJwb3J0c3dpZ2dlciIsImV4cCI6MTc4MzAwNTY2MCwic3ViIjoid2llbmVyIn0.z-H6a8mrxtynuaIX8eR6sC83GhywwADM72rb0ZEVkHQ" -dict key.txt |
生成(symmetric key)对称密钥:密钥为secret1
更改sub改为administrator,使用创建的对称密钥进行签名
验证
注入 jwk 头部
JWK是指用于JWT的密钥。它可以是对称加密密钥(例如密码),也可以是非对称加密密钥(例如公钥/私钥对)。JWK用于生成和验证JWT的签名,确保只有拥有正确密钥的一方能够对JWT进行操作。JWK无法验证所提供的密钥是否来自可信来源。伪造一个RSA密钥对,使用JWK将公钥发送给服务端。服务端使用公钥验证签名
新建RSA密钥对
更改sub声明
点击攻击,选择嵌入JWK攻击
成功插入JWK
验证:
注入jku头部
与JWK类似,JKU是JWT Header中的一个字段,该字段包含一个URI,用于指定用于验证令牌密钥的服务器。
创建一个网站,body部分放入生成rsa公钥(创建rsa密钥对部分省略)
在头部插入jku,参数为存放公钥网站,更改sub.使用创建的rsa私钥进行签名
KID路径遍历
在这个漏洞中,攻击者利用了 JWT 头部(Header)中的 kid(Key ID) 参数。服务端的漏洞在于:它将 kid 的值直接拼接到了文件路径中,去服务器本地读取密钥文件。
如果系统没有对 kid 进行严格的过滤,攻击者可以输入 ../../../../dev/null。 此时,路径拼接变成 /path/to/keys/../../../../dev/null,实际上指向了 /dev/null
这意味着,服务端在验签时,使用的“密钥”变成了一个空字符串。攻击者只要在本地使用空字符串作为密钥对伪造的 JWT 进行签名,就能完美通过服务端的校验。
创建一个对称密钥,k参数置为空:
将kid指向系统的一个空文件,更改sub
使用创建的密钥进行签名
验证:
算法混淆
服务器暴露了jwk文件,访问 /jwks.json进行读取,可获取rsa公钥
生成rsa密钥对,将公钥粘贴到key框
右键单击刚刚创建的密钥条目,然后选择Copy Public Key as PEM。对该 PEM 密钥进行 Base64 编码
新建对称密钥。在对话框中,单击“生成”以生成 JWK 格式的新密钥。将 k 属性的生成值替换为Base64 编码的PEM。
将算法改为HS256,更改sub
使用创建的密钥进行签名
验证:
算法混淆(无公钥版)
思路:
- 获取两个不同的有效 Token
- 恢复公钥
- 转换公钥格式(关键避坑点)
- 伪造并利用算法混淆
拉取sig2n工具:
计算公钥值:
1 | docker run --rm -it portswigger/sig2n <token1> <token2> |
X.509 和 PKCS#1 是密码学中关于非对称加密(尤其是 RSA 算法)的两个重要标准。
X.509 是密码学里公钥证书的格式标准。X.509 证书已应用在包括 TLS/SSL 在内的众多网络协议里,同时它也用在很多非在线应用场景里,比如电子签名服务。X.509 证书里含有公钥、身份信息(比如网络主机名,组织的名称或个体名称等)和签名信息(可以是证书签发机构 CA 的签名,也可以是自签名)。
PKCS(Public-Key Cryptography Standards,公钥密码学标准)是由 RSA 安全公司在 20 世纪 90 年代制定的一系列行业标准。规定 RSA 密钥的底层数学成分应该怎么排列。我们知道 RSA 密钥是由一堆数学数字(如模数 n、公钥指数 e、私钥指数 d)组成的,PKCS#1 决定了把这些数字按什么顺序拼成一段二进制数据。
文本外观(PEM 格式):
- 公钥: 文本开头通常是
-----BEGIN RSA PUBLIC KEY----- - 私钥: 文本开头通常是
-----BEGIN RSA PRIVATE KEY-----
1 | Found n with multiplier 6: |
通过验证,确定正确公钥值
生成恶意对称密钥,将k替换为X.509格式的公钥:
更改算法和sub,使用恶意密钥进行签名:
