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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?php
//A webshell is wait for you
ini_set('session.serialize_handler', 'php');
session_start();
class OowoO
{
public $mdzz;
function __construct()
{
$this->mdzz = 'phpinfo();';
}

function __destruct()
{
eval($this->mdzz);
}
}
if(isset($_GET['phpinfo']))
{
$m = new OowoO();
}
else
{
highlight_string(file_get_contents('index.php'));
}
?>

分析这段代码,如果 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
2
3
4
5
6
7
8
Array(
[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:"] => __PHP_Incomplete_Class Object
(
[__PHP_Incomplete_Class_Name] => OowoO
[mdzz] => system("cat /var/www/html/demo.php");
)

)

| 前的内容被成功当成了 key。

现在的问题就是这么控制 SESSION 的值。这里看到 phpinfo() 中 session.upload_progress.enabled 的值为Onsession.upload_progress.cleanup的值为Off手册对 session.upload_progress.enabled 的说明

利用的话就是,如果上述两个参数分别为On和Off,则在文件上传时,和session.upload_progress.name 值同名的参数。就是将文件上传进度,存储到 SESSION 中。这里也就是我们能控制 SESSION 内容的地方。

原理就是这两个点利用点。之后拿 flag 的过程参考jarvisoj-web 。此外通过这个题目还需要了解和明白的是,SESSION 的存取过程,是一次序列化和反序列化的过程。存的过程是序列化,取的过程是反序列化。