AliyunCTF2025 Espresso Coffee 解析
字数 1903 2025-08-29 08:30:24

AliyunCTF2025 Espresso Coffee 反序列化漏洞分析与利用

1. 漏洞背景与概述

本漏洞涉及GraalVM的Java虚拟机(Espresso)中的Continuation反序列化问题。攻击者可以通过精心构造的序列化数据,在反序列化时修改Continuation的执行流程,最终实现任意命令执行。

2. 关键概念解析

2.1 Continuation机制

Continuation是GraalVM提供的一种控制流抽象,允许保存和恢复执行状态。关键特性包括:

  • 可序列化/反序列化
  • 通过resume()方法恢复执行
  • 通过suspend()方法暂停执行

执行流程:

  1. 第一次执行resume()会进入EntryPoint#start
  2. 遇到suspend()时暂停
  3. 下次resume()会从上一次suspend()的位置继续

2.2 FrameRecord结构

Continuation的核心是stackFrameHead属性,它记录了方法执行状态:

  • method: 当前执行的方法
  • bci (bytecode index): 当前执行的字节码位置
  • pointers: 方法上下文(局部变量表)
    • 非静态方法:pointers[0]=this,pointers[1...]=参数+局部变量
    • 静态方法:pointers[0]=第一个参数

3. 漏洞原理分析

3.1 攻击思路

通过反序列化修改FrameRecord

  1. 篡改method指向恶意方法(如Runtime.exec()
  2. 控制bci指向合适的字节码位置
  3. 设置pointers提供必要的参数

3.2 字节码执行分析

关键字节码指令:

  • aload_X: 加载局部变量表位置X的值到操作数栈
  • invokevirtual: 调用实例方法
  • areturn: 返回方法结果

示例分析(Runtime.exec()):

0: aload_0         // 加载this
1: aload_1         // 加载第一个参数
2: aconst_null     // 压入null
3: aconst_null     // 压入null
4: invokevirtual   // 调用exec(String, null, null)
5: areturn         // 返回结果

3.3 执行控制限制

关键限制:

  1. bci必须指向invoke类型指令,否则报错"Target bci is not a valid bytecode"
  2. 前两帧必须为固定方法:
    • 第一帧:ContinuationImpl.run()
    • 第二帧:ContinuationEntryPoint.start()

4. 漏洞利用技术

4.1 寻找合适gadget

理想gadget需要满足:

  1. 包含危险方法调用
  2. 不依赖this对象
  3. 参数可控

发现sun.print.PSPrinterJob$PrinterSpooler.printExecCmd方法:

83: astore_2       // 存储null到局部变量2
86: invokestatic   // 调用Runtime.getRuntime()
89: aload_2        // 加载局部变量2(null)
90: invokevirtual  // 调用exec(null)

4.2 控制返回值

通过添加额外帧控制返回值:

  1. 第一帧:bci=83,执行printExecCmd
  2. 第二帧:HashMap.removeNodebci=283,执行:
    286: aload 10    // 加载局部变量10
    289: areturn     // 返回该值
    

设置pointers[10]为命令数组String[]{"calc"}

4.3 动态代理绕过限制

由于第二帧必须是ContinuationEntryPoint.start(),但无实现类:

  1. 使用动态代理创建ContinuationEntryPoint实例
  2. 序列化后手动修改InvocationHandler为JDK内置类

5. 完整利用步骤

5.1 环境准备

  1. 获取调试环境:GraalVM序列化文档
  2. 简化调试流程:
    • 第一次运行resume()生成序列化文件
    • 第二次运行读取文件,在resume()前断点检查continuation

5.2 EXP构造

修改PersistApp.main()

public static void main(String[] args) throws Exception {
    // 1. 创建动态代理的ContinuationEntryPoint
    InvocationHandler handler = new CustomHandler();
    ContinuationEntryPoint entryPoint = (ContinuationEntryPoint) Proxy.newProxyInstance(
        ContinuationEntryPoint.class.getClassLoader(),
        new Class<?>[] { ContinuationEntryPoint.class },
        handler);
    
    // 2. 创建Continuation并序列化
    Continuation continuation = new Continuation(entryPoint);
    continuation.resume();
    
    // 3. 反序列化修改
    // - 修改stackFrameHead的method和bci
    // - 设置pointers[10]为命令数组
    // - 替换InvocationHandler为JDK内置类
    
    // 4. 触发漏洞
    continuation.resume();
}

5.3 关键修改点

  1. 设置第一帧:

    • method: sun.print.PSPrinterJob$PrinterSpinter.printExecCmd
    • bci: 83
    • pointers: 适当构造
  2. 设置第二帧:

    • method: HashMap.removeNode
    • bci: 283
    • pointers[10]: String[]{"calc"}

6. 防御建议

  1. 禁止反序列化Continuation对象
  2. 检查stackFrameHead的方法合法性
  3. 限制动态代理的使用
  4. 更新GraalVM到修复版本

7. 总结

本漏洞通过精心构造Continuation的序列化数据,利用GraalVM的continuation机制和动态代理特性,实现了从反序列化到任意命令执行的完整利用链。关键在于理解FrameRecord结构和字节码执行流程,以及绕过Continuation的执行限制。

AliyunCTF2025 Espresso Coffee 反序列化漏洞分析与利用 1. 漏洞背景与概述 本漏洞涉及GraalVM的Java虚拟机(Espresso)中的Continuation反序列化问题。攻击者可以通过精心构造的序列化数据,在反序列化时修改Continuation的执行流程,最终实现任意命令执行。 2. 关键概念解析 2.1 Continuation机制 Continuation是GraalVM提供的一种控制流抽象,允许保存和恢复执行状态。关键特性包括: 可序列化/反序列化 通过 resume() 方法恢复执行 通过 suspend() 方法暂停执行 执行流程: 第一次执行 resume() 会进入 EntryPoint#start 遇到 suspend() 时暂停 下次 resume() 会从上一次 suspend() 的位置继续 2.2 FrameRecord结构 Continuation的核心是 stackFrameHead 属性,它记录了方法执行状态: method : 当前执行的方法 bci (bytecode index): 当前执行的字节码位置 pointers : 方法上下文(局部变量表) 非静态方法: pointers[0] =this, pointers[1...] =参数+局部变量 静态方法: pointers[0] =第一个参数 3. 漏洞原理分析 3.1 攻击思路 通过反序列化修改 FrameRecord : 篡改 method 指向恶意方法(如 Runtime.exec() ) 控制 bci 指向合适的字节码位置 设置 pointers 提供必要的参数 3.2 字节码执行分析 关键字节码指令: aload_X : 加载局部变量表位置X的值到操作数栈 invokevirtual : 调用实例方法 areturn : 返回方法结果 示例分析( Runtime.exec() ): 3.3 执行控制限制 关键限制: bci 必须指向 invoke 类型指令,否则报错"Target bci is not a valid bytecode" 前两帧必须为固定方法: 第一帧: ContinuationImpl.run() 第二帧: ContinuationEntryPoint.start() 4. 漏洞利用技术 4.1 寻找合适gadget 理想gadget需要满足: 包含危险方法调用 不依赖this对象 参数可控 发现 sun.print.PSPrinterJob$PrinterSpooler.printExecCmd 方法: 4.2 控制返回值 通过添加额外帧控制返回值: 第一帧: bci=83 ,执行 printExecCmd 第二帧: HashMap.removeNode 中 bci=283 ,执行: 设置 pointers[10] 为命令数组 String[]{"calc"} 4.3 动态代理绕过限制 由于第二帧必须是 ContinuationEntryPoint.start() ,但无实现类: 使用动态代理创建 ContinuationEntryPoint 实例 序列化后手动修改 InvocationHandler 为JDK内置类 5. 完整利用步骤 5.1 环境准备 获取调试环境: GraalVM序列化文档 简化调试流程: 第一次运行 resume() 生成序列化文件 第二次运行读取文件,在 resume() 前断点检查 continuation 5.2 EXP构造 修改 PersistApp.main() : 5.3 关键修改点 设置第一帧: method: sun.print.PSPrinterJob$PrinterSpinter.printExecCmd bci: 83 pointers: 适当构造 设置第二帧: method: HashMap.removeNode bci: 283 pointers[ 10]: String[]{"calc"} 6. 防御建议 禁止反序列化Continuation对象 检查 stackFrameHead 的方法合法性 限制动态代理的使用 更新GraalVM到修复版本 7. 总结 本漏洞通过精心构造Continuation的序列化数据,利用GraalVM的continuation机制和动态代理特性,实现了从反序列化到任意命令执行的完整利用链。关键在于理解FrameRecord结构和字节码执行流程,以及绕过Continuation的执行限制。