XCTF2018-Final_bestphp

this challenge is from XCTF2018-Final
这是一道代码审计题,代码并不多,但是耗了很多时间才做出来。enter description here

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php 
highlight_file(__FILE__);
error_reporting(0);
ini_set('open_basedir', '/var/www/html:/tmp');
$file='function.php';
$func=isset($_GET['function'])?$_GET['function']:'filters';
call_user_func($func,$_GET);
include($file);
session_start();
$_SESSION['name']=$_POST['name'];
if($_SESSION['name']=='admin'){
header('location:admin.php');
}
?>

根据代码,尝试了访问admin.php,没有发现东西。这里的include是重点,有一个思路是传入function=extract,然后进行变量覆盖,改变$file的值。such asenter description here自然地,我们可以通过伪协议来读文件,当然,由于open_basedir的限制,我们能读的文件是非常有限的。enter description hereadmin.php:

1
2
3
4
5
6
7
8
<?php
if(empty($_SESSION['name'])){
session_start();
#echo 'hello ' + $_SESSION['name'];
}else{
die('you must login with admin');
}
?>

function.php:

1
2
3
4
5
6
7
8
9
<?php
function filters($data){
foreach($data as $key=>$value){
if(preg_match('/eval|assert|exec|passthru|glob|system|popen/i',$value)){
die('Do not hack me!');
}
}
}
?>

在后续的发掘中,我们还发现了一个test.php文件enter description here源码:

1
2
<?php
print_r(get_defined_functions());

刚开始当然是很迷的,首先,function.php可以说是毫无作用的,因为它里面的filter函数在include之前就被call_user_func调用了,那时候还没定义它。其次,admin.php也显得十分鸡肋,因为它没有任何有用的操作,甚至写的代码可以用诡异来形容,还没使用session_start就使用$_SESSION,而且唯一的一句执行的echo还被注释掉了。另外,这里用到的函数是call_user_func,而不是call_user_func_array,数组会被当作一个整体传到函数里,这就让很多能想到的函数失去了作用。

strip_tags+爆破

很快,我想到了之前看过的一篇王一航师傅写的文章,这个点我前几天的文章里也有提到,就是用string.strip_tags导致php崩溃,然后包含/tmp目录下的tmp_file。我当时测了一下,发现是会导致崩溃的,说明php版本是符合这个做法的。但是当时队里大佬们看了之后没有太在意,因为爆破量实在太大,临时文件的文件名是php??????,6位的随机大小写字母加数字组合,更重要的一点是,当时题目把环境改好之后,有两个队伍几乎秒出,所以包括我在内都觉得可能不是这个方法,但是后来证明,这个方法也是可以的,先爆破生成大量tmp_file就可以,不需要太久的耗时就能出来。像这样构造数据包,然后多线程爆破enter description here这时候/tmp目录下一堆tmp_fileenter description here另一方面,爆破文件包含enter description here坚持住,会爆出来的。

更改session的存储路径

这个方法是Gaia大佬发现的~
我们平时在使用session_start的时候,恐怕绝大多数情况都是直接session_start()吧,根本没有人在意,这个函数是可以传参数的,而且,传进去的就是一个数组!enter description here传进去的数组的作用就是,修改session的配置,而且会动态覆盖掉php.ini里的session配置。属性非常多enter description here第一个就非常醒目,session.save_path!没错,我们要做的就是,利用题目里的session操作,修改session的存储路径,把session记录存到/tmp目录下,然后直接包含/tmp/sess_{PHPSESSID}文件就可以了。其实这个操作也可以由session_save_path()函数来完成,但是这个函数传入的参数是个字符串,不适用于此题。过程:enter description hereenter description here然后执行系统命令找flag即可(系统命令函数不受open_basedir影响)