codeql java中关于污点传播规则
字数 2932 2025-10-01 14:05:52

CodeQL Java 污点传播规则详解

一、概述

CodeQL 的污点分析(Taint Tracking)用于追踪不可信数据(source)在程序中的传播路径,直到危险使用点(sink)。污点传播规则定义了数据如何在各种操作中保持污染状态,是提高漏洞检测准确性的关键。

二、基本规则结构

2.1 标准检测模板

import semmle.code.java.dataflow.FlowSources
private import semmle.code.java.dataflow.TaintTracking
import BaseInjectionFlow::PathGraph

module BaseFlowConfig implements DataFlow::ConfigSig {
  predicate isSource(DataFlow::Node source) { 
    source instanceof ActiveThreatModelSource 
  }
  
  predicate isSink(DataFlow::Node sink) { 
    sink instanceof ceshiServiceSink 
  }
  
  predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
    none()
  }
  
  predicate observeDiffInformedIncrementalMode() { 
    any() 
  }
}

module BaseInjectionFlow = TaintTracking::Global<BaseFlowConfig>;

from BaseInjectionFlow::PathNode source, BaseInjectionFlow::PathNode sink
where BaseInjectionFlow::flowPath(source, sink)
select sink.getNode(), source, sink, "cccccc", source.getNode(), "this user input"

2.2 污点传播架构

QueryInjectionFlowConfig (implements DataFlow::ConfigSig)
            ↓
TaintTracking::Global<QueryInjectionFlowConfig>
            ↓
AddTaintDefaults<Config> (添加默认污点传播规则)
            ↓
defaultAdditionalTaintStep() (定义在 TaintTrackingUtil.qll)

三、默认传播逻辑

3.1 核心文件

  • semmle.code.java.dataflow.internal.TaintTrackingUtil
  • semmle.code.java.dataflow.TaintTracking
  • codeql.dataflow.TaintTracking

3.2 表达式级别传播 (localAdditionalTaintExprStep)

字符串操作传播

  • 字符串拼接 (AddExpr):"hello" + taintedVar
  • 赋值拼接 (AssignAddExpr):str += taintedVar
  • 字符串模板 (StringTemplateExpr):Java 字符串模板表达式
  • 逻辑表达式 (LogicExpr):&&|| 操作

方法调用传播(返回值)

  • 构造函数步骤 (constructorStep):

    • InputStream 包装器构造函数
    • 通过扩展构造的包装器
    • TaintPreservingCallable 构造函数
  • 限定符到方法调用 (qualifierToMethodStep):

    • StringWriter.getBuffer() / StringWriter.toString()
    • ObjectInputStream.read*() 方法
    • Spring 不受信任数据类型的 getter 方法
    • TaintPreservingCallable 返回污点,且 returnsTaintFrom(-1)(从 qualifier 返回污染)
    • JAX-RS 资源方法的 getter
  • 参数到方法调用 (argToMethodStep):

    • Base64 编码/解码:Base64.decodeBase64(), Base64.encodeBase64*()
    • Spring ResponseEntity:ResponseEntity.ok(), ResponseEntity.of()
    • Spring ResponseEntityBodyBuilder:body() 方法
    • TaintPreservingCallable 参数传播(参数 i ≥ 0)

特殊操作

  • 比较步骤 (comparisonStep):与常量的比较或相等测试
  • 序列化步骤 (serializationStep):通过 ObjectOutputStream 的数据序列化
  • 格式化步骤 (formatStep):通过 Formatter 的字符串格式化

3.3 更新级别传播 (localAdditionalTaintUpdateStep)

  • 限定符到参数 (qualifierToArgumentStep):obj.method(arg) 中从 obj 到 arg
  • 参数到参数 (argToArgStep):方法参数之间的污点传播
  • 参数到限定符 (argToQualifierStep):从方法参数到调用对象

3.4 容器内容传播

  • 数组内容:DataFlow::ArrayContent
  • 集合内容:DataFlow::CollectionContent
  • Map 值内容:DataFlow::MapValueContentMapKeyContent
  • 继承污点的内容:TaintInheritingContent

3.5 流总结步骤

  • 库代码流总结:FlowSummaryImpl 定义的本地步骤

3.6 入口点字段步骤 (entrypointFieldStep)

  • 字段读取传播:从威胁模型源类型的对象到其字段的污点传播
  • 适用场景:当源是 ActiveThreatModelSource 参数类型的字段访问时

四、自定义传播规则

4.1 AdditionalTaintStep 扩展

官方内部扩展机制,用于框架特定传播规则:

// semmle.code.java.dataflow.FlowSources 中的示例
private class InputStreamAdditionalTaintStep extends AdditionalTaintStep {
  override predicate step(DataFlow::Node node1, DataFlow::Node node2) {
    exists(ConstructorCall cc, MethodAccess ma |
      // InputStream 包装器构造函数传播
      cc.getConstructedType() instanceof InputStreamWrapperType and
      node1.asExpr() = cc.getArgument(0) and
      node2.asExpr() = cc
    )
  }
}

4.2 TaintPreservingCallable 扩展

更灵活的扩展方式,支持参数到参数的污点传播:

class MyTaintPreservingCallable extends TaintPreservingCallable {
  // 从参数到返回值的传播
  override predicate returnsTaintFrom(int i) {
    i = 0 and this instanceof StringValueOfCall
  }
  
  // 参数到参数的传播(修改对象状态)
  override predicate transfersTaint(int src, int sink) {
    src = 0 and sink = -1 and // 从第一个参数到调用对象
    this instanceof CollectionAddMethod
  }
}

transfersTaint 参数含义

  • src:污点源的位置索引
    • src >= 0:方法参数索引(0 = 第一个参数)
    • src = -1:调用对象(qualifier)
  • sink:污点目标的位置索引
    • sink >= 0:方法参数索引(会被修改)
    • sink = -1:调用对象(会被修改)

与 returnsTaintFrom 的区别

方面 returnsTaintFrom transfersTaint
传播目标 方法返回值 方法参数或调用对象
对象状态 创建新对象 修改现有对象
使用场景 result = obj.method(param) obj.method(param) 修改 obj 或 param
数据流类型 值流 引用流/状态更新

4.3 库规则自定义(推荐)

使用 YAML 文件定义外部库的污点传播行为:

# ext/com.squareup.okhttp.model.yml
- type: model
  input: 
    package: "com.squareup.okhttp"
    type: "Request.Builder"
    method: "url(String)"
  output:
    kind: "taint"  # 关键:必须是 taint 才能在污点分析中使用
    type: "Request.Builder"
    index: "return"

qlpack.yml 中添加引用:

models:
  - ext/com.squareup.okhttp.model.yml

五、实际应用案例

5.1 String.valueOf() 传播缺失问题

官方规则中,String.valueOf(url) 的污点传播可能缺失,可通过 TaintPreservingCallable 扩展:

// propagate.qll
private class StringValueOfCall extends MethodAccess {
  StringValueOfCall() {
    this.getMethod().hasName("valueOf") and
    this.getMethod().getDeclaringType().hasName("String")
  }
}

class StringValueOfTaint extends TaintPreservingCallable {
  StringValueOfTaint() { this instanceof StringValueOfCall }
  
  override predicate returnsTaintFrom(int i) {
    i = 0 // 从第一个参数传播到返回值
  }
}

5.2 SSRF 检测中的 OkHttp 传播

对于 com.squareup.okhttp(非 okhttp3),需要手动添加传播规则:

# ext/com.squareup.okhttp.model.yml
- type: model
  input: 
    package: "com.squareup.okhttp"
    type: "Request.Builder"
    method: "url(String)"
  output:
    kind: "taint"
    type: "Request.Builder"
    index: "return"

- type: model
  input: 
    package: "com.squareup.okhttp"
    type: "Request.Builder"
    method: "build()"
  output:
    kind: "taint"
    type: "Request"
    index: "return"

- type: model
  input: 
    package: "com.squareup.okhttp"
    type: "OkHttpClient"
    method: "newCall(Request)"
  output:
    kind: "taint"
    type: "Call"
    index: "return"

六、最佳实践

  1. 优先使用库规则:YAML 模型文件更简洁易维护
  2. 合理选择扩展方式
    • 通用传播规则:使用 TaintPreservingCallable
    • 框架特定规则:使用 AdditionalTaintStep
    • 外部库模型:使用 YAML 模型文件
  3. 注意传播方向
    • 值传播:returnsTaintFrom(创建新对象)
    • 引用传播:transfersTaint(修改现有对象)
  4. 测试验证:确保自定义规则不会引入误报或漏报

七、总结

CodeQL 提供了多层次的污点传播机制,从默认的表达式级别传播到高度可定制的扩展机制。实际应用中需要注意:

  1. 官方默认规则并不完整,需要根据目标代码库特点进行补充
  2. 不同的扩展方式适用于不同场景,需要合理选择
  3. 库规则自定义是官方推荐的发展方向,提供了更简洁的语法
  4. 污点传播的准确性直接影响漏洞检测的漏报率和误报率

通过深入理解污点传播机制并合理扩展,可以显著提高 CodeQL 在复杂代码库中的漏洞检测能力。

CodeQL Java 污点传播规则详解 一、概述 CodeQL 的污点分析(Taint Tracking)用于追踪不可信数据(source)在程序中的传播路径,直到危险使用点(sink)。污点传播规则定义了数据如何在各种操作中保持污染状态,是提高漏洞检测准确性的关键。 二、基本规则结构 2.1 标准检测模板 2.2 污点传播架构 三、默认传播逻辑 3.1 核心文件 semmle.code.java.dataflow.internal.TaintTrackingUtil semmle.code.java.dataflow.TaintTracking codeql.dataflow.TaintTracking 3.2 表达式级别传播 (localAdditionalTaintExprStep) 字符串操作传播 字符串拼接 ( AddExpr ): "hello" + taintedVar 赋值拼接 ( AssignAddExpr ): str += taintedVar 字符串模板 ( StringTemplateExpr ):Java 字符串模板表达式 逻辑表达式 ( LogicExpr ): && 和 || 操作 方法调用传播(返回值) 构造函数步骤 ( constructorStep ): InputStream 包装器构造函数 通过扩展构造的包装器 TaintPreservingCallable 构造函数 限定符到方法调用 ( qualifierToMethodStep ): StringWriter.getBuffer() / StringWriter.toString() ObjectInputStream.read*() 方法 Spring 不受信任数据类型的 getter 方法 TaintPreservingCallable 返回污点,且 returnsTaintFrom(-1) (从 qualifier 返回污染) JAX-RS 资源方法的 getter 参数到方法调用 ( argToMethodStep ): Base64 编码/解码: Base64.decodeBase64() , Base64.encodeBase64*() Spring ResponseEntity: ResponseEntity.ok() , ResponseEntity.of() Spring ResponseEntityBodyBuilder: body() 方法 TaintPreservingCallable 参数传播(参数 i ≥ 0) 特殊操作 比较步骤 ( comparisonStep ):与常量的比较或相等测试 序列化步骤 ( serializationStep ):通过 ObjectOutputStream 的数据序列化 格式化步骤 ( formatStep ):通过 Formatter 的字符串格式化 3.3 更新级别传播 (localAdditionalTaintUpdateStep) 限定符到参数 ( qualifierToArgumentStep ): obj.method(arg) 中从 obj 到 arg 参数到参数 ( argToArgStep ):方法参数之间的污点传播 参数到限定符 ( argToQualifierStep ):从方法参数到调用对象 3.4 容器内容传播 数组内容: DataFlow::ArrayContent 集合内容: DataFlow::CollectionContent Map 值内容: DataFlow::MapValueContent 或 MapKeyContent 继承污点的内容: TaintInheritingContent 3.5 流总结步骤 库代码流总结: FlowSummaryImpl 定义的本地步骤 3.6 入口点字段步骤 (entrypointFieldStep) 字段读取传播:从威胁模型源类型的对象到其字段的污点传播 适用场景:当源是 ActiveThreatModelSource 参数类型的字段访问时 四、自定义传播规则 4.1 AdditionalTaintStep 扩展 官方内部扩展机制,用于框架特定传播规则: 4.2 TaintPreservingCallable 扩展 更灵活的扩展方式,支持参数到参数的污点传播: transfersTaint 参数含义 src :污点源的位置索引 src >= 0 :方法参数索引(0 = 第一个参数) src = -1 :调用对象(qualifier) sink :污点目标的位置索引 sink >= 0 :方法参数索引(会被修改) sink = -1 :调用对象(会被修改) 与 returnsTaintFrom 的区别 | 方面 | returnsTaintFrom | transfersTaint | |------|------------------|----------------| | 传播目标 | 方法返回值 | 方法参数或调用对象 | | 对象状态 | 创建新对象 | 修改现有对象 | | 使用场景 | result = obj.method(param) | obj.method(param) 修改 obj 或 param | | 数据流类型 | 值流 | 引用流/状态更新 | 4.3 库规则自定义(推荐) 使用 YAML 文件定义外部库的污点传播行为: 在 qlpack.yml 中添加引用: 五、实际应用案例 5.1 String.valueOf() 传播缺失问题 官方规则中, String.valueOf(url) 的污点传播可能缺失,可通过 TaintPreservingCallable 扩展: 5.2 SSRF 检测中的 OkHttp 传播 对于 com.squareup.okhttp (非 okhttp3 ),需要手动添加传播规则: 六、最佳实践 优先使用库规则 :YAML 模型文件更简洁易维护 合理选择扩展方式 : 通用传播规则:使用 TaintPreservingCallable 框架特定规则:使用 AdditionalTaintStep 外部库模型:使用 YAML 模型文件 注意传播方向 : 值传播: returnsTaintFrom (创建新对象) 引用传播: transfersTaint (修改现有对象) 测试验证 :确保自定义规则不会引入误报或漏报 七、总结 CodeQL 提供了多层次的污点传播机制,从默认的表达式级别传播到高度可定制的扩展机制。实际应用中需要注意: 官方默认规则并不完整,需要根据目标代码库特点进行补充 不同的扩展方式适用于不同场景,需要合理选择 库规则自定义是官方推荐的发展方向,提供了更简洁的语法 污点传播的准确性直接影响漏洞检测的漏报率和误报率 通过深入理解污点传播机制并合理扩展,可以显著提高 CodeQL 在复杂代码库中的漏洞检测能力。