【Academy】反序列化漏洞Insecure deserialization

什么是序列化?

序列化是将复杂的数据结构(如对象及其字段)转换为可以作为顺序字节流发送和接收的“更丝滑”格式的过程。序列化数据使以下操作变得更简单:

  • 将复杂数据写入进程间内存、文件或数据库
  • 例如,通过网络、在应用程序的不同组件之间或在API调用中发送复杂数据

至关重要的是,当序列化一个对象时,它的状态也会被持久化。换句话说,对象的属性及其指定的值都会被保留。

序列化与反序列化

反序列化是将此字节流恢复为原始对象的完全功能副本的过程,其状态与序列化时完全相同。然后,网站的逻辑可以与这个反序列化的对象交互,就像与任何其他对象交互一样。

许多编程语言都提供了对序列化的本地支持。对象的序列化方式取决于语言。一些语言将对象序列化为二进制格式,而另一些语言使用不同的字符串格式,具有不同程度的人类可读性。请注意,原始对象的所有属性都存储在序列化数据流中,包括任何私有字段。若要防止字段被序列化,必须在类声明中将其显式标记为“transient”。

请注意,当使用不同的编程语言时,序列化可能被称为编组(Ruby)或pickle(Python)。在此上下文中,这些术语与“序列化”同义。

什么是不安全的反序列化?

不安全的反序列化是指用户可控的数据被网站认证。这可能使攻击者能够操纵序列化对象,以便将有害数据传递到应用程序代码中。

甚至可以用完全不同类的对象替换序列化对象。令人担忧的是,网站可用的任何类的对象都将被反序列化和实例化,而不管期望的是哪个类。由于这个原因,不安全的反序列化有时被称为“对象注入”漏洞。

非预期的类的对象可能会导致异常。然而,到目前为止,损害可能已经造成。许多基于反序列化的攻击在反序列化完成之前就完成了。这意味着反序列化过程本身就可以发起攻击,即使网站本身的功能不直接与恶意对象交互。因此,基于强类型语言逻辑的网站也容易受到这些技术的攻击。

不安全的反序列化漏洞是如何产生的?

不安全的反序列化通常会出现,因为人们普遍缺乏对反序列化用户可控数据的危险性的理解。理想情况下,用户输入根本不应该被格式化。

然而,有时网站所有者认为他们是安全的,因为他们对反序列化数据进行了某种形式的额外检查。这种方法通常是无效的,因为实际上不可能实现验证或清理来解释每一种可能性。这些检查也存在根本性的缺陷,因为它们依赖于在数据被反序列化后对其进行检查,在许多情况下,这对于防止攻击来说为时已晚。

漏洞也可能因为假设对象是可信的而出现。特别是在使用具有二进制序列化格式的语言时,开发人员可能会认为用户无法有效地读取或操作数据。然而,虽然可能需要更多的努力,但攻击者利用二进制序列化对象的可能性与利用基于字符串的格式的可能性一样。

由于现代网站中存在大量依赖关系,基于反序列化的攻击也成为可能。一个典型的站点可能实现许多不同的库,每个库也有自己的依赖关系。这就创建了一个庞大的类和方法库,很难安全地进行管理。由于攻击者可以创建这些类中任何一个的实例,因此很难预测哪些方法可以在恶意数据上调用。如果攻击者能够将一长串意外的方法调用链接在一起,将数据传递到与初始源完全无关的接收器中,则情况尤其如此。因此,几乎不可能预测恶意数据的流动并堵住每一个潜在的漏洞。

简而言之,可以说不可能安全地反序列化不受信任的输入。

不安全的反序列化有什么影响?

不安全的反序列化的影响可能非常严重,因为它为大量增加的攻击面提供了入口点。它允许攻击者以有害的方式重用现有的应用程序代码,导致许多其他漏洞,通常是远程代码执行。

即使在无法执行远程代码的情况下,不安全的反序列化也可能导致特权提升、任意文件访问和拒绝服务攻击。

识别不安全的反序列化漏洞

既然您已经熟悉了序列化和反序列化的基础知识,我们可以看看如何利用不安全的反序列化漏洞。

在本节中,将教您如何使用PHP、Ruby和Java实现中的示例来利用一些常见的场景。我们希望证明利用不安全的序列化实际上比许多人想象的要容易得多。如果您能够使用预先构建的小工具链,甚至在黑盒测试期间也是如此。

我们还将指导您完成创建自己的高严重性基于恶意攻击的过程。虽然这些通常需要访问源代码,但一旦理解了基本概念,它们也可能比您想象的更容易学习。

无论您是在白盒测试还是黑盒测试中,识别不安全的反序列化都相对简单。

在审核过程中,您应该查看传递到网站的所有数据,并尝试识别任何看起来像序列化数据的内容。如果您知道不同语言使用的格式,则可以相对容易地识别序列化数据。在本节中,我们将展示来自PHP和Java序列化的示例。一旦识别出序列化数据,就可以测试是否能够控制它。

PHP序列化格式

PHP使用一种人类可读的字符串格式,字母表示数据类型,数字表示每个条目的长度。例如,考虑一个具有以下属性的User对象:

$user->name = "carlos";
$user->isLoggedIn = true;

序列化时,该对象可能看起来像这样:

O:4:"User":2:{s:4:"name":s:6:"carlos"; s:10:"isLoggedIn":b:1;}

这可以解释如下:

  • O:4:"User" -具有4个字符的类名为"User"的对象
  • 2 -对象有2个属性
  • s:4:"name" -第一个属性的键是4个字符的字符串"name"
  • s:6:"carlos" -第一个属性的值是6个字符的字符串"carlos"
  • s:10:"isLoggedIn" -第二个属性的键是10个字符的字符串"isLoggedIn"
  • b:1 -第二个属性的值是布尔值true

PHP序列化的原生方法是serialize()unserialize()。如果你有源代码访问权限,你应该从代码中的任何地方开始寻找unserialize(),并进一步调查。

Java序列化格式

某些语言(例如Java)使用二进制序列化格式。这是更难以阅读,但你仍然可以识别序列化的数据,如果你知道如何识别一些特征。例如,序列化的Java对象总是开始相同的字节,在十六进制中编码为ac ed ,在Base64中编码为rO0

任何实现接口java.io.Serializable的类都可以被序列化和反序列化。如果您有源代码访问权限,请记下使用readObject()方法的任何代码,该方法用于从InputStream读取和重新编译数据。

利用不安全的反序列化漏洞

操作序列化对象

利用某些非序列化漏洞就像更改序列化对象中的属性一样简单。由于对象状态是持久化的,因此您可以研究序列化的数据以识别和编辑感兴趣的属性值。然后,您可以通过其恶意化进程将恶意对象传递到网站中。这是基本恶意攻击的第一步。
一般来说,在操作序列化对象时可以采用两种方法。您可以直接以字节流的形式编辑对象,也可以用相应的语言编写一个简短的脚本来自己创建和序列化新对象。在使用二进制序列化格式时,后一种方法通常更容易。

修改对象属性

当篡改数据时,只要攻击者保留了有效的序列化对象,则反序列化过程将创建具有修改的属性值的服务器端对象。
举一个简单的例子,考虑一个网站,它使用序列化的User对象在cookie中存储有关用户会话的数据。如果攻击者在HTTP请求中发现了这个序列化的对象,他们可能会解码它以找到以下字节流:

O:4:"User":2:{s:8:"username";s:6:"carlos";s:7:"isAdmin";b:0;}

isAdmin属性是一个明显的兴趣点。攻击者只需将属性的布尔值更改为1(true),重新编码对象,并使用此修改后的值覆盖其当前cookie。单独地讲,这没有任何影响。但是,假设网站使用此Cookie来检查当前用户是否可以访问某些管理功能:

$user = unserialize($_COOKIE);
if ($user->isAdmin === true) {
// allow access to admin interface
}

此易受攻击的代码将根据Cookie中的数据实例化User对象,包括攻击者修改的isAdmin属性。在任何时候都不会检查序列化对象的真实性。然后将此数据传递到条件语句中,在这种情况下,将允许轻松的权限升级。

修改数据类型

我们已经看到了如何修改序列化对象中的属性值,但也有可能提供意外的数据类型。
基于PHP的逻辑在比较不同数据类型时,由于其松散的比较运算符(==)的行为,特别容易受到这种操作的影响。例如,如果您在整数和字符串之间执行松散的比较,PHP将尝试将字符串转换为整数,这意味着5 == "5"计算为true
不寻常的是,这也适用于任何以数字开头的字母数字字符串。在这种情况下,PHP将有效地将整个字符串转换为基于初始数字的整数值。字符串的其余部分被完全忽略。因此,5 == "5 of something" 实际上被视为5 == 5
当比较一个字符串的整数0时,这变得更加奇怪:

0 == "Example string" // true

为什么?因为没有数字,也就是说,字符串中有0个数字。PHP将整个字符串视为整数0。
考虑这样一种情况:将这个松散的比较运算符与来自一个实体化对象的用户可控数据结合使用。这可能会导致危险的逻辑缺陷。

$login = unserialize($_COOKIE)
if ($login['password'] == $password) {
// log in successfully
}

假设攻击者修改了password属性,使其包含整数0而不是预期的字符串。只要存储的密码不是以数字开头,条件将始终返回true,从而启用绕过身份验证。请注意,只有反序列化保留了数据类型这才是唯一可能的。如果代码直接从请求中获取密码,则0将被转换为字符串,条件的计算结果将为false。
请注意,在修改任何序列化对象格式的数据类型时,一定要记住也要更新序列化数据中的任何类型标签和长度指示符。否则,序列化的对象将被损坏,并且不会被反序列化。
当直接使用二进制格式时,我们建议使用Hackvertor扩展,可从BApp商店获得。使用Hackvertor,您可以将序列化数据修改为字符串,它将自动更新二进制数据,相应地调整偏移量。这可以为您节省大量的手动工作。

使用应用程序功能

除了简单地检查属性值之外,网站的功能还可能对来自反序列化对象的数据执行危险的操作。在这种情况下,您可以使用不安全的验证来传递意外的数据,并利用相关的功能来造成损害。
例如,作为网站的“删除用户”功能的一部分,用户的个人资料图片通过访问$user->image_location属性中的文件路径来删除。如果此$user是从序列化对象创建的,则攻击者可以通过传入修改后的对象并将image_location设置为任意文件路径来利用此漏洞。删除自己的用户帐户也会删除此任意文件。
此示例依赖于攻击者通过用户可访问的功能手动调用危险方法。然而,当您创建自动将数据传递到危险方法的漏洞时,不安全的反序列化变得更加有趣。这是通过使用“魔术方法”实现的。

魔术方法

魔术方法是一个特殊的方法子集,你不必显式调用。相反,它们在特定事件或场景发生时自动调用。魔术方法是各种语言中面向对象编程的共同特征。它们有时通过在方法名前面加前缀或用双下划线括起来来表示。
开发人员可以向类中添加魔术方法,以便预先确定当相应的事件或场景发生时应该执行什么代码。魔术方法被调用的确切时间和原因因方法而异。PHP中最常见的例子之一是__construct(),每当类的对象被实例化时就会调用它,类似于Python的__init__。通常,像这样的构造函数魔术方法包含初始化实例属性的代码。但是,开发人员可以自定义魔术方法来执行他们想要的任何代码。
Magic方法被广泛使用,它们本身并不代表漏洞。但是,当它们执行的代码处理攻击者可控制的数据时,例如,来自一个可编程对象的数据时,它们可能会变得危险。攻击者可以利用此漏洞,在满足相应条件时自动调用已验证数据上的方法。
在这种情况下,最重要的是,有些语言有一些神奇的方法,可以在自动化过程中自动调用。例如,PHP的unserialize()方法查找并调用对象的__wakeup()魔术方法。
在Java中,这同样适用于ObjectInputStream.readObject()方法,该方法用于从初始字节流中读取数据,本质上就像一个构造函数,用于“重新初始化”一个序列化的对象。但是,Serializable类也可以声明自己的readObject()方法,如下所示:

private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException
{
    // implementation
}

一个以这种方式声明的readObject()方法就像是一个魔法方法,在反序列化过程中被调用。这允许类更紧密地控制其自身字段的反序列化。
您应该密切关注任何包含这些类型的神奇方法的类。它们允许您在对象被完全反序列化之前将数据从序列化对象传递到网站的代码中。这是创建更高级漏洞的起点。

注入任意对象

正如我们所看到的,偶尔可以通过简单地编辑网站提供的对象来利用不安全的反序列化。但是,注入任意对象类型可以打开更多的可能性。
在面向对象编程中,对象可用的方法由其类决定。因此,如果攻击者可以操纵作为序列化数据传入的对象类,他们就可以影响在序列化之后甚至在序列化期间执行的代码。
反序列化方法通常不检查它们要反序列化的内容。这意味着您可以传入网站可用的任何可序列化类的对象,并且该对象将被反序列化。这实际上允许攻击者创建任意类的实例。这个对象不属于预期的类这一事实并不重要。意外的对象类型可能会在应用程序逻辑中导致异常,但恶意对象届时已经实例化。
如果攻击者可以访问源代码,他们可以详细研究所有可用的类。为了构造一个简单的exploit,他们会寻找包含反序列化魔法方法的类,然后检查它们中是否有任何一个对可控数据执行危险操作。然后,攻击者可以传入此类的序列化对象,以使用其魔术方法进行攻击。
包含这些反序列化魔法方法的类也可以用来发起更复杂的攻击,包括一长串的方法调用,称为“小工具链”。

小工具链

“小工具”是存在于应用程序中的代码片段,可以帮助攻击者实现特定目标。单个小工具可能不会直接对用户输入做出任何有害的事情。然而,攻击者的目标可能只是调用一个方法,将他们的输入传递到另一个小工具。通过以这种方式将多个小工具链接在一起,攻击者可能会将其输入传递到危险的“接收器小工具”中,在那里它可能会造成最大的损害。
重要的是要理解,与其他类型的利用不同,小工具链不是攻击者构建的链式方法的有效负载。所有代码都已经存在于网站上。攻击者唯一控制的是传递到小工具链中的数据。这通常是使用在初始化期间调用的魔术方法来完成的,有时称为“kick-off gadget”。
在野外,许多不安全的反序列化漏洞只能通过使用小工具链来利用。这有时可能是一个简单的一步或两步链,但构造高严重性的攻击可能需要更复杂的对象实例化和方法调用序列。因此,能够构建小工具链是成功利用不安全的虚拟化的关键方面之一。

使用预建的小工具链

有几种工具可以提供一系列预先发现的链,这些链已在其他网站上成功利用。即使您无法访问源代码,也可以使用这些工具来识别和利用不安全的虚拟化漏洞,而且相对来说工作量不大。这种方法之所以成为可能,是因为包含可利用的小工具链的库的广泛使用。例如,如果Java的Apache Commons Collections库中的gadget链可以在一个网站上被利用,那么实现该库的任何其他网站也可以使用相同的链被利用。
一个这样的Java语言化工具是“ysosarial”。这使您可以为您认为目标应用程序正在使用的库选择一个提供的小工具链,然后传递要执行的命令。然后,它根据选定的链创建适当的序列化对象。这仍然涉及一定量的试验和错误,但它比手动构建自己的小工具链要少得多。
注意
在Java版本16及更高版本中,您需要设置一系列命令行参数以使Java运行ysosarial。举例来说:

java -jar ysoserial-all.jar \
   --add-opens=java.xml/com.sun.org.apache.xalan.internal.xsltc.trax=ALL-UNNAMED \
   --add-opens=java.xml/com.sun.org.apache.xalan.internal.xsltc.runtime=ALL-UNNAMED \
   --add-opens=java.base/java.net=ALL-UNNAMED \
   --add-opens=java.base/java.util=ALL-UNNAMED \
   [payload] '[command]'

并不是ysosarial中的所有小工具链都允许您运行任意代码。相反,它们可以用于其他目的。例如,您可以使用以下方法来帮助您快速检测几乎任何服务器上的不安全的身份验证:

  • URLDNS链触发对提供的URL的DNS查找。 最重要的是,它不依赖于使用特定易受攻击的库的目标应用程序,并且可以在任何已知的Java版本中工作。这使其成为用于检测目的的最通用的小工具链。如果您在流量中发现了序列化的对象,您可以尝试使用此小工具链生成一个对象,该对象触发与Burp Collaborator服务器的DNS交互。如果是这样,您可以确定目标上发生了错误化。
  • JRMPClient是另一个通用链,您可以使用它进行初始检测。它使服务器尝试建立到所提供的IP地址的TCP连接。请注意,您需要提供原始IP地址而不是主机名。此链在所有出站流量(包括DNS查找)都有防火墙的环境中可能很有用。您可以尝试使用两个不同的IP地址生成有效负载:本地IP地址和防火墙外部IP地址。如果应用程序立即响应本地地址的有效负载,但挂起外部地址的有效负载,导致响应延迟,这表明小工具链工作,因为服务器试图连接到防火墙地址。在这种情况下,响应中细微的时间差异可以帮助您检测服务器上是否发生反序列化,即使是在盲测情况下。

PHP通用小工具链

大多数经常遭受不安全的反序列化漏洞的语言都有等效的概念验证工具。例如,对于基于PHP的站点,您可以使用“PHP通用小工具链”(PHPGGC)。
注意
值得注意的是,该漏洞是用户可控制数据的反序列化,而不仅仅是网站代码或其任何库中的小工具链的存在。小工具链只是一旦它被注入的一种操纵有害数据流的手段。这也适用于依赖不受信任数据的反序列化的内存损坏漏洞。换句话说,即使网站确实设法插入了所有可能的小工具链,它仍然可能容易受到攻击。

使用文档化的小工具链

可能并不总是存在可用于利用目标应用所使用的框架中的已知小工具链的专用工具。在这种情况下,它总是值得在线查看,看看是否有任何记录的漏洞,你可以手动调整。调整代码可能需要对语言和框架有一些基本的理解,有时可能需要自己序列化对象,但这种方法仍然比从头开始构建漏洞要少很多工作量。
即使你找不到可以使用的小工具链,你仍然可以获得有价值的知识,帮助你创建自己的自定义exploit。

创建自己的exploit

当现成的小工具链和记录的exploit不成功时,您需要创建自己的exploit。
要成功构建自己的小工具链,您几乎肯定需要访问源代码。第一步是研究这个源代码,以确定一个类,它包含一个魔术方法,这个魔术方法是在递归化过程中调用的。评估这个魔术方法执行的代码,看看它是否直接对用户可控的属性做了任何危险的事情。这总是值得检查以防万一。
如果这个魔术方法本身不可利用,它可以作为小工具链的“启动小工具”。研究kick-off小工具调用的任何方法。其中任何一个会对你控制的数据造成危险吗?如果没有,请仔细查看它们随后调用的每个方法,依此类推。
重复此过程,跟踪您可以访问的值,直到您到达死胡同或确定可控数据传递到一个危险的接收器小工具。
一旦您已经了解了如何在应用程序代码中成功构造小工具链,下一步就是创建包含有效负载的序列化对象。这只是一个研究源代码中的类声明并创建一个有效的序列化对象的案例,该对象具有利用漏洞所需的适当值。正如我们在前面所看到的,在使用基于字符串的序列化格式时,这相对简单。
使用二进制格式,例如在构造Java反序列化漏洞时,可能特别麻烦。当对现有对象进行微小更改时,您可能会习惯于直接使用字节。但是,当进行更重要的更改时,例如传入一个全新的对象,这很快就变得不切实际了。为了自己生成和序列化数据,用目标语言编写自己的代码通常要简单得多。
在创建自己的小工具链时,要注意利用这些额外的攻击面来触发次级漏洞的机会。
通过仔细研究源代码,您可以发现更长的小工具链,这些小工具链可能允许您构建高度严重的攻击,通常包括远程代码执行。

PHAR 反序列化

到目前为止,我们主要关注的是利用网站显式地将用户输入序列化的数据反序列化漏洞。然而,在PHP中,即使没有明显使用unserialize()方法,有时也可以利用反序列化。
PHP提供了几个URL样式的包装器,您可以在访问文件路径时使用它们来处理不同的协议。其中之一是phar://包装器,它提供了一个访问PHP Archive(.phar)文件的流接口。
PHP文档显示PHAR清单文件包含序列化的元数据。至关重要的是,如果您在phar:// stream上执行任何文件系统操作,此元数据将被隐式地格式化。这意味着phar://流可能成为利用不安全反序列化的向量,前提是您可以将此流传递到文件系统方法中。
对于明显危险的文件系统方法,例如include()fopen(),网站可能已经实施了反制措施,以减少恶意使用它们的可能性。然而,像file_exists()这样的方法,虽然没有那么明显的危险,但可能不会受到很好的保护。
这种技术还要求您以某种方式将PHAR上传到服务器。例如,一种方法是使用图像上传功能。如果你能够创建一个多语言文件,用一个简单的PHAR伪装成一个JPG,你有时可以绕过网站的验证检查。如果你可以强制网站从phar://流加载这个多语言的“JPG”,则您通过PHAR元数据注入的任何有害数据都将被反序列化。由于PHP读取流时不检查文件扩展名,因此文件是否使用图像扩展名并不重要。
只要网站支持对象的类,就可以通过这种方式调用__wakeup()__destruct()魔术方法,从而允许您使用此技术启动小工具链。

利用内存损坏进行反序列化攻击

即使不使用小工具链,仍然有可能利用不安全的反序列化漏洞。如果所有其他方法都失败了,通常会有公开记录的内存损坏漏洞,可以通过不安全的反序列化来利用这些漏洞。这通常会导致远程代码执行。
类似PHP的unserialize()这样的私有化方法很少能够抵御这类攻击,并且会暴露出大量的攻击面。这本身并不总是被认为是一个漏洞,因为这些方法最初并不打算处理用户可控制的输入。

如何防止不安全的反序列化漏洞

一般来说,除非绝对必要,否则应避免对用户输入的反序列化。在许多情况下,它可能导致的高严重性的漏洞利用,以及防范这些漏洞的难度,都超过了使用它带来的收益。

如果您确实需要对来自不可信来源的数据进行反序列化,请采用可靠的措施来确保数据未被篡改。例如,您可以实现数字签名来检查数据的完整性。但是,请记住,任何检查都必须在开始反序列化过程之前进行。否则,它们几乎没有用处。

如果可能的话,您应该避免完全使用通用的反序列化功能。这些方法中的序列化数据包含原始对象的所有属性,包括可能包含敏感信息的私有字段。相反,您可以创建自己的类特定的序列化方法,这样您至少可以控制哪些字段是公开的。

最后,请记住,该漏洞是用户输入数据的反序列化,而不是随后处理数据的工具链产生的。不要试图消除测试过程中发现的工具链。尝试将它们全部剔除是不切实际的,因为你的网站上几乎肯定存在跨库依赖关系。在任何时间,公开或泄漏的内存调试崩溃等信息也是一个攻击的切入点,这意味着您的应用程序无论如何都可能受到攻击。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值