**前言:**前面已经了解了文件上传的基础,接下来详细的进行学习,首先学习后端黑白名单绕过

涉及知识
文件上传常见验证
文件上传的常见验证方式有:后缀名(直接)、类型(间接)、文件头(间接)等
后缀名验证
黑名单,白名单
后缀名黑名单
明确不允许上传的格式后缀,其他文件名后缀允许
如:asp php jsp aspx cgi war 等
绕过:如果php phtml..没有定义到后名单里,可以用这后缀名绕过限制,依旧可以达到上传效果
后缀名白名单
明确可以上传的格式后缀,其他文件名后缀不允许
如:jpg png zip rar gif 等
使用白名单验证要更安全
文件类型验证:MIME 信息
MIME
MIME(Multipurpose Internet Mail Extensions)多用途互联网邮件扩展类型。是设定某种扩展名的文件用一种应用程序来打开的方式类型,当该扩展名文件被访问的时候,浏览器会自动使用指定应用程序来打开。多用于指定一些客户端自定义的文件名,以及一些媒体文件打开方式
判断机制
Content-Type称之为MIME信息,一些网站是通过这个值来判断文件是何种格式。
当上传一个文件到网站时,该文件会自带一个信息,此信息和使用何种脚本没有关系。
利用
上传脚本文件时,如果网站使用MIME信息栏判断文件类型,就可以修改Content-Type信息来伪造,让对方误认为我们上传的是一个合法文件。
因此单独使用MIME信息判断文件类型的方法是不严谨的,有空子可钻。
抓包介绍MIME信息
在文件上传时抓包,通过数据包介绍MIME信息:

查看数据包详细
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| POST /PenTest/uploadlabs/Pass-01/index.php HTTP/1.1 Host: localhost User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:97.0) Gecko/20100101 Firefox/97.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8 Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 Accept-Encoding: gzip, deflate Content-Type: multipart/form-data; boundary=---------------------------352318732238310643913205352540 Content-Length: 202979 Origin: http://localhost Connection: close Referer: http://localhost/PenTest/uploadlabs/Pass-01/index.php Upgrade-Insecure-Requests: 1
-----------------------------352318732238310643913205352540
Content-Disposition: form-data; name="upload_file"; filename="WEBæ¼æ´-æä»¶ä¸ä¼ 导å¾.png" Content-Type: image/png
PNG
|
上述数据包中的
1 2
| Content-Disposition: form-data; name="upload_file"; filename="WEBæ¼æ´-æä»¶ä¸ä¼ 导å¾.png" Content-Type: image/png //文件类型
|
就是文件的MIME信息
文件头验证:内容头信息
文件头
文件头是直接位于文件中的一段数据,是文件的一部分
判定机制
通过文件头部信息,判断文件的格式
利用
上传脚本文件时,如果网站使用文件头判断文件类型,就可以修改数据包中文件头的值,伪造文件头,让对方服务器判定我们上传的文件是合法文件。
因此单独使用文件头验证是不严谨的。
不同文件的文件头查看举例
PNG文件头

GIF文件头

PHP文件上传相关代码
创建一个文件上传HTML表单
允许用户从表单上传文件是非常有用的。
请看下面这个供上传文件的 HTML 表单:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <html> <head> <meta charset="utf-8"> <title>菜鸟教程(runoob.com)</title> </head> <body>
<form action="upload_file.php" method="post" enctype="multipart/form-data"> <label for="file">文件名:</label> <input type="file" name="file" id="file"><br> <input type="submit" name="submit" value="提交"> </form>
</body> </html>
|
将以上代码保存到 form.html 文件中。

有关上面的 HTML 表单的一些注意项列举如下:
<form> 标签的 enctype 属性规定了在提交表单时要使用哪种内容类型。在表单需要二进制数据时,比如文件内容,请使用 “multipart/form-data“。
<input> 标签的 type=”file” 属性规定了应该把输入作为文件来处理。举例来说,当在浏览器中预览时,会看到输入框旁边有一个浏览按钮。
**注释:**允许用户上传文件是一个巨大的安全风险。请仅仅允许可信的用户执行文件上传操作。
创建PHP脚本接收上传表单数据
upload_file.php文件含有供上传文件的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13
| <?php if ($_FILES["file"]["error"] > 0) { echo "错误:" . $_FILES["file"]["error"] . "<br>"; } else { echo "上传文件名: " . $_FILES["file"]["name"] . "<br>"; echo "文件类型: " . $_FILES["file"]["type"] . "<br>"; echo "文件大小: " . ($_FILES["file"]["size"] / 1024) . " kB<br>"; echo "文件临时存储的位置: " . $_FILES["file"]["tmp_name"]; } ?>
|
通过使用 PHP 的全局数组 $_FILES,你可以从客户计算机向远程服务器上传文件。
当客户端提交后,我们获得了一个$_FILES 数组,$_FILES数组内容如下:
第一个下标是表单的 input标签的name属性的值,比如上述HTML代码中<input type="file" name="file" id="file">,其name属性值就是“file”;
第二个下标可以是 “name”、”type”、”size”、”tmp_name” 或 “error”。如下所示:
$_FILES["file"]["name"] - 上传文件的名称
$_FILES["file"]["type"] - 上传文件的类型
$_FILES["file"]["size"] - 上传文件的大小,以字节计
$_FILES["file"]["tmp_name"] - 存储在服务器的文件的临时副本的名称
$_FILES["file"]["error"] - 由文件上传导致的错误代码
这是一种非常简单文件上传方式。基于安全方面的考虑,应当增加有关允许哪些用户上传文件的限制。
PHP脚本增加上传文件限制
在这个脚本中,我们增加了对文件上传的限制。用户只能上传 .gif、.jpeg、.jpg、.png 文件,文件大小必须小于 200 kB:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| <?php
$allowedExts = array("gif", "jpeg", "jpg", "png"); $temp = explode(".", $_FILES["file"]["name"]); $extension = end($temp); if ((($_FILES["file"]["type"] == "image/gif") || ($_FILES["file"]["type"] == "image/jpeg") || ($_FILES["file"]["type"] == "image/jpg") || ($_FILES["file"]["type"] == "image/pjpeg") || ($_FILES["file"]["type"] == "image/x-png") || ($_FILES["file"]["type"] == "image/png")) && ($_FILES["file"]["size"] < 204800) && in_array($extension, $allowedExts)) { if ($_FILES["file"]["error"] > 0) { echo "错误:: " . $_FILES["file"]["error"] . "<br>"; } else { echo "上传文件名: " . $_FILES["file"]["name"] . "<br>"; echo "文件类型: " . $_FILES["file"]["type"] . "<br>"; echo "文件大小: " . ($_FILES["file"]["size"] / 1024) . " kB<br>"; echo "文件临时存储的位置: " . $_FILES["file"]["tmp_name"]; } } else { echo "非法的文件格式"; } ?>
|
PHP脚本保存被上传的文件
上面的实例在服务器的 PHP 临时文件夹中创建了一个被上传文件的临时副本。
这个临时的副本文件会在脚本结束时消失。要保存被上传的文件,我们需要把它拷贝到另外的位置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| <?php
$allowedExts = array("gif", "jpeg", "jpg", "png"); $temp = explode(".", $_FILES["file"]["name"]); echo $_FILES["file"]["size"]; $extension = end($temp); if ((($_FILES["file"]["type"] == "image/gif") || ($_FILES["file"]["type"] == "image/jpeg") || ($_FILES["file"]["type"] == "image/jpg") || ($_FILES["file"]["type"] == "image/pjpeg") || ($_FILES["file"]["type"] == "image/x-png") || ($_FILES["file"]["type"] == "image/png")) && ($_FILES["file"]["size"] < 204800) && in_array($extension, $allowedExts)) { if ($_FILES["file"]["error"] > 0) { echo "错误:: " . $_FILES["file"]["error"] . "<br>"; } else { echo "上传文件名: " . $_FILES["file"]["name"] . "<br>"; echo "文件类型: " . $_FILES["file"]["type"] . "<br>"; echo "文件大小: " . ($_FILES["file"]["size"] / 1024) . " kB<br>"; echo "文件临时存储的位置: " . $_FILES["file"]["tmp_name"] . "<br>"; if (file_exists("upload/" . $_FILES["file"]["name"])) { echo $_FILES["file"]["name"] . " 文件已经存在。 "; } else { move_uploaded_file($_FILES["file"]["tmp_name"], "upload/" . $_FILES["file"]["name"]); echo "文件存储在: " . "upload/" . $_FILES["file"]["name"]; } } } else { echo "非法的文件格式"; } ?>
|
上面的脚本检测了文件是否已存在,如果不存在,则把文件拷贝到名为 “upload” 的目录下。
文件上传演示操作如下所示:

演示案例——upload-labs 关卡分析
使用upload-labs中的查看源码功能来分析各种文件上传时文件类型的判定机制
服务器端代码使用PHP搭建,上传后门尽量使用PHP后门,其他类型脚本未必能够解析。
注意:PHP是弱类型语言
黑名单绕过
Pass-2(MIME类型)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| $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.'文件夹不存在,请手工创建!'; } }
|
可见此关卡判断文件类型的依据是MIME信息,可以通过修改Content-Type值来绕过,实现脚本上传
直接上传.php文件会报错

抓包后修改Content-Type值为合法类型image/gif

修改后再发送可见上传成功

Pass-3(特殊解析后缀——.php5等后缀解析成php)
apache服务器能够使用php解析.phtml .php3后缀
黑名单限制php上传,但apache对php3.php5.phtml等也可以直接解析为php,故可将php后缀改为php3等再上传,如果Apache不能解析,在Apache的配置文件httpd.conf文件中添语句php3即可
开关:更改Apache的 httpd.conf 配置文件,添加.php3 和 phtml,取消前面注释,保存并退出即可实现php解析.phtml .php3,大约在第403行,即下图所示行中添加需要解析的类型格式

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| $is_upload = false; $msg = null; if (isset($_POST['submit'])) { if (file_exists(UPLOAD_PATH)) { $deny_ext = array('.asp','.aspx','.php','.jsp'); $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); $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 = '不允许上传.asp,.aspx,.php,.jsp后缀文件!'; } } else { $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!'; } }
|
deldot()函数,是作者自定义的函数,作用是删除文件名末尾的点,它在靶场文件夹下面的common.php文件中,代码如下所示
函数功能:若文件名为a.jpg.......返回结果为a.jpg,即删除文件名末尾的所有.(如果有的话)
详细见http://localhost/deldot.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <?php function deldot($s){ for($i = strlen($s)-1;$i>0;$i--){ $c = substr($s,$i,1); if($i == strlen($s)-1 and $c != '.'){ return $s; }
if($c != '.'){ return substr($s,0,$i+1); } } } ?>
|
本关卡使用黑名单限制php上传,但apache对php3.php5.phtml等也可以直接解析为php,故可将php后缀改为php3等,如果Apache不能解析,在Apache的配置文件httpd.conf文件中添语句php3即可。
这个开关一般是开启的,如果关闭,则不能采用这种方法绕过。
Pass-4(.htaccess解析)
仅存在于Apache中
.htaccess文件(或者”分布式配置文件”),全称是Hypertext Access(超文本入口)。提供了针对目录改变配置的方法,即在一个特定的文档目录中放置一个包含一个或多个指令的文件, 以作用于此目录及其所有子目录。作为用户,所能使用的命令受到限制
概述来说,htaccess文件是Apache服务器中的一个配置文件,它负责相关目录下的网页配置。通过htaccess文件,可以帮我们实现:网页301重定向、自定义404错误页面、改变文件扩展名、允许/阻止特定的用户或者目录的访问、禁止目录列表、配置默认文档等功能。
下面通过htaccess来实现上传文件后缀自定义
创建.htaccess文件
1 2 3
| <FilesMatch "cimer"> SetHandler application/x-httpd-php </FilesMatch>
|
上述代码含义是:如果文件名中匹配到出现cimer字符串,就将其当做application/x-httpd-php(即Content-Type值)文件进行解析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| $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); $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文件,因此先上传一个.htaccess文件,上传成功:

则接下来文件名中含有cimer的文件都会被当做php文件来解析,上传cimer.jpg文件

访问上传文件的目录http://localhost/PenTest/uploadlabs/upload/cimer.jpg
可见cimer.jpg文件被当做PHP文件来解析了
其他文件名中不含cimer的图片文件不会被当做PHP文件来解析,因为不满足.htaccess中的要求。

添加在图片文件末尾的PHP代码也成功执行了

Pass-6(大小写绕过)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| $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); $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的文件,可以成功上传

Pass-7(空格绕过)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| $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); 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 +的文件(+表示前面是一个空格,实际不写)。
经过处理后的文件名后缀仍然可能存在空格,而黑名单中的文件名都是没有空格的,因此可以借此绕过黑名单。
注意:Windows中如果文件名最后存在空格,系统会默认删除,因此我们上传时先使用burp抓包,在文件名后添加空格,这样才能成功绕过。上传到Windows服务器保存后,又会自动删除文件名最后的空格,Linux服务器也会删除文件名最后的空格。
上传一个.php的文件,burp抓包,文件名添加空格

上传成功

Pass-8(点绕过)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| $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); $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 . '文件夹不存在,请手工创建!'; } }
|
本关卡对文件名后缀处理时,缺少删除文件末尾的点的操作,因此在上传一个文件名末尾是·的文件就能绕过黑名单。
注意:Windows中如果文件名最后存在.,系统不能创建,因此我们上传时先使用burp抓包,在文件名后添加.,这样才能成功绕过。上传到Windows服务器保存后,又会自动删除文件名最后的.,Linux服务器会保留.。
上传一个.php的文件,burp抓包,文件名添加.

上传成功

Pass-9(::$DATA绕过)
必须是Windows下的PHP有如下特性:
在window的时候如果文件名+”::$DATA”会把::$DATA之后的数据当成文件流处理,不会检测后缀名,且保持::$DATA之前的文件名,他的目的就是不检查后缀名
例如:上传时的文件名为”phpinfo.php::$DATA”
Windows会自动去掉末尾的::$DATA变成”phpinfo.php”
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| $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的操作,因此在上传一个文件名末尾是::$DATA的文件就能绕过黑名单。
注意:Windows中文件名最后不允许特殊符号存在,系统不能创建,因此我们上传时先使用burp抓包,在文件名后添加::$DATA,这样才能成功绕过。上传到Windows服务器保存后,又会自动删除文件名最后的::$DATA。
上传一个.php的文件,burp抓包,文件名添加::$DATA

上传成功

Pass-10(点空格绕过)
一次过滤和循环过滤
代码将字符串里的php替换为空
一次过滤
a.php –> a.
a.pphphp –> a.php 将中间的php过滤掉,还有个php
循环过滤 递归过滤
a.pphphp –> a. 只要有php出现就会一直过滤掉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| $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); $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的文件,burp抓包,文件名添加. .

上传成功

Pass-11(双后缀名绕过)
str_replace() 和str_ireplace()函数
把字符串 “Hello world!” 中的字符 “world” 替换为 “Shanghai”:
1 2 3
| <?php echo str_replace("world","Shanghai","Hello world!"); ?>
|
运行结果Hello Shanghai!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| $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 . '文件夹不存在,请手工创建!'; } }
|
本关卡上传脚本文件时,会将后缀名替换为空,之后再保存在服务器
尽管能够成功上传,但是到服务器之后没有后缀名,脚本无法被解析

但是代码中只使用了一次替换为空而不是循环替换,即只将首次出现且在黑名单中的文件名后缀形式替换为空,因此可以使用双写后缀名进行绕过
a.pphphp—>a.php中间的php会被替换成空
上传一个.pphphp文件,上传成功

白名单绕过
Pass-12(%00截断——GET)
%00截断原理
URL的%00截断本质上是ASCII码的中断值0截断
截断的核心,就是chr(0)这个字符,这个字符不为空(Null),也不是空字符(“”),更不是空格。
当程序在输出含有chr(0)变量时,chr(0)后面的数据会被停止,换句话说,就是误把它当成结束符,后面的数据直接忽略,这就导致漏洞产生
下面是用 URL 编码形式表示的 ASCII 字符
即在url中%00表示ascll码中的0 ,而ascii中0作为特殊字符保留,表示字符串结束,所以当url中出现%00时就会认为读取已结束
比如:
0x开头表示16进制,0在十六进制中是00, 0x00就是%00解码成的16进制

这里在php的后面添加了一个空格和字母a,其实a写不写都可以,这里加a是为了显示空格的位置。
空格是为了占位,方便修改00。
然后打开hex,(空格的16进制为0x20)修改16进制内容,把20改成00:

修改后

%00和0x00的区别
%00是URL中的,即%00是URL编码
0x00是hex格式下的文件命名
%00截断的条件
%00需要PHP版本小于5.3.4
打开php的配置文件php-ini,将magic_quotes_gpc设置为Off

绕过操作
phpstudy调整PHP版本为5.2.17,关闭magic_quotes_gpc开关。
分析代码,发现是白名单限制,将文件名最后一个点后面的格式与白名单进行对比。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| $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类型文件!"; } }
|
代码的关键一句在于 $img_path = $_GET['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext;,这句代码确定了上传文件1.jpg在服务器上被更改名称为$_GET['save_path']/随机值.jpg
通过抓包可以看到数据包提交的GET参数save_path的值为../upload/,也就是说最终保存到服务器的文件名称就由1.jpg变成了../upload//随机值.jpg

因此设法使得$img_path的值出现00截断,就能成功上传所需的脚本文件。
直接在raw格式数据包中修改文件名添加%00截断
我们设想,在数据包中将图片文件命名为1.php%00.jpg,则.jpg后缀符合白名单规定,预测其可以上传,当 $img_path = $_GET['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext;拼接新的文件名后,move_uploaded_file($temp_file,$img_path)执行移动文件操作,应该能在upload目录下得到预期的1.php的脚本文件。也即
.jpg后缀符合白名单可以上传
- 拼接最终文件名时
%00截断生效
- 上传目录中得到
1.php的脚本文件
按照上面的思路进行测试,上传一个图片马(PHP脚本写在文件最末),发现上传到服务器的文件仍然是名为随机值的图片文件。
抓包修改文件名添加%00截断

上传结果仍然是名为随机值的图片文件,而不是预期的PHP文件。

出错原因
==猜测原因==:raw格式数据包并非URL编码(GET提交参数那里里是URL编码,添加%00后可以被自动解码,解码之后得到00截断,参考下面在save_path的值中添加%00截断的方法),在raw格式数据包中添加%00(URL编码),这个%00并不会被解码为中断值0。
那么上传的文件名就经历如下过程:
- 上传图片马,文件名为
1.jpg
- raw格式下文件名添加%00截断
1.php%00.jpg
- 无URL解码$_FILES[‘upload_file’][‘name’]得到的文件名并不会截断即为
1.php%00.jpg
- strrpos($_FILES[‘upload_file’][‘name’],”.”)得到的文件名后缀为
jpg
- 拼接得到最终文件名为
$img_path=../upload//随机值.jpg
因此不能直接在raw格式的数据包下的上传文件名中添加%00,而要在hex格式的数据包下的上传文件名中添加00,即在十六进制的数据包中添加00截断
具体做法是,先在raw格式的数据包的上传文件名中添加空格,打开hex格式查看,在上传文件名的那一行寻找20,因为20是空格的十六进制码,把这个20改为00,则上传以后会出现截断。
在hex格式数据包中修改文件名添加00截断
在十六进制的数据包中添加00截断,则预期.jpg能够符合白名单上传,并且在拼接最终文件名时00截断生效
按照上面的思路进行测试,上传一个图片马(PHP脚本写在文件最末),发现无法成功上传文件
抓包在hex格式下修改文件名添加00截断

返回错误,文件名不符合白名单要求,上传失败。
错误原因
在多数情况下,截断绕过都是用在文件名后添加hex形式的%00(如上所示操作)来测试,例如filename='1.php%00.jpg',但是由于在PHP中,$_FILES['upload_file']['name']在得到文件名时,%00之后的内容已经被截断了,所以$_FILES['upload_file']['name']中得到的文件名后缀是php,而不是jpg,因而此时不能通过if(in_array($file_ext,$ext_arr))的检查。
又因为GET方法提交的参数save_path的值也会参与最终文件名的拼接,所以应该在save_path的值中添加%00截断。
在save_path的值中添加%00截断
我们又修改数据包中save_path的值为../upload/webshell.php%00(GET提交参数这里是URL编码,添加%00后可以被自动解码,解码之后得到00截断)。即在保存路径中添加截断上传
抓包修改save_path的值添加%00截断

成功上传名为pass12.php的脚本文件

Pass-13(%00截断——POST)
分析代码,仍然是白名单限制,但是save_path不是由GET方法提交,而是POST方法提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| $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 = $_POST['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类型文件!"; } }
|
绕过操作
需要注意的是
基于以上的注意事项,在修改POST方法提交的save_path参数并添加%00截断时,由于%00是中断值0的URL编码,因此
- 修改POST方法提交的
save_path参数并添加%00

- 右键解码
%00,在burp中显示为空格(或可直接在hex格式下的数据包中进行改包添加00截断)

修改数据包之后提交

可见如Pass-12一样,成功利用截断上传了一个名为pass13.php的脚本文件

涉及资源
无