You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

老旧Java项目中CSRF Token掩码处理方案咨询

问题描述

我维护着一个老旧Java项目,最近外部代码审计提出两项CSRF Token优化要求:一是定期轮换Token,避免会话全程使用同一个;二是对Token做掩码处理,防止在BurpSuite这类工具中直接被识别出来。目前Token轮换的实现思路已经确定并在推进,但还没找到Token掩码处理的有效方案,想请教相关实现思路。注:该Token会在报告下载场景的URL中暴露,示例请求如下:

GET /xyz/testcontroller/begin?event=Y&CSRF_TOKEN=-653632r324354546432tret455364&id=3576632446475442342
Host: xyz.com
Cookie: JSESSIONID=452455535455762565; FileDownload=null; JSESSIONID=452455535455762565;
 < other security parameters>
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.5414.75 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
<some more parameters>
Referer: xxxxxx
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Connection: close

CSRF Token掩码处理实现思路

1. 对称加密可逆掩码(适合快速改造,兼容性强)

  • 用AES这类对称加密算法对原始CSRF Token加密,加密结果做URL安全的Base64编码后作为URL参数传递。
  • 后端接收请求时,先解码并解密得到原始Token,再执行正常校验逻辑。
  • 关键细节:
    • 加密密钥从配置文件/环境变量加载,禁止硬编码;密钥长度符合算法要求(如AES需16/24/32字节)。
    • 采用URL安全的Base64编码,避免+//等字符在URL中被转义导致解析失败。
    • 示例代码:
      import javax.crypto.Cipher;
      import javax.crypto.spec.SecretKeySpec;
      import java.nio.charset.StandardCharsets;
      import java.util.Base64;
      
      public class CsrfMaskUtil {
          // 从配置读取密钥,示例为16字节AES密钥
          private static final String SECRET_KEY = "your-secure-16byte-key";
      
          public static String maskToken(String rawToken) throws Exception {
              SecretKeySpec keySpec = new SecretKeySpec(SECRET_KEY.getBytes(), "AES");
              Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
              cipher.init(Cipher.ENCRYPT_MODE, keySpec);
              byte[] encryptedBytes = cipher.doFinal(rawToken.getBytes(StandardCharsets.UTF_8));
              return Base64.getUrlEncoder().encodeToString(encryptedBytes);
          }
      
          public static String unmaskToken(String maskedToken) throws Exception {
              SecretKeySpec keySpec = new SecretKeySpec(SECRET_KEY.getBytes(), "AES");
              Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
              cipher.init(Cipher.DECRYPT_MODE, keySpec);
              byte[] decodedBytes = Base64.getUrlDecoder().decode(maskedToken);
              return new String(cipher.doFinal(decodedBytes), StandardCharsets.UTF_8);
          }
      }
      

2. 哈希加盐掩码(不可逆,依赖会话存储)

  • 原始Token存在用户会话中,URL传递的是原始Token+固定盐值的哈希结果(如SHA-256)。
  • 后端校验时,从会话取出原始Token,用相同盐值计算哈希,与URL中的掩码值比对。
  • 关键细节:
    • 盐值需固定且保密,避免泄露后被逆向破解。
    • 适合已在会话中存储Token的场景,无需额外加密逻辑。
    • 示例代码:
      import java.security.MessageDigest;
      import java.nio.charset.StandardCharsets;
      
      public class CsrfMaskUtil {
          private static final String FIXED_SALT = "your-confidential-salt";
      
          public static String generateMaskedToken(String rawToken) throws Exception {
              MessageDigest digest = MessageDigest.getInstance("SHA-256");
              byte[] hashBytes = digest.digest((rawToken + FIXED_SALT).getBytes(StandardCharsets.UTF_8));
              // 转十六进制字符串
              StringBuilder sb = new StringBuilder();
              for (byte b : hashBytes) {
                  sb.append(String.format("%02x", b));
              }
              return sb.toString();
          }
      
          public static boolean validateMaskedToken(String maskedToken, String rawTokenFromSession) throws Exception {
              if (rawTokenFromSession == null) return false;
              String expectedMask = generateMaskedToken(rawTokenFromSession);
              return expectedMask.equals(maskedToken);
          }
      }
      

3. 字符规则替换(轻量方案,快速上线)

  • 对原始Token的字符做固定规则的替换混淆,比如字母移位、数字映射,后端接收后反向还原。
  • 关键细节:
    • 规则需具备一定复杂度,避免被轻易逆向,但无需过度设计。
    • 确保替换后的字符均为URL安全字符,无特殊符号。
    • 示例代码:
      public class CsrfMaskUtil {
          // 掩码:字母+5循环移位,数字+5循环移位
          public static String maskToken(String rawToken) {
              StringBuilder masked = new StringBuilder();
              for (char c : rawToken.toCharArray()) {
                  if (Character.isLowerCase(c)) {
                      masked.append((char) ((c - 'a' + 5) % 26 + 'a'));
                  } else if (Character.isUpperCase(c)) {
                      masked.append((char) ((c - 'A' + 5) % 26 + 'A'));
                  } else if (Character.isDigit(c)) {
                      masked.append((char) ((c - '0' + 5) % 10 + '0'));
                  } else {
                      masked.append(c);
                  }
              }
              return masked.toString();
          }
      
          // 解掩码:反向移位
          public static String unmaskToken(String maskedToken) {
              StringBuilder raw = new StringBuilder();
              for (char c : maskedToken.toCharArray()) {
                  if (Character.isLowerCase(c)) {
                      raw.append((char) ((c - 'a' - 5 + 26) % 26 + 'a'));
                  } else if (Character.isUpperCase(c)) {
                      raw.append((char) ((c - 'A' - 5 + 26) % 26 + 'A'));
                  } else if (Character.isDigit(c)) {
                      raw.append((char) ((c - '0' - 5 + 10) % 10 + '0'));
                  } else {
                      raw.append(c);
                  }
              }
              return raw.toString();
          }
      }
      

通用注意事项

  • 统一处理逻辑:通过过滤器(Filter)拦截所有包含CSRF_TOKEN参数的请求,先完成解掩码/校验,再传递给业务控制器,减少业务代码修改量。
  • URL安全验证:确保掩码后的Token无特殊字符,避免URL编码导致解析失败。
  • 测试验证:抓包确认BurpSuite无法直接识别掩码后的Token为CSRF Token(工具通常识别格式规整的Token字符串,掩码后格式改变即可规避)。

内容的提问来源于stack exchange,提问作者Sam

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 22:20:41