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浏览器为例,因为它对代理的支持更干净,较少受系统代理影响。

  1. BurpSuite代理监听设置 :启动BurpSuite,进入 Proxy -> Options 标签页。确保 Proxy Listeners 中有一个运行在 127.0.0.1:8080 的监听器(默认通常已有)。如果没有,点击 Add ,绑定到 127.0.0.1 ,端口设为 8080 。
  2. 浏览器代理设置 :在Firefox中,进入 设置 -> 网络设置 -> 手动代理配置 。HTTP代理和HTTPS代理均填写 127.0.0.1 ,端口 8080 。 务必勾选“也为 HTTPS 使用此代理” 。下方的“不使用代理”列表保持为空。
  3. 安装BurpSuite CA证书(至关重要) :这是拦截HTTPS流量的前提。在浏览器中访问 http://burpsuite 或 127.0.0.1:8080 ,点击 CA Certificate 链接下载证书文件( cacert.der )。然后在Firefox的 设置 -> 隐私与安全 -> 证书 -> 查看证书 -> 证书机构 中,点击 导入 ,选择下载的 cacert.der 文件,勾选“信任此证书机构以标识网站”,确定。
  4. 验证抓包 :打开BurpSuite的 Proxy -> Intercept ,确保 Intercept is on 。然后在浏览器中访问任何一个HTTP网站(如 http://testphp.vulnweb.com ),你应该能看到请求被拦截在BurpSuite中。这一步成功,说明代理通道已打通。

实操心得:很多人卡在抓不到HTTPS包,99%的原因是证书没安装好或浏览器没信任。Firefox使用自己的证书库,所以必须手动导入。Chrome/Edge使用系统证书库,需要将 cacert.der 转换为 .pem 或 .crt 格式后,导入到系统的受信任根证书颁发机构。这是一个必须迈过去的坎。

3.2 针对文件上传的BurpSuite优化配置

默认配置可能不够高效,针对文件上传测试,我习惯做以下调整:

  1. 关闭不必要的拦截 :在 Proxy -> Intercept 页面,完成初始测试后,建议将拦截模式切换到 Intercept is off 。我们更多时候使用 Proxy -> HTTP history 查看历史记录,然后右键发送到 Repeater 进行精细化的重放和修改。持续开启拦截会打断正常的浏览流程。
  2. 配置项目作用域(Target Scope) :在 Target -> Scope 中,添加你的靶场地址(例如 http://靶场IP:端口 )。这样, Proxy history 和 Site map 会自动聚焦于目标,过滤掉大量无关的第三方流量(如浏览器插件、统计代码等),让信息更清晰。
  3. 准备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.jpg
    
    其中 shell.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)

  1. 在浏览器中,访问靶场的上传页面,选择一个 正常的jpg图片 进行上传。同时,打开BurpSuite的代理历史记录(Proxy History)。
  2. 在历史记录中找到这个上传请求,右键发送到 Repeater 模块。
  3. 在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--
    
  4. 关键修改点 :
    • 文件名(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类型检查的关键。
  5. 点击 Send 发送修改后的请求。观察响应。如果成功,响应中通常会包含文件存储的路径,比如 uploads/shell.jpg 。 务必记录下这个路径 。

注意事项:如果服务器返回错误,提示“文件内容不合法”,说明它可能不仅检查文件头,还检查了文件内容中是否存在 <?php 等危险字符串。这时,我们需要对PHP代码进行变形。例如,使用 <script language=“php”>@eval($_POST[‘cmd’]);</script> (仅在某些特定PHP版本有效),或者将代码编码后利用 php://filter 等复杂技巧。在EasyUpload靶场中,通常到图片马这一步就能成功。

4.2 第二步:上传.user.ini文件

这是整个攻击的灵魂,也是最容易出错的一步。 .user.ini 文件本身是文本文件,但服务器可能不允许上传 .ini 后缀。

  1. 在浏览器中,再次选择上传。这次,我们可以选择一个普通的文本文件(.txt),或者还是选一个图片文件。目的是 捕获上传请求的原始格式 。
  2. 同样在BurpSuite的Proxy History中找到这个新请求,发送到Repeater。
  3. 在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 。确保后面有正确的换行。
  4. 点击 Send 发送。成功的响应通常比较隐蔽,可能只是简单的“上传成功”,不会像图片一样返回预览路径。我们需要 假设它上传到了和图片马相同的目录 。

4.3 第三步:触发Webshell执行

上传完成后,攻击链已经布设好,但我们的代码还没有执行。 .user.ini 需要被一个PHP文件调用才能生效。

  1. 寻找目录下的现有PHP文件 :回顾第一步中图片马上传成功返回的路径,例如 uploads/shell.jpg 。那么, .user.ini 和 shell.jpg 应该都在 uploads/ 目录下。我们需要访问这个目录下 任何一个已经存在的、正常的PHP文件 。这个文件可能是 index.php 、 upload.php ,或者靶场专门预留的 view.php 等。
  2. 访问触发文件 :在浏览器中访问 http://靶场地址/uploads/index.php (假设存在)。如果配置成功,这个 index.php 在执行前,会自动包含 shell.jpg 。由于 shell.jpg 末尾包含PHP代码,这段代码就会被服务器解析执行。
  3. 连接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实战中高频问题排查

  1. 请求发送后无响应或连接超时 :

    • 检查代理状态 :确认BurpSuite的Proxy监听器是开启的,且浏览器代理设置正确。
    • 检查证书 :如果是HTTPS网站,再次确认BurpSuite的CA证书已在浏览器中安装并信任。
    • 关闭拦截 :在 Proxy -> Intercept 确认 Intercept is off ,避免请求卡在拦截状态。
    • 关闭其他代理工具 :确保系统没有运行其他VPN、代理软件或防火墙软件干扰。
  2. 修改请求后服务器返回500错误 :

    • 检查格式 :在修改multipart/form-data请求体时,极易破坏边界符( ------WebKitFormBoundary… )。确保边界符在开头、分隔处和结尾完全一致,且末尾有 -- 结束。 最佳实践是:在Raw或Hex视图下,只修改需要改动的部分(如filename和文件内容),不要动边界符本身。
    • 检查内容长度 :如果你替换了文件内容,文件大小变了,但请求头中的 Content-Length 字段没有更新,会导致服务器解析错误。BurpSuite通常会自动更新这个长度,但有时需要手动在Raw视图下修正,或者干脆删除 Content-Length 头,让BurpSuite自动重算。
  3. 上传成功但无法触发 :

    • 路径问题 :确认 .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 防御视角与安全建议

通过攻击过程,我们更能理解如何防御:

  1. 文件上传目录隔离 :将用户上传的文件存放到一个独立的、非Web可访问的目录,或者至少确保该目录没有执行PHP的权限(通过配置服务器,如Nginx的 location ~* \.(jpg|png|gif)$ { deny all; } 是错误的,这会阻止图片访问;正确做法是移除该目录的PHP解析引擎)。
  2. 重命名与随机化 :对上传的文件进行强制重命名,使用不可预测的规则(如MD5(时间戳+随机数)),并隐藏原始文件名。这能有效防止用户精准定位文件。
  3. 白名单校验 :不仅校验后缀名,还要在服务端校验文件的MIME类型,甚至文件头(Magic Bytes)。
  4. 内容安全检查 :对图片等文件进行二次渲染(如图片压缩、缩放),可以破坏附加在末尾的恶意代码。对非图片文件进行严格的内容安全扫描。
  5. 禁用危险功能 :在php.ini中关闭 allow_url_include ,并考虑禁用 auto_prepend_file 等危险配置,或严格限制 .user.ini 的使用。
  6. 最小权限原则 :运行Web服务的用户(如www-data)应具备尽可能低的系统权限。

这套利用 .user.ini 和图片马组合拳的攻击方式,其威力在于利用了PHP的配置特性和文件检查的盲区。在实战渗透测试中,它常常作为在获取了有限上传能力后,向获取命令执行权限迈进的关键一步。理解它,既能帮助我们更有效地发现漏洞,也能从根源上思考如何构建更坚固的防御体系。在攻防世界的EasyUpload靶场里走通这一遍流程,你会发现很多看似坚固的防御,其实都建立在一些默认的、脆弱的假设之上。

更多推荐