使用太阿(Tai-e)进行静态代码安全分析(spring-boot篇二)
字数 1258 2025-08-05 11:39:43
使用太阿(Tai-e)进行静态代码安全分析(Spring Boot篇二)
概述
Tai-e是针对Java的静态程序分析框架,支持指针分析、数据流分析和污点分析等多种静态程序分析技术。本教学文档重点讲解如何使用Tai-e分析Spring Boot应用中的安全漏洞,特别是解决依赖注入和控制反转带来的分析挑战。
原理分析
控制反转(IoC)与依赖注入(DI)
控制反转是一种设计原则,其中应用程序的控制权被反转,由框架或容器负责管理对象的生命周期和控制对象之间的关系。依赖注入是IoC的一种实现方式,通过构造函数、方法参数或属性注入依赖关系。
示例代码:
@Configuration
public class AppConfig {
@Bean
public MyServiceImpl myService() {
return new MyServiceImpl();
}
}
@RestController
public class MovieRecommenderController {
@Autowired
private MyServiceImpl service;
@GetMapping("/")
public void doSomeAction(){
service.action();
}
}
指针分析中的函数调用处理
Tai-e中污点分析是作为指针分析的插件实现的。指针分析需要处理四种Java方法调用:
- invokeinterface和invokevirtual - 动态分派
- invokespecial - 直接调用(构造方法、私有方法等)
- invokestatic - 静态方法调用
关键代码:
private void processCall(CSVar recv, PointsToSet pts) {
Context context = recv.getContext();
Var var = recv.getVar();
for (Invoke callSite : var.getInvokes()) {
pts.forEach(recvObj -> {
JMethod callee = CallGraphs.resolveCallee(
recvObj.getObject().getType(), callSite);
// ...
});
}
}
问题分析
Spring的IoC容器控制对象的实例化,使得传统指针分析无法通过dispatch获取callsite具体调用的方法。解决方案是分析出当前对象对应的具体实现类,然后在指针分析处理call调用时获取具体方法。
控制反转处理实现
类层次分析(CHA)实现方式
-
堆抽象:使用New语句创建对象表示
A a = new A(); // PT(a)={NewObj{<New: void main(java.lang.String[])>[0@L4] new A}} -
处理流程:
- 遍历所有方法,提取Invoke语句
- 跳过静态方法和特殊调用
- 检查接收者是否已有指向信息
- 根据对象类型(接口或类)收集可能的实现类
- 将实现类添加到对象的指向集中
关键代码:
solver.addPointsTo(solver.getCSManager().getCSVar(csMethod.getContext(), var),
solver.getHeapModel().getMockObj(() -> "DEPENDENCY_INJECTION",
implementor.getName(), implementor.getType()));
基于注解的配置解析
更精确的方法是通过解析Spring注解来识别依赖关系。
-
注入点识别:
- 查找带有@Resource、@Autowired或@Inject注解的字段
-
寻找实现类:
- 查找带有@Service或@Component注解的类
-
关联实现类与注入点:
- 分析LoadField语句,将实现类与字段访问关联
关键代码:
// 注入点识别
List<JField> injectedFields = new ArrayList<>();
World.get().getClassHierarchy().allClasses()
.map(JClass::getDeclaredFields)
.flatMap(Collection::stream)
.forEach(field -> {
boolean isInjectedField = field.hasAnnotation("javax.annotation.Resource") ||
field.hasAnnotation("org.springframework.beans.factory.annotation.Autowired") ||
field.hasAnnotation("javax.inject.Inject");
if(isInjectedField){
injectedFields.add(field);
}
});
// 寻找实现类
List<JClass> implementationClasses = new ArrayList<>();
implementationClasses.addAll(
World.get().getClassHierarchy().allClasses()
.filter(cls -> cls.hasAnnotation("org.springframework.stereotype.Service") ||
cls.hasAnnotation("org.springframework.stereotype.Component"))
.collect(Collectors.toSet())
);
基于XML的配置解析
原理与注解方式类似,但需要解析XML文件内容来确定依赖关系。
完整实现代码
package pascal.taie.analysis.pta.plugin.taint;
import pascal.taie.World;
import pascal.taie.analysis.pta.core.cs.context.Context;
import pascal.taie.analysis.pta.core.solver.Solver;
import pascal.taie.analysis.pta.plugin.Plugin;
import pascal.taie.ir.exp.InvokeInstanceExp;
import pascal.taie.ir.exp.Var;
import pascal.taie.ir.stmt.Invoke;
import pascal.taie.language.classes.JClass;
import java.util.ArrayList;
import java.util.Collection;
public class DependencyInjectionHandler implements Plugin {
private Solver solver;
private boolean isCalled;
public DependencyInjectionHandler(){
isCalled = false;
}
@Override
public void setSolver(Solver solver) {
this.solver = solver;
}
@Override
public void onPhaseFinish() {
if(isCalled) return;
isCalled = true;
// 1. 基于注解的注入点识别
List<JField> injectedFields = new ArrayList<>();
World.get().getClassHierarchy().allClasses()
.map(JClass::getDeclaredFields)
.flatMap(Collection::stream)
.forEach(field -> {
boolean isInjectedField = field.hasAnnotation("javax.annotation.Resource") ||
field.hasAnnotation("org.springframework.beans.factory.annotation.Autowired") ||
field.hasAnnotation("javax.inject.Inject");
if(isInjectedField) injectedFields.add(field);
});
// 2. 寻找实现类
List<JClass> implementationClasses = new ArrayList<>();
implementationClasses.addAll(
World.get().getClassHierarchy().allClasses()
.filter(cls -> cls.hasAnnotation("org.springframework.stereotype.Service") ||
cls.hasAnnotation("org.springframework.stereotype.Component"))
.collect(Collectors.toSet())
);
// 3. 关联实现类与注入点
injectedFields.forEach(field -> {
JClass jClass = field.getDeclaringClass();
Collection<JClass> subClasses = World.get().getClassHierarchy()
.getAllSubclassesOf(World.get().getClassHierarchy()
.getClass(field.getType().getName()));
List<JClass> implementors = new ArrayList<>(subClasses);
implementors.retainAll(implementationClasses);
Set<CSMethod> csMethodSet = solver.getCallGraph().reachableMethods()
.filter(csMethod -> csMethod.getMethod().getDeclaringClass().equals(jClass))
.collect(Collectors.toSet());
csMethodSet.forEach(csMethod -> {
List<Var> vars = csMethod.getMethod().getIR().getStmts().stream()
.filter(stmt -> stmt instanceof LoadField loadField &&
loadField.getFieldAccess().getFieldRef().resolve().equals(field))
.map(stmt -> (LoadField) stmt)
.map(AssignStmt::getLValue)
.toList();
implementors.forEach(implementor -> {
vars.forEach(var -> {
solver.addPointsTo(solver.getCSManager().getCSVar(csMethod.getContext(), var),
solver.getHeapModel().getMockObj(() -> "DEPENDENCY_INJECTION",
implementor.getName(), implementor.getType()));
});
});
});
});
}
}
结果展示与使用
- 下载代码并设置环境:
git clone https://github.com/lcark/Tai-e-demo
cd Tai-e-demo/spring-boot-2
git submodule update --init
-
将
DependencyInjectionHandler.java添加到Tai-e源码并重新编译 -
运行分析:
java -cp ~/Downloads/Tai-e/build/tai-e-all-0.5.1-SNAPSHOT.jar pascal.taie.Main --options-file=options.yml
- 成功检测到SSRF漏洞调用链:
org.joychou.controller.SSRF#RestTemplateUrlBanRedirects
org.joychou.impl.HttpServiceImpl#RequestHttpBanRedirects
org.springframework.web.client.RestTemplate#exchange
参考链接
- Spring Framework官方文档: https://docs.spring.io/spring-framework/reference/core/beans/child-bean-definitions.html
- Tai-e官方文档: https://tai-e.pascal-lab.net/pa4.html
- 示例代码仓库: https://github.com/lcark/Tai-e-demo
通过本教学文档,您应该能够理解如何使用Tai-e分析Spring Boot应用中的安全漏洞,特别是处理控制反转和依赖注入带来的分析挑战。