当前位置: 首页 > 后端技术 > PHP

不安全的反序列化

时间:2023-03-29 22:21:55 PHP

Web安全的不安全反序列化在本节中,我们将解释什么是不安全的反序列化,并描述它如何使网站面临高度破坏性的攻击。我们将重点关注典型场景,并演示一些在PHP、Ruby和Java中反序列化的具体示例。最后,将介绍一些避免不安全反序列化漏洞的方法。利用不安全的反序列化通常更加困难。然而,它有时比您想象的要简单得多。如果您不熟悉反序列化,本节包含一些您应该首先熟悉的重要背景信息。如果您已经了解反序列化的基础知识,则可以直接跳到学习如何利用它。什么是序列化序列化是将复杂的数据结构(如对象及其字段)转换为“更扁平”格式的过程,在这种格式中,数据可以作为字节流序列发送和接收。序列化数据使以下操作变得更容易:将复杂数据写入进程间内存、文件或数据库发送复杂数据,例如,通过网络或API调用,以及在应用程序的不同组件之间传递复杂数据当一个对象是序列化,它的状态也被保留。换句话说,对象的属性和它们的赋值都被保留了下来。序列化与反序列化反序列化是将字节流还原为原始对象的精确副本的过程。然后网站的逻辑可以像任何其他对象一样与这个反序列化的对象进行交互。许多编程语言都提供了对序列化的原生支持。究竟如何序列化对象取决于特定的语言。有的语言将对象序列化为二进制格式,有的则序列化为字符串格式,可读性不同。请注意,原始对象的所有属性都存储在序列化数据流中,包括所有私有字段。为了防止一个字段被序列化,它必须在类声明中显式地标记为“transient”。请注意,当使用不同的编程语言时,序列化可能被称为marshalling(Ruby)或pickling(Python),这些术语与“序列化”同义。什么是不安全反序列化不安全反序列化是指用户控制下的数据被网站反序列化。这可能使攻击者能够操纵序列化对象,以便将有害数据传递到应用程序代码中。甚至可以用完全不同类的对象替换序列化对象。令人担忧的是,网站上可用的任何类的对象都将被反序列化和实例化,无论该类是否是预期的类。因此,不安全的反序列化有时被称为“对象注入”对象注入漏洞。意外类的对象可能会导致异常。不过,在此之前,损害可能已经造成。许多基于反序列化的攻击在反序列化完成之前完成。这意味着反序列化过程本身可能会受到攻击,即使网站的功能不直接与恶意对象交互也是如此。因此,逻辑基于强类型语言的网站也容易受到这些技术的攻击。不安全的反序列化漏洞是如何发生的不安全的反序列化通常是由于普遍缺乏对用户可控数据的反序列化有多危险的理解而产生的。理想情况下,用户输入根本不应该被反序列化。一些网站所有者认为他们是安全的,因为他们对反序列化数据进行了某种形式的额外检查。然而,这种方法通常是无效的,因为几乎不可能验证或预测所有可能的场景。这些检查也存在根本性缺陷,因为它们依赖于反序列化后的数据检查,而在许多情况下,反序列化已为时已晚,无法防止攻击。漏洞也可能出现,因为反序列化的对象通常被认为是真实的。特别是在使用二进制序列化格式的语言时,开发人员可能会认为用户无法有效地读取或操作数据。然而,虽然这可能需要更多的努力,但攻击者利用二进制序列化对象的可能性与利用基于字符串的格式的可能性一样大。由于现代网站中存在大量依赖项,因此也可能发生基于反序列化的攻击。一个站点可能会使用许多不同的库,每个库都有自己的依赖关系,从而创建了大量难以安全管理的类和方法。由于攻击者可以创建这些类中的任何一个的实例,因此很难预测可以对恶意数据调用哪些方法。如果攻击者能够将一长串意外方法调用链接在一起,将数据传递到与原始源完全无关的接收器中,则尤其如此。因此,几乎不可能预测恶意数据的流动并堵住每一个潜在的漏洞。简而言之,不可能安全地反序列化不受信任的输入。不安全的反序列化会造成什么影响?不安全的反序列化的影响可能很严重,因为它提供了一个入口点,导致攻击面大大增加。它允许攻击者以有害的方式重用现有的应用程序代码,从而导致许多其他漏洞,例如远程代码执行。即使在无法远程执行代码的情况下,不安全的反序列化也可能导致权限提升、访问任意文件和拒绝服务攻击。下面将详细介绍如何利用不安全的反序列化漏洞。如何防止不安全的反序列化漏洞通常,除非绝对必要,否则应避免对用户输入进行反序列化。在许多情况下,防御其潜在的高严重性漏洞的难度超过了收益。如果您确实需要反序列化来自不受信任来源的数据,请采取强有力的措施确保数据未被篡改。例如,您可以实施数字签名来检查数据的完整性。但是,请记住,必须在开始反序列化之前完成任何检查。否则,检查是没有用的。如果可能,您应该避免使用通用的反序列化函数。这些方法的序列化数据包含原始对象的所有属性,以及可能包含敏感信息的私有字段。相反,您应该创建自己的特定于类的序列化方法来控制公开的字段。最后,请记住,该漏洞是用户输入的反序列化,而不是随后处理数据的工具链的存在。不要依赖于尝试消除在测试期间识别的工具链,由于跨库依赖性,这是不切实际的。在任何给定时间,公开记录的内存损坏漏洞也意味着应用程序可能容易受到攻击。利用不安全的反序列化漏洞在本节中,我们将通过PHP、Ruby和Java中的反序列化示例,教您如何利用一些常见的漏洞场景。我们希望证明利用不安全的反序列化实际上比许多人认为的要容易得多。即使在黑盒测试期间,如果您能够使用预构建的工具链。我们还将指导您创建基于高严重性反序列化漏洞的攻击。尽管这些通常需要访问源代码,但一旦理解了基本概念,它们也比您想象的更容易学习。我们将涵盖以下主题:如何识别不安全的反序列化修改网站所需的序列化对象将恶意数据传递到危险的网站功能中注入任意对象类型链接方法调用以控制数据流入危险的接收器手动创建您自己的高级漏洞PHAR反序列化注意:虽然很多实验和例子都是基于PHP的,但是大部分的开发技巧对于其他语言同样有效。如何识别不安全的反序列化识别不安全的反序列化相对简单,无论您使用白盒测试还是黑盒测试。在审核期间,您应该查看来自您网站的所有传入数据,并尝试识别任何看起来像序列化的内容。如果您知道不同语言使用的格式,则可以相对容易地识别序列化数据。在本节中,我们将展示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-第二个属性的值是布尔值truePHP序列化本机方法是serialize()和unserialize()。如果您有权访问源代码,您应该首先到处寻找unserialize()并进一步调查。Java序列化格式某些语言(例如Java)使用二进制序列化格式。这更难阅读,但如果您知道如何识别某些信号,您仍然可以识别序列化数据。例如,序列化的Java对象总是以十六进制编码的ac和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(真),然后重新编码该对象,并用此修改后的值覆盖当前的cookie。单独来看,这是没有用的。但是,如果网站使用此cookie来检查当前用户是否可以访问某些管理功能:$user=unserialize($_COOKIE);if($user->isAdmin===true){//allowaccesstoadmininterface}上面的代码将根据cookie中的数据实例化一个User对象,包括攻击者修改的isAdmin属性,并且不会检查序列化对象的真实性。此时修改数据直接升级权限。这种简单的场景并不常见。但是,以这种方式编辑属性值代表了攻击的第一步。修改数据类型除了修改序列化对象中的属性值外,我们还可以提供意想不到的数据类型。像PHP这样使用松散比较运算符==来比较不同数据类型的弱类型语言,特别容易受到这种操作的影响。例如,如果您在整数和字符串之间执行松散比较,PHP将尝试将字符串转换为整数,这意味着5=="5"的计算结果为真。特别是,这也适用于任何字母数字字符串。PHP会将整个字符串转换为初始数字的整数值,而字符串的其余部分将被完全忽略。因此,5=="5ofsomething"实际上被视为5==5。当将字符串与整数0进行比较时,这会变得更奇怪:0=="Examplestring"//true因为字符串中没有数字,PHP会将整个字符串视为整数0。考虑这样一种情况,将这种松散的比较运算符与来自反序列化对象的用户可控数据一起使用可能会导致危险的逻辑缺陷。$login=unserialize($_COOKIE)if($login['password']==$password){//登录成功}假设攻击者将密码属性修改为整数0而不是预期的字符串。那么只要存储的密码不是数字开头,就会导致认证通过。请注意,这只是一种可能性,因为反序列化会保留数据类型,如果代码直接从请求中获取密码,则0将转换为字符串并且条件将计算为false。请注意,在修改任何序列化对象格式的数据类型时,请务必记住同时更新序列化数据中的任何类型标签和长度指示符。否则,序列化后的对象会被破坏,无法反序列化。直接使用二进制格式时,我们建议使用BApp商店提供的Hackvertor扩展。使用Hackvertor,你可以将序列化数据修改为字符串,它会自动更新二进制数据并相应地调整偏移量,从而节省大量手动工作。除了简单地检查属性值之外,使用应用程序功能,网站功能可能会对反序列化对象中的数据执行危险操作。在这种情况下,您可以使用不安全的反序列化来传递意外数据并利用相关功能进行破坏。例如,作为站点“删除用户”功能的一部分,可以通过访问$user->image_location属性来删除用户的个人资料图片。如果这个$user来自一个序列化的对象,攻击者可以通过传入一个修改了image_location的对象来将其设置为任何文件路径。删除他们自己的用户帐户也会删除这个任意文件。此示例依赖于攻击者通过用户可访问的函数手动调用危险方法。但是,当您构建自动将数据传递给危险方法的漏洞时,不安全的反序列化变得更加有趣。这是通过使用“魔术方法”实现的。魔法方法魔法方法是一种特殊的方法子集,不需要显式调用。相反,它们会在某些事件或场景发生时自动调用。魔术方法是各种语言面向对象编程的共同特征。它们有时通过在方法名称前加前缀或用双下划线包围来表示。开发人员可以在类中添加魔术方法,以预先确定在相应事件或场景发生时应执行哪些代码。调用魔术方法的确切时间和原因因方法而异。PHP中最常见的示例之一是__construct(),它在实例化类的对象时调用,类似于Python的__init__。通常,像这样的构造函数魔术方法包含初始化实例属性的代码。但是,开发人员可以自定义魔术方法来执行他们想要的任何代码。魔术方法的广泛使用本身并不代表漏洞。但是当它们执行的代码操纵攻击者可以控制的数据(例如,来自反序列化对象的数据)时,它们可能会变得危险。攻击者可以利用此漏洞在满足相应条件时自动调用反序列化数据的方法。这种情况下的底线是某些语言具有在反序列化期间自动调用的魔术方法。例如,PHP的unserialize()方法查找并调用对象的__wakeup()魔术方法。在Java反序列化中,readObject()方法也是如此,它本质上就像一个“重新初始化”序列化对象的构造函数。ObjectInputStream.readObject()方法用于从初始字节流中读取数据。但是,可序列化类也可以声明自己的readObject()方法,如下所示:privatevoidreadObject(ObjectInputStreamin)throwsIOException,ClassNotFoundException{...};这允许类对其自身字段的反序列化有更严格的控制。最重要的是,以这种方式声明的readObject()方法充当反序列化期间调用的魔术方法。您应该密切注意任何包含此类魔术方法的类。它们允许您在对象完全反序列化之前将数据从序列化对象传递到您的网站代码中。这是利用更高级漏洞的起点。注入任意对象正如我们所见,不安全的反序列化偶尔会被网站提供的编辑对象所利用。然而,注入任意对象开辟了更多的可能性。在面向对象的编程中,对象可用的方法由其类决定。因此,如果攻击者可以操纵作为序列化数据传入的对象类,他就可以影响反序列化之后甚至反序列化期间执行的代码。反序列化方法通常不检查反序列化的内容。这意味着您可以传入网站可用的任何可序列化类的对象,该对象将被反序列化。这允许攻击者创建任意类的实例。该对象不属于预期类这一事实并不重要。意外的对象类型可能会导致应用程序逻辑异常,但恶意对象已经被实例化。如果攻击者可以访问源代码,他们就可以详细研究所有可用的类。为了构造一个简单的攻击,他们寻找包含反序列化魔法方法的类,然后检查它们是否对受控数据执行危险操作。然后,攻击者将传入此类的序列化对象,以使用其魔术方法进行攻击。包含这些反序列化魔术方法的类也可用于发起更复杂的攻击,其中涉及方法调用链,称为“小工具链”。调用链“小工具”是应用程序中存在的一段代码,可帮助攻击者实现特定目标。单个小工具不能直接对用户输入产生任何有害影响。然而,攻击者的目标可能只是调用一个将其输入传递给另一个小工具的方法。通过以这种方式将多个小工具链接在一起,攻击者可能会将他们的输入传递给一个危险的“接收器小工具”,从而造成最大的破坏。重要的是要了解,与其他类型的攻击不同,小工具链接不是攻击者构造的链接方法的有效负载。所有代码都已经存在于网站上。攻击者唯一控制的是传递到小工具链中的数据。这通常是通过在反序列化期间调用魔术方法来完成的,有时称为“启动小工具”。许多不安全的反序列化漏洞只能通过使用小工具链来利用。有时这可能是一个简单的一步或两步链,但构建高危害攻击可能需要更精细的对象实例化和方法调用序列。因此,能够构建小工具链是成功利用不安全反序列化的关键因素之一。使用预构建的小工具链手动识别小工具链可能是一个相当艰巨的过程,如果没有源代码访问权限几乎是不可能的。幸运的是,有一些方法可以使用您可以先尝试的预建小工具链。有多种工具可帮助您轻松构建小工具链。这些工具提供了一份已在其他网站上被利用的预先发现的小工具链列表。一旦在目标站点上发现了不安全的反序列化漏洞,即使您无法访问源代码,您也可以使用这些工具来尝试利用它。由于包含可利用小工具链的库的广泛可用性,这种方法成为可能。例如,如果一个依赖于Java的ApacheCommonsCollections库的小工具链可以在一个网站上被利用,那么使用该库的任何其他网站也可以使用相同的链被利用。Java反序列化的工具之一是“ysoserial”。您只需指定一个您认为目标应用程序正在使用的库,然后提供尝试执行的命令,该工具将根据给定库的已知小工具链创建适当的序列化对象。这仍然需要一定的尝试,但比手动构建您自己的小工具链要容易得多。大多数经常受到不安全反序列化攻击的语言都有匹配的概念验证工具。例如,对于基于PHP的站点,可以使用“PHP通用小工具链”(PHPGGC)。请务必注意,网站代码或其任何库中存在的小工具链并不是导致漏洞的原因。该漏洞是用户可控数据的反序列化,gadget链只是注入后操纵数据流的一种手段。这也适用于依赖于不可信数据反序列化的各种内存损坏漏洞。因此,即使他们设法管理了所有可能插入的小工具链,该站点仍可能容易受到攻击。使用已记录的小工具链,您可以查看是否有任何可用于攻击目标网站的已记录漏洞。即使没有用于自动生成序列化对象的专用工具,您仍然可以找到流行框架的文档化小工具链并手动调整它们。如果您找不到要使用的小工具链,您仍然可以获得宝贵的知识,您可以使用这些知识来创建您自己的自定义漏洞。创建您自己的漏洞当现成的小工具链和记录在案的漏洞不成功时,您需要创建您自己的漏洞。为了成功构建您自己的小工具链,您几乎肯定需要访问源代码。第一步是研究此源代码以确定包含在反序列化期间调用的魔法方法的类。评估这个魔术方法执行的代码,看看它是否直接使用用户可控的属性做任何危险的事情。如果魔术方法本身不可利用,它可以作为您的小工具链的起点。调查启动小工具调用的任何方法。这些行为是否会对您控制的数据构成风险?如果没有,则检查他们随后调用的每个方法,依此类推。重复这个过程,跟踪你可以访问的值,直到你到达死胡同或识别一个危险的接收器小工具,你的可控数据被传递到其中。一旦您弄清楚如何在您的应用程序代码中成功构建一个小工具链,下一步就是创建一个包含有效负载的序列化对象。这只需要研究源代码中的类声明并创建一个有效的序列化对象,其中包含利用该漏洞所需的适当值。正如我们在之前的实验中所见,这在使用基于字符串的序列化格式时相对简单。使用二进制格式,例如在构建Java反序列化漏洞时,可能会特别麻烦。在对现有对象进行小的更改时,直接使用字节可能会很舒服。然而,当进行更重要的更改时,例如传入一个全新的对象,这很快就会变得不切实际。为了自己生成和序列化数据,用目标语言编写自己的代码通常要简单得多。在创建您自己的小工具链时,请注意利用这个额外的攻击面触发次级漏洞的机会。通过仔细研究源代码,您可以发现更长的小工具链,这些小工具可能允许您构建高严重性攻击,通常包括远程代码执行。PHAR反序列化到目前为止,我们主要研究了反序列化是如何利用网站明确反序列化用户输入的漏洞。但是,在PHP中,有时即使不显式使用unserialize()方法也可以利用反序列化漏洞。当您访问不同的文件时,PHP提供了不同的处理方式。其中之一是phar://,它提供了一个流畅的界面来访问PHP存档(.phar)文件。PHP文档显示PHAR清单文件包含序列化元数据。至关重要的是,如果您对phar://流执行文件系统操作,其元数据将被隐式反序列化。这意味着phar://流可能是利用不安全反序列化的潜在点,前提是该流可以传递到文件系统方法中。对于明显危险的文件系统方法,例如include()或fopen(),网站可能已经实施了对策以减少它们被恶意使用的可能性。然而,像file_exists()这样看起来并不明显危险的方法可能没有得到很好的保护。此技术要求您以某种方式将PHAR上传到服务器。例如,一种方法是使用图片上传功能。如果您能够将PHAR伪装成简单的JPG文件,您有时可以绕过网站的验证检查。如果您可以强制网站加载此伪装成JPG的PHAR流,则通过PHAR元数据注入的任何有害数据都将被反序列化。由于PHP在读取流时不检查文件扩展名,因此文件是否使用图像扩展名并不重要。只要网站支持该对象的类,就可以通过这种方式调用__wakeup()和__destruct()魔术方法,从而允许您使用该技术启动小工具链。通过内存损坏利用反序列化即使不使用小工具链,也有可能利用不安全的反序列化。如果一切都失败了,通常会有公开记录的内存损坏漏洞,可以通过不安全的反序列化来利用这些漏洞。这些通常会导致远程代码执行。诸如PHP的unserialize()之类的反序列化方法很少针对此类攻击进行强化,从而暴露出很大的攻击面。这本身并不总是被视为漏洞,因为这些方法最初并不是为了处理用户可控的输入。