使用太阿(Tai-e)进行静态代码安全分析(spring-boot篇三)
字数 1369 2025-08-05 08:19:13
使用太阿(Tai-e)进行静态代码安全分析(Spring Boot篇三)
概述
Tai-e是针对Java的静态程序分析框架,支持指针分析、数据流分析、污点分析等多种静态程序分析技术。本教程主要讲解如何使用Tai-e进行以下分析:
- 分析Spring Boot应用,支持控制反转、依赖注入、面向切面编程等特性
- 分析基于MyBatis框架的SQL注入漏洞
- 优化输出结果
- Java API提取,支持企业API安全分析
Java API提取
分析目标
提取Controller提供的对外API,例如/sqli/jdbc/vuln这样的路由信息。
Tai-e IR分析
Tai-e的工作流程是:Java源代码 → Soot Jimple IR → Tai-e IR。我们可以根据Tai-e IR中的注解信息拼接获取路由。
示例Tai-e IR格式:
@org.springframework.web.bind.annotation.RestController
@org.springframework.web.bind.annotation.RequestMapping({"/sqli"})
public class org.joychou.controller.SQLI extends java.lang.Object {
@org.springframework.web.bind.annotation.RequestMapping({"/jdbc/vuln"})
public java.lang.String jdbc_sqli_vul(@org.springframework.web.bind.annotation.RequestParam("username") java.lang.String username) {
// ...
}
}
开发新的程序分析
Tai-e提供三种扩展模式:
- MethodAnalysis - 需要实现
analyze(IR)方法,输入是每个方法的IR - ClassAnalysis - 需要实现
analyze(Jclass)方法,输入是每个类 - ProgramAnalysis - 需要实现
analyze()方法,是整个程序的分析
示例:MethodAnalysis实现
package pascal.taie.analysis.extractapi;
import pascal.taie.analysis.MethodAnalysis;
import pascal.taie.config.AnalysisConfig;
import pascal.taie.ir.IR;
public class TestMethod extends MethodAnalysis {
public static final String ID = "testmethodanalysis";
public TestMethod(AnalysisConfig config) {
super(config);
}
@Override
public Object analyze(IR ir) {
System.out.println(ir.getMethod().getName());
return null;
}
}
注册分析
在resource/tai-e-analyses.yml中添加:
- description: test method analysis
analysisClass: pascal.taie.analysis.extractapi.TestMethod
id: testmethodanalysis
运行分析
两种方式:
- 命令行参数:
-a testmethodanalysis - 配置文件:
analyses: testmethodanalysis: ;
实现API提取
POJO定义
// 存储方法路由信息
public record MethodRouter(String methodName, String path) {}
// 存储类路由和方法路由列表
public record Router(String className, String classPath, List<MethodRouter> methodRouters) {}
提取API程序分析
- 获取所有应用类:
World.get().getClassHierarchy().applicationClasses()
- 获取含有Mapping注解的Method及Path:
jClass.getDeclaredMethods().forEach(jMethod -> {
if (!jMethod.getAnnotations().stream()
.filter(annotation -> annotation.getType().matches("org.springframework.web.bind.annotation.\\w+Mapping"))
.toList().isEmpty()) {
MethodRouter methodRouter = new MethodRouter(
jMethod.getName(),
formatMappedPath(getPathFromAnnotation(jMethod.getAnnotations()))
);
methodRouters.add(methodRouter);
}
});
- 注解路径提取方法:
public String getPathFromAnnotation(Collection<Annotation> annotations) {
ArrayList<String> path = new ArrayList<>();
annotations.stream()
.filter(annotation -> annotation.getType().matches("org.springframework.web.bind.annotation.\\w+Mapping"))
.forEach(annotation -> path.add(Objects.requireNonNull(annotation.getElement("value")).toString()));
return path.size() == 1 ? path.get(0) : null;
}
- 组建Router对象:
Router router = new Router(
jClass.getName(),
formatMappedPath(getPathFromAnnotation(jClass.getAnnotations())),
methodRouters
);
routers.add(router);
添加MyBatis Sink点
MyBatis介绍
MyBatis是持久层框架/半自动ORM,支持自定义SQL、存储过程和高级映射。有两种配置方式:
- XML形式
- 注解形式
MyBatis SQL注入
MyBatis参数拼接方式:
#{parameterName}- 使用预编译,安全${parameterName}- 直接拼接字符串,易导致SQL注入
注解形式分析
实现步骤
- 筛选出有
@Mapper注解的类:
List<JClass> list = World.get().getClassHierarchy().applicationClasses().toList();
for (JClass jClass : list) {
if (!jClass.getAnnotations().stream()
.filter(annotation -> annotation.getType().matches("org.apache.ibatis.annotations.Mapper"))
.toList().isEmpty()) {
// ...
}
}
- 筛选出有
@Select注解的方法:
jClass.getDeclaredMethods().forEach(jMethod -> {
if (!jMethod.getAnnotations().stream()
.filter(annotation -> annotation.getType().matches("org.apache.ibatis.annotations.Select"))
.toList().isEmpty()) {
// ...
}
});
- 对
$进行正则匹配:
String valueFromAnnotation = getValueFromAnnotation(jMethod.getAnnotations());
if (valueFromAnnotation != null && valueFromAnnotation.contains("$")) {
Pattern pattern = Pattern.compile("\\$\\{([^}]+)\\}");
Matcher matcher = pattern.matcher(valueFromAnnotation);
// ...
}
- 从注解获取value的方法:
public static String getValueFromAnnotation(Collection<Annotation> annotations) {
ArrayList<String> value = new ArrayList<>();
annotations.stream()
.filter(annotation -> annotation.getType().matches("org.apache.ibatis.annotations..*"))
.forEach(annotation -> value.add(Objects.requireNonNull(annotation.getElement("value")).toString()));
return value.size() == 1 ? value.get(0) : null;
}
- 匹配参数并创建Sink点:
while (matcher.find()) {
String sink = matcher.group(1);
int paramCount = jMethod.getParamCount();
for (int i = 0; i < paramCount; i++) {
String paramValue = getValueFromAnnotation(jMethod.getParamAnnotations(i));
if (paramValue.contains(sink)) {
Sink sink1 = new Sink(jMethod, new IndexRef(IndexRef.Kind.VAR, i, null));
sinkList.add(sink1);
}
}
}
Taint-config加载流程
- 在
TaintAnalysis.setSolver()中加载taint-config文件 - 使用Jackson自定义反序列化读取taintconfig文件
- 在
deserializeSinks方法中添加自定义Sink点:
List<Sink> mybatisSinks = AddMybatisSinkHandler.AddMybatisSink();
sinks.addAll(mybatisSinks);
XML形式分析
XML形式比注解形式多一个步骤:需要用id寻找对应的方法。
具体使用方法
- 下载代码:
git clone https://github.com/lcark/Tai-e-demo
cd Tai-e-demo/spring-boot-3
git submodule update --init
-
将POJO和ExtractApi文件放到
src/main/java/pascal/taie/analysis/extractapi -
添加analysis到
tai-e-analyses.yml -
构建fatjar包
-
运行Tai-e:
java -cp ~/Downloads/Tai-e/build/tai-e-all-0.5.1-SNAPSHOT.jar pascal.taie.Main --options-file=options.yml