Java反序列化概念
在说反序列化之前,先说说序列化
序列化就是将对象转化为字节流,利于存储和被引用
反序列化与序列化恰恰相反,是将字节流转化为对象
序列化的格式:json序列化,xml序列化,二进制序列化,SOAP序列化
序列化调用的函数
- 序列化:
java.io.ObjectOutputStream 类中的 writeObject() - 实现 Serializable 和 Externalizable 接口的类才能被序列化
反序列化调用的函数
- 反序列化:
java.io.ObjectInputStream 类中的 readObject()
漏洞原理
思考:为什么在反序列化的时候会产生漏洞呢,下面这两个点是关键的
- 某个函数的参数输入可控
- 输入的命令被反序列化(也就是被执行)
漏洞原理
原理:在 Java中,重写的方法会优先执行。如果重写了readObject() ,并且函数中某个参数的输入可控,那么攻击者就可以输入任意命令(代码)。在反序列化过程中调用readObject() 方法时,就会执行恶意命令,造成攻击。
关键:输入的数据进行了反序列化操作
漏洞危害
由于上述介绍说到输入可控 ,因此可以结合远程命令执行(RCE),也可以通过RCE进行webshell上传(写文件)等
漏洞出现点
漏洞经常存在于一些 web组件中。例如 weblogic,Fastjson,JBoss,WebSphere,Jenkins,OpenNMS,Shiro
需要了解以下知识
- HTTP请求的参数cookie,Parameters
- RMI协议,完全基于序列化
- JNDI
- JMX 协议,用于处理序列化对象
RMI 协议(Remote Method Protocol):远程方法调用。在JAVA 中,只要一个类 extends了 java.rmi.Remote接口,这个类就可以被客户端远程访问,并提供服务。
RMI协议默认端口1099,基于socket协议
JNDI(Java Naming and Directory Interface,Java命名和目录接口):是一个接口,能够查找和访问各种命名和目录服务。比如LDAP,DNS,RMI,CORBA。
JNDI提供统一的客户端API,通过不同的访问提供者接口JNDI 服务供应接口(SPI)的实现,由管理者将JNDI API 映射为特定的命名服务和目录系统,使得 Java 应用程序可以和这些命名服务和目录服务直接进行交互。
漏洞挖掘
-
确定反序列化的输入点 首先再出readObject()方法的调用,找到之后进行下一步的注入操作 查找方法
-
再考察应用的Class Path中是否包含Apache Commons Collections库 -
生成反序列化 payload -
执行payload
漏洞防御
-
类的白名单校验机制
- 传入的的数据在反序列化之前,对类型名做一个检测,不符合白名单的类不进行反序列化操作。
- 例如:Runtime 肯定不会在白名单中
-
禁止JVM执行外部命令 Runtime.exec
这个举措可以通过扩展 SecurityManager 可以实现
序列化与反序列化代码
Fun.java
package com.test;
import java.io.*;
public class Fun {
public static void main(String args[]) throws IOException, ClassNotFoundException {
Fun f = new Fun();
f.ser();
f.unser();
//System.out.println(f);
}
//序列化
public void ser() throws IOException {
FileOutputStream fos = new FileOutputStream("test.txt"); //实例化文件输出流
ObjectOutputStream oos = new ObjectOutputStream(fos); //实例化对象输出流,输出到fos对象
Stu stu1= new Stu();
stu1.setId(1);
stu1.setName("Alice");
oos.writeObject(stu1); //将stu1 写入对象流,存储在本地
oos.close(); //关闭对象输出流
//fos.close(); //关闭文件输出流
System.out.println("Serializable Success");
}
//反序列化
public void unser() throws IOException, ClassNotFoundException {
FileInputStream ins = new FileInputStream("test.txt"); //实例化文件输入流
ObjectInputStream ois = new ObjectInputStream(ins); //实例化对象输入流,从ins对象中获取数据
Stu stu2 = (Stu) ois.readObject();
System.out.println("Unserializbale Success");
System.out.println(stu2.getName());
}
}
Stu.java
package com.test;
import java.io.Serializable;
public class Stu implements Serializable {
int id;
String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
参考文章
浅谈Java反序列化漏洞原理
JAVA反序列化漏洞浅析
|