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