概念:
序列化是将复杂的数据结构(例如对象及其字段)转换为“更扁平”格式的过程,该格式可以作为字节顺序流发送和接收。
序列化数据使其更容易:
将复杂数据写入进程间内存,文件或数据库
例如,通过网络,在应用程序的不同组件之间或在API调用中发送复杂的数据
至关重要的是,在序列化对象时,其状态也将保留。换句话说,将保留对象的属性及其分配的值。
反序列化是将字节流还原为原始对象的完整功能副本的过程,其状态与序列化时的状态完全相同。
然后,网站的逻辑可以与此反序列化的对象进行交互,就像与任何其他对象进行交互一样。
PHP
漏洞原理
php对象控制
所有php里面的值都可以使用函数serialize()来返回一个包含字节流的字符串来表示,unserialize()函数能够重新把字符串变回php原来的值。
反序列化一个对象将会保存对象的所有变量。但是不会保存对象的方法,只会保存类的名字。为了能够unserialize()一个对象,这个对象的类必须已经定义过。
如果序列化类A的一个对象,将会返回一个跟类A相关,而且包含了对象所有变量值的字符串。
如果要想在另外一个文件中解序列化一个对象,这个对象的类必须在解序列化之前定义,可以通过包含一个该类的文件或使用函数spl_autoload_register()来实现。
魔术方法
__construct 构造函数,在创建对象时候初始化对象,一般用于对变量赋初值
__destruct 析构函数,和构造函数相反,在对象不再被使用时(将所有该对象的引用设为null)或者程序退出时自动调用
__toString 当一个对象被当作一个字符串被调用,把类当作字符串使用时触发,返回值需要为字符串,例如echo打印出对象就会调用此方法
__wakeup() 使用unserialize时触发,反序列化恢复对象之前调用该方法
__sleep() 使用serialize时触发 ,在对象被序列化前自动调用,该函数需要返回以类成员变量名作为元素的数组(该数组里的元素会影响类成员变量是否被序列化。只有出现在该数组元素里的类成员变量才会被序列化)
__destruct() 对象被销毁时触发
__call() 在对象中调用不可访问的方法时触发,即当调用对象中不存在的方法会自动调用该方法
__callStatic() 在静态上下文中调用不可访问的方法时触发
__get() 读取不可访问的属性的值时会被调用(不可访问包括私有属性,或者没有初始化的属性)
__set() 在给不可访问属性赋值时,即在调用私有属性的时候会自动执行
__isset() 当对不可访问属性调用isset()或empty()时触发
__unset() 当对不可访问属性调用unset()时触发
__invoke() 当脚本尝试将对象调用为函数时触发
额外提一下__tostring的具体触发场景:
(1) echo( o b j ) / p r i n t ( obj) / print( obj)/print(obj) 打印时会触发
(2) 反序列化对象与字符串连接时
(3) 反序列化对象参与格式化字符串时
(4) 反序列化对象与字符串进行
==
比较时(PHP进行==
比较的时候会转换参数类型)(5) 反序列化对象参与格式化SQL语句,绑定参数时
(6) 反序列化对象在经过php字符串函数,如 strlen()、addslashes()时
(7) 在in_array()方法中,第一个参数是反序列化对象,第二个参数的数组中有toString返回的字符串的时候toString会被调用
(8) 反序列化的对象作为 class_exists() 的参数的时候
对象序列化包括如下步骤:
-
创建一个对象输出流,它可以包装一个其他类型的目标输出流,如文件输出流;
-
通过对象输出流的writeObject()方法写对象
对象反序列化的步骤如下:
-
创建一个对象输入流,它可以包装一个其他类型的源输入流,如文件输入流;
-
通过对象输入流的readObject()方法读取对象
反序列化利用与POP链
介绍完了漏洞原理与序列化的实现过程,接下来说说漏洞的利用。
操作序列化对象
不安全的反序列化就包括了用户可以对序列化对象进行修改,这可能导致一些越权,代码执行等漏洞。
修改幅度有大有小,有的是仅仅修改序列化中的部分字符,有的则是重新生成一个序列化对象,传给网站进行反序列化。
在处理序列化对象时可以采用两种方法: 可以直接以对象的字节流形式对其进行编辑,也可以使用相应的语言编写简短的脚本来自己创建和序列化新对象。
使用二进制序列化格式时,后一种方法通常更容易。
1、修改对象属性
这属于修改幅度较小的情况,仅仅修改属性不会使反序列化报错,也保留了原有对象的结构。
举一个简单的例子,考虑一个使用序列化User对象的网站,该网站将有关用户会话的数据存储在cookie中。如果攻击者在HTTP请求中发现了序列化对象,则可能会对其进行解码以找到以下内容:
O:4:“User”:2:{s:8:“username”😒:6:“carlos”; s:7:“isAdmin”🅱️0;}
注意到这里的isAdmin属性,攻击者可以简单地将该属性的布尔值更改为1(true),重新编码对象,然后使用此修改后的值覆盖其当前cookie。
2、修改数据类型
我们已经看到了如何修改序列化对象中的属性值,修改数据类型有时候也会有意想不到的效果,这种效果可能基于PHP 的弱等于比较 ==
例如,如果在整数和字符串之间执行弱比较,PHP将尝试将字符串转换为整数,即结果5 == "5"为true。
这也适用于以数字开头的任何字母数字字符串。
在这种情况下,PHP将根据初始数字有效地将整个字符串转换为整数值。
字符串的其余部分将被完全忽略。
因此,5 == "5 of something"在实践中被视为5 == 5
比较0 :
0==“Example string”// true
因为没有数字,所以字符串中的数字为0。PHP将整个字符串视为整数0
考虑这种松散的比较运算符与反序列化对象中的用户可控制数据一起使用的情况。这可能会导致危险的逻辑缺陷。
l o g i n = u n s e r i a l i z e ( login = unserialize(login=unserialize(_COOKIE)
if($login[‘password’]== $password){
// log in successfully
}
假设攻击者修改了password属性,使其包含整数0而不是预期的字符串。只要存储的密码不是以数字开头,该条件将始终返回true 从而身份验证绕过。
请注意,以任何序列化的对象格式修改数据类型时,务必记住也要更新序列化数据中的任何类型标签和长度指示符,这一点很重要。否则,序列化的对象将被破坏并且不会被反序列化.
POP链
在反序列化中,我们所能控制的数据就是对象中的各个属性值,所以在PHP的反序列化有一种漏洞利用方法叫做 “面向属性编程” ,即 POP( Property Oriented Programming)。
和二进制漏洞中常用的ROP技术类似。
在ROP中我们往往需要一段初始化gadgets来开始我们的整个利用过程,然后继续调用其他gadgets。
在PHP反序列化漏洞利用技术POP中,对应的初始化gadgets就是wakeup() 或者是destruct() 方法, 在最理想的情况下能够实现漏洞利用的点就在这两个函数中,但往往我们需要从这个函数开始,逐步的跟进在这个函数中调用到的所有函数,直至找到可以利用的点为止。
下面列举些在跟进其函数调用过程中需要关注一些很有价值的函数。
几个可用的POP链方法
命令执行:
exec()
passthru()
popen()
system()
文件操作:
file_put_contents()
file_get_contents()
unlink()