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()方法暂停执行
执行流程:
- 第一次执行
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()):
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 执行控制限制
关键限制:
bci必须指向invoke类型指令,否则报错"Target bci is not a valid bytecode"- 前两帧必须为固定方法:
- 第一帧:
ContinuationImpl.run() - 第二帧:
ContinuationEntryPoint.start()
- 第一帧:
4. 漏洞利用技术
4.1 寻找合适gadget
理想gadget需要满足:
- 包含危险方法调用
- 不依赖this对象
- 参数可控
发现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 控制返回值
通过添加额外帧控制返回值:
- 第一帧:
bci=83,执行printExecCmd - 第二帧:
HashMap.removeNode中bci=283,执行:286: aload 10 // 加载局部变量10 289: areturn // 返回该值
设置pointers[10]为命令数组String[]{"calc"}
4.3 动态代理绕过限制
由于第二帧必须是ContinuationEntryPoint.start(),但无实现类:
- 使用动态代理创建
ContinuationEntryPoint实例 - 序列化后手动修改
InvocationHandler为JDK内置类
5. 完整利用步骤
5.1 环境准备
- 获取调试环境:GraalVM序列化文档
- 简化调试流程:
- 第一次运行
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 关键修改点
-
设置第一帧:
- method:
sun.print.PSPrinterJob$PrinterSpinter.printExecCmd - bci: 83
- pointers: 适当构造
- method:
-
设置第二帧:
- method:
HashMap.removeNode - bci: 283
- pointers[10]:
String[]{"calc"}
- method:
6. 防御建议
- 禁止反序列化Continuation对象
- 检查
stackFrameHead的方法合法性 - 限制动态代理的使用
- 更新GraalVM到修复版本
7. 总结
本漏洞通过精心构造Continuation的序列化数据,利用GraalVM的continuation机制和动态代理特性,实现了从反序列化到任意命令执行的完整利用链。关键在于理解FrameRecord结构和字节码执行流程,以及绕过Continuation的执行限制。