JWT相关漏洞介绍
JWT相关漏洞介绍
1、JWT基础介绍
JWT常用于微服务,由于国内微服务常见于Java,所以JWT漏洞大都发生在Java应用中。
为什么用JWT?
JWT只通过算法实现对Token合法性的验证,不依赖数据库,Memcached的等存储系统,因此可以做到跨服务器验证,只要密钥和算法相同,不同服务器程序生成的Token可以互相验证,一旦我们掌握了token的构造,便可模仿任何已知用户进行操作。
以下面这段JWT token为例eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
JWT令牌结构分为三部分,每部分中间使用点(.)分割,比如:xxxx.yyyyy.zzzzz
Header 头部包括令牌的类型(即JWT)及使用的哈希算法(如HMAC SHA256或RSA)
payload
第二部分是负载,内容也是一个json对象,它是存放有效信息的地方,它可以存放jwt提供的现成字段,比 如:iss(签发者),exp(过期时间戳), sub(面向的用户)等,也可自定义字段。
Signature
第三部分是签名,此部分用于防止jwt内容被篡改。
2、漏洞一(alg: none攻击)
每个JWT令牌应该在返回给客户端之前进行签名,如果令牌未签名,则客户端将能够更改令牌内容,从而造成越权操作。
推荐工具:jwt_tool.py
alg:none攻击通俗易懂的讲就是由于开发代码使用错误,后端不校验签名,导致攻击者可以在不需要密钥的情况下也可以控制伪造令牌。
实验1:
题目要求:尝试修改令牌成为管理员用户,然后重置投票数(只有管理员可以修改)
抓包发送看看结果,可以看到提示只有admin用户才可以重置
将token解密
eyJhbGciOiJIUzUxMiJ9.eyJpYXQiOjE2NDg5NzM3NDAsImFkbWluIjoiZmFsc2UiLCJ1c2VyIjoiVG9tIn0.-55OhNElIUr5xMJS2U0k0o0t-Li2VSExwjoFf2CrQFi3XLjwcelAu1yN-7L0bnPk_EFOEgPXsa8ItaTNNn44aw
黑盒测试第一反应肯定是将"admin" : "false"修改为"admin" : "true",尝试将tom设置为admin权限。由于我们提前看过代码了,可以利用alg:none攻击
python jwt_tool.py eyJhbGciOiJIUzUxMiJ9.eyJpYXQiOjE2NDg5NzM3NDAsImFkbWluIjoiZmFsc2UiLCJ1c2VyIjoiVG9tIn0.-55OhNElIUr5xMJS2U0k0o0t-Li2VSExwjoFf2CrQFi3XLjwcelAu1yN-7L0bnPk_EFOEgPXsa8ItaTNNn44aw -T
将生成的token带入cookie绕过获得admin权限
修复建议:
验证令牌时使用正确代码:使用parseClaimsJws获取token,禁止使用parse获取。
课堂作业
代码片段一
try { Jwt jwt = Jwts.parser().setSigningKey(JWT_PASSWORD).parseClaimsJws(accessToken); Claims claims = (Claims) jwt.getBody(); String user = (String) claims.get("user"); boolean isAdmin = Boolean.valueOf((String) claims.get("admin")); if (isAdmin) { removeAllUsers(); } else { log.error("You are not an admin user"); } } catch (JwtException e) { throw new InvalidTokenException(e); }
代码片段二
try { Jwt jwt = Jwts.parser().setSigningKey(JWT_PASSWORD).parse(accessToken); Claims claims = (Claims) jwt.getBody(); String user = (String) claims.get("user"); booelean isAdmin = Boolean.valueOf((String) claims.get("admin")); if (isAdmin) { removeAllUsers(); } else { log.error("You are not an admin user"); } } catch (JwtException e) { throw new InvalidTokenException(e); }
哪一段代码具有漏洞风险
3、漏洞二(密钥爆破)
我们知道JWT分为三部分,前两部分由base64编码而成,而第三部分由第一部分的加密方式及key加密而成,当应用不存在alg:none漏洞时,我们需要key才能构造一个完整可用的token。此处介绍token的密钥破解,关键在于字典要庞大。
实验2:尝试修改下面token,使其成为WebGoat用户的token
eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJXZWJHb2F0IFRva2VuIEJ1aWxkZXIiLCJhdWQiOiJ3ZWJnb2F0Lm9yZyIsImlhdCI6MTY0ODEwOTI1NywiZXhwIjoxNjQ4MTA5MzE3LCJzdWIiOiJ0b21Ad2ViZ29hdC5vcmciLCJ1c2VybmFtZSI6IlRvbSIsIkVtYWlsIjoidG9tQHdlYmdvYXQub3JnIiwiUm9sZSI6WyJNYW5hZ2VyIiwiUHJvamVjdCBBZG1pbmlzdHJhdG9yIl19.xtDJKmWvAH-pleTEnDuOkOC0PbCE9IU9854oS1FiLTA
我们使用工具尝试爆破key
可以看到key的结果为shipping,将key带入并修改username参数为WebGoat,记住token有效期为1分钟
可以看到密钥硬编码在代码中
修复建议:
1、使用强密钥,满足密码构成需求
2、代码中不要出现硬编码,一旦代码泄露造成认证失效,见shiro550硬编码漏洞
4、漏洞三(令牌刷新)
该漏洞偏向逻辑漏洞
在实际工作中较难碰到,需要有刷新令牌的功能
实验3:模拟tom为相关产品付费。需要结果代码审计
首先模拟Jerry登录
第二个接口说明可以使用alg:none攻击从而绕过认证,我们尝试将其修改
成功了,但该题并不是想让我们学习alg:none攻击
看到第三个接口
我们继续看第三段接口
仅校验是否存在user和refreshToken,未校验两者对应关系,可以用来刷新任何用户的过期token。思路:先用Jerry用户登录获取Jerry的token,利用newtoken接口刷新Tom过期的接口获得新的token,最后使用获得的新的token进行销账。
利用步骤
JWT还有SQL注入、XXE等利用方式,由于结合了多种漏洞形式,刚接触的人可能接受不了,后续有机会再了解。
总结
1、用parseClaimsJws获取token,禁止使用parse获取
2、禁止使用弱密钥、尝试将密钥写进数据库,不要硬编码写在代码里
3、调用刷新token的接口时做好用户名与token的校验