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

Checkmarx 9扫描报Log forging日志伪造漏洞问题咨询

方法getCorrelationId从getHeader元素获取用户输入,该元素的取值未经过正确的安全净化与校验即在代码中流转,最终在startTransaction方法中被用于写入审计日志,可能引发Log forging(日志伪造)风险。

Log Forging问题成因

核心风险逻辑:未经过滤的用户可控输入被直接写入日志时,攻击者可以在输入中插入\r(回车)、\n(换行)等控制字符,人为截断当前日志行,伪造虚假的日志条目(比如伪造管理员操作记录、错误报警信息),干扰审计溯源,甚至绕过SIEM、日志分析系统的检测规则。
当前代码的污点源非常明确:CORRELATION_ID_HEADER请求头完全由客户端可控,攻击者可以随意构造该头的内容传入。

现有代码未通过扫描的原因

代码中使用StringEscapeUtils.escapeJava做处理的方案不满足日志伪造防护要求,Checkmarx不会将其识别为有效净化手段,原因有两点:

  • StringEscapeUtils.escapeJava的设计目标是转义Java源代码字符串字面量中的特殊字符,并非面向日志注入场景设计,不会针对性拦截日志伪造依赖的CR、LF等控制字符的注入风险,甚至会出现不必要的双重转义问题——代码在getCorrelationId和startTransaction里对同一个值做了两次escapeJava处理,属于冗余逻辑。
  • 代码完全没有对correlationId做合法性校验:链路追踪ID本身有通用的格式规范(通常为UUID、或仅包含字母/数字/中划线的定长字符串),没有白名单校验的情况下,静态扫描规则会判定输入仍存在被恶意构造的可能。

另外代码存在逻辑隐患:从request取到头的值后没有先判空就直接传入转义方法,虽然commons-lang3的工具方法对null入参做了兼容,但逻辑严谨性不足。

正确修复方案

按照优先级做三层处理即可彻底解决该问题,同时满足Checkmarx的扫描规则要求:

  • 白名单格式校验优先:对从请求头获取的correlationId做正则匹配,仅允许符合格式要求的值通过,不符合的值直接丢弃,使用默认生成的合法correlationId替代。
  • 针对性移除日志风险字符:放弃使用escapeJava,专门过滤掉输入中所有ASCII控制字符(字符编码小于0x20的字符,包含\r、\n、退格、换页等所有可能干扰日志格式的控制符)。
  • 修正代码逻辑问题:调整空判断顺序,移除重复转义逻辑,所有日志打印统一使用参数化格式,避免无意义的字符串拼接。
修复后参考代码
// 提前定义correlationId白名单正则,根据实际业务的ID格式调整,这里以通用的UUID/链路ID格式为例
private static final Pattern CORRELATION_ID_PATTERN = Pattern.compile("^[a-zA-Z0-9\\-]{1,64}$");
private static final String DEFAULT_CORRELATION_ID = "unknown";

private String getCorrelationId(HttpServletRequest request) {
    String headerValue = request.getHeader(CORRELATION_ID_HEADER);
    // 先判空再处理
    if (headerValue == null || headerValue.isEmpty()) {
        return DEFAULT_CORRELATION_ID;
    }
    // 移除所有ASCII控制字符,从根源杜绝换行伪造
    String sanitized = headerValue.chars()
            .filter(c -> c >= 0x20)
            .collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append)
            .toString();
    // 白名单格式校验,不匹配则返回默认值
    if (CORRELATION_ID_PATTERN.matcher(sanitized).matches()) {
        return sanitized;
    }
    return DEFAULT_CORRELATION_ID;
}

private void startTransaction(HttpServletRequest request, String serviceName, Object... args) {
    String correlationId = getCorrelationId(request);
    // getCorrelationId返回值已经完成净化,无需重复处理
    if (DEFAULT_CORRELATION_ID.equals(correlationId)) {
        logger.info("{} service:{} args:{}", LOG_SERVICE_TYPE, serviceName, args);
    } else {
        logger.error("{} service:{} correlationId:{} args:{}", LOG_SERVICE_TYPE, serviceName, correlationId, args);
    }
}
补充说明

不要依赖日志框架的参数化打印能力防范日志伪造:参数化打印仅能解决日志表达式注入(比如早年Log4j2的JNDI注入类问题),不会自动过滤输入中的换行控制字符,必须手动做字符过滤和格式校验才能彻底解决Log Forging风险。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 16:31:36