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.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同名变量时,上传进度可以在SESSION](http://php.net/manual/zh/reserved.variables.session.php)中获得。当PHP检测到这种POST请求时,它会在[

S​ESSION](http://php.net/manual/zh/reserved.variables.session.php)中获得。当PHP检测到这种POST请求时,它会在[_SESSION中添加一组数据, 索引是session.upload_progress.prefix 与 session.upload_progress.name连接在一起的值。

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

    

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

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

PHP反序列化入门之session反序列化_第1张图片

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

例题一

PHP反序列化入门之session反序列化_第2张图片

PHP反序列化入门之session反序列化_第3张图片

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

    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 即可。

    
" value="123" />

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

PHP反序列化入门之session反序列化_第4张图片

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

例题二

PHP反序列化入门之session反序列化_第5张图片

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,
        "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 信息,上面的请求结果如下:

PHP反序列化入门之session反序列化_第6张图片

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

    

这样题目中的 call_user_func(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,
        '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);
    ?>

 

PHP反序列化入门之session反序列化_第7张图片

PHP反序列化入门之session反序列化_第8张图片

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

PHP反序列化入门之session反序列化_第9张图片

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

你可能感兴趣的:(php学习笔记)