:::info 我们首先应当知道序列化和反序列化是什么才可以更好的进行利用

:::

反序列化 - 图1

介绍

序列化

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

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

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

反序列化

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

反序列化 - 图2

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

请注意,在使用不同的编程语言时,序列化可能被称为marshalling(Ruby)或pickling(Python)。这些术语在这里与”序列化“同义。

不安全的序列化

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

甚至有可能用一个完全不同类的对象来替换一个序列化的对象。令人震惊的是,网站可用的任何类的对象都会被反序列化和实例化,无论预期的是哪个类。因此,不安全的反序列化有时被称为“对象注入”漏洞。

一个非预期的类的对象可能会引发一个异常。然而,到此时损害可能已经造成了。许多基于反序列化的攻击是在反序列化完成之前完成的。这意味着,即使网站自身的功能没有与恶意对象直接交互,反序列化过程本身也可以发起攻击。出于这个原因,那些逻辑基于强类型语言的网站也可能会受到这些技术的攻击。

产生原因

不安全的反序列化通常是由于对反序列化用户可控数据的危险性普遍缺乏了解而产生的。理想情况下,用户输入的数据根本就不应该被反序列化。

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

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

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

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

影响

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

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

攻击

如何识别不安全的反序列化

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

在审计过程中,你应该查看所有被传入到网站的数据,并尝试识别任何看起来像序列化数据的内容。如果知道不同语言使用的格式,就可以相对容易地识别出序列化的数据

Tip

对于Burp Suite Professional的用户,Burp Scanner将自动标记任何可能包含序列化对象的HTTP报文。

PHP 序列化格式

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

  1. $user->name = "carlos";
  2. $user->isLoggedIn = true;

序列化后,这个对象可能看起来像这样:

  1. 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请求中发现了这个序列化对象,他们可能会对其进行解码以发现如下字节流:

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

isAdmin属性是一个明显的有趣点。攻击者可以简单地将该属性的布尔值改为1(true),重新编码该对象,并用这个修改后的值覆盖他们当前的cookie。在独立的情况下,这不会有任何影响。但是,假设该网站使用这个cookie来检查当前用户是否有权访问某些管理功能:

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

这段存在漏洞的代码会根据cookie中的数据实例化一个User对象,包括攻击者修改的isAdmin属性。任何时候都不会检查序列化对象的真实性。这个数据随后被传递到条件语句中,在这种情况下,可以轻松地进行权限提升。

这种简单的情况在野外并不常见。然而,以这种方式编辑一个属性值,展示了进入不安全的反序列化所暴露的大量攻击面的第一步。

修改数据类型

由于PHP在比较不同数据类型时弱比较运算符(==)的行为,因此基于PHP的逻辑特别容易受到这种操纵的影响。例如,如果在一个整数和一个字符串之间进行弱比较,PHP会试图将字符串转换为整数,这意味着 5 == “5”的值为true。

不寻常的是,这也适用于任何以数字开头的字母数字字符串。在这种情况下,PHP会将整个字符串转换为基于初始数字的整数值。字符串的其余部分将被完全忽略。因此,5 == “5 of something”在实践中被视为5 == 5。

当将一个字符串与整数0进行比较时,就变得更加奇怪了:

  1. 0 == "Example string" // true

为什么?因为这个字符串中没有数字,也就是0个数字。PHP将整个字符串视为整数0。

考虑一下这种弱比较运算符与来自反序列化对象的用户可控数据结合使用的情况。这有可能导致危险的逻辑缺陷。

  1. $login = unserialize($_COOKIE)
  2. if ($login['password'] == $password) {
  3. // log in successfully
  4. }

假设攻击者修改了密码属性,使其包含整数0而不是预期的字符串。只要存储的密码不是以数字开头,该条件就会一直返回true,从而实现认证绕过。请注意,这只是因为反序列化时保留了数据类型。如果代码直接从请求中获取密码,则0将被转换为字符串,条件将被评估为false。

请注意,当修改任何序列化对象格式中的数据类型时,请务必记住更新序列化数据中的任何类型标签和长度指示符。否则,序列化的对象会被破坏,并且无法被反序列化。

使用应用程序功能

除了简单地检查属性值外,网站的功能还可能对反序列化对象的数据执行危险的操作。在这种情况下,你可以使用不安全的反序列化来传入未预期的数据,并利用相关功能来进行破坏。

例如,作为网站“删除用户”功能的一部分,通过访问$user->image_location属性中的文件路径来删除用户的个人资料图片。如果这个$user是由一个序列化的对象创建的,攻击者可以通过传入一个修改过的对象,将image_location设置为一个任意文件路径来利用这一点。删除他们自己的用户账户也会删除这个任意文件。

魔术方法

魔术方法是不必显式调用的特殊方法子集。相反,只要发生特定事件或场景,它们就会被自动调用。魔术方法是各种语言面向对象编程的一个共同特征。它们有时通过在方法名前面或者周围加上双下划线来表示。

开发人员可以向一个类中添加魔术方法,以便预先确定在相应事件或场景发生时应执行哪些代码。调用魔术方法的确切时间和原因因方法而异。PHP中最常见的示例之一是construct(),每当类的对象被实例化时调用,类似于Python的init__。通常情况下,像这样的构造函数魔术方法包含初始化实例属性的代码。然而,魔法方法可以由开发人员自定义,以执行他们想要的任何代码。

魔法方法被广泛使用,其本身并不代表一个漏洞。但是当它们执行的代码处理攻击者可控的数据时,例如来自反序列化对象的数据,它们就会变得危险。攻击者可以利用这一点,在满足相应条件时自动调用反序列化数据的方法。

在这种情况下最重要的是,一些语言有在反序列化期间会自动调用的魔术方法。例如,PHP的unserialize()方法会寻找并调用一个对象的__wakeup()魔法方法。

在Java反序列化中,同样适用于ObjectInputStream.readObject()方法,该方法用于从初始字节流中读取数据,本质上就像一个构造函数,用于“重新初始化”一个序列化对象。然而,Serializable类也可以声明自己的readObject()方法,如下所示:

  1. private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException
  2. {
  3. // implementation
  4. }

以这种方式声明的readObject()方法充当了在反序列化期间被调用的魔术方法。这允许类更紧密地控制其自身字段的反序列化。

你应该密切关注任何包含这些类型的魔法方法的类。它们允许你在对象完全反序列化之前,将数据从序列化的对象传递到网站的代码中。这是创建更高级漏洞利用的起点。

注入任意对象

正如我们所见,有时可以通过简单地编辑网站提供的对象来利用不安全的反序列化。然而,注入任意的对象类型可以开辟更多的可能性。

在面向对象编程中,一个对象可用的方法由其类决定。因此,如果攻击者能够操纵作为序列化数据传入的对象类,他们就可以影响在反序列化之后甚至在反序列化期间执行的代码。

反序列化方法通常不检查它们正在反序列化的内容。这意味着你可以传入网站可用的任何可序列化类的对象,并且该对象将被反序列化。这实际上允许攻击者创建任意类的实例。这个对象不属于预期的类这一事实并不重要。未预期的对象类型可能会在应用逻辑中引起异常,但此时恶意对象已经被实例化了。

如果攻击者可以访问源代码,他们可以详细地研究所有可用的类。为了构建一个简单的利用,他们会寻找包含反序列化魔术方法的类,然后检查它们其中是否有对可控数据进行危险操作。然后,攻击者可以传入该类的序列化对象,以使用其魔术方法进行攻击。

Gadget链

“gadget”是存在于应用程序中的一段代码,可以帮助攻击者实现特定的目标。一个单独的gadget可能不会直接对用户输入做任何有害的事情。然而,攻击者的目标可能只是调用一个方法,将他们的输入传递给另一个gadget。通过以这种方式将多个gadget链接在一起,攻击者有可能将他们的输入传递到一个危险的“sink gadget”,在那里可以造成最大的破坏。

重要的是要理解,与其他一些类型的漏洞利用不同,gadget链不是攻击者构造的链式方法的payload。所有代码都已存在于网站上。攻击者唯一控制的是传递到gadget链的数据。这通常是通过一个在反序列化期间调用的魔术方法完成的,有时被称为“kick-off gadget”。

在野外,许多不安全的反序列化漏洞只有通过使用gadget链才能被利用。有时可能是一个简单的一两步链,但构造高严重性的攻击可能需要更复杂的对象实例化和方法调用序列。因此,能够构造gadget链是成功利用不安全反序列化的一个关键方面。

使用预构建的gadget链

手动识别gadget链是一个相当艰巨的过程,如果没有源代码访问权几乎是不可能的。幸运的是,有几个选项可以让你先尝试使用预构建的gadget链。

有几个工具可以提供一系列预先发现的链,这些链已经在其他网站上被成功利用过。即使无权访问源代码,你也可以使用这些工具来识别和利用不安全的反序列化漏洞,而只需付出相对较少的努力。这种方法之所以能够实现,是因为广泛使用包含可利用的gadget链的库。例如,如果Java的Apache Commons Collections库中的一个gadget链可以在一个网站上被利用,那么任何其他实现该库的网站也可能使用相同的链进行利用。

ysoserial

一个用于Java反序列化的工具“ysoserial”。它使你可以为你认为目标应用程序正在使用的库选择一个所提供的gadget链,然后传入一个想执行的命令。然后它根据所选的链创建一个适当的序列化对象。这仍然涉及到一定量的反复试验,但与手动构建你自己的gadget链相比要省力得多。

请注意,并非ysoserial中的所有gadget链都能使你运行任意代码。相反,它们可能对其他目的有用。例如,你可以使用下面的链来帮助你快速检测几乎任何服务器上不安全的反序列化:

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

PHP通用Gadget链

大多数经常遭受不安全反序列化漏洞影响的语言都有相应的概念验证工具。例如,对于基于PHP的站点,你可以使用“PHP Generic Gadget Chains”(PHPGGC)。

注意

重要的是要注意,该漏洞是对用户可控数据的反序列化,而不是仅仅在网站代码或其任何库中存在一个gadget链。gadget链只是一种在有害数据被注入后对其进行操纵的手段。这也适用于各种依赖于不可信数据反序列化的内存损坏漏洞。换句话说,即使一个网站以某种方式设法堵住了所有可能的gadget链,它仍然可能是脆弱的。

使用记录的gadget链

在目标应用程序使用的框架中,可能并不是总有专门的工具可用于利用已知的gadget链。在这种情况下,总是值得在网上寻找,看看是否有任何已记录的利用方法来手动调整。调整代码可能需要对语言和框架有一些基本的了解,有时可能需要自己序列化对象,但这种方法仍然比从头开始构建一个漏洞利用要省力得多。

创建自己的漏洞利用

当现成的gadget链和记录的利用不成功时,你将需要创建自己的漏洞利用。

要成功构建你自己的gadget链,肯定需要访问源代码。第一步是研究此源代码,找出一个包含在反序列化期间被调用的魔术方法的类。评估这个魔术方法执行的代码,看它是否直接对用户可控属性做了什么危险操作。这始终是值得检查的,以防万一。

如果这个魔法方法本身不可利用,它还可以作为gadget链的“kick-off gadget”。研究kick-off gadget调用的任何方法。这些方法中是否有对你控制的数据做一些危险的事情? 如果没有,请仔细查看它们随后调用的每个方法,依此类推。

重复这个过程,跟踪你可以访问的值,直到到达一个死胡同,或确定一个你的可控数据被传入其中的危险的sink gadget。

一旦你弄清楚了如何在应用程序代码中成功构建gadget链,下一步就是创建一个包含你的payload的序列化对象。这只是研究源代码中的类声明并创建有效的序列化对象的一种情况,该对象具有你利用所需的适当值。正如我们在之前的实验中所见,在处理基于字符串的序列化格式时,相对简单。

处理二进制格式,例如构建Java反序列化漏洞利用时,可能会特别麻烦。在对现有对象进行细微更改时,你可能会很乐意直接使用字节。然而,当进行更显著的更改时,例如传入一个全新的对象,这很快就会变得不切实际。在目标语言中编写你自己的代码,以便自己生成和序列化数据,这通常要简单得多。

在创建自己的gadget链时,要注意利用这个额外的攻击面来触发二次漏洞的机会。

通过仔细研究源代码,你可以发现更长的gadget链,这些gadget链可能允许你构建高严重性的攻击,通常包括远程代码执行。

PHAR反序列化

到目前为止,我们主要研究了利用网站显式反序列化用户输入的反序列化漏洞。然而,在PHP中即使没有明显使用unserialize()方法,有时也可以利用反序列化。

PHP提供了几个URL样式的包装器,可以在访问文件路径时使用它们来处理不同的协议。其中一个是phar://包装器,它为访问PHP归档(.phar)文件提供了一个流接口。

PHP官方文档显示PHAR清单文件包含序列化的元数据。重要的是,如果对一个phar://流进行任何文件系统操作,这些元数据就会被隐式反序列化。这意味着phar://流有可能成为利用不安全反序列化的载体,只要你能把这个流传给文件系统方法。

对于明显危险的文件系统方法,如include()或fopen(),网站可能已经采取了对策来减少它们被恶意使用的可能性。然而,诸如file_exists()等没有那么明显危险的方法,可能就没有得到很好的保护。

这种技术还需要你以某种方式将PHAR上传到服务器上。例如,一种方法是使用图像上传功能。如果你能够创建一个polyglot文件,将PHAR伪装成一个简单的JPG,有时可以绕过网站的验证检查。如果你能强迫网站从phar://流中加载这个polyglot “JPG”,通过PHAR元数据注入的任何有害数据都会被反序列化。由于PHP读取流时不检查文件扩展名,因此文件使用图像扩展名并不重要。

只要该对象的类被网站支持,wakeup()和destruct()魔术方法都可以通过这种方式调用,允许你使用这种技术潜在地启动gadget链。

使用内存损坏利用反序列化

即使不使用gadget链,仍有可能利用不安全的反序列化。如果一切都失败了,通常会有公开记录的内存损坏漏洞,可以通过不安全的反序列化来利用这些漏洞。这些通常会导致远程代码执行。

诸如PHP的unserialize()之类的反序列化方法很少能够抵御此类攻击,并暴露出大量的攻击面。这本身并不总被认为是一个漏洞,因为这些方法最初并不是为了处理用户可控的输入。

防御

一般来说,除非绝对必要,否则应避免对用户输入进行反序列化。在许多情况下,它可能带来的高严重性的漏洞,以及对它们进行保护的困难,都超过了它的好处。