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.TaintTrackingUtilsemmle.code.java.dataflow.TaintTrackingcodeql.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)
- Base64 编码/解码:
特殊操作
- 比较步骤 (
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 扩展
官方内部扩展机制,用于框架特定传播规则:
// 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"
六、最佳实践
- 优先使用库规则:YAML 模型文件更简洁易维护
- 合理选择扩展方式:
- 通用传播规则:使用 TaintPreservingCallable
- 框架特定规则:使用 AdditionalTaintStep
- 外部库模型:使用 YAML 模型文件
- 注意传播方向:
- 值传播:
returnsTaintFrom(创建新对象) - 引用传播:
transfersTaint(修改现有对象)
- 值传播:
- 测试验证:确保自定义规则不会引入误报或漏报
七、总结
CodeQL 提供了多层次的污点传播机制,从默认的表达式级别传播到高度可定制的扩展机制。实际应用中需要注意:
- 官方默认规则并不完整,需要根据目标代码库特点进行补充
- 不同的扩展方式适用于不同场景,需要合理选择
- 库规则自定义是官方推荐的发展方向,提供了更简洁的语法
- 污点传播的准确性直接影响漏洞检测的漏报率和误报率
通过深入理解污点传播机制并合理扩展,可以显著提高 CodeQL 在复杂代码库中的漏洞检测能力。