使用太阿(Tai-e)进行静态代码安全分析(spring-boot篇三)
字数 1369 2025-08-05 08:19:13

使用太阿(Tai-e)进行静态代码安全分析(Spring Boot篇三)

概述

Tai-e是针对Java的静态程序分析框架,支持指针分析、数据流分析、污点分析等多种静态程序分析技术。本教程主要讲解如何使用Tai-e进行以下分析:

  1. 分析Spring Boot应用,支持控制反转、依赖注入、面向切面编程等特性
  2. 分析基于MyBatis框架的SQL注入漏洞
  3. 优化输出结果
  4. 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提供三种扩展模式:

  1. MethodAnalysis - 需要实现analyze(IR)方法,输入是每个方法的IR
  2. ClassAnalysis - 需要实现analyze(Jclass)方法,输入是每个类
  3. 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

运行分析

两种方式:

  1. 命令行参数:-a testmethodanalysis
  2. 配置文件:analyses: testmethodanalysis: ;

实现API提取

POJO定义

// 存储方法路由信息
public record MethodRouter(String methodName, String path) {}

// 存储类路由和方法路由列表
public record Router(String className, String classPath, List<MethodRouter> methodRouters) {}

提取API程序分析

  1. 获取所有应用类:
World.get().getClassHierarchy().applicationClasses()
  1. 获取含有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);
    }
});
  1. 注解路径提取方法:
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;
}
  1. 组建Router对象:
Router router = new Router(
    jClass.getName(), 
    formatMappedPath(getPathFromAnnotation(jClass.getAnnotations())),
    methodRouters
);
routers.add(router);

添加MyBatis Sink点

MyBatis介绍

MyBatis是持久层框架/半自动ORM,支持自定义SQL、存储过程和高级映射。有两种配置方式:

  1. XML形式
  2. 注解形式

MyBatis SQL注入

MyBatis参数拼接方式:

  • #{parameterName} - 使用预编译,安全
  • ${parameterName} - 直接拼接字符串,易导致SQL注入

注解形式分析

实现步骤

  1. 筛选出有@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()) {
        // ...
    }
}
  1. 筛选出有@Select注解的方法:
jClass.getDeclaredMethods().forEach(jMethod -> {
    if (!jMethod.getAnnotations().stream()
        .filter(annotation -> annotation.getType().matches("org.apache.ibatis.annotations.Select"))
        .toList().isEmpty()) {
        // ...
    }
});
  1. $进行正则匹配:
String valueFromAnnotation = getValueFromAnnotation(jMethod.getAnnotations());
if (valueFromAnnotation != null && valueFromAnnotation.contains("$")) {
    Pattern pattern = Pattern.compile("\\$\\{([^}]+)\\}");
    Matcher matcher = pattern.matcher(valueFromAnnotation);
    // ...
}
  1. 从注解获取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;
}
  1. 匹配参数并创建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加载流程

  1. TaintAnalysis.setSolver()中加载taint-config文件
  2. 使用Jackson自定义反序列化读取taintconfig文件
  3. deserializeSinks方法中添加自定义Sink点:
List<Sink> mybatisSinks = AddMybatisSinkHandler.AddMybatisSink();
sinks.addAll(mybatisSinks);

XML形式分析

XML形式比注解形式多一个步骤:需要用id寻找对应的方法。

具体使用方法

  1. 下载代码:
git clone https://github.com/lcark/Tai-e-demo
cd Tai-e-demo/spring-boot-3
git submodule update --init
  1. 将POJO和ExtractApi文件放到src/main/java/pascal/taie/analysis/extractapi

  2. 添加analysis到tai-e-analyses.yml

  3. 构建fatjar包

  4. 运行Tai-e:

java -cp ~/Downloads/Tai-e/build/tai-e-all-0.5.1-SNAPSHOT.jar pascal.taie.Main --options-file=options.yml

参考链接

  1. Jackson Deserialization
  2. Tai-e Demo
使用太阿(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格式: 开发新的程序分析 Tai-e提供三种扩展模式: MethodAnalysis - 需要实现 analyze(IR) 方法,输入是每个方法的IR ClassAnalysis - 需要实现 analyze(Jclass) 方法,输入是每个类 ProgramAnalysis - 需要实现 analyze() 方法,是整个程序的分析 示例:MethodAnalysis实现 注册分析 在 resource/tai-e-analyses.yml 中添加: 运行分析 两种方式: 命令行参数: -a testmethodanalysis 配置文件: analyses: testmethodanalysis: ; 实现API提取 POJO定义 提取API程序分析 获取所有应用类: 获取含有Mapping注解的Method及Path: 注解路径提取方法: 组建Router对象: 添加MyBatis Sink点 MyBatis介绍 MyBatis是持久层框架/半自动ORM,支持自定义SQL、存储过程和高级映射。有两种配置方式: XML形式 注解形式 MyBatis SQL注入 MyBatis参数拼接方式: #{parameterName} - 使用预编译,安全 ${parameterName} - 直接拼接字符串,易导致SQL注入 注解形式分析 实现步骤 筛选出有 @Mapper 注解的类: 筛选出有 @Select 注解的方法: 对 $ 进行正则匹配: 从注解获取value的方法: 匹配参数并创建Sink点: Taint-config加载流程 在 TaintAnalysis.setSolver() 中加载taint-config文件 使用Jackson自定义反序列化读取taintconfig文件 在 deserializeSinks 方法中添加自定义Sink点: XML形式分析 XML形式比注解形式多一个步骤:需要用id寻找对应的方法。 具体使用方法 下载代码: 将POJO和ExtractApi文件放到 src/main/java/pascal/taie/analysis/extractapi 添加analysis到 tai-e-analyses.yml 构建fatjar包 运行Tai-e: 参考链接 Jackson Deserialization Tai-e Demo