PHP代码
<?php
header("Content-Type:text/html; charset=utf-8");
require_once('pclzip.lib.php');
// echo "";
// var_dump($_POST['username']);exit;
// // var_dump(file_get_contents("php://input"));exit;
// // var_dump($GLOBALS['HTTP_RAW_POST_DATA']);exit;
// if (!isset($GLOBALS['HTTP_RAW_POST_DATA'])) {
// exit('环境不支持');
// }
// // 创建图片存储文件夹
// $dir = 'upload/';
// if (!file_exists($dir)) {
// mkdir($dir);
// }
function check_dir($dir){
$handle = opendir($dir);
while(($f = readdir($handle)) !== false){
if(!in_array($f, array('.', '..'))){
if(is_dir($dir.$f)){
check_dir($dir.$f.'/');
}else{
$ext = strtolower(substr(strrchr($f, '.'), 1));
if(!in_array($ext, array('jpg', 'gif', 'png'))){
unlink($dir.$f);
}
}
}
}
}
// // 创建图片存储的临时文件夹
// $temp = $dir.'member/1/';
// if (!file_exists($temp)) {
// mkdir($temp);
// }
// $filename = $temp.'avatar.zip'; // 存储flashpost图片
// file_put_contents($filename, $GLOBALS['HTTP_RAW_POST_DATA']);
// //第三次绕过
// // $zip=new ZipArchive;//新建一个ZipArchive的对象
// // if(!$zip->open($filename)){
// // //check_dir($dir);
// // exit("fail to open zip file");
// // }
// // if(!$zip->extractTo($temp)) {
// // exit("fail to extract zip file");
// // }
// $archive = new PclZip($filename);
// if ($archive->extract(PCLZIP_OPT_PATH, $temp, PCLZIP_OPT_REPLACE_NEWER) == 0) {
// check_dir($dir);
// exit("解压失败");
// }
// check_dir($dir);
// exit('success');
$file = $_FILES['file'];
if (!$file) {
exit("请勿上传空文件");
}
$name = $file['name'];
$dir = 'upload/';
$ext = strtolower(substr(strrchr($name, '.'), 1));
//递归删除 zip 1 web.php
// function check_dir($dir)
// {
// $handle = opendir($dir);
// while (($f = readdir($handle)) !== false) {
// if (!in_array($f, array('.', '..'))) {
// $ext = strtolower(substr(strrchr($f, '.'), 1));
// if (!in_array($ext, array('jpg', 'gif', 'png'))) {
// unlink($dir . $f);
// }
// }
// }
// }
// mkdir($dir);
if (!is_dir($dir)) {
mkdir($dir);
}
/**
* 1.未进行递归删除,导致文件夹内的webshell得以保留
* 修复方法(递归删除,又造成了新的问题)
* 2.由于先解压,再删除,导致暴力getshell
* (时间竞争拿下webshell)
* 3.将上传目录改为随机数,不能访问,自然不能getshell
* (可以创造一个加压出错的zip包,虽然解压出错,但其他webshell,以及被解压出来
* 同时,出错直接exit结束程序,不会执行后续递归删除操作
* )
* 4.解压即便失败,依然使用递归删除,然后退出,这样一来,可以将解压出的文件删除
* (制造一个特殊名称的webshell,../../../../web.php,当解压时,直接解压到根目录
* 随后,无论如何删除,也不能影响我们getshell
* )
* 可以将压缩解压缩操作放在非web目录下执行,将需要的文件移动到web目录下,这样可以从
* 根本解决以上问题
*
* 找到问题的关键,才可以解决,不然无异于盲人摸象。
*/
$temp_dir = $dir.md5(time(). rand(1000,9999)).'/';
// $temp_dir = $dir . 'member/1/';
if (!is_dir($temp_dir)) {
mkdir($temp_dir);
}
if (in_array($ext, array('zip', 'jpg', 'gif', 'png'))) {
if ($ext == 'zip') {
// $zip = new ZipArchive;
// if(!$zip->open($file['tmp_name'])) {
// echo "fail";
// return false;
// }
// if(!$zip->extractTo($temp_dir)) {
// check_dir($temp_dir);
// exit("fail to extract");
// }
$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($temp_dir);
exit('上传成功!');
} else {
move_uploaded_file($file['tmp_name'], $temp_dir . '/' . $file['name']);
check_dir($temp_dir);
exit('上传成功!');
}
} else {
exit('仅允许上传zip、jpg、gif、png文件!');
}
HTML代码
DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>文件上传章节练习题title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<style type="text/css">
.login-box{
margin-top: 100px;
height: 500px;
border: 1px solid #000;
}
body{
background: white;
}
.btn1{
width: 200px;
}
.d1{
display: block;
height: 400px;
}
style>
head>
<body>
<form method="post" action="upload.php" enctype="multipart/form-data">
<input type="file" name="file" value=""/>
<input type="submit" name="submit" value="upload"/>
form>
body>
html>
header("Content-Type:text/html; charset=utf-8");
require_once('pclzip.lib.php');
$file = $_FILES['file'];
if (!$file) {
exit("请勿上传空文件");
}
$name = $file['name'];
$dir = 'upload/';
$ext = strtolower(substr(strrchr($name, '.'), 1)); //拿出最后的后缀为.的后面内容
function check_dir($dir)
{
$handle = opendir($dir);
while (($f = readdir($handle)) !== false) {
if (!in_array($f, array('.', '..'))) {
$ext = strtolower(substr(strrchr($f, '.'), 1));
if (!in_array($ext, array('jpg', 'gif', 'png'))) {
unlink($dir . $f); //直接删除
}
}
}
}
$temp_dir = $dir . 'member/1/'; //临时文件,这里1其实应该代表有用户ID
if (!is_dir($dir)) { //判断文件有无文件
mkdir($dir);
}
if (in_array($ext, array('zip', 'jpg', 'gif', 'png'))) { //判断文件后缀
if ($ext == 'zip') {
$archive = new PclZip($file['tmp_name']); //打开压缩包,后面进行解压
if ($archive->extract(PCLZIP_OPT_PATH, $temp_dir, PCLZIP_OPT_REPLACE_NEWER) == 0) {
check_dir($dir);
exit("解压失败");
}
check_dir($temp_dir); //运用check_dir函数,判断文件把不符合规则的文件类型删除
exit('上传成功!');
} else {
move_uploaded_file($file['tmp_name'], $temp_dir . '/' . $file['name']);
// check_dir($temp_dir);
exit('上传成功!');
}
} else {
exit('仅允许上传zip、jpg、gif、png文件!');
}
上述过程就是上传压缩包,然后打开压缩包看里面文件类型进行删除与保留。
此过程想要上传恶意代码很简单,因为它打开压缩包打开的内容包括各种文件。但是它只扫描它打开的这层文件中的内容。即不会扫描文件夹里的内容。

上传压缩包,并且尝试在内容里面加入PHP文件

上传文件结果

查看结果:创建了upload文件,并且发现在文件夹1中只有图片,成功过滤了PHP文件


由代码查看发现他编码进行递归查询,如此就不会第二次打开文件夹的。为此我们才有包含文件夹进行绕过删除文件。
创建文件夹web1

将web1文件压缩同图片文件一起压缩

上传压缩包

弹出的第二行错显示了
Permission denied,表示它没有权限删除我们创建的文件夹web1。
查看上传内容:文件夹没有被删除,但是php文件被删除了。

继续查看web1文件夹的内容

我们发现web1文件没有删除为此就绕过删除了。
这不用时间竞争型漏洞,因为要试很多次。【也可以用,只是麻烦了】
有更好的把上传的压缩包包含文件夹
unlink:直接删除 【只能删除文件不能删除文件夹,因为没有递归删除】
既然我们知道了它没有进行递归查询,没有检测文件夹内容。我们就修改函数使用递归
function check_dir($dir){
$handle = opendir($dir);
while(($f = readdir($handle)) !== false){
if(!in_array($f, array('.', '..'))){
if(is_dir($dir.$f)){ //判断是否还有文件夹
check_dir($dir.$f.'/'); //再次调用函数
}else{
$ext = strtolower(substr(strrchr($f, '.'), 1));
if(!in_array($ext, array('jpg', 'gif', 'png'))){
unlink($dir.$f);
}
}
}
}
}
函数内再次检测了文件,对文件夹进行检测。
function check_dir($dir){
$handle = opendir($dir);
while(($f = readdir($handle)) !== false){
if(!in_array($f, array('.', '..'))){
if(is_dir($dir.$f)){ //判断是否还有文件夹
check_dir($dir.$f.'/'); //再次调用函数
}else{
$ext = strtolower(substr(strrchr($f, '.'), 1));
if(!in_array($ext, array('jpg', 'gif', 'png'))){
unlink($dir.$f);
}
}
}
}
}
上述代码进行解压压缩包,对文件再次检索查看是否有文件夹。当有文件夹再次运行函数打开文件夹,再在文件夹中再次检索。直到没有文件夹检索为至。
这个方法虽然解决了第一次上传文件夹不背扫描问题,但是并没有解决第一个上传仍然存在的时间竞争漏洞。为此我们可以采用时间竞争漏洞绕过
选择新建文件夹.zip

成功上传我们查看文件夹web1 是否内部php被过滤

我们发现它成功过滤了文件夹内的PHP文件

在删除之前访问上传的php文件,从而执行上传文件中的php代码。
竞争型文件上传过程介绍
文件上传过程:
服务器获取文件>>>保存上传临时文件>>>重命名移动临时文件
编写PHP代码,将要删除前重命名实现移动文件【跳三层到xss_location目录】
fputs(fopen('../../../payload.php','w'),'');
?>
上传web1.zip 【其中1.php代码修改为了上述代码】

开启抓包

将手工代理开启【代理端口与Burp Suite设置的一样】


将本地地址改为本机IP一致

上传文件抓包

上图成功被拦截,并且拦截的是压缩包 【拦截中的PK代表压缩包】

将其抓取内容放到爆破中

先clear掉符号

随后找个数字进行添加字符

让其运行2000次

如果想手动访问也可以就要访问
169.254.2.70/xss_location/mermber/1/1/web1.php,手动刷新页面。【要关拦截】
开始访问

结果查看

因为时间竞争型漏洞是猜到了存储文件的,为此我们将其创建文件名方式进行修改。使用对文件随机数进行命名。
$temp_dir = $dir.md5(time(). rand(1000,9999)).'/';
// $temp_dir = $dir . 'member/1/';
$temp_dir = $dir.md5(time(). rand(1000,9999)).'/';
// $temp_dir = $dir . 'member/1/';
不同之前是将上传文件创建的 文件夹随机命令。导致无法猜测到文件名进行恶意代码上传。

从结果上来看我们确实是无法在解压后运用条件绕过了删除,那我们就可以考虑在解压过程中下手。即实现当解压一半出错,它直接跳转解压失败不进行条件判断删除PHP文件了。
这个问题其实需要看具体情况,看解压的那个程序的容忍程度,我这里就以两个解压的程序作为例子:


修改内容并且保存

进行解压

如此我们发现出错了。但是PHP依旧解压了
此时用7zip解压就会出错,解压出的PHP是完好的,另一个文件是图片出现错所以无法显示了,【如果是txt,会显示文件但是因为报错内容无法显示就只有txt文件】

5.接下来我们说一下PHP的解压。
采用一个PHP的解压代码测试【为了方便我把压缩包移动到同级目录下】

function unzip($zipname, $path) {
$zip = new ZipArchive;
if(!$zip->open($zipname)) {
echo "fail";
return false;
}
if(!$zip->extractTo($path)) {
echo "fail to extract";
return false;
}
$zip->close();
return true;
}
if(unzip('./web1.zip', './')) {
echo "success zip";
}else {
echo "failt to unzip";
}
采用在Windows中使用PHP来解压

他成功解压了并且中目录里

如果压缩入txt文件【文件内容为zzzzzz,压缩包为hhhh.zip,修改php内容文件为hhhh.zip】 同样先修改deCrc
依旧成功
并且不同Windows会是txt文件没有内容
综上我们发现在PHP中是好像无采用压缩包报错绕过,但是如果文件内容无法修改就文件
这也说明ZipArchive的容忍度比较高。 那么我们又如何让ZipArchive出错呢?最简单的方法,我们可以在文件名上下功夫。
比如,Windows下不允许文件名中包含冒号(:),我们就可以在010editor中将2.txt的deFileName属性的值改成“2.tx:”,

再次解压

发现对文件解压时出错了。【目录下也解压出来php,没有txt】
Linux下也有类似的方法,我们可以将文件名改成5个斜杠(/)
总结;
1.Windows系统下7zip的容忍度很低,只要压缩包中某一个文件的CRC校验码出错,就会报错退出
2.PHP代码中 .。
使用zip解压时deCrc,我们随便把值改成其他的值,然后保存就可以出错。
使用7z情况下修改文件名Windows下不允许文件名中包含冒号(:),我们就可以在010editor中将2.txt的deFileName属性的值改成“2.tx:”
3.linux系统将文件名改成5个斜杠(/)【前提这个压缩至少有五个字符可以修改】
采用上述的压缩包上传。
因为使用PHP类要修改一下
// //第三次绕过
$zip=new ZipArchive;//新建一个ZipArchive的对象
if(!$zip->open($filename)){
//check_dir($dir);
exit("fail to open zip file");
}
if(!$zip->extractTo($temp)) {
exit("fail to extract zip file");
}
// $archive = new PclZip($filename);
// if ($archive->extract(PCLZIP_OPT_PATH, $temp, PCLZIP_OPT_REPLACE_NEWER) == 0) {
// check_dir($dir);
// exit("解压失败");
// }
结果表示未能提取【上传了hhhh.zip,包含php文件,与txt文件】
查看上传文件是否成功有php

发现成功上传php,但是因为txt出错没有被上传。并且因为出错绕过了判断而没有删除
既然我们知道他是在是解压过程在避开递归删除
如此对应防御:如果在解压到php后就先递归删除再解压下一个文件,就算退出但是第一个已经递归删除过了就解决问题了
if ($archive->extract(PCLZIP_OPT_PATH, $temp_dir, PCLZIP_OPT_REPLACE_NEWER) == 0) {
check_dir($dir); //解压成功一个后进行递归删除
exit("解压失败");
}
if ($archive->extract(PCLZIP_OPT_PATH, $temp_dir, PCLZIP_OPT_REPLACE_NEWER) == 0) {
check_dir($dir); //解压成功一个后进行递归删除
exit("解压失败");
}
代码的之前代码基础上当解码失败后,不会直接保存而是再进行删除递归后才真正的结束、
相较于第三次的防御解决方法 可以尝试命名php为的value为…/…/aaa.php
即解压完后修改名称,如此是解压到上级目录 使用工具修改aaaaaaaaa.php改为…/…/aaa.php
同之前一样修改【改为txt:,与…/…/aaa.php】

php的修改只要是有aaaaaaaaaaaaaa.php原来名字的文件都修改

查看上上级目录是否有对应php文件

1、第一次绕过代码没有文件再次检测,采用传包含文件夹的压缩包绕过或使用时间竞争漏洞绕过
2、第二个绕过代码添加了递归对文件夹再次检测,但由于没有间竞争漏洞绕过问题。使用间竞争漏洞绕过
3、第三次绕过代码进行随机命名文件无法猜文件名间竞争漏洞绕过失效,但是可以采用解压一半报错保存php文件
4、第四次绕过代码对报错保留的文件进行了递归删除,但是我们可以欺骗系统将解码的文件判定为上级目录文件规避了递归在当前目录下是所有检测。
其实这种漏洞补一处出现处,要从根本解决。
根本原因:它是在网站的根目录下解压文件,用户可以访问。
解决:为此在临时文件夹中解压,再把需要的文件提取到网站目录下。