Java反序列化漏洞学习实践一-从Serializbale接口开始先弹一个计算器

0x0、基本概念

1、什么是序列化和反序列化

Serialization(序列化)是指把Java对象保存为二进制字节码的过程;反序列化deserialization是把二进制码重新转换成Java对象的过程。

2、什么情况下需要序列化

a)当你想把的内存中的对象保存到一个文件中或者数据库中时候;

b)当你想用套接字在网络上传送对象的时候;

c)当你想通过RMI传输对象的时候;

总之,序列化的用途就是传递和存储。

3、如何实现序列化

将需要序列化的类实现Serializable接口就可以了,Serializable接口中没有任何方法,可以理解为一个标记,即表明这个类可以被序列化。

序列化与反序列化都可以理解为“写”和“读”操作 ,通过如下这两个方法可以将对象实例进行“序列化”与“反序列化”操作。

/**

* 写入对象内容

*/

private void writeObject(java.io.ObjectOutputStream out)

/**

* 读取对象内容

*/

private void readObject(java.io.ObjectInputStream in)

4、一些注意点

当然,并不是一个实现了序列化接口的类的所有字段及属性,都是可以序列化的:

如果该类有父类,则分两种情况来考虑:

1.如果该父类已经实现了可序列化接口,则其父类的相应字段及属性的处理和该类相同;

2.如果该类的父类没有实现可序列化接口,则该类的父类所有的字段属性将不会序列化,并且反序列化时会调用父类的默认构造函数来初始化父类的属性,而子类却不调用默认构造函数,而是直接从流中恢复属性的值。

如果该类的某个属性标识为static类型的,则该属性不能序列化。

如果该类的某个属性采用transient关键字标识,则该属性不能序列化。

a)当一个父类实现序列化,子类自动实现序列化,不需要显式实现Serializable接口;

b)当一个对象的实例变量引用其他对象,序列化该对象时也把引用对象进行序列化;

0x1、简单实例

以下代码是一个简单的序列化和反序列化操作的demo。

package 反序列化;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class deserTest implements Serializable {
//创建一个简单的可被序列化的类,它的实例化后的对象就是可以被序列化的
private static final long serialVersionUID = 1L;
private int n;
public deserTest(int n) {
this.n=n;
}
@Override
public String toString() {
return "deserTest [n=" + n + ", getClass()=" + getClass() + ", hashCode()=" + hashCode() + ", toString()="
+ super.toString() + "]";
}
public static void main(String[] args) {
deserTest x = new deserTest(5);
operation.ser(x);
operation.deser();
x.toString();
}
}


class operation{
public static void ser(Object obj) {
//序列化操作,写操作
try {
ObjectOutputStream ooStream = new ObjectOutputStream(new FileOutputStream("object.obj"));
//ObjectOutputStream能把Object输出成Byte流
ooStream.writeObject(obj);
ooStream.flush();
ooStream.close();

} catch (FileNotFoundException e) {
e.printStackTrace();
}catch (IOException e) {
// TODO: handle exception
e.printStackTrace();
}
}
public static void deser() {
//反序列化,读取操作
try {
ObjectInputStream iiStream = new ObjectInputStream(new FileInputStream("object.obj"));
Object xObject = iiStream.readObject();
System.out.println(xObject);
iiStream.close();
} catch (FileNotFoundException e) {
// TODO: handle exception
e.printStackTrace();
}catch (IOException e) {
// TODO: handle exception
e.printStackTrace();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}

} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}

img

0x2、自定义反序列化的行为–弹个计算器

自定义序列化和反序列化过程,就是重写writeObject和readObject方法。

对以上代码进行改造,加入readObject方法的重写,再重写函数中加入自己的代码逻辑。

package 反序列化;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class deserTest2 implements Serializable {
//创建一个简单的可被序列化的类,它的实例化后的对象就是可以被序列化的
private static final long serialVersionUID = 1L;
private int n;
public deserTest2(int n) {
this.n=n;
}

@Override
public String toString() {
return "deserTest2 [n=" + n + ", getClass()=" + getClass() + ", hashCode()=" + hashCode() + ", toString()="
+ super.toString() + "]";
}

private void readObject(java.io.ObjectInputStream in) throws IOException,ClassNotFoundException{
in.defaultReadObject();
Runtime.getRuntime().exec("open /System/Applications/Calculator.app");
System.out.println("test");
}
public static void main(String[] args) {
deserTest2 x = new deserTest2(5);
operation1.ser(x);
operation1.deser();
x.toString();
}
}


class operation1{
public static void ser(Object obj) {
//序列化操作,写操作
try {
ObjectOutputStream ooStream = new ObjectOutputStream(new FileOutputStream("object.obj"));
//ObjectOutputStream能把Object输出成Byte流
ooStream.writeObject(obj);
ooStream.flush();
ooStream.close();

} catch (FileNotFoundException e) {
e.printStackTrace();
}catch (IOException e) {
// TODO: handle exception
e.printStackTrace();
}
}
public static void deser() {
//反序列化,读取操作
try {
ObjectInputStream iiStream = new ObjectInputStream(new FileInputStream("object.obj"));
Object xObject = iiStream.readObject();
System.out.println(xObject);
iiStream.close();
} catch (FileNotFoundException e) {
// TODO: handle exception
e.printStackTrace();
}catch (IOException e) {
// TODO: handle exception
e.printStackTrace();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}

img

总结:

这是一个极端的例子,在真实场景中,不会有人真的这样直接写一句执行命令的代码在readObject()中。但是一般会有其他的代码逻辑,如果它的代码逻辑里,如果有某一个分支有像method.invoke()这种可以调用其他函数的代码,那么就可以跳转过去,再从这个“其他函数”的角度去看有没有执行代码或调用其他函数的可能。这个“其他函数”也就是所谓的gadget了。

参考:

http://www.cnblogs.com/xdp-gacl/p/3777987.html

http://www.importnew.com/20125.html

http://beautyboss.farbox.com/post/study/shen-ru-xue-xi-javaxu-lie-hua

Author

ol4three

Posted on

2020-09-13

Updated on

2021-03-03

Licensed under


Comments