使用太阿(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方法调用:

  1. invokeinterface和invokevirtual - 动态分派
  2. invokespecial - 直接调用(构造方法、私有方法等)
  3. 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)实现方式

  1. 堆抽象:使用New语句创建对象表示

    A a = new A();
    // PT(a)={NewObj{<New: void main(java.lang.String[])>[0@L4] new A}}
    
  2. 处理流程

    • 遍历所有方法,提取Invoke语句
    • 跳过静态方法和特殊调用
    • 检查接收者是否已有指向信息
    • 根据对象类型(接口或类)收集可能的实现类
    • 将实现类添加到对象的指向集中

关键代码:

solver.addPointsTo(solver.getCSManager().getCSVar(csMethod.getContext(), var),
                  solver.getHeapModel().getMockObj(() -> "DEPENDENCY_INJECTION", 
                  implementor.getName(), implementor.getType()));

基于注解的配置解析

更精确的方法是通过解析Spring注解来识别依赖关系。

  1. 注入点识别

    • 查找带有@Resource、@Autowired或@Inject注解的字段
  2. 寻找实现类

    • 查找带有@Service或@Component注解的类
  3. 关联实现类与注入点

    • 分析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()));
                    });
                });
            });
        });
    }
}

结果展示与使用

  1. 下载代码并设置环境:
git clone https://github.com/lcark/Tai-e-demo
cd Tai-e-demo/spring-boot-2
git submodule update --init
  1. DependencyInjectionHandler.java添加到Tai-e源码并重新编译

  2. 运行分析:

java -cp ~/Downloads/Tai-e/build/tai-e-all-0.5.1-SNAPSHOT.jar pascal.taie.Main --options-file=options.yml
  1. 成功检测到SSRF漏洞调用链:
org.joychou.controller.SSRF#RestTemplateUrlBanRedirects
org.joychou.impl.HttpServiceImpl#RequestHttpBanRedirects
org.springframework.web.client.RestTemplate#exchange

参考链接

  1. Spring Framework官方文档: https://docs.spring.io/spring-framework/reference/core/beans/child-bean-definitions.html
  2. Tai-e官方文档: https://tai-e.pascal-lab.net/pa4.html
  3. 示例代码仓库: https://github.com/lcark/Tai-e-demo

通过本教学文档,您应该能够理解如何使用Tai-e分析Spring Boot应用中的安全漏洞,特别是处理控制反转和依赖注入带来的分析挑战。

使用太阿(Tai-e)进行静态代码安全分析(Spring Boot篇二) 概述 Tai-e是针对Java的静态程序分析框架,支持指针分析、数据流分析和污点分析等多种静态程序分析技术。本教学文档重点讲解如何使用Tai-e分析Spring Boot应用中的安全漏洞,特别是解决依赖注入和控制反转带来的分析挑战。 原理分析 控制反转(IoC)与依赖注入(DI) 控制反转是一种设计原则,其中应用程序的控制权被反转,由框架或容器负责管理对象的生命周期和控制对象之间的关系。依赖注入是IoC的一种实现方式,通过构造函数、方法参数或属性注入依赖关系。 示例代码: 指针分析中的函数调用处理 Tai-e中污点分析是作为指针分析的插件实现的。指针分析需要处理四种Java方法调用: invokeinterface和invokevirtual - 动态分派 invokespecial - 直接调用(构造方法、私有方法等) invokestatic - 静态方法调用 关键代码: 问题分析 Spring的IoC容器控制对象的实例化,使得传统指针分析无法通过dispatch获取callsite具体调用的方法。解决方案是分析出当前对象对应的具体实现类,然后在指针分析处理call调用时获取具体方法。 控制反转处理实现 类层次分析(CHA)实现方式 堆抽象 :使用New语句创建对象表示 处理流程 : 遍历所有方法,提取Invoke语句 跳过静态方法和特殊调用 检查接收者是否已有指向信息 根据对象类型(接口或类)收集可能的实现类 将实现类添加到对象的指向集中 关键代码: 基于注解的配置解析 更精确的方法是通过解析Spring注解来识别依赖关系。 注入点识别 : 查找带有@Resource、@Autowired或@Inject注解的字段 寻找实现类 : 查找带有@Service或@Component注解的类 关联实现类与注入点 : 分析LoadField语句,将实现类与字段访问关联 关键代码: 基于XML的配置解析 原理与注解方式类似,但需要解析XML文件内容来确定依赖关系。 完整实现代码 结果展示与使用 下载代码并设置环境: 将 DependencyInjectionHandler.java 添加到Tai-e源码并重新编译 运行分析: 成功检测到SSRF漏洞调用链: 参考链接 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应用中的安全漏洞,特别是处理控制反转和依赖注入带来的分析挑战。