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
JWT 组成
  • Header(头部) : 描述 JWT 的元数据,定义了生成签名的算法(alg)以及 Token 的类型。Header 被 Base64Url 编码后成为 JWT 的第一部分。
  • Payload(载荷) : 用来存放实际需要传递的数据,包含声明(Claims),如subjti。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 里面,以后客户端发出的所有请求都会携带这个令牌。

 JWT 身份验证示意图

简化后的步骤如下:

  1. 用户向服务器发送用户名、密码以及验证码用于登陆系统;
  2. 如果用户用户名、密码以及验证码校验正确的话,服务端会返回已经签名的 Token,也就是 JWT;
  3. 客户端收到 Token 后自己保存起来(比如浏览器的 localStorage );
  4. 用户以后每次向后端发请求都在 Header 中带上这个 JWT ;
  5. 服务端检查 JWT 并从中获取用户相关信息。

burpsuite JWT labs

未验证签名

原因:开发者只将传入的令牌传递给 decode() 方法,并没有验证令牌。可随意改动有效载荷部分。

decode() 可以帮助你看到:

  • header 声称使用了什么算法
  • sub` 指向哪个用户或服务
  • exp` 是否已经过去
  • 有哪些 scope 或 role

Verify() 验证会检查:

  • 签名是否匹配可信 secret 或 public key。
  • 算法是否在应用允许列表中。
  • iss` 是否是可信签发方。
  • aud` 是否匹配当前服务。
  • exp`、`nbf` 和时钟偏差规则是否满足。

对cookie进行base64解密。然后将sub改成administrator,加密

image-20260621153033125

替换cookie访问/admin页面:

image-20260621153014908

成功授权访问后,找到删除carlos的链接。利用伪造的cookie进行删除:

image-20260621155323858

有缺陷的签名验证

服务器配置不安全,会接受未签名的 JWT。

将alg改为none

sub改为administrator

image-20260702214315997

使用未签名的JWT成功访问/admin

image-20260702214153374

弱签名密钥

该lab使用HMAC SHA256(对称加密)算法进行签名,使用jwt-tool对密钥进行爆破

1
2
D:\TOOLS>jwt-brute-force-tool-windows-amd64.exe crack -token "eyJraWQiOiJjNTQxN2ZiOS1hMjRiLTQxZWYtYjRhMi0zNjJhZGM0NTA4ZDciLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJwb3J0c3dpZ2dlciIsImV4cCI6MTc4MzAwNTY2MCwic3ViIjoid2llbmVyIn0.z-H6a8mrxtynuaIX8eR6sC83GhywwADM72rb0ZEVkHQ" -dict key.txt
成功破解密钥!密钥为: secret1

生成(symmetric key)对称密钥:密钥为secret1

image-20260702223639361

更改sub改为administrator,使用创建的对称密钥进行签名

image-20260702223304182 image-20260702223400983

验证

image-20260702224026394

注入 jwk 头部

JWK是指用于JWT的密钥。它可以是对称加密密钥(例如密码),也可以是非对称加密密钥(例如公钥/私钥对)。JWK用于生成和验证JWT的签名,确保只有拥有正确密钥的一方能够对JWT进行操作。JWK无法验证所提供的密钥是否来自可信来源。伪造一个RSA密钥对,使用JWK将公钥发送给服务端。服务端使用公钥验证签名

新建RSA密钥对

image-20260703154252785

更改sub声明

点击攻击,选择嵌入JWK攻击

image-20260703154416055

成功插入JWK

image-20260703154722768

验证:

image-20260703154516165

注入jku头部

与JWK类似,JKU是JWT Header中的一个字段,该字段包含一个URI,用于指定用于验证令牌密钥的服务器。

创建一个网站,body部分放入生成rsa公钥(创建rsa密钥对部分省略)

image-20260703215114281 image-20260703221511455

在头部插入jku,参数为存放公钥网站,更改sub.使用创建的rsa私钥进行签名

image-20260703221328820 image-20260703221626310

KID路径遍历

在这个漏洞中,攻击者利用了 JWT 头部(Header)中的 kid(Key ID) 参数。服务端的漏洞在于:它将 kid 的值直接拼接到了文件路径中,去服务器本地读取密钥文件。

如果系统没有对 kid 进行严格的过滤,攻击者可以输入 ../../../../dev/null。 此时,路径拼接变成 /path/to/keys/../../../../dev/null,实际上指向了 /dev/null

这意味着,服务端在验签时,使用的“密钥”变成了一个空字符串。攻击者只要在本地使用空字符串作为密钥对伪造的 JWT 进行签名,就能完美通过服务端的校验。

创建一个对称密钥,k参数置为空:

image-20260703222952725

将kid指向系统的一个空文件,更改sub

image-20260703222836543

使用创建的密钥进行签名

image-20260703223100987

验证:

image-20260703223222031

算法混淆

服务器暴露了jwk文件,访问 /jwks.json进行读取,可获取rsa公钥

image-20260703231155335

生成rsa密钥对,将公钥粘贴到key框

image-20260703233051370

右键单击刚刚创建的密钥条目,然后选择Copy Public Key as PEM。对该 PEM 密钥进行 Base64 编码

image-20260703233533757

新建对称密钥。在对话框中,单击“生成”以生成 JWK 格式的新密钥。将 k 属性的生成值替换为Base64 编码的PEM。

image-20260703233450471

将算法改为HS256,更改sub

image-20260703233758954

使用创建的密钥进行签名

image-20260703233836822

验证:

image-20260703232820959 image-20260703233958605

算法混淆(无公钥版)

思路:

  1. 获取两个不同的有效 Token
  2. 恢复公钥
  3. 转换公钥格式(关键避坑点)
  4. 伪造并利用算法混淆

拉取sig2n工具:

image-20260704114740256

计算公钥值:

1
docker run --rm -it portswigger/sig2n <token1> <token2>
image-20260704115154066

X.509PKCS#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
2
3
4
5
Found n with multiplier 6:
Base64 encoded x509 key: LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUFzaHNTaUladWhtN1kwTUxpZjFWKwozQUVVL1BNbTFzNmEyTzZhNWprWmtBV1NXNmFzaFNEcWRGa3JaUzJwbzZObmZpRG1pUDFkRmpXWHplY1N5dC95CmV0VkZWVGo4S0RsbTF6bS9MazkvQUNqTkZoVWlGV0tLeFBkNHlNSkNDcjc0K1l2SkZaVWd6dE9SSTh4Kzl6RlcKWnJVNEEvZnF1dzZ6ZXFDbnAvODkwejN3OFBHcHJFcFEza1FIK0g0bVMwMkhSSllWbXMyais1TlE0UmJUUE16YQozZ1YwbXgza3ZCRnl5b3RBN2pVald6dzlQL2w3ZlI1U2QyVGN3M0VselVKa3Y2QjJ0RFhXekR5YXlJcHRWVm84Cm8zK00vdHl5K3ErRExTTHBjbm95b0JWcDhiN3hkVTZlYWwvcGdCQUNZYlorK0g2SFg5aWwrN05OZGpvdTdxM2wKY1FJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg==
Tampered JWT: eyJraWQiOiI4YzNiYWRkMC04YWZjLTRlN2ItYWVjNy02ZDNlOGE1NjUzOTciLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiAicG9ydHN3aWdnZXIiLCAiZXhwIjogMTc4MzIyMzQzMywgInN1YiI6ICJ3aWVuZXIifQ.SL-siZQjI69iRZNVwAy-QtOCAD2WI4WR15DJBM1Vw8Q
Base64 encoded pkcs1 key: LS0tLS1CRUdJTiBSU0EgUFVCTElDIEtFWS0tLS0tCk1JSUJDZ0tDQVFFQXNoc1NpSVp1aG03WTBNTGlmMVYrM0FFVS9QTW0xczZhMk82YTVqa1prQVdTVzZhc2hTRHEKZEZrclpTMnBvNk5uZmlEbWlQMWRGaldYemVjU3l0L3lldFZGVlRqOEtEbG0xem0vTGs5L0FDak5GaFVpRldLSwp4UGQ0eU1KQ0NyNzQrWXZKRlpVZ3p0T1JJOHgrOXpGV1pyVTRBL2ZxdXc2emVxQ25wLzg5MHozdzhQR3ByRXBRCjNrUUgrSDRtUzAySFJKWVZtczJqKzVOUTRSYlRQTXphM2dWMG14M2t2QkZ5eW90QTdqVWpXenc5UC9sN2ZSNVMKZDJUY3czRWx6VUprdjZCMnREWFd6RHlheUlwdFZWbzhvMytNL3R5eStxK0RMU0xwY25veW9CVnA4Yjd4ZFU2ZQphbC9wZ0JBQ1liWisrSDZIWDlpbCs3Tk5kam91N3EzbGNRSURBUUFCCi0tLS0tRU5EIFJTQSBQVUJMSUMgS0VZLS0tLS0K
Tampered JWT: eyJraWQiOiI4YzNiYWRkMC04YWZjLTRlN2ItYWVjNy02ZDNlOGE1NjUzOTciLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiAicG9ydHN3aWdnZXIiLCAiZXhwIjogMTc4MzIyMzQzMywgInN1YiI6ICJ3aWVuZXIifQ.Un_-1iZo6qPDW3mVLzjPXmjzcmn_bs_SQGDhyNrQ0Yg

通过验证,确定正确公钥值

image-20260704115722937

生成恶意对称密钥,将k替换为X.509格式的公钥:

image-20260704120600496

更改算法和sub,使用恶意密钥进行签名:

image-20260704120908356
Previous postnacos历史漏洞复现Next postJS逆向之某平台登录密码加密