PHP反序列化
PHP反序列化:
关于反序列化的情况:
|
反序列化前输出结果:
反序列化后输出结果:
|
O:4:”test”这部分是对象名字的描述
:2:这部分是有两个成员属性,然后到花括号开始描述类的内部的内容。
s就是string
如果成员不是使用public进行标记,而是使用pravite,则会出现一下的情况:
|
其中,testage是作为age的替换出现的。
当private标注的成员属性进行反序列化的时候,会将格式改为%00类名%00成员名的格式
其中%00会占用一个字节的长度,因此,本来只有7个字符,但是实际反序列化出来之后,会显示有9个字节。
当使用protect之后,格式变为:
%00*%00成员名
就像是上面的结果变为了*sex一样,本来是sex。
在url中也要进行%00的添加,否则利用不成功。
魔术方法:
当没有对$_GET或是$_POST传入的数据进行过滤的时候,容易造成攻击。
特别是利用php的嵌入化特性,可以造成xss攻击。
这是一个反序列化的漏洞代码
|
这里可以看见的是,变量$a没有对get方法传入的值进行过滤,一旦传入的值是一个序列化后的特殊构造值,则会产生错误的后果。
主要是因为在删除对象之前会调用_destruct(),然后进行一个echo,经过字符的传输,会产生一个简易的XSS。
整个反序列化的流程:
首先创建一个类,建立一个类对应的对象,这个时候会首先调用_construct(),然后再将对象进行序列化,在进行序列化的时候,会首先调用_sleep(),然后再进行反序列化,然后在进行反序列化的时候,会直接调用_wakeup()这个函数,然后当消灭对象的时候再会调用_destruct()这个方法。
__sleep()方法不需要接受任何参数,但是需要返回一个数组。在数组中包含需要串行化的属性。没有被包含在数组中的属性将会在串行化的时候被忽略。如果没有在类中声明__sleep()方法,对象中的所有属性都将被串行化(就是相当于是没有做出任何的改变)。
所返回的数组需要自己进行定义,其中的每一个名字都可以是一个属性,返还这个数组之后,就会自动的只将数组中的属性进行串行化。
在调用unserialize()函数将对象反串行化的时候,将会自动的调用对象中存在的__wakeup()方法,用来在二进制串重新组成一个对象时,为新对象中的成员属性重新初始化。
__wakeup()方法将会在使用unserialize()函数的时候被自动调用(这两个魔术方法都是写在对象里面,不是单独写的)
可以在__wakeup()的内部直接为对象中的属性赋值。
PHP 7.1+版本:
在PHP7.1版本以上,对于序列化中的成员变量类型不敏感,当有对不可见字符串进行过滤的时候,可以将public类型的变量输入,然后进行绕过。
CVE-2016-7124 __wakeup绕过
__wakeup魔法函数简介
unserialize()
会检查是否存在一个 __wakeup()
方法。如果存在,则会先调用 __wakeup()
方法,预先准备对象需要的资源
反序列化时,如果表示对象属性个数的值大于真实的属性个数时就会跳过__wakeup()
的执行
漏洞影响版本:
php5 < 5.6.25
php7 < 7.0.10
也就是这里的对象成员个数,如果说这里的个数多了,就会直接跳过对于_wakeup()的执行。
常见于绕过_wakeup()中对于成员属性的再次赋值。
session反序列化漏洞:
session
英文翻译为”会话”,两个人聊天从开始到结束就构成了一个会话。PHP
里的session
主要是指客户端浏览器与服务端数据交换的对话,从浏览器打开到关闭,一个最简单的会话周期
cookie与session的区别:
https://www.zhihu.com/question/19786827
PHP session工作流程
会话的工作流程很简单,当开始一个会话时,PHP
会尝试从请求中查找会话 ID
(通常通过会话 cookie
),如果发现请求的Cookie
、Get
、Post
中不存在session id
,**PHP
就会自动调用php_session_create_id
函数创建一个新的会话**,并且在http response
中通过set-cookie
头部发送给客户端保存,例如登录如下网页Cokkie、Get、Post
都不存在session id
,于是就使用了set-cookie
头有时候浏览器用户设置会禁止
cookie
,当在客户端cookie
被禁用的情况下,php
也可以自动将session id
添加到url
参数中以及form
的hidden
字段中,但这需要将php.ini
中的session.use_trans_sid
设为开启,也可以在运行时调用ini_set
来设置这个配置项.
会话开始之后,PHP
就会将会话中的数据设置到 $_SESSION
变量中,如下述代码就是一个在 $_SESSION
变量中注册变量的例子:
|
主要的一个重点还是session相较于cookie来说的话,是存储在服务器中的。因此,可以直接通过改写session文件进行多种攻击。
php.ini配置
php.ini
里面有如下六个相对重要的配置
session.save_path="" --设置session的存储位置 |
如phpstudy
下上述配置如下:
session.save_path = "/tmp" --所有session文件存储在/tmp目录下 |
对于session文件来说,文件名的格式都是:
sess_xxxxx |
一般来说是默认使用文件的形式进行存储。
这种类似的形式,后面部分的填充是自动生成的,也就是session id部分。
session.serialize_handler
,它定义的引擎有三种
|处理器名称|存储格式||php| 键名 + 竖线 + 经过serialize()函数序列化处理的值|
|php_binary| 键名的长度对应的 ASCII 字符 + 键名 + 经过serialize()函数序列化处理的值|
|php_serialize(php>5.5.4)|经过serialize()函数序列化处理的数组|
php处理器
首先来看看session.serialize_handler
等于php
时候的序列化结果,代码如下
<?php |
(这里是序列化和反序列化的引擎语言使用PHP)
可以直接看见sessionid的,使用f12打开控制台,然后在存储选项可以看见。
session是$_SESSION['session']
的键名,在|之后是传入的GET参数,经过序列化之后的值。
键名 + 竖线 + 经过serialize()函数序列化处理的值
图片中键名是session,后面的部分是序列化的值,表示string类型,有7位,内容
php_binary处理器:
再来看看session.serialize_handler
等于php_binary
时候的序列化结果
|
为了更能直观的体现出格式的差别,因此这里设置了键值长度为 35
,35
对应的 ASCII
码为#
,所以最终的结果如下
键名的长度对应的 ASCII 字符 + 键名 + 经过serialize()函数序列化处理的值
这部分会在键名前加一个和键名长度相等的ASCII码表示的字符。
php_serialize 处理器
最后就是session.serialize_handler
等于php_serialize
时候的序列化结果,代码如下
|
结果如下
|php_serialize(php>5.5.4)|经过serialize()函数序列化处理的数组|
其中a:1表示$_SESSION中有一个元素,同时在花括号里面的内容是传入的GET的值。
利用:
php
处理器和php_serialize
处理器这两个处理器生成的序列化格式本身是没有问题的,但是如果这两个处理器混合起来用,就会造成危害。形成的原理就是在用session.serialize_handler = php_serialize
存储的字符可以引入 |
, 再用session.serialize_handler = php
格式取出$_SESSION
的值时, |会被当成键值对的分隔符,在特定的地方会造成反序列化漏洞。
键名 + 竖线 + 经过serialize()函数序列化处理的值
|php_serialize(php>5.5.4)|经过serialize()函数序列化处理的数组|
也就是说,可以通过php_serialize来引入|,然后当php进行解析的时候,就会将其中的|前判断为键名。
遇到’|’时会将之看做键名与值的分割符,从而造成了歧义,导致其在解析session文件时直接对’|’后的值进行反序列化处理。
这里可能会有一个小疑问,为什么在解析session文件时直接对’|’后的值进行反序列化处理,这也是处理器的功能?这个其实是因为session_start()这个函数,可以看下官方说明:
直接看这个博客:https://www.cnblogs.com/hello-py/articles/13501786.html
简单的总结一下,这里主要是有两个部分的问题:
1、首先是对于session文件使用了两种不同的处理器来进行存储和读取,一个是php_serialize,这里存储的方式是使用a:1{}这种格式,而另外一个是php处理器,这里使用的是键名|值这种方式。当传入的值经过php_serialize处理器进行存储的时候,可以传入|
这个符号,当再次使用php处理器进行读取的时候,会直接将|
前面的部分读取为键名,然后将后面的部分读取为内容。
2、看上面的截图,当上述部分成立了之后,会自动的对值的部分进行反序列化。
对于是否有session反序列化漏洞,最好可以通过查看phpinfo()配置来进行了解。
Phar拓展反序列化攻击面:
phar文件简介
概念
一个php
应用程序往往是由多个文件构成的,如果能把他们集中为一个文件来分发和运行是很方便的,这样的列子有很多,比如在window
操作系统上面的安装程序、一个jquery
库等等,为了做到这点php
采用了phar
文档文件格式,这个概念源自java
的jar
,但是在设计时主要针对 PHP 的 Web 环境,与 JAR
归档不同的是Phar
归档可由 PHP
本身处理,因此不需要使用额外的工具来创建或使用,使用php
脚本就能创建或提取它。phar
是一个合成词,由PHP
和 Archive
构成,可以看出它是php
归档文件的意思(简单来说phar
就是php
压缩文档,不经过解压就能被 php
访问并执行)
phar组成结构
stub:它是phar的文件标识,格式为xxx<?php xxx; __HALT_COMPILER();?>; |
eg:
php
内置了一个Phar
类来处理相关操作
|
注意:要将php.ini中的phar.readonly选项设置为Off,否则无法生成phar文件。
php Version>=5.3.0
这里有两个关键点:
一是文件标识,必须以__HALT_COMPILER();?>
结尾,但前面的内容没有限制,也就是说我们可以轻易伪造一个图片文件或者其它文件来绕过一些上传限制;
二是反序列化,phar
存储的meta-data
信息以序列化方式存储,当文件操作函数通过phar://
伪协议解析phar
文件时就会将数据反序列化,而这样的文件操作函数有很多
漏洞成因:
phar
存储的meta-data
信息以序列化方式存储,当文件操作函数通过phar://
伪协议解析phar
文件时就会将数据反序列化
毕竟通过setmetadata()这个方法传入的是一个对象。
既然有序列化,自然会有反序列化。
有序列化数据必然会有反序列化操作,php
一大部分的文件系统函数在通过phar://
伪协议解析phar
文件时,都会将meta-data
进行反序列化
在网上扒了一张图
将phar伪造成其他格式的文件
在前面分析phar
的文件结构时可能会注意到,php
识别phar
文件是通过其文件头的stub
,更确切一点来说是__HALT_COMPILER();?>
这段代码,对前面的内容或者后缀名是没有要求的。那么我们就可以通过添加任意的文件头+修改后缀名的方式将phar
文件伪装成其他格式的文件
|
利用条件
phar文件要能够上传到服务器端。
如file_exists()
,fopen()
,file_get_contents()
,file()
等文件操作的函数
要有可用的魔术方法作为“跳板”。
文件操作函数的参数可控,且:
、/
、phar
等特殊字符没有被过滤。
漏洞验证
环境准备
upload_file.php`,后端检测文件上传,文件类型是否为gif,文件后缀名是否为gif |
文件内容
upload_file.php
|
upload_file.html
<body> |
file_un.php
|
直接将文件头和文件名改成gif格式的,然后在通过phar://这个伪协议进行访问,就能够造成攻击。