PHP 中 SESSION 序列化
在 PHP 中,SESSION 是经过序列化后,存储到文件当中。
当在 PHP 文件中执行了 session_start() 之后,如果有接收到从客户端传来的 SESSID,则会将对应 SESSION 文件中的内容,反序列化后存储到 $_SESSION 这个全局变量中。
序列化后的 SESSION 存储方式有三种。由 php.ini 文件中的 session.serialize-handler 参数控制。默认为 php 方式。三种方式的存储特点如下表。
存储方式 | 存储特点 |
---|---|
php | 键名+竖线+经过serialize()函数序列处理的值 |
php_serialize(php>5.5.4) | 经过serialize()函数序列化处理的值 |
php_binary | 键名的长度对应的ASCII字符+键名+经过serialize()函数序列化处理的值 |
存储方式 | 内容示例 | ||
---|---|---|---|
php | a\ | s:5:”hello”;b\ | s:4:”word”; |
php_serialize(php>5.5.4) | a:2:{s:1:”a”;s:5:”hello”;s:1:”b”;s:4:”word”;} | ||
php_binary | as:5:”hello”;bs:4:”word”; [这里键名对应的ASCII显示不出来] |
写这篇笔记的原因 主要是想整理下在做 Jarvis OJ 这个平台时,web题中遇到的一题,用到了这个知识点。是 PHPINFO 这题
下面对PHPINFO题目涉及到的两个利用点解释一下。
1 |
|
分析这段代码,如果 get 提交了 phpinfo 这个参数,就会实例化一个 OowoO 类的对象,在实例化对象时,先执行构造方法,将成员属性 $mdzz 赋值为 phpinfo(); 。然后在脚本执行结束时,对象被销毁前,执行析构方法,通过 eval 函数将 phpinfo(); 执行。最终页面上输出 phpinfo() 的内容。
通过查看给出的phpinfo页面,可以看到
配置项 session.serialize_handler ,默认的值为 php_serialize 而当前脚本中,设置成了 php
这个点,就是本题的突破口。
php_serialize 的存储方式之前说了,是将整个 $_SESSION 数组序列化进行存储。而 php 存储方式是以 key|serialize(value) 的方式将 $_SESSION 中的键值分开存储。所以如果在用php_serialize 方式进行存储时,多了一个 | ,然后用 php 方式读取,则会将 | 前的内容作为 key ,| 后的内容作为 value。举例如下:
php_serialize 方式存储的 session 内容为 a:1:{s:19:”upload_progress_123”;a:5:{s:10:”start_time”;i:1554482850;s:14:”content_length”;i:384;s:15:”bytes_processed”;i:384;s:4:”done”;b:1;s:5:”files”;a:1:{i:0;a:7:{s:10:”field_name”;s:4:”file”;s:4:”name”;s:73:”|O:5:”OowoO”:1:{s:4:”mdzz”;s:37:”system(“cat /var/www/html/demo.php”);”;}
现在在脚本中将 session 存储方式更改为 php 读取到的 $_SESSION 中的值为
1 | Array( |
| 前的内容被成功当成了 key。
现在的问题就是这么控制 SESSION 的值。这里看到 phpinfo() 中 session.upload_progress.enabled 的值为On 且session.upload_progress.cleanup的值为Off。手册对 session.upload_progress.enabled 的说明
利用的话就是,如果上述两个参数分别为On和Off,则在文件上传时,和session.upload_progress.name 值同名的参数。就是将文件上传进度,存储到 SESSION 中。这里也就是我们能控制 SESSION 内容的地方。
原理就是这两个点利用点。之后拿 flag 的过程参考jarvisoj-web 。此外通过这个题目还需要了解和明白的是,SESSION 的存取过程,是一次序列化和反序列化的过程。存的过程是序列化,取的过程是反序列化。