一.反序列化的基本内容
类对象方法
类(Class): 用来描述具有相同的属性和方法的对象的集合。
它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。
对象:通过类定义的数据结构实例。对象包括两个数据成员(类变量和实例变量)和方法。
对象:某一个具体的事物、实体或事例
类是类型,比如人类,犬类;
对象是某种类的一个实例(实际的例子),比如身为人类的你就是一个对象,你家养的狗就属于犬类的一个对象,或者说一个实例;
你可能经常会听到“实例化对象”,这句话的意思就是创建对象,此处的实例名词作动词用,作名词时,可以理解为实例与对象就是一回事
方法:类中定义的函数
魔术方法在类或对象的某些事件出发后会自动执行
在PHP中以两个下划线开头的方法,如果希望PHP调用这些魔术方法,首先必须在类中定义,否则PHP不会执行未创建的魔术方法。
序列化和反序列化
序列化是将复杂的数据结构(例如对象及其字段)转换为“更扁平”格式的过程,该格式可以作为字节顺序流发送和接收。
序列化数据使其更容易:
将复杂数据写入进程间内存,文件或数据库
例如,通过网络,在应用程序的不同组件之间或在API调用中发送复杂的数据
至关重要的是,在序列化对象时,其状态也将保留。换句话说,将保留对象的属性及其分配的值。
反序列化是将字节流还原为原始对象的完整功能副本的过程,其状态与序列化时的状态完全相同。
然后,网站的逻辑可以与此反序列化的对象进行交互,就像与任何其他对象进行交互一样。
许多编程语言为序列化提供本地的支持。对象的具体序列化方式取决于语言。
某些语言将对象序列化为二进制格式,而其他语言则使用不同的字符串格式,同时具有不同程度的人类可读性。
请注意,所有原始对象的属性都存储在序列化的数据流中,包括任何私有字段。
为防止字段被序列化,必须在类声明中将其显式标记为“ transient”。
在PHP中,序列化用于存储或传递 PHP 的值的过程中,同时不丢失其类型和结构。
public(公共的):在本类内部、外部类、子类都可以访问
protect(受保护的):只有本类或子类或父类中可以访问
private(私人的):只有本类内部可以使用
不安全的反序列化
不安全的反序列化是指网站对用户可控制的数据进行反序列化时,攻击者能够操纵序列化的对象,以将有害数据传递到应用程序代码中。
甚至有可能用完全不同类的对象替换序列化的对象。
更夸张的是,将对网站可用的任何类别的对象进行反序列化和实例化,而与预期的类别无关。
因此,不安全的反序列化有时称为“对象注入”漏洞。
意外类的对象可能会导致异常。
但是,到此时,损坏可能已经造成。
许多基于反序列化的攻击是在反序列化完成之前完成的。
这意味着即使网站自身的功能未直接与恶意对象进行交互,反序列化过程本身也可以发起攻击。
因此,其逻辑基于强类型语言的网站也可能容易受到这些技术的攻击。
漏洞成因
会出现不安全的反序列化,是因为人们普遍缺乏对用户可控制数据进行反序列化的危险程度的了解。理想情况下,绝对不应该对用户输入数据进行反序列化。
由于通常认为反序列化对象是可信任的,因此也可能会出现漏洞。
尤其是当使用具有二进制序列化格式的语言时,开发人员可能会认为用户无法有效读取或操纵数据。
但是,尽管可能需要付出更多的努力,但攻击者仍有可能利用二进制序列化的对象,就象利用基于字符串的格式一样。
由于现代网站中存在大量依赖关系,因此基于反序列化的攻击也成为可能。
一个典型的站点可能会实现许多不同的库,每个库也都有自己的依赖性。
这会创建大量难以安全管理的类和方法。由于攻击者可以创建任何这些类的实例,因此很难预测可以对恶意数据调用哪些方法。
如果攻击者能够将一系列意想不到的方法调用链接在一起,并将数据传递到与初始源完全无关的接收器,则尤其如此。
因此,几乎不可能预料到恶意数据的流动并堵塞(修复)每个潜在的漏洞。
简而言之,反序列化不受信任的输入是不安全的。
漏洞影响
不安全的反序列化的影响可能非常严重,因为它为大规模增加攻击面提供了切入点。它允许攻击者以有害的方式重用现有的应用程序代码,从而导致许多其他漏洞,通常是远程执行代码(RCE)。
即使在无法执行远程代码的情况下,不安全的反序列化也可能导致越权,任意文件访问和拒绝服务攻击。
二.按编程语言分类
无论是白盒测试还是黑盒测试,识别不安全的反序列化都是相对简单的。
在审核期间,我们应该查看传递到网站的所有数据,并尝试识别任何看起来像序列化数据的内容。
如果知道不同语言使用的格式,则可以相对轻松地识别序列化数据。
在本节中,我们将展示多种语言的序列化示例及特性。识别序列化数据后,就可以测试是否能够控制它。
1.PHP
漏洞原理
php对象控制
所有php里面的值都可以使用函数serialize()来返回一个包含字节流的字符串来表示,unserialize()函数能够重新把字符串变回php原来的值。
反序列化一个对象将会保存对象的所有变量。但是不会保存对象的方法,只会保存类的名字。为了能够unserialize()一个对象,这个对象的类必须已经定义过。
如果序列化类A的一个对象,将会返回一个跟类A相关,而且包含了对象所有变量值的字符串。
如果要想在另外一个文件中解序列化一个对象,这个对象的类必须在解序列化之前定义,可以通过包含一个该类的文件或使用函数spl_autoload_register()来实现。
魔法方法
魔术方法是不必显式调用的方法的特殊子集。而是在发生特定事件或场景时自动调用它们。
魔术方法是各种语言的面向对象编程的共同特征。有时通过在方法名称前添加前缀或双下划线来表示它们。开发人员可以将魔术方法添加到类中,以便预先确定在发生相应事件或场景时应执行什么代码。何时以及为何调用魔术方法的确切方法因方法而异。
下面是比较典型的PHP反序列化漏洞中可能会用到的魔术方法:
__wakeup (void)
unserialize( )会检查是否存在一个wakeup( ) 方法。如果存在,则会先调用__wakeup 方法,预先准备对象需要的资源。
__construct ([ mixed $args [, $...]])
具有构造函数的类会在每次创建新对象时先调用此方法。
__destruct (void)
析构函数会在到某个对象的所有引用都被删除或者当对象被显式销毁时执行。
__toString (void)
__toString( ) 方法用于一个类被当成字符串时应怎样回应。例如 echo $obj;应该显示些什么。此方法必须返回一个字符串,否则将发出一条 E_RECOVERABLE_ERROR 级别的致命错误。
序列化实现
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
反序列化利用与POP链
介绍完了漏洞原理与序列化的实现过程,接下来说说漏洞的利用。
操作序列化对象
不安全的反序列化就包括了用户可以对序列化对象进行修改,这可能导致一些越权,代码执行等漏洞。
修改幅度有大有小,有的是仅仅修改序列化中的部分字符,有的则是重新生成一个序列化对象,传给网站进行反序列化。
在处理序列化对象时可以采用两种方法: 可以直接以对象的字节流形式对其进行编辑,也可以使用相应的语言编写简短的脚本来自己创建和序列化新对象。
使用二进制序列化格式时,后一种方法通常更容易。
1、修改对象属性
这属于修改幅度较小的情况,仅仅修改属性不会使反序列化报错,也保留了原有对象的结构。
举一个简单的例子,考虑一个使用序列化User对象的网站,该网站将有关用户会话的数据存储在cookie中。如果攻击者在HTTP请求中发现了序列化对象,则可能会对其进行解码以找到以下内容:
O:4:“User”:2:{s:8:“username”😒:6:“carlos”; s:7:“isAdmin”🅱️0;}
注意到这里的isAdmin属性,攻击者可以简单地将该属性的布尔值更改为1(true),重新编码对象,然后使用此修改后的值覆盖其当前cookie。
burpsuite官网的实验地址:[(http://r6d.cn/MgdW)
登录后查看Cookie,base64解码后为
O:4:“User”:2:{s:8:“username”;s:6:“wiener”;s:5:“admin”;b:0;}
尝试修改为(经过尝试需要每一次都修改,burpsuite直接修改cookie值)
O:4:“User”:2:{s:8:“username”;s:6:“wiener”;s:5:“admin”;b:1;}
再base64加密,url加密得到
Tzo0OiJVc2VyIjoyOntzOjg6InVzZXJuYW1lIjtzOjY6IndpZW5lciI7czo1OiJhZG1pbiI7YjoxO30%3D
修改之后得到了管理员权限:
2、修改数据类型
我们已经看到了如何修改序列化对象中的属性值,修改数据类型有时候也会有意想不到的效果,这种效果可能基于PHP 的弱等于比较 ==
例如,如果在整数和字符串之间执行弱比较,PHP将尝试将字符串转换为整数,即结果5 == "5"为true。
这也适用于以数字开头的任何字母数字字符串。
在这种情况下,PHP将根据初始数字有效地将整个字符串转换为整数值。
字符串的其余部分将被完全忽略。
因此,5 == "5 of something"在实践中被视为5 == 5
比较0 :
0==“Example string”// true
因为没有数字,所以字符串中的数字为0。PHP将整个字符串视为整数0
考虑这种松散的比较运算符与反序列化对象中的用户可控制数据一起使用的情况。这可能会导致危险的逻辑缺陷。