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

本节中,我们将使用PHP、Ruby和Java反序列化的例子教你如何利用一些常见场景。我们希望证明,利用不安全的反序列化实际上比许多人认为的要容易得多。如果你能够使用预构建的gadget链,那么在黑盒测试中也是如此。

我们还将指导你创建自己的基于反序列化高严重性攻击的过程。尽管这些攻击通常需要访问源代码,但一旦理解了基本概念,它们也会比你想象的更容易学习。特别是,我们将涵盖以下主题:

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

  • 修改网站所期望的序列化对象

  • 将恶意数据传入危险的网站功能

  • 注入任意的对象类型

  • 链方法调用以控制数据流向危险的sink gadgets

  • 手动创建你自己的高级利用

  • PHAR反序列化

注意

尽管许多实验和示例都基于PHP,但大多数利用技术对其他语言也同样有效。

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

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

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

Tip

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

PHP序列化格式

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

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

When serialized, this object may look something like this:

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

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属性。任何时候都不会检查序列化对象的真实性。这个数据随后被传递到条件语句中,在这种情况下,可以轻松地进行权限提升。

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

LAB

修改序列化对象

修改数据类型

我们已经了解了如何修改序列化对象中的属性值,但也可以提供非预期的数据类型。

由于PHP在比较不同数据类型时弱比较运算符(==)的行为,因此基于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
}

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

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

LAB

修改序列化数据类型

当直接处理二进制格式时,我们建议使用Hackvertor扩展,可从BApp store获得。使用Hackvertor,你可以将序列化数据修改成一个字符串,它将自动更新二进制数据,相应地调整偏移量。可以为你节省大量的手动工作。

使用应用程序功能

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

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

LAB

使用应用程序功能来利用不安全的反序列化

这个例子依赖于攻击者通过用户可访问的功能手动调用危险方法。然而,当你创建自动将数据传入危险方法的漏洞时,不安全的反序列化变得更加有趣。这可以通过使用“魔术方法”来实现。

魔术方法

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

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

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

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

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

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

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

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

注入任意对象

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

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

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

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

LAB

PHP中的任意对象注入

包含这些反序列化魔术方法的类也可用于发起更复杂的攻击,涉及一长串方法调用,被称为“gadget链”。

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链相比要省力得多。

LAB

利用Apache Commons的Java反序列化

请注意,并非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)。

LAB

使用预构建的gadget链利用PHP反序列化

注意

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

使用记录的gadget链

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

LAB

使用记录的gadget链利用Ruby反序列化

即使找不到一个可以使用的gadget链,你仍然可以获得宝贵的知识,来帮助你创建自己的自定义利用。

创建自己的利用

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

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

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

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

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

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

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

LAB

为Java反序列化开发一个自定义的gadget链

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

LAB

为PHP反序列化开发一个自定义的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链。

LAB

使用PHAR反序列化去部署一个gadget链

这项创新技术被列入我们的2018年十大Web黑客技术。

阅读更多

2018年十大Web黑客技术

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

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

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

Last updated