详解Java自动代码审计工具实现
字数 1091 2025-08-09 13:33:57

Java自动代码审计工具实现详解

0x01 背景

本文详细讲解Java自动代码审计工具的实现原理,重点分析数据流分析和方法调用图构建技术,帮助理解如何自动化检测Java应用中的安全漏洞。

0x02 数据流分析

基本原理

静态分析中的数据流分析通过算法得到Basic Block并建立Control Flow Graph(CFG),根据传递函数迭代生成每个Basic Block的in/out集直到不再变化,得到保守的分析结果。

简化实现方式

采用模拟JVM执行过程的方式进行分析:

  1. 定义两个核心数据结构:

    public class OperandStack<T> {
        private final LinkedList<Set<T>> stack;
        // pop push方法
    }
    
    public class LocalVariables<T> {
        private final ArrayList<Set<T>> array;
        // set get方法
    }
    
  2. 在方法进入时初始化数据结构:

    public void visitCode() {
        super.visitCode();
        localVariables.clear();
        operandStack.clear();
        if ((this.access & Opcodes.ACC_STATIC) == 0) {
            localVariables.add(new HashSet<>());
        }
        for (Type argType : Type.getArgumentTypes(desc)) {
            for (int i = 0; i < argType.getSize(); i++) {
                localVariables.add(new HashSet<>());
            }
        }
    }
    

SQL注入案例分析

分析以下代码的字节码执行过程:

public List<User> selectUser(String name) {
    String sql = "select * from t_user where name=\"" + name + "\"";
    List<User> users = jdbcTemplate.query(sql, new BeanPropertyRowMapper(User.class));
    return users;
}

字符串拼接字节码分析

NEW java/lang/StringBuilder
DUP
INVOKESPECIAL java/lang/StringBuilder.<init> ()V
LDC "select * from t_user where name=\""
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
ALOAD 1
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
LDC "\""
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
ASTORE 2
  1. NEW创建StringBuilder对象
  2. DUP复制栈顶引用
  3. INVOKESPECIAL调用构造方法
  4. LDC加载常量字符串
  5. INVOKEVIRTUAL调用append方法
  6. ALOAD 1加载name参数
  7. 再次调用append方法
  8. toString()转换并存储结果

数据库查询字节码分析

ALOAD 0
GETFIELD org/sec/cidemo/dao/impl/SQLIDaoImpl.jdbcTemplate : Lorg/springframework/jdbc/core/JdbcTemplate;
ALOAD 2
NEW org/springframework/jdbc/core/BeanPropertyRowMapper
DUP
LDC Lorg/sec/cidemo/model/User;.class
INVOKESPECIAL org/springframework/jdbc/core/BeanPropertyRowMapper.<init> (Ljava/lang/Class;)V
INVOKEVIRTUAL org/springframework/jdbc/core/JdbcTemplate.query (Ljava/lang/String;Lorg/springframework/jdbc/core/RowMapper;)Ljava/util/List;
ASTORE 3

关键点:拼接后的SQL字符串作为参数传递给jdbcTemplate.query方法。

污点传播分析

  1. 假设name参数是用户可控输入(污点源)
  2. 通过模拟JVM指令执行跟踪污点传播
  3. 当污点数据到达敏感方法(如SQL执行)时判定为漏洞

0x03 方法调用图

调用关系分析

分析整个应用的方法调用链,例如:

Controller.select(1) -> Service.selectUser(1) -> Dao.selectUser(1)

实现步骤

  1. 使用ASM分析所有字节码(包括依赖库)
  2. 遍历所有class文件的方法
  3. 记录方法间的调用关系

关键代码实现

@Override
public void visitCode() {
    super.visitCode();
    int localIndex = 0;
    int argIndex = 0;
    if ((this.access & Opcodes.ACC_STATIC) == 0) {
        localVariables.set(localIndex, "arg" + argIndex);
        localIndex += 1;
        argIndex += 1;
    }
    for (Type argType : Type.getArgumentTypes(desc)) {
        localVariables.set(localIndex, "arg" + argIndex);
        localIndex += argType.getSize();
        argIndex += 1;
    }
}

@Override
public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
    Type[] argTypes = Type.getArgumentTypes(desc);
    if (opcode != Opcodes.INVOKESTATIC) {
        Type[] extendedArgTypes = new Type[argTypes.length + 1];
        System.arraycopy(argTypes, 0, extendedArgTypes, 1, argTypes.length);
        extendedArgTypes[0] = Type.getObjectType(owner);
        argTypes = extendedArgTypes;
    }
    
    switch (opcode) {
        case Opcodes.INVOKESTATIC:
        case Opcodes.INVOKEVIRTUAL:
        case Opcodes.INVOKESPECIAL:
        case Opcodes.INVOKEINTERFACE:
            int stackIndex = 0;
            for (int i = 0; i < argTypes.length; i++) {
                int argIndex = argTypes.length - 1 - i;
                Type type = argTypes[argIndex];
                Set<String> taint = operandStack.get(stackIndex);
                if (taint.size() > 0) {
                    for (String argSrc : taint) {
                        int srcArgIndex = Integer.parseInt(argSrc.substring(3));
                        discoveredCalls.add(new CallGraph(
                            new MethodReference.Handle(new ClassReference.Handle(this.owner), this.name, this.desc),
                            new MethodReference.Handle(new ClassReference.Handle(owner), name, desc),
                            srcArgIndex, argIndex));
                    }
                }
                stackIndex += type.getSize();
            }
            break;
        default:
            throw new IllegalStateException("unsupported opcode: " + opcode);
    }
    super.visitMethodInsn(opcode, owner, name, desc, itf);
}

逆拓扑排序

为了确保从最底层方法开始分析,需要使用逆拓扑排序算法处理调用图:

  1. 深度优先遍历方法调用图
  2. 将没有子方法的方法先加入结果集
  3. 逐步回溯直到所有方法都被处理

示例调用图排序过程:

方法调用关系:
method1 -> method3, method4
method2 -> method6
method3 -> method7, method8
method6 -> method1, method2

排序结果:7,8,3,6,2,4,1

0x04 返回值分析

反射XSS案例

@RequestMapping("/reflection")
@ResponseBody
public String reflection(@RequestParam("data") String message) {
    return xssService.reflection(message);
}

@Override
public String reflection(String message) {
    if (!message.equals("test")) {
        return message;
    }
    return "error";
}

字节码分析:

ALOAD 1
LDC "test"
INVOKEVIRTUAL java/lang/String.equals (Ljava/lang/Object;)Z
...
ALOAD 1
ARETURN
  1. 污点参数message被加载到操作数栈
  2. 通过ARETURN指令返回污点数据
  3. 判断为反射XSS漏洞

0x05 总结

完整的Java自动代码审计工具实现包含:

  1. 数据流分析:模拟JVM执行过程,跟踪污点传播

    • 操作数栈和局部变量表模拟
    • 字节码指令处理
  2. 方法调用图:构建完整调用链

    • ASM字节码分析
    • 方法调用关系记录
    • 逆拓扑排序确定分析顺序
  3. 漏洞判定:污点到达敏感方法时报警

    • SQL注入:污点到达SQL执行方法
    • XSS:污点通过返回值输出
  4. 扩展能力

    • 支持多种漏洞类型检测
    • 可配置的敏感方法列表
    • 灵活的污点传播规则

通过这种系统化的方法,可以实现高效的Java应用自动化代码审计,发现潜在的安全漏洞。

Java自动代码审计工具实现详解 0x01 背景 本文详细讲解Java自动代码审计工具的实现原理,重点分析数据流分析和方法调用图构建技术,帮助理解如何自动化检测Java应用中的安全漏洞。 0x02 数据流分析 基本原理 静态分析中的数据流分析通过算法得到Basic Block并建立Control Flow Graph(CFG),根据传递函数迭代生成每个Basic Block的in/out集直到不再变化,得到保守的分析结果。 简化实现方式 采用模拟JVM执行过程的方式进行分析: 定义两个核心数据结构: 在方法进入时初始化数据结构: SQL注入案例分析 分析以下代码的字节码执行过程: 字符串拼接字节码分析 NEW 创建StringBuilder对象 DUP 复制栈顶引用 INVOKESPECIAL 调用构造方法 LDC 加载常量字符串 INVOKEVIRTUAL 调用append方法 ALOAD 1 加载name参数 再次调用append方法 toString() 转换并存储结果 数据库查询字节码分析 关键点:拼接后的SQL字符串作为参数传递给 jdbcTemplate.query 方法。 污点传播分析 假设 name 参数是用户可控输入(污点源) 通过模拟JVM指令执行跟踪污点传播 当污点数据到达敏感方法(如SQL执行)时判定为漏洞 0x03 方法调用图 调用关系分析 分析整个应用的方法调用链,例如: 实现步骤 使用ASM分析所有字节码(包括依赖库) 遍历所有class文件的方法 记录方法间的调用关系 关键代码实现 逆拓扑排序 为了确保从最底层方法开始分析,需要使用逆拓扑排序算法处理调用图: 深度优先遍历方法调用图 将没有子方法的方法先加入结果集 逐步回溯直到所有方法都被处理 示例调用图排序过程: 0x04 返回值分析 反射XSS案例 字节码分析: 污点参数 message 被加载到操作数栈 通过 ARETURN 指令返回污点数据 判断为反射XSS漏洞 0x05 总结 完整的Java自动代码审计工具实现包含: 数据流分析 :模拟JVM执行过程,跟踪污点传播 操作数栈和局部变量表模拟 字节码指令处理 方法调用图 :构建完整调用链 ASM字节码分析 方法调用关系记录 逆拓扑排序确定分析顺序 漏洞判定 :污点到达敏感方法时报警 SQL注入:污点到达SQL执行方法 XSS:污点通过返回值输出 扩展能力 : 支持多种漏洞类型检测 可配置的敏感方法列表 灵活的污点传播规则 通过这种系统化的方法,可以实现高效的Java应用自动化代码审计,发现潜在的安全漏洞。