Java反序列化

  • Java反序列化是近些年安全业界研究的重点领域之一,在Apache Commons Collections 、JBoss 、WebLogic 等常见容器、库中均发现有该类漏洞,而且该类型漏洞容易利用,造成的破坏很大,因此影响广泛。
  • Java 序列化是指把 Java 对象转换为字节序列的过程,序列化后的字节数据可以保存在文件、数据库中;而Java 反序列化是指把字节序列恢复为 Java 对象的过程。
  • 序列化和反序列化通过ObjectInputStream.readObject()ObjectOutputStream.writeObject()方法实现。
  • 在java中任何类如果想要序列化必须实现java.io.Serializable接口
    1
    2
    3
    public class Hello implements java.io.Serializable {
    String name;
    }
  • java.io.Serializable其实是一个空接口,在java中该接口的唯一作用是对一个类做一个 标记 让jre确定这个类是可以序列化的。
  • 同时java中支持在类中定义如下函数:
    1
    2
    3
    4
    private void writeObject(java.io.ObjectOutputStream out)
    throws IOException
    private void readObject(java.io.ObjectInputStream in)
    throws IOException, ClassNotFoundException;
  • 这两个函数不是java.io.Serializable的接口函数,而是约定的函数,如果一个类实现了这两个函数,那么在序列化和反序列化的时候ObjectInputStream.readObject()和ObjectOutputStream.writeObject()会主动调用这两个函数。这也是反序列化产生的根本原因
  • 例如:
    1
    2
    3
    4
    5
    6
    public class Hello implements java.io.Serializable {
    String name;
    private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
    Runtime.getRuntime().exec(name);
    }
    }
  • 该类在反序列化的时候会执行命令,我们构造一个序列化的对象,name为恶意命令,那么在反序列化的时候就会执行恶意命令。
  • 在反序列化的过程中,攻击者仅能够控制“数据”,无法控制如何执行,因此必须借助被攻击应用中的具体场景来实现攻击目的
  • 例如上例中存在一个执行命令的可以序列化的类(Hello),利用该类的readObject函数中的命令执行场景来实现攻击

Java反序列化分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import java.io.*;

class MyObject implements Serializable{
public String name;
//重写readObject()方法
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException, IOException {
//执行默认的readObject()方法
in.defaultReadObject();
//执行打开计算器程序命令
Runtime.getRuntime().exec("calc.exe");
}
}

public class testSerialize {
public static void main(String args[]) throws Exception{
//定义myObj对象
MyObject myObj = new MyObject();
myObj.name = "hi";
//创建一个包含对象进行反序列化信息的”object”数据文件
FileOutputStream fos = new FileOutputStream("object");
ObjectOutputStream os = new ObjectOutputStream(fos);
//writeObject()方法将myObj对象写入object文件
os.writeObject(myObj);
os.close();
//从文件中反序列化obj对象
FileInputStream fis = new FileInputStream("object");
ObjectInputStream ois = new ObjectInputStream(fis);
//恢复对象
MyObject objectFromDisk = (MyObject)ois.readObject();
System.out.println(objectFromDisk.name);
ois.close();
}
}
  • 首先我们定义了一个Myobject类并继承了Serializable接口,并且重写了readObject方法。我们知道在反序列化时会执行readObject方法。而我们在readObject()方法中写入了Runtime.getRuntime().exec(“calc.exe”),在反序列化时就会执行相应的命令。

Java反序列化漏洞检测

  • 反序列化漏洞需要依赖执行链来完成攻击payload执行。由于反序列化漏洞的特性,在检测的时候漏洞扫描工具一般聚焦已知漏洞的检测,而未知漏洞的检测,安全工具能力非常有限,一般需要专业人员通过安全审计代码审计等方式发现。
  • java反序列化漏洞依赖于两个因素:
  1. 应用是否有反序列化接口
  2. 应用中是否包含有漏洞的组件
  • 因此对应的漏洞扫描工具也需要根据这两个因素进行检测。

白盒工具测试

  • 白盒代码审计工具,可通过在调用链中查找是否有发序列化的操作:
  • 调用链的入口不同框架是不同的。
  • 调用链中一旦发现有发序列化操作ObjectInputStream.readObject()则该接口存在序列化操作
  • 但仅仅依靠以上信息不足以判断是否存在漏洞,还需要判断代码中是否有存在执行链的三方依赖。在java中,一般通过分析pox.xml build.gradle文件来分析是否包含有漏洞的组件。

黑盒漏洞扫描器检测

  • web漏洞扫描器检测原理和白盒工具不一样。
  • 首先漏洞扫描器要解决的是识别出反序列化的请求,在这里需要注意的是web漏洞扫描是无法通过爬虫方式直接发现反序列化接口的,因此往往需要配合其他web漏洞扫描器的组件(例如代理组件)来识别反序列化接口
  • 如今web漏洞扫描器都提供了代理组件来发现应用的http请求,爬虫组件可通过前台页面触发请求进入代理组件;但在API场景下,还是需要测试人员进行API调用该操作才能够产生http请求数据。
  • 在截获到http请求数据后,代理组件可以通过两种方式判断一个请求是否是序列化请求:
  1. 通过http请求的Content-Type,具体来说ContentType: application/x-java-serialized-object 是序列化请求的请求头
  2. 检查请求数据的开头是否是 0xaced,有时候序列化请求不存在正确的content-type,此时需要根据数据来判断是否是序列化请求
  • 在确定一个接口是序列化接口的时候会漏洞扫描器会发送探测payload判断接口是否有反序列化漏洞,这里的攻击payload类似于ysoserial 工具,由于绝大多数情况下不可能看到回显(http返回数据没有攻击执行结果),因此只能进行盲注,即发送 sleep 10 这样的命令,根据响应时间判断是否有漏洞。