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内容被篡改。

image-20220324160511996

jwt_token

2、漏洞一(alg: none攻击)

每个JWT令牌应该在返回给客户端之前进行签名,如果令牌未签名,则客户端将能够更改令牌内容,从而造成越权操作。

推荐工具:jwt_tool.py

alg:none攻击通俗易懂的讲就是由于开发代码使用错误,后端不校验签名,导致攻击者可以在不需要密钥的情况下也可以控制伪造令牌。

image-20220324165642633

实验1:

题目要求:尝试修改令牌成为管理员用户,然后重置投票数(只有管理员可以修改)

抓包发送看看结果,可以看到提示只有admin用户才可以重置

image-20220324163112614

将token解密

eyJhbGciOiJIUzUxMiJ9.eyJpYXQiOjE2NDg5NzM3NDAsImFkbWluIjoiZmFsc2UiLCJ1c2VyIjoiVG9tIn0.-55OhNElIUr5xMJS2U0k0o0t-Li2VSExwjoFf2CrQFi3XLjwcelAu1yN-7L0bnPk_EFOEgPXsa8ItaTNNn44aw

image-20220324162739996

黑盒测试第一反应肯定是将"admin" : "false"修改为"admin" : "true",尝试将tom设置为admin权限。由于我们提前看过代码了,可以利用alg:none攻击

python jwt_tool.py eyJhbGciOiJIUzUxMiJ9.eyJpYXQiOjE2NDg5NzM3NDAsImFkbWluIjoiZmFsc2UiLCJ1c2VyIjoiVG9tIn0.-55OhNElIUr5xMJS2U0k0o0t-Li2VSExwjoFf2CrQFi3XLjwcelAu1yN-7L0bnPk_EFOEgPXsa8ItaTNNn44aw -T

image-20220324164159464

将生成的token带入cookie绕过获得admin权限

image-20220324165157212

修复建议:

验证令牌时使用正确代码:使用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

image-20220324170305968

我们使用工具尝试爆破key

image-20220324170634370

可以看到key的结果为shipping,将key带入并修改username参数为WebGoat,记住token有效期为1分钟

image-20220324170847329

image-20220324170918143

image-20220324171113338

可以看到密钥硬编码在代码中

修复建议:

1、使用强密钥,满足密码构成需求

2、代码中不要出现硬编码,一旦代码泄露造成认证失效,见shiro550硬编码漏洞

4、漏洞三(令牌刷新)

该漏洞偏向逻辑漏洞

在实际工作中较难碰到,需要有刷新令牌的功能

实验3:模拟tom为相关产品付费。需要结果代码审计

首先模拟Jerry登录

image-20220324172013463

image-20220324171937362

image-20220324172123577

第二个接口说明可以使用alg:none攻击从而绕过认证,我们尝试将其修改

image-20220324172543791

成功了,但该题并不是想让我们学习alg:none攻击

看到第三个接口

image-20220324172635939

我们继续看第三段接口

image-20220324173926359

仅校验是否存在user和refreshToken,未校验两者对应关系,可以用来刷新任何用户的过期token。思路:先用Jerry用户登录获取Jerry的token,利用newtoken接口刷新Tom过期的接口获得新的token,最后使用获得的新的token进行销账。

利用步骤

image-20220324180741981

image-20220324180909995

image-20220324180958034

JWT还有SQL注入、XXE等利用方式,由于结合了多种漏洞形式,刚接触的人可能接受不了,后续有机会再了解。

总结

1、用parseClaimsJws获取token,禁止使用parse获取

2、禁止使用弱密钥、尝试将密钥写进数据库,不要硬编码写在代码里

3、调用刷新token的接口时做好用户名与token的校验

posted @ 2022-03-24 18:18  我要变超人  阅读(2755)  评论(0编辑  收藏  举报