本文原创作者:cone
花费了点时间研究了下PHP序列化,写在这里。一是方便自己知识点记忆,二是帮助同样有疑问的人,欢迎指正错误,不喜勿喷.
0×01.确定配置文件
首先系统使用的ubuntu apt-get install方式安装的php5,所以在/etc/php5中两个文件夹中有php.ini 分别为cli与apache2 如下列
命令:
root@ubuntu:/etc/php5# ll -l apache2/ cli/ apache2/: total 156 drwxr-xr-x 3 root root 4096 Dec 16 19:04 ./ drwxr-xr-x 5 root root 4096 May 28 2015 ../ drwxr-xr-x 2 root root 4096 Jul 14 09:14 conf.d/ -rw-r--r-- 1 root root 69890 Dec 16 19:04 php.ini -rw-r--r-- 1 root root 69890 Sep 15 13:09 php.ini~ cli/: total 148 drwxr-xr-x 3 root root 4096 Dec 11 17:39 ./ drwxr-xr-x 5 root root 4096 May 28 2015 ../ drwxr-xr-x 2 root root 4096 Jul 14 09:14 conf.d/ -rw-r--r-- 1 root root 69568 Dec 11 17:39 php.ini -rw-r--r-- 1 root root 69570 Dec 11 17:39 php.ini~
如何确定apche2使用了哪个php.ini其实可以通过输出phpinfo 查看到,此处的cli目录中的php.ini配置文件是在命令行时使用的配置文件。
1. PHP序列化机制
PHP中的配置文件php.ini中含有几项配置项
session.save_path="" --设置session的存储路径 session_set_save_handler="" --设定用户自定义存储函数,如果想使用PHP内置会话存储机制之外的可以使用本函数(数据库等方式) session.auto_start boolen --指定会话模块是否在请求开始时启动一个会话,默认为0不启动 session.serialize_handler string --定义用来序列化/反序列化的处理器名字。默认使用php
-当session.auto_start设置为True的时候或者手动session_start()创建新会话,PHP内部调会调用会话管理器。
- 下面是一段代码其中session_set_save_handler()设置用户自定义会话存储函数。此处还是使用默认的存储方式以文件方式存储服务器,存储目录与配置文件中php.ini中的session.save_path相关。
- 下面这行代码因未设定序列化处理器的名字,所以默认使用PHP中内置的序列化处理器(php)。
class FileSessionHandler { private $savePath; function open($savePath, $sessionName) { $this->savePath = $savePath; if (!is_dir($this->savePath)) { mkdir($this->savePath, 0777); } echo __FUNCTION__."<BR>"; return true; } function close() { echo __FUNCTION__."<BR>"; return true; } function read($id) { echo __FUNCTION__."<BR>"; return (string)@file_get_contents("$this->savePath/sess_$id"); } function write($id, $data) { echo __FUNCTION__."<BR>"; return file_put_contents("$this->savePath/sess_$id", $data) === false ? false : true; } function destroy($id) { $file = "$this->savePath/sess_$id"; if (file_exists($file)) { unlink($file); } echo __FUNCTION__."<BR>"; return true; } function gc($maxlifetime) { foreach (glob("$this->savePath/sess_*") as $file) { if (filemtime($file) + $maxlifetime < time() && file_exists($file)) { unlink($file); } } echo __FUNCTION__."<BR>"; return true; } function end(){ echo 'Script end then call register_shutdown_functiond'."<br>"; } } $handler = new FileSessionHandler(); session_set_save_handler( array($handler, 'open'), array($handler, 'close'), array($handler, 'read'), array($handler, 'write'), array($handler, 'destroy'), array($handler, 'gc') ); // 下面这行代码可以防止使用对象作为会话保存管理器时可能引发的非预期行为 register_shutdown_function(array($handler,end)); session_start();
上面代码执行的结果为
open read Script end then call register_shutdown_functiond write close
然后查看session.seve_path中的值,相应的目录可以看到如下类似文件
sess_fifslo9a7j78bv8koc0k4g0lk2 sess_p4g23nl7mi7od8uohtpojd42b5
其中sess_后边的一串数值即是浏览器中的sessionid数值,打开相对应的session文件(sess_fifslo9a7j78bv8koc0k4g0lk2 )可查看其内容发现如下:
name|s:2:"tb";
可以发现其为序列化之后的内容,当浏览器带着sessionid再次访问脚本时,服务器端根据sessionid读取session文件并将其反序列化.
简单总结一下以上代码:
其实就是通过session_start()手动创建新的会话,会话使用PHP内置序列化方式(php.ini中默认session.serialize_handler为php),通过session_set_save_handler()设定的方式进行存储
0×03.序列化引发的漏洞
PHP中有三种序列化方式
| 处理器 | 对应的存储格式 | | ------------------ |:---------------------| | php_binary | 键名的长度对应的ASCII字符+键名+经过serialize() 函数反序列处理的值 | | php | 键名+竖线+经过serialize()函数反序列处理的值 | |php_serialize (php>=5.5.4) |经过serialize()函数反序列处理的数组|
看一个经过php处理器序列化过的值
name|s:6:"whoami";address|s:7:"beijing";
其中每个session值是以";"分割开来,例如
name|s:6:"whoami";
是其中一个session值,"|"前面表示session名称,"|"后边表示session具体信息,包含数据类型,长度,数据内容。
当session.auto_start = 0 如果php进行序列化的时候使用的是php_serialize处理器,反序列化的时候使用的是内置的处理器php,通过构造特殊的数据就会使PHP产生安全问题了,这也是Joomla这次产生漏洞的根本原因。
参考如下代码,感谢ryat老师。
foo1.php
ini_set('session.serialize_handler', 'php_serialize'); session_start(); $_SESSION['ryat'] = $_GET['ryat'];
foo2.php
ini_set('session.serialize_handler', 'php'); //or session.serialize_handler set to php in php.ini session_start(); class ryat { var $hi; function __wakeup() { echo 'hi'; } function __destruct() { echo $this->hi; } }
当访问foo1.php时提交数据如下:
foo1.php?ryat=|O:4:"ryat":1:{s:2:"hi";s:4:"ryat";}
如上通过引入"|"当访问foo2.php时可以看到成功实例化了ryat类,并自动调用了__wakeup()及_destruct()函数
访问foo2.php后输出
hiryat
引用:
https://github.com/80vul/phpcodz/blob/master/research/pch-013.md
http://securitycafe.ro/2015/01/05/understanding-php-object-injection/#content
http://php.net/manual/zh/function.serialize.php
http://php.net/manual/zh/session.configuration.php#ini.session.auto-start
http://php.net/manual/zh/function.session-start.php
http://php.net/manual/zh/function.session-set-save-handler.php
http://php.net/manual/zh/class.sessionhandlerinterface.php