发现反序列化漏洞通常非常简单。当源代码可用时,可以归结为寻找对它的调用readObject()
以及找到一种使用户输入达到该功能的方法。如果没有可用的源代码,则可以通过查找二进制blob或base64编码的对象(由“ rO0 ..”识别)来发现线上的序列化对象。困难在于剥削。当然,您可以将ysoserial的所有攻击都扔给它,并希望获得最好的,但是,如果它不起作用,则没有其他尝试。
在这一点上缺少的重要信息是有关远程应用程序的类路径的信息。如果我们知道要加载的库,则可以构造一个小工具链(例如,调整现有的ysoserial利用以匹配远程应用程序上库的版本)。这就是EnumJavaLibs的思想来源:只是让它反序列化来自不同(流行)的第三方Java库的任意对象。进一步来说:
- 创建最常用的Java库的本地数据库
- 对于这些库中的每一个,找到一个可序列化的类
- 创建此对象的实例,对其进行序列化,然后将其发送到远程应用程序
- 如果得到
ClassNotFoundException
支持,我们知道该库不在类路径中
我们已经在GitHub上发布了该项目的代码,以及可以构建库数据库(JavaClassDB.py)的工具。您可以在此处下载预编译的版本。
JMX / RMI
JMX / RMI满足上述条件,因此是可以完成Java类路径枚举的完美示例。当端点运行的是易受攻击的Java版本(即,在JEP290实施之前)时,默认情况下(无论是否需要身份验证),JMX / RMI端点都容易受到攻击。这是因为JMX / RMI端点公开了RMIServerImpl_Stub.newClient(Object o)
反序列化提供的任何对象的函数(接受Object类型的参数的RMI函数应始终为红色标记)。
EnumJavaLibs具有特定的“ RMI”模式,可让您指定RMI服务的IP和端口。然后它将newClient()
从数据库的jar中为每个序列化对象调用该函数。发生反序列化错误时,我们将通过RMI返回完整的堆栈跟踪,因此我们可以轻松读取是否找到了该类-并推断该库是否已加载到类路径中。
网络应用
因为探查Web应用程序的方式是视情况而定的,所以Enumjavalibs不会为您执行HTTP请求。相反,它将为您创建一个具有(类名,base64_encoded_serialized_object)的CSV文件,然后您可以使用该文件自行构建请求。一种方法是使用Burp入侵者。
内部构造
该工具使用了一些Java“技巧”,我想提供一些更多的细节。第一个是动态加载库。自Java 11以来,这变得更加困难(但并非不可能),这就是为什么该工具应使用Java 8进行编译的原因。使用URLClassLoader.addUrl()
可以加载由其路径指定的jar文件。由于JDK未公开此功能,因此需要进行反射以使其可访问。
加载jar后,我们将逐一遍历所有类,并尝试对其进行序列化。对我们来说哪个类都没有关系,我们想要的只是jar文件中的某个类。如果远程服务器能够反序列化此类,则很有可能加载了它所来自的库。但是我们如何序列化jar文件中的任意类呢?通常,序列化发生如下:
- 使用“ new…”实例化该类的对象。
- 使用ObjectOutputStream序列化对象
为了实例化对象,我们需要有关其构造函数的信息。我们可以通过反射得到它,但是如果它有一个将另一个对象作为参数的构造函数呢?或者如果这些参数需要满足一些条件,否则构造函数将抛出异常怎么办?这导致了复杂的情况,很难自动处理。幸运的是,有一种方法可以实例化对象而无需调用构造函数。而且不是很漂亮。但这有效。
它被称为Unsafe类,它使您可以执行Java通常出于种种原因而禁止您执行的操作。其中之一是在不调用构造函数的情况下实例化类。这出于我们的目的而起作用,因为在远程端ClassNotFoundException
,该类是根据类的名称抛出的–实际上,我们并不关心是否正确初始化了该类。因此,通过Unsafe实例化之后,我们可以序列化对象,并根据您运行工具的方式,通过RMI发送该对象或将Base64编码版本存储在输出文件中。
误报
可能发生在远程服务器上反序列化的类实际上可以追溯到多个库。回想一下,我们基于FQDN和SerialVersionUID的组合来区分类。因此,当可序列化类未在源代码中指定自定义SerialVersionUID时,就不会发生冲突(这意味着FQDN相同)是不可想象的。但是即使在这种情况下,我们仍然可以确定使用的是哪个库–而不是确切的版本。以FQDN为例“ org.apache.commons.fileupload.DiskFileUpload”;我们可以确定它来自commons-fileupload库,尽管由于不同版本之间的SerialVersionUID相同,我们可能无法确定确切的版本