文件上传漏洞实战:利用.user.ini与图片马绕过限制
1. 项目概述:一次绕过上传限制的实战演练
最近在复现攻防世界的靶场,遇到了一个挺有意思的上传类题目——EasyUpload。这个靶场模拟了一个常见的Web应用场景:一个允许用户上传文件的页面,但后端设置了各种过滤规则来阻止恶意文件的上传。我们的目标很明确,就是想办法上传一个能执行系统命令的Webshell,从而拿到服务器的控制权。题目本身不算复杂,但其中涉及到的
.user.ini
利用和图片马制作,是文件上传漏洞中非常经典且实用的两种绕过技巧。很多新手朋友可能对BurpSuite的配置和抓包改包过程感到头疼,这次我就把整个流程,从环境准备、漏洞分析、绕过构造到最终利用,连同BurpSuite里那些容易踩坑的配置细节,一起掰开揉碎了讲清楚。无论你是刚开始接触Web安全,还是想巩固一下文件上传漏洞的知识点,这篇实战记录都能给你提供一个清晰的思路和可复现的操作指南。
2. 靶场环境分析与核心思路拆解
2.1 靶场初步侦察与功能点分析
拿到一个靶场,第一步永远是信息收集。访问EasyUpload靶场的地址,我们通常会看到一个文件上传的界面。作为攻击者,我们需要扮演一个“挑剔的用户”,去试探这个上传功能的每一个边界。
首先,尝试上传一个最简单的PHP Webshell,比如内容为
<?php @eval($_POST[‘cmd’]);?>
的
shell.php
文件。十有八九,页面会直接返回一个错误,提示“文件类型不允许”或“文件内容非法”。这告诉我们,后端有基础的文件类型检查。接着,我们可以尝试修改文件扩展名,比如上传
shell.php5
,
shell.phtml
,
shell.phps
等,看服务器是否识别这些偏门的PHP执行后缀。同时,也要留意返回的响应头,有时错误信息会直接暴露在后端的
Content-Type
字段或自定义的Header里。
另一个关键动作是查看网页源代码。前端往往会有JavaScript进行文件类型的白名单校验(例如只允许
.jpg
,
.png
,
.gif
)。这种校验是完全可以被绕过的,我们只需要用BurpSuite拦截请求并修改即可,但它给了我们一个初步的“允许名单”提示。更重要的是,源代码里可能会注释掉一些隐藏的上传路径、提示信息,甚至是其他功能点。对于EasyUpload,经过初步测试,我们可能会发现它只允许上传图片文件(通过MIME类型或后缀名判断),并且会对文件内容进行简单的检查,例如使用
getimagesize()
函数验证是否为真实的图片。
2.2 核心绕过技术:.user.ini与图片马的原理
当直接上传PHP文件被阻截时,我们就需要利用一些“组合拳”来绕过防御。这里涉及两个核心概念:
1. 图片马(Image Shell)
图片马的本质是一个包含恶意代码的图片文件。为什么它能绕过检查?因为像
getimagesize()
、
exif_imagetype()
这类函数,只检查文件的
文件头(Magic Bytes)
。例如,一个JPEG图片的文件头是
FF D8 FF E0
,PNG的是
89 50 4E 47
。我们可以在一个正常的图片文件末尾,追加我们的PHP代码。当服务器用图片检查函数去读它时,看到正确的文件头就认为它是图片;而当这个文件被以PHP方式解析时(前提是后缀名是
.php
或服务器配置错误),PHP解析器会从
<?php
标签开始执行,忽略掉前面的图片二进制数据。但在这个靶场中,服务器很可能强制要求后缀为图片格式,所以我们不能直接让图片马以
.php
执行。这时就需要
.user.ini
来帮忙。
2. .user.ini文件的作用
.user.ini
是PHP特有的配置文件,在开启
user_ini.filename
和
user_ini.cache_ttl
选项后生效。它的影响力仅限于该文件所在的目录及其所有子目录。最关键的一个指令是
auto_prepend_file
。如果我们能在目标服务器的某个可访问目录下上传一个
.user.ini
文件,并写入
auto_prepend_file=shell.jpg
,那么,该目录下
所有
的PHP文件在执行时,都会自动在顶部包含(
include
)我们指定的
shell.jpg
文件。如果
shell.jpg
是我们制作的图片马,那么恶意代码就会被执行。
这个靶场的巧妙之处就在于,它可能允许上传
.ini
文件,或者对
.user.ini
文件的检查不严格。我们的攻击链就清晰了:先上传一个包含PHP代码的图片马(如
shell.jpg
),再上传一个设置自动包含的
.user.ini
文件。最后,去访问该目录下任何一个
已经存在的、正常的PHP文件
(比如
index.php
),我们的图片马中的代码就会被触发。
2.3 工具选型与BurpSuite的必要性
为什么一定要用BurpSuite?因为前端的校验是“纸老虎”,真正的攻防发生在浏览器与服务器之间的HTTP请求/响应里。BurpSuite作为中间代理,可以让我们看到、拦截并修改这些原始数据包。
-
绕过前端校验
:浏览器根据文件选择框的
.value属性进行校验,我们直接在BurpSuite里把filename=“shell.php”改成filename=“shell.jpg”即可。 - 探测后端规则 :通过反复修改请求中的文件名、Content-Type、文件内容,观察服务器的不同反应,可以逆向推断出后端使用了哪种过滤方式(黑名单/白名单、检查文件头、检查内容关键字等)。
-
构造复杂Payload
:制作图片马需要将二进制数据和文本代码拼接,在BurpSuite的Repeater模块中直接编辑十六进制(Hex)视图是最精准的方式。同样,
.user.ini文件也需要通过BurpSuite来上传,确保其格式和内容完全正确。
注意:整个实验必须在 授权 的环境下进行,例如攻防世界靶场、自己搭建的DVWA或Upload-Labs等练习平台。切勿对任何非授权的网站进行测试。
3. 实操环境搭建与BurpSuite关键配置
3.1 本地代理环境配置
要让BurpSuite能抓到浏览器的流量,需要完成浏览器和BurpSuite的双向配置。这里以最常用的Firefox浏览器为例,因为它对代理的支持更干净,较少受系统代理影响。
-
BurpSuite代理监听设置
:启动BurpSuite,进入
Proxy->Options标签页。确保Proxy Listeners中有一个运行在127.0.0.1:8080的监听器(默认通常已有)。如果没有,点击Add,绑定到127.0.0.1,端口设为8080。 -
浏览器代理设置
:在Firefox中,进入
设置->网络设置->手动代理配置。HTTP代理和HTTPS代理均填写127.0.0.1,端口8080。 务必勾选“也为 HTTPS 使用此代理” 。下方的“不使用代理”列表保持为空。 -
安装BurpSuite CA证书(至关重要)
:这是拦截HTTPS流量的前提。在浏览器中访问
http://burpsuite或127.0.0.1:8080,点击CA Certificate链接下载证书文件(cacert.der)。然后在Firefox的设置->隐私与安全->证书->查看证书->证书机构中,点击导入,选择下载的cacert.der文件,勾选“信任此证书机构以标识网站”,确定。 -
验证抓包
:打开BurpSuite的
Proxy->Intercept,确保Intercept is on。然后在浏览器中访问任何一个HTTP网站(如http://testphp.vulnweb.com),你应该能看到请求被拦截在BurpSuite中。这一步成功,说明代理通道已打通。
实操心得:很多人卡在抓不到HTTPS包,99%的原因是证书没安装好或浏览器没信任。Firefox使用自己的证书库,所以必须手动导入。Chrome/Edge使用系统证书库,需要将
cacert.der转换为.pem或.crt格式后,导入到系统的受信任根证书颁发机构。这是一个必须迈过去的坎。
3.2 针对文件上传的BurpSuite优化配置
默认配置可能不够高效,针对文件上传测试,我习惯做以下调整:
-
关闭不必要的拦截
:在
Proxy->Intercept页面,完成初始测试后,建议将拦截模式切换到Intercept is off。我们更多时候使用Proxy->HTTP history查看历史记录,然后右键发送到Repeater进行精细化的重放和修改。持续开启拦截会打断正常的浏览流程。 -
配置项目作用域(Target Scope)
:在
Target->Scope中,添加你的靶场地址(例如http://靶场IP:端口)。这样,Proxy history和Site map会自动聚焦于目标,过滤掉大量无关的第三方流量(如浏览器插件、统计代码等),让信息更清晰。 -
准备Repeater和Intruder
:文件上传测试中,
Repeater用于单次请求的精细修改和测试,Intruder可用于批量测试文件名后缀、MIME类型等。提前熟悉这两个模块的界面。
3.3 制作图片马与.user.ini文件
在开始攻击前,我们需要准备好两个Payload文件。注意,不是在本地创建两个文件,而是 在BurpSuite的请求体中构造它们 。
图片马(shell.jpg)的制作思路 : 找一个正常的小图片(比如1x1像素的白色jpg,体积小)。我们有两种方式制作:
-
命令行拼接(推荐,最准确)
:在Linux或Mac的终端,或者Windows的Git Bash/Cmd中,使用
copy /b命令。
其中# Windows copy /b normal.jpg + shell.php webshell.jpg # Linux/Mac cat normal.jpg shell.php > webshell.jpgshell.php是一个文本文件,内容为<?php @eval($_POST[‘cmd’]);?>。这样生成的webshell.jpg,用图片查看器打开正常,用文本编辑器打开末尾能看到PHP代码。 - BurpSuite直接构造 :在Repeater中,先上传一个正常图片,然后在Hex视图下,直接找到请求体中图片二进制数据的末尾,接着写入我们的PHP代码的十六进制形式。这种方式更底层,但操作复杂。
.user.ini文件的内容 : 它的内容非常简单,就是一个文本配置:
auto_prepend_file=shell.jpg
注意,这里的
shell.jpg
必须和我们即将上传的图片马的文件名
完全一致
。它表示“自动在该目录下所有PHP文件执行前,先包含
shell.jpg
这个文件”。
4. 分步攻击流程与BurpSuite实战操作
4.1 第一步:上传图片马(shell.jpg)
- 在浏览器中,访问靶场的上传页面,选择一个 正常的jpg图片 进行上传。同时,打开BurpSuite的代理历史记录(Proxy History)。
-
在历史记录中找到这个上传请求,右键发送到
Repeater模块。 -
在Repeater中,我们主要修改**请求体(Body)**部分。一个典型的多部分表单数据(multipart/form-data)上传请求体如下:
------WebKitFormBoundaryABC123 Content-Disposition: form-data; name="upload_file"; filename="normal.jpg" Content-Type: image/jpeg [这里是图片的二进制数据] ------WebKitFormBoundaryABC123 Content-Disposition: form-data; name="submit" upload ------WebKitFormBoundaryABC123-- -
关键修改点
:
-
文件名(filename)
:将
filename="normal.jpg"修改为filename="shell.jpg"。这一步可能绕过基于后缀名的检查。 -
文件内容
:我们需要用我们制作好的图片马(包含PHP代码的jpg文件)的二进制内容,替换掉
[这里是图片的二进制数据]部分。为了做到这一点,最好先在Repeater的 Hex视图 中操作。找到图片二进制数据的起始和结束位置,将其整体替换为图片马文件的十六进制数据。如果你是用命令行制作的图片马,可以直接在BurpSuite的Proxy->Options->Match and Replace中添加规则,或者更简单的方法:在Repeater的Raw视图里,先把整个请求复制出来,用一个文本编辑器(支持Hex编辑的,如010 Editor)进行二进制替换,然后再粘贴回去。 -
Content-Type
:保持
Content-Type: image/jpeg不变。这是绕过MIME类型检查的关键。
-
文件名(filename)
:将
-
点击
Send发送修改后的请求。观察响应。如果成功,响应中通常会包含文件存储的路径,比如uploads/shell.jpg。 务必记录下这个路径 。
注意事项:如果服务器返回错误,提示“文件内容不合法”,说明它可能不仅检查文件头,还检查了文件内容中是否存在
<?php等危险字符串。这时,我们需要对PHP代码进行变形。例如,使用<script language=“php”>@eval($_POST[‘cmd’]);</script>(仅在某些特定PHP版本有效),或者将代码编码后利用php://filter等复杂技巧。在EasyUpload靶场中,通常到图片马这一步就能成功。
4.2 第二步:上传.user.ini文件
这是整个攻击的灵魂,也是最容易出错的一步。
.user.ini
文件本身是文本文件,但服务器可能不允许上传
.ini
后缀。
- 在浏览器中,再次选择上传。这次,我们可以选择一个普通的文本文件(.txt),或者还是选一个图片文件。目的是 捕获上传请求的原始格式 。
- 同样在BurpSuite的Proxy History中找到这个新请求,发送到Repeater。
-
在Repeater中进行如下关键修改:
-
文件名(filename)
:这是重中之重。将
filename=”test.txt”修改为filename=“.user.ini”。注意,文件名前面有一个点,这是一个隐藏文件。有些上传逻辑在处理带点的文件名时可能会出错或被过滤,需要尝试。 -
Content-Type
:将
Content-Type: text/plain修改为Content-Type: image/jpeg或application/octet-stream。目的是伪装成非文本文件,绕过可能的内容类型检查。 -
文件内容
:将整个文件内容区域,替换为我们准备好的
.user.ini内容,即auto_prepend_file=shell.jpg。确保后面有正确的换行。
-
文件名(filename)
:这是重中之重。将
-
点击
Send发送。成功的响应通常比较隐蔽,可能只是简单的“上传成功”,不会像图片一样返回预览路径。我们需要 假设它上传到了和图片马相同的目录 。
4.3 第三步:触发Webshell执行
上传完成后,攻击链已经布设好,但我们的代码还没有执行。
.user.ini
需要被一个PHP文件调用才能生效。
-
寻找目录下的现有PHP文件
:回顾第一步中图片马上传成功返回的路径,例如
uploads/shell.jpg。那么,.user.ini和shell.jpg应该都在uploads/目录下。我们需要访问这个目录下 任何一个已经存在的、正常的PHP文件 。这个文件可能是index.php、upload.php,或者靶场专门预留的view.php等。 -
访问触发文件
:在浏览器中访问
http://靶场地址/uploads/index.php(假设存在)。如果配置成功,这个index.php在执行前,会自动包含shell.jpg。由于shell.jpg末尾包含PHP代码,这段代码就会被服务器解析执行。 -
连接Webshell
:此时,虽然访问的是
index.php,但实际上已经执行了我们的恶意代码。我们可以使用中国菜刀、蚁剑这类Webshell管理工具,或者直接用HackBar等浏览器插件,以POST方式向http://靶场地址/uploads/index.php提交数据cmd=system(‘whoami’);,来验证是否能够执行系统命令。如果返回了当前服务器的用户名(如www-data),则说明攻击成功,我们已经拿到了一个Webshell。
5. 深度绕过的技巧与问题排查实录
5.1 常见过滤机制与绕过方法
在实际靶场或真实环境中,防御措施可能更复杂。以下是一些进阶的绕过思路:
-
黑名单过滤
:服务器禁止上传
.php,.phtml,.php5,.php7等。可以尝试冷门后缀如.phar,.inc,.pht,或者利用系统特性如.php.(末尾空格,Windows会去除)、.php::$DATA(NTFS流,Windows)。 -
白名单过滤
:只允许
.jpg,.png,.gif。此时.user.ini本身可能无法上传。需要寻找其他漏洞组合,比如:-
文件包含漏洞
:如果网站存在文件包含漏洞(
include($_GET[‘file’])),我们可以直接上传图片马,然后通过包含漏洞来执行它。此时就不需要.user.ini了。 -
解析漏洞
:特定服务器(如旧版IIS、Nginx错误配置)可能存在解析漏洞,例如
shell.jpg/.php会被解析为PHP文件,或者shell.jpg%00.php(空字节截断,需特定PHP版本)。
-
文件包含漏洞
:如果网站存在文件包含漏洞(
-
内容检查
:检查文件内容是否包含
<?php,eval,assert等关键字。-
使用短标签
:
<?= @eval($_POST[‘cmd’]);?>,但短标签需要short_open_tag=On。 -
使用其他标签
:
<script language=”php”>@eval($_POST[‘cmd’]);</script>。 -
编码/加密
:使用
base64_encode、rot13等编码函数包裹代码,并在Webshell中用对应的解码函数执行。例如:<?php eval(base64_decode(‘编码后的字符串’));?>。 -
利用图片EXIF信息
:有些检查不严的系统,允许将代码写入图片的EXIF注释等信息中,再用
<?php exif_read_data(‘shell.jpg’, ‘COMMENT’);?>等方式读取执行。
-
使用短标签
:
5.2 BurpSuite实战中高频问题排查
-
请求发送后无响应或连接超时 :
- 检查代理状态 :确认BurpSuite的Proxy监听器是开启的,且浏览器代理设置正确。
- 检查证书 :如果是HTTPS网站,再次确认BurpSuite的CA证书已在浏览器中安装并信任。
-
关闭拦截
:在
Proxy->Intercept确认Intercept is off,避免请求卡在拦截状态。 - 关闭其他代理工具 :确保系统没有运行其他VPN、代理软件或防火墙软件干扰。
-
修改请求后服务器返回500错误 :
-
检查格式
:在修改multipart/form-data请求体时,极易破坏边界符(
------WebKitFormBoundary…)。确保边界符在开头、分隔处和结尾完全一致,且末尾有--结束。 最佳实践是:在Raw或Hex视图下,只修改需要改动的部分(如filename和文件内容),不要动边界符本身。 -
检查内容长度
:如果你替换了文件内容,文件大小变了,但请求头中的
Content-Length字段没有更新,会导致服务器解析错误。BurpSuite通常会自动更新这个长度,但有时需要手动在Raw视图下修正,或者干脆删除Content-Length头,让BurpSuite自动重算。
-
检查格式
:在修改multipart/form-data请求体时,极易破坏边界符(
-
上传成功但无法触发 :
-
路径问题
:确认
.user.ini和图片马上传到了 同一个目录 。.user.ini的作用范围是其所在目录及子目录。 -
PHP配置问题
:目标服务器可能没有开启
user_ini.filename功能(默认有时是关闭的)。这意味着.user.ini根本不会生效。这种情况下,此攻击链无效。 -
文件名问题
:服务器可能对上传的文件进行了重命名(例如使用时间戳+随机数)。如果
shell.jpg被重命名为_12345.jpg,那么.user.ini里的auto_prepend_file=shell.jpg就指向了一个不存在的文件。需要查看上传成功后的响应,获取最终的文件名。 - 访问的PHP文件不对 :尝试访问该目录下可能存在的其他PHP文件,或者查看网页源代码中是否有其他PHP文件的链接。
-
路径问题
:确认
5.3 防御视角与安全建议
通过攻击过程,我们更能理解如何防御:
-
文件上传目录隔离
:将用户上传的文件存放到一个独立的、非Web可访问的目录,或者至少确保该目录没有执行PHP的权限(通过配置服务器,如Nginx的
location ~* \.(jpg|png|gif)$ { deny all; }是错误的,这会阻止图片访问;正确做法是移除该目录的PHP解析引擎)。 - 重命名与随机化 :对上传的文件进行强制重命名,使用不可预测的规则(如MD5(时间戳+随机数)),并隐藏原始文件名。这能有效防止用户精准定位文件。
- 白名单校验 :不仅校验后缀名,还要在服务端校验文件的MIME类型,甚至文件头(Magic Bytes)。
- 内容安全检查 :对图片等文件进行二次渲染(如图片压缩、缩放),可以破坏附加在末尾的恶意代码。对非图片文件进行严格的内容安全扫描。
-
禁用危险功能
:在php.ini中关闭
allow_url_include,并考虑禁用auto_prepend_file等危险配置,或严格限制.user.ini的使用。 - 最小权限原则 :运行Web服务的用户(如www-data)应具备尽可能低的系统权限。
这套利用
.user.ini
和图片马组合拳的攻击方式,其威力在于利用了PHP的配置特性和文件检查的盲区。在实战渗透测试中,它常常作为在获取了有限上传能力后,向获取命令执行权限迈进的关键一步。理解它,既能帮助我们更有效地发现漏洞,也能从根源上思考如何构建更坚固的防御体系。在攻防世界的EasyUpload靶场里走通这一遍流程,你会发现很多看似坚固的防御,其实都建立在一些默认的、脆弱的假设之上。
更多推荐

所有评论(0)