PHP文件上传流量层面WAF绕过
字数 1309 2025-08-27 12:33:43
PHP文件上传流量层面WAF绕过技术详解
一、简介
PHP文件上传实现规范为RFC1867,本文基于PHP 7.3.4 + nginx 1.20.1环境分析,相关源码可在GitHub获取。PHP解析multipart/form-data HTTP请求体的入口函数是SAPI_POST_HANDLER_FUNC。
二、文件解析流程
PHP处理文件上传的基本流程如下:
- 接收HTTP请求
- 解析multipart/form-data格式
- 处理Content-Disposition头部
- 解析文件名和其他元数据
- 存储上传文件到临时目录
- 填充
$_FILES超全局变量
三、关键绕过技术
1. 前向截断
技术原理:
\和/字符会对文件名进行前向截断。例如info.txt/info.php会被处理为info.php。
关键函数:
php_ap_basename()函数会寻找\和/字符最后出现的位置并进行截断:
static char *php_ap_basename(const zend_encoding *encoding, char *path) {
char *s = strrchr(path, '\\');
char *s2 = strrchr(path, '/');
if (s && s2) {
if (s > s2) { ++s; } else { s = ++s2; }
return s;
} else if (s) { return ++s; }
else if (s2) { return ++s2; }
return path;
}
设计初衷:
解决IE浏览器上传文件时传递全路径名的问题。
2. 后向截断
技术原理:
\0(NULL字节)会对文件名进行后向截断。例如info.php\0xxx会被处理为info.php。
实现细节:
- 解析header时仅对内存进行copy
- 使用
strlen获取filename长度,遇到\0即终止 - 同样适用于
$_POST变量名,但不适用于变量值
3. 文件名末尾反斜杠忽略
技术原理:
当出现\后紧跟引号字符时,PHP会忽略\只取引号的值。
关键函数:
php_ap_getword()中的处理逻辑:
if (*pos == '\\' && pos[1] && pos[1] == quote) {
pos += 2;
}
4. 分号影响解析
技术原理:
类似filename=info.php;.txt;的字符串会被解析为info.php。
解析流程:
- 先使用
;进行分词,得到filename=info.php和.txt - 然后使用
=进行分词,将filename解析为info.php
5. 双写filename
技术原理:
PHP按照从前到后的顺序解析Content-Disposition,后面相同的变量名会覆盖前面的值。
四、上传失败场景分析
1. 文件名首字符为NULL
当filename首字符为\0时,上传会失败:
if (filename[0] == '\0') {
#if DEBUG_FILE_UPLOAD
sapi_module.sapi_error(E_NOTICE, "No file uploaded");
#endif
cancel_upload = UPLOAD_ERROR_D;
}
2. name首字符为右方括号
当name首字符为]时,会导致上传失败:
while (*tmp) {
if (*tmp == '[') { c++; }
else if (*tmp == ']') {
c--;
if (tmp[1] && tmp[1] != '[') {
skip_upload = 1;
break;
}
}
if (c < 0) { skip_upload = 1; break; }
tmp++;
}
五、实战应用技巧
- 组合使用技术:可以灵活组合前向截断、后向截断等技术
- 文件名构造:
- 使用
/或\进行路径截断 - 使用
\0进行后缀截断 - 使用
;进行参数分隔
- 使用
- Content-Disposition构造:
- 双写filename参数
- 使用特殊字符干扰解析
六、防御建议
- 对上传文件名进行严格过滤
- 检查NULL字节等特殊字符
- 限制上传文件扩展名
- 对上传文件内容进行验证
- 使用随机重命名策略
七、参考资源
通过深入理解PHP文件上传的内部机制,可以发现更多潜在的绕过技术。建议开发者结合PHP源码进行更深入的分析,以构建更安全的文件上传功能。