1. 项目概述:一次攻防演练中的文件上传漏洞复盘
最近在内部的一次红蓝对抗演练中,我作为攻击方,成功利用了一个典型的文件上传漏洞拿下了目标系统的权限。整个过程从信息收集、漏洞发现、绕过防御到最终获取Shell,环环相扣,其中BurpSuite的抓包和改包技巧起到了关键作用。这次实战让我对文件上传漏洞的利用链有了更深的体会,也积累了一些绕过常见防护措施的技巧。今天,我就从一个攻击者的视角,完整复盘这次渗透过程,并分享其中用到的核心工具技巧和思考逻辑。无论你是安全测试人员想提升实战能力,还是开发人员想了解攻击者思路以加固自身系统,这篇文章都能提供一份详实的参考。
文件上传功能几乎是所有Web应用的标配,它方便了用户,但也为攻击者打开了一扇危险的大门。一个未经严格校验的上传点,很可能直接成为通往服务器内网的“高速公路”。本次实战的目标是一个内容管理系统(CMS),我们将看到攻击者如何一步步将一张“图片”变成控制服务器的“后门”。
2. 前期信息收集与目标分析
在发起任何攻击之前,充分的信息收集是成功的一半。盲目测试不仅效率低下,还容易触发告警。这次的目标是一个企业门户网站,我们的第一步就是摸清它的“家底”。
2.1 网站结构与技术栈识别
我首先使用浏览器对目标网站进行了常规浏览,重点关注“关于我们”、“联系我们”、“用户中心”等可能存在上传功能的页面。同时,打开浏览器的开发者工具(F12),查看网络请求和页面源代码。
关键操作与发现:
- 查看响应头:在Network标签页中,查看任意一个页面请求的HTTP响应头。我发现了
X-Powered-By: PHP/7.2.24和Server: Apache/2.4.41 (Ubuntu)。这立刻明确了后端语言是PHP,Web服务器是Apache,运行在Ubuntu系统上。PHP环境意味着我们可以尝试上传.php后缀的Webshell。 - 目录扫描与敏感文件发现:使用
dirsearch这类目录扫描工具,对目标进行初步探测。命令类似:python3 dirsearch.py -u https://target.com -e php,html,zip,bak。扫描结果中,除了常见的admin、upload目录外,还发现了一个/uploads/目录,访问后直接列出了已上传的文件,这是一个重要信号,说明上传的文件可能被直接存储在可访问的Web路径下。 - 寻找上传点:通过手动浏览和扫描结果,在用户头像设置页面找到了上传点。页面提示“支持上传JPG、PNG格式图片,大小不超过2MB”。这是一个非常典型的图片上传功能。
注意:信息收集阶段要尽可能安静。我会调整目录扫描工具的线程数和延迟,避免对目标服务器造成过大压力或触发基于频率的防护规则。同时,使用不同的User-Agent也是基本操作。
2.2 客户端校验机制初探
找到上传点后,我没有急于上传恶意文件,而是先分析前端的防御措施。我尝试选择了一个.php文件进行上传。
首次测试结果:点击上传按钮后,页面立刻弹窗提示“文件类型不支持,请上传图片格式!”,文件并未发生网络上传。这说明存在前端JavaScript校验。
绕过方法:这是最简单的防护。打开浏览器开发者工具的“Sources”或“调试器”面板,找到负责上传的JavaScript文件,通常可以搜索file、upload、check等关键词。我很快找到了一个名为validateFileType()的函数,它通过检查文件名后缀(如.jpg,.png)来过滤。绕过方法有两种:
- 禁用浏览器JavaScript:直接在浏览器设置中禁用JS,然后重试上传。
- 拦截并修改请求:更常用的方法是让前端校验通过,然后在文件被发送到服务器的途中进行“调包”。这就是BurpSuite登场的时候。我保持JS开启,正常选择一个
.jpg图片文件,但在点击上传前,先打开BurpSuite的代理拦截功能。
3. 核心工具配置与抓包技巧详解
BurpSuite是Web安全测试的“瑞士军刀”,在文件上传漏洞利用中,其代理和重放功能至关重要。很多新手卡在抓不到包或改包无效上,这里详细说明我的配置和技巧。
3.1 BurpSuite代理环境搭建
首先,确保你的浏览器网络流量经过BurpSuite。
- Burp端配置:打开BurpSuite,在
Proxy->Options标签页下,确保代理监听器(Proxy Listeners)是启用的,默认是127.0.0.1:8080。记住这个地址和端口。 - 浏览器端配置:以Chrome为例,安装
SwitchyOmega这类代理管理插件。新建一个情景模式,配置代理协议为HTTP,代理服务器为127.0.0.1,端口为8080。然后选择这个模式。 - 安装CA证书:为了拦截HTTPS流量,必须在浏览器中安装BurpSuite的CA证书。在浏览器中访问
http://burp或127.0.0.1:8080,点击“CA Certificate”下载证书文件,然后在浏览器的证书管理设置中导入并信任该证书。 - 验证:访问任意HTTP网站,查看BurpSuite的
Proxy->Intercept标签页,如果显示“Intercept is on”且有请求内容,说明配置成功。
3.2 精准抓包与拦截策略
面对文件上传请求,精准抓包是第一步。
- 开启拦截:在BurpSuite的
Proxy->Intercept标签页,确保按钮状态是“Intercept is on”。 - 执行上传操作:回到浏览器,选择那个用于绕过前端校验的合法图片文件(比如
test.jpg),点击上传按钮。 - 分析请求包:此时浏览器会卡住,因为请求被BurpSuite拦截了。在Intercept标签页,你会看到一个
POST请求,其Content-Type为multipart/form-data。这就是文件上传的请求体。重点关注以下几个部分:POST /upload.php HTTP/1.1:上传处理程序的路径。Content-Disposition: form-data; name="file"; filename="test.jpg":这里包含了前端传递的文件名。这是我们后续改包的重点字段之一。- 请求体最后部分是一串乱码,那是图片文件的二进制内容。
实操心得:有时点击上传后,Burp里瞬间闪过多个请求,难以精准拦截到上传的那个
POST请求。我的技巧是:先开启拦截,执行操作,如果请求太复杂,可以先点“Intercept is off”放行所有请求,然后去Proxy->HTTP history历史记录里找到刚才那条POST /upload.php的请求。右键选择“Send to Repeater”,这样就可以在Repeater模块里进行稳定的重放和修改测试,比在Intercept里反复开关拦截要高效得多。
4. 漏洞利用:绕过服务器端校验
拦截到上传请求只是开始,真正的挑战在于绕过服务器端的校验。服务器端的防护通常有多层,我们需要逐一分析并尝试绕过。
4.1 绕过Content-Type校验
这是较弱的防护方式。服务器可能只检查HTTP头中的Content-Type字段。
- 攻击前请求:在BurpSuite中,我们看到
Content-Type: image/jpeg。 - 绕过方法:即使我们上传一个
.php文件,只要将这里的Content-Type改为image/jpeg或image/png,就可能绕过检查。在Burp的Repeater模块中,直接修改这个字段的值,然后发送请求,观察响应。
4.2 绕过黑名单/白名单后缀校验
这是更常见的防护。服务器检查文件名后缀。
- 攻击方法:在拦截的请求中,找到
filename="test.jpg"这一行。- 大小写绕过:尝试改为
filename="test.Php"或filename="test.PHP"。某些系统在Linux下大小写敏感,但Windows服务器或配置不当的检查逻辑可能不敏感。 - 特殊后缀绕过:尝试
filename="test.php5"、filename="test.phtml"、filename="test.phps"。这些后缀在某些服务器配置下,依然会被当作PHP文件解析。 - 双写/点号绕过:尝试
filename="test.p.phphp"(如果过滤逻辑是简单删除php字符串,可能剩下.php),或filename="test.php."、filename="test.php(末尾加空格)。这在处理文件名时可能引发解析差异。 - 路径拼接绕过:如果上传路径可控,可尝试
filename="../../../test.jpg"(路径遍历),或利用服务器解析特性,如filename="test.jpg/.php"(在某些特定环境下可能被解析为PHP)。
- 大小写绕过:尝试改为
4.3 绕过文件内容头校验(Magic Number)
许多系统会读取文件的前几个字节(魔数)来判断文件真实类型,而不仅仅是看后缀。
- JPEG文件的魔数是
FF D8 FF E0。 - PNG文件的魔数是
89 50 4E 47。 - 绕过方法:制作一个“图片马”。先准备一个简单的PHP Webshell,内容如
<?php @eval($_POST['cmd']);?>。然后使用十六进制编辑器(如010 Editor)或Linux下的cat命令,在Webshell内容之前插入图片的魔数。- Linux下命令示例:
cat real.jpg shell.php > shell.jpg.php。这样生成的文件,用图片查看器打开显示正常,但服务器如果配置不当(如未检测文件内容尾部,或解析优先级问题),当以.php后缀访问时,PHP解释器会从<?php标签开始执行,忽略前面的图片二进制数据。 - 在BurpSuite中,我们无法直接修改二进制内容体,但可以先制作好“图片马”,然后在上传时,将
filename改为shell.jpg.php,同时保持Content-Type为image/jpeg。这是一种组合拳。
- Linux下命令示例:
4.4 实战绕过过程记录
在本次实战中,我遇到了组合防护。
- 第一层:前端JS校验,已通过Burp拦截绕过。
- 第二层:服务器端检查后缀黑名单(
.php,.php5,.phtml等)。我尝试.php、.Php、.php7均被拦截,返回“文件类型非法”。 - 第三层:尝试
.php(点号空格),Burp中修改filename为"test.php "(注意末尾空格)。发送请求后,返回“上传成功”!但访问/uploads/test.php却返回404。查看服务器返回的完整响应,发现其中包含了文件存储路径:File saved to: /var/www/html/uploads/test.php。这说明服务器端存储时去掉了末尾空格,实际保存为test.php。 - 解析漏洞利用:我立刻想到,这是不是存在解析漏洞?我上传了一个名为
test.php.jpg的文件。响应成功,且存储路径显示为test.php.jpg。直接访问/uploads/test.php.jpg,服务器返回了乱码(图片二进制被直接输出),并未解析。这说明服务器没有将.php.jpg当作PHP执行。 - 关键突破:我回想起Apache的解析特性:它会从右往左解析后缀,直到遇到可识别的类型。如果
.php不可识别,它会尝试.jpg。但更重要的是一个古老但可能存在的配置:AddHandler或AddType。我构造了文件名test.php.xxx。上传成功!访问/uploads/test.php.xxx——奇迹发生了,浏览器提示下载,而不是执行。这说明.xxx没有被当作PHP处理。 - 最终Payload:我猜测服务器可能配置了
AddType application/x-httpd-php .php .phtml,但没有其他。我需要一个服务器不认识,但访问时又能触发PHP解析的后缀。我尝试了test.php.(末尾点号)、test.php::$DATA(NTFS流,对Linux无效),均未成功。最后,我尝试了空字节截断(虽然在高版本PHP中已修复,但值得一试)。在Burp中修改filename为"test.php%00.jpg"(注意,要在Hex视图下将%00修改为真正的空字节00)。上传后,服务器返回路径为/uploads/test.php!空字节后的.jpg被截断。访问/uploads/test.php,成功看到了空白页(因为文件内容还是图片二进制,没有有效的PHP标签)。这说明我成功控制了存储的文件名。 - 上传Webshell:我将文件内容替换为真正的PHP Webshell代码,再次使用
filename="shell.php%00.jpg"进行上传。访问/uploads/shell.php,返回空白(这是正常的,因为Webshell需要POST参数)。使用curl或Burp的Repeater,向/uploads/shell.php发送一个POST请求,参数为cmd=system('whoami');。服务器返回了www-data。至此,Webshell上传并执行成功。
5. 权限提升与内网渗透思路
获取Webshell(www-data权限)通常只是第一步。为了证明漏洞的危害性,或在实际渗透中获取更大价值,需要进行权限提升和横向移动。
5.1 信息收集与权限提升
在Webshell中执行命令,收集系统信息:
whoami/id:查看当前用户。uname -a:查看内核版本。cat /etc/passwd:查看系统用户。ps aux:查看进程,寻找root权限运行的服务。find / -perm -4000 -type f 2>/dev/null:查找SUID特权文件。sudo -l:查看当前用户可以以root身份执行哪些命令(需要密码)。
在本次环境中,通过sudo -l发现,www-data用户被允许以root身份无需密码运行/usr/bin/vi。这是一个经典的提权路径。我们可以通过sudo vi打开一个文件,然后在vi中执行:!bash或:!/bin/sh来启动一个root shell。
5.2 内网探测与横向移动
拿到服务器权限后,内网可能是更广阔的战场。
- 网络信息收集:
ifconfig/ip addr查看内网IP段。netstat -antp查看网络连接和监听端口。 - 探测内网存活主机:上传一个静态编译的
nmap二进制文件,或使用脚本如for i in {1..254}; do ping -c 1 192.168.1.$i & done进行存活探测。 - 收集敏感信息:在Web目录、配置文件(如
/etc/passwd,/etc/shadow(需root))、数据库配置文件(如config.php,wp-config.php)、用户家目录、历史命令(.bash_history)中寻找数据库密码、SSH密钥、其他系统凭证等。 - 利用凭证横向移动:如果找到SSH私钥或密码,可以尝试登录内网其他机器。数据库密码可能在其他Web应用中复用。
重要注意事项:权限提升和内网渗透高度依赖于具体环境,没有通用方法。上述
vi提权只是一个例子。在实际授权测试中,这些操作必须在获得明确授权的前提下进行,并且要非常小心,避免对业务系统造成不可逆的影响。我们的目标是证明漏洞存在及其潜在危害,而非破坏。
6. 防御方案与安全开发建议
从这次攻击复盘,我们可以从防御者角度总结出多层防护策略,这些应该是开发和安全人员必须落实的。
6.1 前端与后端双重校验
- 前端校验:为了用户体验,可以做,但必须明白这只能防君子,不能防黑客。任何前端校验都可以被绕过。
- 后端校验(核心):
- 白名单校验:严格限定允许上传的后缀名列表(如
.jpg,.jpeg,.png,.gif),拒绝任何不在列表中的文件。绝对不要使用黑名单。 - 文件类型校验:使用服务器的API获取文件的MIME类型(如PHP的
mime_content_type()或finfo_file()),并与白名单后缀进行映射校验。不要信任客户端传来的Content-Type。 - 文件内容校验:检查文件头(魔数),确保文件内容与实际后缀匹配。例如,一个
.jpg文件必须以FF D8开头。 - 文件重命名:上传后,使用随机生成的文件名(如UUID)替换原始文件名,并保留原始后缀。例如,
a1b2c3d4.jpg。这可以防止攻击者直接访问或预测文件路径。 - 目录隔离:将上传的文件存储在Web根目录之外。通过后端脚本(如
readfile())来读取和提供这些文件。如果必须放在Web目录下,务必配置服务器(如Apache的.htaccess或Nginx的location规则)禁止直接执行上传目录下的脚本文件。
- 白名单校验:严格限定允许上传的后缀名列表(如
6.2 服务器安全配置
- 及时更新:保持操作系统、Web服务器(Apache/Nginx)、编程语言(PHP/Python)及所有组件的最新版本,修复已知的解析漏洞。
- 最小权限原则:运行Web服务的用户(如
www-data)应具有最低必要权限。禁止其使用sudo,避免其访问敏感目录。 - 禁用危险函数:在PHP中,可以在
php.ini中禁用eval(),system(),exec(),shell_exec()等危险函数。 - 配置解析规则:明确服务器对文件的解析规则。避免使用模糊的
AddHandler配置。对于上传目录,可以强制设置所有文件为application/octet-stream类型,强制浏览器下载而非执行。
6.3 安全监测与响应
- 日志审计:详细记录上传操作,包括时间、IP、原始文件名、存储路径、文件大小、MD5等。定期审计日志,发现异常上传行为(如频繁上传、尝试非常规后缀)。
- WAF(Web应用防火墙):部署WAF可以拦截许多常见的攻击payload,包括文件上传漏洞利用的请求。
- 文件内容安全扫描:对上传的图片等文件,可以使用开源或商业的扫描引擎,检测其中是否隐藏了恶意代码。
- 定期渗透测试:邀请专业的安全团队或使用自动化工具对系统进行定期的安全评估,主动发现类似漏洞。
文件上传漏洞的利用与防御是一场持续的博弈。攻击者的手法在进化,防御者的策略也需要层层加固。通过这次完整的实战复盘,我希望不仅展示了攻击链,更能让防御者理解每一层防护为何如此重要。安全是一个整体,任何一个环节的疏忽都可能导致全盘沦陷。对于开发者而言,在设计上传功能时,必须将“不信任任何客户端输入”作为铁律,实施上述多层防御策略,才能有效守护系统安全。