关于php:Web-安全-之-Insecure-deserialization

3次阅读

共计 9154 个字符,预计需要花费 23 分钟才能阅读完成。

Insecure deserialization

在本节中,咱们将介绍什么是不平安的反序列化,并形容它是如何使网站蒙受高危害性攻打的。咱们将重点介绍典型的场景,并演示一些 PHP、Ruby 和 Java 反序列化的具体示例。最初也会介绍一些防止不平安的反序列化破绽的办法。

利用不平安的反序列化通常比拟艰难。然而,它有时比你设想的要简略得多。如果您不相熟反序列化,那么本节将蕴含一些重要的背景信息,您应该首先相熟这些信息。如果您曾经理解反序列化的基础知识,那么能够间接跳到学习如何利用它。

什么是序列化

序列化是将简单的数据结构(如对象及其字段)转换为“更扁平”的格局的过程,该格局的数据能够作为字节流序列发送和接管。序列化的数据让以下过程更简略:

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

要害的是,当序列化一个对象时,其状态也将放弃不变。换句话说,对象的属性及其赋值都会被保留。

序列化 vs 反序列化

反序列化是将字节流还原为与原始对象完全相同的正本的过程。而后,网站的逻辑就能够与这个反序列化的对象进行交互,就像与任何其余对象进行交互一样。

许多编程语言提供对序列化的本地反对。具体如何序列化对象取决于具体语言。一些语言将对象序列化为二进制格局,而另一些语言则会序列化为具备不同水平的可读性的字符串格局。请留神,原始对象的所有属性都存储在序列化数据流中,包含所有公有字段。为了避免字段被序列化,必须在类申明中将其显式标记为 ”transient”。

请留神,当应用不同的编程语言时,序列化可能被称为 marshalling(Ruby)或 pickling(Python),这些术语与“序列化”同义。

什么是不平安的反序列化

不平安的反序列化是指用户可管制的数据被网站反序列化。这会使攻击者可能操纵序列化的对象,以便将无害数据传递到利用程序代码中。

甚至能够用齐全不同类的对象替换序列化对象。令人担忧的是,网站上任何可用的类的对象都将被反序列化和实例化,而不论这个类是不是预期的类。因而,不平安的反序列化有时被称为 “object injection” 对象注入破绽。

一个意外类的对象可能会导致异样。不过,在此之前,侵害可能曾经造成。许多基于反序列化的攻打在反序列化完结之前就曾经实现。这意味着能够攻打反序列化的过程自身,即便网站的性能不间接与歹意对象交互。因而,其逻辑基于强类型语言的网站也容易受到这些技术的攻打。

不平安的反序列化破绽是如何呈现的

不平安的反序列化呈现通常是因为人们广泛不足对用户可控数据进行反序列化的危险水平的理解。现实状况下,基本不应该对用户输出进行反序列化。

有些网站所有者认为他们很平安,因为其会对反序列化的数据进行某种模式的附加查看。然而,这种办法通常是有效的,因为简直不可能验证或预料到所有可能产生的状况。这些查看在基本上也是有缺点的,因为它们依赖于在数据被反序列化后对其进行查看,在许多状况下,这对于避免攻打来说曾经太晚了。

破绽产生也可能是因为反序列化的对象通常被认为是可信的。特地是在应用二进制序列化格局的语言时,开发人员可能会认为用户无奈无效地读取或操作数据。然而,只管这可能须要更多致力,但攻击者利用二进制序列化对象的可能性与利用基于字符串的格局的可能性是一样的。

因为古代网站中存在大量依赖项,因而基于反序列化的攻打也成为可能。一个站点可能应用许多不同的库,每个库也都有本人的依赖项,这就产生了一个难以平安治理的存在大量类和办法的池子。因为攻击者能够创立这些类中的任何一个实例,因而很难预测能够对歹意数据调用哪些办法。如果攻击者可能将一长串意外的办法调用链接在一起,并将数据传递到与初始源齐全无关的接收器中,则尤其如此。因而,简直不可能预测到歹意数据的流动并梗塞每个潜在的破绽。

简而言之,平安地反序列化不受信赖的输出是不可能的。

不平安的反序列化会造成什么影响

不平安的反序列化的影响可能十分重大,因为它提供了一个切入点,从而导致攻击面大幅减少。它容许攻击者以无害的形式重用现有的利用程序代码,从而导致许多其余破绽,比方近程代码执行.

即便在无奈执行近程代码的状况下,不平安的反序列化也可能导致权限晋升、拜访任意文件和拒绝服务攻打。

如何利用不平安的反序列化破绽

下文会有具体阐明。

如何避免不平安的反序列化破绽

一般来说,除非相对必要,否则应该防止用户输出的反序列化。在许多状况下,进攻其潜在的高危破绽的难度超过了其带来的益处。

如果的确须要反序列化来自不受信赖的源的数据,请采取弱小的措施以确保数据未被篡改。例如,您能够实现一个数字签名来检查数据的完整性。然而,请记住,任何查看都必须在开始反序列化之前进行。否则,查看就没什么用途了。

如果可能,您应该防止应用通用的反序列化性能。这些办法的序列化数据蕴含了原始对象的所有属性,以及可能蕴含敏感信息的公有字段。相同,你应该创立本人特定类的序列化办法,以管制公开字段。

最初,请记住,该破绽是用户输出的反序列化,而不是随后解决数据的工具链的存在。不要依赖于试图打消测试过程中辨认的工具链,因为跨库依赖的存在,这是不切实际的。在任何给定的工夫,公开记录的内存损坏破绽也意味着应用程序可能会受到攻打。


利用 insecure deserialization 破绽

在本节中,咱们将通过 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 – 第二个属性的值是布尔值 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

因为字符串中没有数字,PHP 会将整个字符串视为整数 0。

思考这样一种状况:将这个涣散的比拟运算符与来自反序列化对象的用户可控数据一起应用,这可能导致危险的逻辑缺点。

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

假如攻击者批改了 password 属性,使其为整数 0 而不是预期的字符串。那么只有存储的明码不是以数字结尾,就会导致身份验证通过。请留神,这只是一种可能性,因为反序列化可保留数据类型,如果代码间接从申请中获取明码,则 0 将转换为字符串,并且条件的评估后果为 false。

请留神,在批改任何序列化对象格局的数据类型时,务必记住也要更新序列化数据中的任何类型标签和长度批示符。否则,序列化的对象将损坏,并且不会反序列化。

当间接应用二进制格局时,咱们倡议应用 Hackvertor 扩大,其能够从 BApp store 中取得。应用 Hackvertor,你能够将序列化数据批改为字符串,它将自动更新二进制数据,并相应地调整偏移量,这能够节俭大量的手动操作。

应用应用程序性能

除了简略地查看属性值之外,网站的性能还可能对反序列化对象中的数据执行危险的操作。在这种状况下,你能够应用不平安的反序列化来传递意外的数据,并利用相干性能造成侵害。

例如,作为网站“删除用户”性能的一部分,通过拜访 $user->image_location 属性能够删除用户的个人资料图片。如果这个 $user 来源于序列化对象,则攻击者能够通过传入一个批改了 image_location 的对象将其设置为任意一个文件门路。删除他们本人的用户帐户也会删除这个任意文件。

此示例依赖于攻击者通过用户可拜访的性能手动调用危险办法。然而,当你结构将数据主动传递到危险办法的破绽利用时,不平安的反序列化将变得更加乏味。这是通过应用“魔术办法”来实现的。

魔术办法

魔术办法是不用显式调用的办法的非凡子集。相同,它们会在特定事件或场景产生时主动调用。魔术办法是各种语言中面向对象编程的一个独特特色。它们有时通过在办法名后面加上前缀或用双下划线突围来示意。

开发人员能够向类中增加魔术办法,以便预先确定在相应的事件或场景产生时应该执行哪些代码。调用魔术办法的确切工夫和起因因办法而异。PHP 中最常见的例子之一是 __construct(),其在实例化类的对象时调用,相似于 Python 的 __init__。通常,像这样的构造函数魔术办法蕴含初始化实例属性的代码。然而,开发人员能够自定义魔术办法来执行他们想要的任何代码。

魔术办法被宽泛应用,其自身并不代表破绽。但当它们执行的代码对攻击者可管制的数据(例如,来自反序列化对象的数据)进行解决时,它们可能变得危险。攻击者可利用此破绽在满足相应条件时主动调用反序列化数据上的办法。

在这种状况下,最重要的是,某些语言具备在反序列化过程中主动调用的魔术办法。例如,PHP 的 unserialize() 办法查找并调用对象的 __wakeup() 神奇的办法。

在 Java 反序列化中,这同样实用于 readObject() 办法,它实质上相似于“从新初始化”序列化对象的构造函数。这个 ObjectInputStream.readObject() 办法用于从初始字节流中读取数据。然而,可序列化类也能够申明本人的 readObject() 办法如下:

private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {...};

这使得类能够更严密地管制本人字段的反序列化。最重要的是以这种形式申明的 readObject() 办法充当了在反序列化期间调用的魔术办法。

你应该亲密关注蕴含这类魔术办法的任何类。它们容许你在对象齐全反序列化之前,将数据从序列化对象传递到网站代码中。这是利用更高级破绽的终点。

注入任意对象

正如咱们所看到的,偶然能够通过编辑网站提供的对象来利用不平安的反序列化。然而,注入任意对象能够带来更多的可能性。

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

反序列化办法通常不查看反序列化的内容。这意味着你能够传入网站可用的任何可序列化类的对象,并且该对象将被反序列化。这容许了攻击者创立任意类的实例。该对象不是预期类的事实并不重要。意外的对象类型可能会导致利用程序逻辑中的异样,然而歹意对象曾经实例化。

如果攻击者有权拜访源代码,他们能够具体研究所有可用的类。为了结构一个简略的攻打,他们会寻找蕴含反序列化魔术办法的类,而后查看其中是否有任何类对可控数据执行危险操作。而后,攻击者将会传入这个类的序列化对象,以应用其魔术办法进行攻打。

蕴含这些反序列化魔术办法的类也可用于发动更简单的攻打,这波及一系列的办法调用,称为 “gadget chain” 调用链。

调用链

“gadget” 是应用程序中存在的一段代码,能够帮忙攻击者实现特定指标。单个 gadget 不能间接对用户输出造成任何无害影响。然而,攻击者的指标可能只是调用一个将其输出传递到另一个 gadget 的办法。通过以这种形式将多个 gadget 链接在一起,攻击者可能会将他们的输出传递到一个危险的 “sink gadget”,从而造成最大的毁坏。

重要的是要理解,与其余类型的攻打不同,gadget 链不是攻击者构建的链式办法的无效负载。所有的代码都曾经存在于网站上。攻击者惟一管制的是传递到 gadget 链中的数据。这通常反序列化期间调用魔术办法来实现,有时称为“启动 gadget”。

许多不平安的反序列化破绽只能通过应用 gadget 链来利用。这有时可能是一个简略的一步或两步链,但构建高危害性攻打可能须要更精密的对象实例化和办法调用序列。因而,可能结构 gadget 链是胜利利用不平安反序列化的关键因素之一。

应用事后构建的 gadget 链

手动辨认 gadget 链可能是一个相当艰巨的过程,如果没有源代码拜访,简直不可能。侥幸的是,有一些办法能够用来解决事后构建的 gadget 链,你能够先尝试一下。

有几种可用的工具能够帮忙你以最小的工作量构建 gadget 链。这些工具提供了一系列事后发现的 gadget 链,这些 gadget 链在其余网站上被利用过。在指标站点上发现了不平安的反序列化破绽后,即便你无权拜访源代码,也能够应用这些工具尝试并利用它。因为蕴含可利用 gadget 链的库的宽泛应用,这种办法成为可能。例如,如果一个依赖 Java 的 ApacheCommons Collections 库的 gadget 链能够在某个网站上被利用,那么应用该库的任何其余网站也能够应用同一个链进行攻打。

其中一个用于 Java 反序列化的工具是 “ysoserial”。你只需指定一个你认为指标应用程序正在应用的库,而后提供一个要尝试并执行的命令,该工具就会依据已知的给定库的 gadget 链创立适当的序列化对象。这依然须要一定量的尝试,但它比手工构建本人的 gadget 链要轻松得多。

大多数常常蒙受不平安反序列化攻打的语言都有匹配的 proof-of-concept 工具。例如,对于基于 PHP 的站点,能够应用 “PHP Generic Gadget Chains”(PHPGGC)。

须要留神的是,网站代码或其任何库中存在的 gadget 链并不是导致该破绽的起因。该破绽是用户可控制数据的反序列化,gadget 链只是在数据被注入后操纵数据流的一种伎俩。这也实用于依赖于非可信数据反序列化的各种内存毁坏破绽。因而,即便他们设法治理每一个可能插入的 gadget 链,网站可能依然是软弱的。

应用有记录的 gadget 链

你能够看看是否有任何记录在案的破绽利用,能够拿来攻打你的指标网站。即便没有用于主动生成序列化对象的专用工具,你依然能够为风行框架找到有记录的 gadget 链并手动调整它们。

如果你找不到一个能够应用的 gadget 链,你依然能够取得有价值的常识,你能够利用这些常识创立本人的自定义破绽利用程序。

创立本人的破绽利用

当现成的 gadget 链和有记录的破绽攻打不胜利时,你须要创立本人的破绽利用。

为了胜利地构建本人的 gadget 链,你简直必定须要拜访源代码。第一步是钻研此源代码,以辨认蕴含反序列化期间调用的魔术办法的类。评估这个魔术办法执行的代码,看看它是否间接应用用户可管制的属性做任何危险的事件。

如果魔术办法自身不可利用,它能够作为你的 gadget 链的启动点。钻研启动 gadget 调用的任何办法。这些操作是否会对你管制的数据造成危险?如果不是,请认真查看它们随后调用的每个办法,依此类推。

反复此过程,跟踪你能够拜访的值,直到你达到死胡同或辨认出一个危险的 sink gadget,你的可控数据被传递到其中。

一旦解决了如何在利用程序代码中胜利地结构 gadget 链,下一步就是创立一个蕴含无效负载的序列化对象。这只需钻研源代码中的类申明并创立一个无效的序列化对象,该对象具备利用破绽所需的适当值。正如咱们在以前的实验室中看到的,应用基于字符串的序列化格局时,这一点绝对简略。

应用二进制格局,例如在构建 Java 反序列化破绽时,可能会特地麻烦。在对现有对象进行小的更改时,间接应用字节可能会很难受。然而,当进行更重要的更改时,例如传入一个全新的对象,这很快就变得不切实际了。为了本人生成和序列化数据,用目标语言编写本人的代码通常要简略得多。

在创立本人的 gadget 链时,要留神利用这个额定的攻击面触发主要破绽的机会。

通过认真钻研源代码,你能够发现更长的 gadget 链,这些 gadget 链可能容许你构建高危险性攻打,通常包含近程代码执行.

PHAR 反序列化

到目前为止,咱们次要钻研了如何利用反序列化破绽,即网站显式地反序列化用户输出。然而,在 PHP 中,有时即便没有显著应用 unserialize() 办法,也有可能能够利用反序列化破绽。

当你拜访不同的文件时,PHP 提供了不同的解决形式。其中之一是 phar://,它提供了一个流式接口来拜访 PHP Archive (.phar) 文件。

PHP 文档揭示了 PHAR 清单文件蕴含序列化的元数据。至关重要的是,如果你对 phar:// 流执行文件系统操作,其元数据会被隐式的反序列化。这意味着 phar:// 流可能是利用不平安的反序列化的潜在点,前提是能够将此流传递到文件系统办法中。

对于显著危险的文件系统办法,例如 include()fopen(),网站很可能曾经施行了反制措施,以缩小它们被歹意应用的可能性。然而,诸如 file_exists() 这类看起来没有显著危险的办法可能没有失去很好的爱护。

此技术要求你通过某种形式将 PHAR 上传到服务器。例如,一种办法是应用图像上传性能。如果你可能将 PHAR 伪装成一个简略的 JPG 文件,你有时能够绕过网站的验证查看。如果你能强制网站加载这个伪装成 JPG 的 PHAR 流,则任何通过 PHAR 元数据注入的无害数据都将被反序列化。因为 PHP 读取流时不查看文件扩展名,因而文件是否应用图像扩展名并不重要。

只有对象的类是由网站反对的,则 __wakeup() __destruct() 魔术办法能够用这种形式调用,从而容许你应用这种技术启动一个 gadget 链。

通过内存毁坏利用反序列化

即便不应用 gadget 链,也有可能利用不平安的反序列化。如果所有其余办法都失败,通常会有公开记录的内存损坏破绽,能够通过不平安的反序列化来利用这些破绽。这些通常会导致近程代码执行。

反序列化办法,例如 PHP 的 unserialize() 很少对这类攻打进行强化,暴露出大量的攻击面。其自身并不总会被认为是一个破绽,因为这些办法一开始并不打算解决用户可管制的输出。

正文完
 0