lim开源cms代码审计
字数 1679 2025-08-22 12:23:19

Lin-CMS-SpringBoot JWT安全漏洞分析与防御教学

1. 漏洞背景

Lin-CMS-SpringBoot是一套基于Java开发的开源内容管理系统。在代码审计过程中发现存在JWT(JSON Web Token)相关的验证逻辑问题,导致可以绕过权限控制访问后台接口,并且存在权限提升漏洞。

2. JWT机制分析

2.1 JWT基本结构

系统使用的JWT包含四个关键字段:

  • type: 固定值
  • scope: 固定值
  • identity: 用户身份标识(从数据库user表获取的user.getId())
  • exp: JWT过期时间(标准JWT字段)

2.2 登录流程分析

  1. 用户访问/cms/user/login接口进行登录

  2. 服务端验证流程:

    • 检查用户名是否存在(不存在则抛出异常)
    • 调用verifyUsernamePassword方法验证用户名、密码和用户ID
    • 验证通过后调用generateTokens方法生成JWT
  3. generateTokens方法生成两个token:

    • access token
    • refresh token

3. 漏洞详情

3.1 固定Secret漏洞(CVE-2022-32430)

问题描述

  • JWT的签名密钥(secret)在application.yml中硬编码为固定值
  • 所有使用相同secret的站点可以互相伪造token

影响

  • 攻击者获取任意一个站点的管理员token后,可以访问所有使用相同secret的站点
  • 无需知道secret值,只需获取一个有效token即可

3.2 权限提升漏洞

问题描述

  • identity字段直接使用数据库中的用户ID
  • 管理员通常为identity=1
  • 由于secret固定,可以伪造任意identity的token

攻击步骤

  1. 获取低权限用户token
  2. 解码token获取结构
  3. 修改identity为1(管理员)
  4. 使用固定secret重新签名
  5. 使用伪造的token访问管理员接口

3.3 Token失效机制缺陷

问题描述

  • 系统仅验证token格式和签名有效性
  • 不验证token是否在服务端存储的有效token列表中
  • 即使用户主动退出登录,未过期的token仍然有效

4. 漏洞复现

4.1 跨站点token复用

  1. 准备两个使用相同secret的Lin-CMS站点(网站1和网站2)
  2. 在网站1上使用管理员账号登录,获取token
  3. 在网站2上抓取需要管理员权限的接口请求
  4. 将网站1的token添加到网站2的请求中
  5. 成功访问网站2的管理员接口

4.2 权限提升攻击

  1. 使用低权限账号登录,获取token
  2. 使用jwt.io等工具解码token
  3. 修改payload中的identity为1
  4. 使用已知的secret重新签名
  5. 使用伪造的token访问管理员接口

5. 修复方案

5.1 安全配置建议

  1. 禁止使用固定secret

    • 每个部署实例应使用随机生成的唯一secret
    • 可通过环境变量或配置中心动态获取secret
  2. 实现token黑名单机制

    // 示例:使用Redis存储失效但未过期的token
    @Component
    public class TokenBlacklist {
        @Autowired
        private RedisTemplate<String, String> redisTemplate;
    
        public void addToBlacklist(String token, long expiration) {
            redisTemplate.opsForValue().set(token, "invalid", expiration, TimeUnit.MILLISECONDS);
        }
    
        public boolean isBlacklisted(String token) {
            return redisTemplate.hasKey(token);
        }
    }
    
  3. 增强identity验证

    // 在验证token时,额外检查用户状态和权限
    public boolean validateToken(String token) {
        // 标准JWT验证
        if (!Jwts.parser().setSigningKey(secret).parseClaimsJws(token)) {
            return false;
        }
    
        // 检查黑名单
        if (tokenBlacklist.isBlacklisted(token)) {
            return false;
        }
    
        // 从token获取用户信息
        Claims claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
        Long userId = claims.get("identity", Long.class);
    
        // 验证用户是否存在且状态正常
        User user = userService.findById(userId);
        if (user == null || !user.isActive()) {
            return false;
        }
    
        return true;
    }
    

5.2 代码层修复

  1. 动态secret实现

    @Configuration
    public class JwtConfig {
        @Value("${jwt.secret}")
        private String secret;
    
        @Bean
        public String jwtSecret() {
            // 优先从环境变量获取
            String envSecret = System.getenv("JWT_SECRET");
            return StringUtils.isNotBlank(envSecret) ? envSecret : secret;
        }
    }
    
  2. 增强token生成逻辑

    public Map<String, String> generateTokens(User user) {
        // 使用更复杂的identity构造方式
        String identity = DigestUtils.md5Hex(user.getId() + System.currentTimeMillis() + randomSalt);
    
        long now = System.currentTimeMillis();
        Date accessExpiration = new Date(now + accessTokenValidity);
        Date refreshExpiration = new Date(now + refreshTokenValidity);
    
        String accessToken = Jwts.builder()
            .setSubject(user.getUsername())
            .claim("identity", identity)
            .claim("role", user.getRole())
            .setIssuedAt(new Date(now))
            .setExpiration(accessExpiration)
            .signWith(SignatureAlgorithm.HS512, jwtSecret)
            .compact();
    
        // 存储identity到用户关联
        userTokenService.storeTokenIdentity(user.getId(), identity);
    
        return Map.of(
            "accessToken", accessToken,
            "refreshToken", refreshToken
        );
    }
    

6. 安全开发建议

  1. JWT最佳实践

    • 使用足够强度的随机secret(至少256位)
    • 设置合理的token过期时间
    • 避免在token中存储敏感信息
    • 实现token刷新机制
  2. 权限验证增强

    @PreAuthorize("hasPermission(#userId, 'USER', 'DELETE')")
    @DeleteMapping("/users/{userId}")
    public ResponseEntity deleteUser(@PathVariable Long userId) {
        // 方法实现
    }
    
  3. 安全审计要点

    • 定期检查secret管理方式
    • 验证token失效机制有效性
    • 检查权限验证是否全面
    • 确保没有硬编码的敏感信息

7. 总结

Lin-CMS-SpringBoot的JWT实现存在多个安全隐患,主要源于固定secret和不足的验证逻辑。通过本教学文档的分析,开发者应当理解:

  1. 动态secret管理的重要性
  2. 完善的token验证机制的必要性
  3. 权限系统的严谨实现方式
  4. 安全审计的关键点

正确实现JWT认证需要综合考虑加密强度、状态管理、权限控制和失效机制等多个方面,才能构建真正安全的认证系统。

Lin-CMS-SpringBoot JWT安全漏洞分析与防御教学 1. 漏洞背景 Lin-CMS-SpringBoot是一套基于Java开发的开源内容管理系统。在代码审计过程中发现存在JWT(JSON Web Token)相关的验证逻辑问题,导致可以绕过权限控制访问后台接口,并且存在权限提升漏洞。 2. JWT机制分析 2.1 JWT基本结构 系统使用的JWT包含四个关键字段: type : 固定值 scope : 固定值 identity : 用户身份标识(从数据库user表获取的user.getId()) exp : JWT过期时间(标准JWT字段) 2.2 登录流程分析 用户访问 /cms/user/login 接口进行登录 服务端验证流程: 检查用户名是否存在(不存在则抛出异常) 调用 verifyUsernamePassword 方法验证用户名、密码和用户ID 验证通过后调用 generateTokens 方法生成JWT generateTokens 方法生成两个token: access token refresh token 3. 漏洞详情 3.1 固定Secret漏洞(CVE-2022-32430) 问题描述 : JWT的签名密钥(secret)在 application.yml 中硬编码为固定值 所有使用相同secret的站点可以互相伪造token 影响 : 攻击者获取任意一个站点的管理员token后,可以访问所有使用相同secret的站点 无需知道secret值,只需获取一个有效token即可 3.2 权限提升漏洞 问题描述 : identity 字段直接使用数据库中的用户ID 管理员通常为 identity=1 由于secret固定,可以伪造任意identity的token 攻击步骤 : 获取低权限用户token 解码token获取结构 修改 identity 为1(管理员) 使用固定secret重新签名 使用伪造的token访问管理员接口 3.3 Token失效机制缺陷 问题描述 : 系统仅验证token格式和签名有效性 不验证token是否在服务端存储的有效token列表中 即使用户主动退出登录,未过期的token仍然有效 4. 漏洞复现 4.1 跨站点token复用 准备两个使用相同secret的Lin-CMS站点(网站1和网站2) 在网站1上使用管理员账号登录,获取token 在网站2上抓取需要管理员权限的接口请求 将网站1的token添加到网站2的请求中 成功访问网站2的管理员接口 4.2 权限提升攻击 使用低权限账号登录,获取token 使用 jwt.io 等工具解码token 修改payload中的 identity 为1 使用已知的secret重新签名 使用伪造的token访问管理员接口 5. 修复方案 5.1 安全配置建议 禁止使用固定secret : 每个部署实例应使用随机生成的唯一secret 可通过环境变量或配置中心动态获取secret 实现token黑名单机制 : 增强identity验证 : 5.2 代码层修复 动态secret实现 : 增强token生成逻辑 : 6. 安全开发建议 JWT最佳实践 : 使用足够强度的随机secret(至少256位) 设置合理的token过期时间 避免在token中存储敏感信息 实现token刷新机制 权限验证增强 : 安全审计要点 : 定期检查secret管理方式 验证token失效机制有效性 检查权限验证是否全面 确保没有硬编码的敏感信息 7. 总结 Lin-CMS-SpringBoot的JWT实现存在多个安全隐患,主要源于固定secret和不足的验证逻辑。通过本教学文档的分析,开发者应当理解: 动态secret管理的重要性 完善的token验证机制的必要性 权限系统的严谨实现方式 安全审计的关键点 正确实现JWT认证需要综合考虑加密强度、状态管理、权限控制和失效机制等多个方面,才能构建真正安全的认证系统。