JSON Web Token(JWT)是目前最流行的跨域身份验证解决方案。直接根据token取出保存的用户信息,以及对token可用性校验,大大简化单点登录。
JWT=header+payload+signature(以.相隔)
例:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NzYxMTk2NTYsInVzZXJuYW1lIjoiemRqIiwicGFzc3dvcmQiOiIxMjMifQ.ud_qtIYt3QywJkmjPZIXVPaW3SnWCFj9dLVYa7iTEIg
下面详细介绍一下每个部分。
头部(Header)
用于描述关于该JWT的最基本的信息,例如其类型以及签名所用的算法等。
{"alg":"HS256","typ":"JWT"}
BASE64编码后为:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
载荷(playload)
(1)标准中注册的声明(建议但不强制使用)
(2)公共的声明
(3)私有的声明(过期时间,用户名等信息)
{"exp":1576119656,"username":"zdj","password":"123"}
BASE64编码后:eyJleHAiOjE1NzYxMTk2NTYsInVzZXJuYW1lIjoiemRqIiwicGFzc3dvcmQiOiIxMjMifQ
签名(signature)
header(base64)+payload(base64)使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分
ud_qtIYt3QywJkmjPZIXVPaW3SnWCFj9dLVYa7iTEIg
注:secret是保存在服务器端的签名私钥,就是我们今天的主角;
基于java实现JWT:
生成token:
public static String Generatetoken(String username, String password) {
String token = "";
try {
// 过期时间
Date date = new Date(System.currentTimeMillis() + EXPIRE_DATE);
// 秘钥及加密算法
Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET);
// 设置头部信息
Map<String, Object> header = new HashMap<String, Object>();
header.put("typ", "JWT");
header.put("alg", "HS256");
// 携带username,password信息,生成签名
token = JWT.create().withHeader(header)
.withClaim("username", username)
.withClaim("password", password).withExpiresAt(date)
.sign(algorithm);
} catch (Exception e) {
e.printStackTrace();
return null;
}
return token;
}
校验token:
public static boolean verify(String token) {
try {
Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET);
JWTVerifier verifier = JWT.require(algorithm).build();
System.out.println("验证token:" + token);
DecodedJWT jwt = verifier.verify(token);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
生成的token为
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NzYxMTk2NTYsInVzZXJuYW1lIjoiemRqIiwicGFzc3dvcmQiOiIxMjMifQ.ud_qtIYt3QywJkmjPZIXVPaW3SnWCFj9dLVYa7iTEIg
综上,我们要伪造token,首先要拿到TOKEN_SECRET,才可以伪造签名,token才能校验通过。
pyjwt 库,可通过 jwt.decode(jwt_str, verify=True, key=key_)
进行签名校验,但导致校验失败的因素不仅密钥错误,还可能是数据部分中预定义字段错误(如,当前时间超过 exp),也可能是 JWT字符串格式错误等等,所以,借助 jwt.decode(jwt_str, verify=True, key=key_) 验证密钥 key_:
1.若签名直接校验失败,则 key_ 为有效密钥;
2.若因数据部分预定义字段错误
(jwt.exceptions.ExpiredSignatureError,
jwt.exceptions.InvalidAudienceError,
jwt.exceptions.InvalidIssuedAtError,
jwt.exceptions.InvalidIssuedAtError,
jwt.exceptions.ImmatureSignatureError)导致校验失败,说明并非密钥错误导致,则 key_ 也为有效密钥;
3.若因密钥错误(jwt.exceptions.InvalidSignatureError)导致校验失败,则 key_ 为无效密钥;
4.若为其他原因(如,JWT 字符串格式错误)导致校验失败,根本无法验证当前 key_ 是否有效。
按此逻辑,快速实现 JWT 密钥暴破功能,代码如下:
准备好key.txt字典,目标token为:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NzYxMjE1NDcsInVzZXJuYW1lIjoiemRqIiwicGFzc3dvcmQiOiIxMjMifQ.mkCLR5Kje9x-z8hRgWBMxnQm8hknOwV1Zd8uSZa3rQY,运行脚本进行爆破:
总结
因此,开发员要十分注意token的密钥强度,不然攻击者可以通过pyjwt爆破得到token密钥,通过header和payload进行signature,进而伪造token发生跨域攻击。