记一次刨根问底的HTTP包WAF绕过
字数 1342 2025-08-05 12:50:18
HTTP包WAF绕过技术深度分析
一、HTTP请求包分析
1.1 原始HTTP请求包
POST /sql/post.php HTTP/1.1
Host: 192.168.1.72
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://192.168.1.76
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://192.168.1.76/sql.html
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
Content-Type: multipart/form-data; boundary=1721837650
Content-Length: 156
--1721837650
Content-Disposition: form-data; name="name\"; filename=";name='username'"
Content-Type: image/jpeg
admin
--1721837650--
1.2 关键点分析
-
Content-Disposition字段的特殊构造:
name="name\"; filename=";name='username'"- 使用反斜杠
\转义了第一个双引号 - 构造了看似文件上传但实际上传递参数的格式
-
参数传递机制:
- 表面上是文件上传(
multipart/form-data) - 实际上是参数传递,最终PHP会解析为
$_POST['username'] = 'admin'
- 表面上是文件上传(
-
WAF绕过原理:
- WAF可能只检查
name=后面的值,而PHP会解析整个字符串 - 反斜杠转义导致WAF和PHP解析不一致
- WAF可能只检查
二、FastCGI协议分析
2.1 FastCGI数据流
..SCRIPT_FILENAME/www/wwwroot/192.168.1.72/sql/post.php..QUERY_STRING..REQUEST_METHODPOST.0CONTENT_TYPEmultipart/form-data; boundary=1721837650..CONTENT_LENGTH156. SCRIPT_NAME/sql/post.php. REQUEST_URI/sql/post.php. DOCUMENT_URI/sql/post.php .DOCUMENT_ROOT/www/wwwroot/192.168.1.72..SERVER_PROTOCOLHTTP/1.1..REQUEST_SCHEMEhttp..GATEWAY_INTERFACECGI/1.1..SERVER_SOFTWAREnginx/1.18.0..REMOTE_ADDR192.168.1.75..REMOTE_PORT59676..SERVER_ADDR192.168.1.72..SERVER_PORT80..SERVER_NAME192.168.1.72..REDIRECT_STATUS200.&SCRIPT_FILENAME/www/wwwroot/192.168.1.72/sql/post.php. SCRIPT_NAME/sql/post.php .PATH_INFO .HTTP_HOST192.168.1.72. HTTP_CACHE_CONTROLmax-age=0..HTTP_UPGRADE_INSECURE_REQUESTS1..HTTP_ORIGINhttp://192.168.1.76.sHTTP_USER_AGENTMozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36.....HTTP_ACCEPTtext/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9..HTTP_REFERERhttp://192.168.1.76/sql.html. HTTP_ACCEPT_ENCODINGgzip, deflate..HTTP_ACCEPT_LANGUAGEzh-CN,zh;q=0.9..HTTP_CONNECTIONclose.0HTTP_CONTENT_TYPEmultipart/form-data; boundary=1721837650..HTTP_CONTENT_LENGTH156.
2.2 FastCGI处理流程
- Nginx接收HTTP请求
- 通过FastCGI协议转发给PHP-FPM处理
- PHP解析FastCGI数据包
- 最终PHP处理结果可能与WAF解析不一致
三、PHP源码级分析
3.1 RFC1867处理函数
PHP处理multipart/form-data的核心函数是rfc1867_post_handler,位于main/rfc1867.c中。
3.2 关键处理流程
-
边界解析:
boundary = strstr(content_type_dup, "boundary"); boundary++; -
内容解析:
while (!multipart_buffer_eof(mbuff)) { char buff[FILLUNIT]; char *cd = NULL, *param = NULL, *filename = NULL, *tmp = NULL; size_t blen = 0, wlen = 0; zend_off_t offset; zend_llist_clean(&header); if (!multipart_buffer_headers(mbuff, &header)) { goto fileupload_done; } -
Content-Disposition解析:
if ((cd = php_mime_get_hdr_value(header, "Content-Disposition"))) { char *pair = NULL; int end = 0; while (isspace(*cd)) { ++cd; } while (*cd && (pair = getword(mbuff->input_encoding, &cd, ';'))) { char *key = NULL, *word = pair; while (isspace(*cd)) { ++cd; } if (strchr(pair, '=')) { key = getword(mbuff->input_encoding, &pair, '='); if (!strcasecmp(key, "name")) { if (param) { efree(param); } param = getword_conf(mbuff->input_encoding, pair); } else if (!strcasecmp(key, "filename")) { if (filename) { efree(filename); } filename = getword_conf(mbuff->input_encoding, pair); } } if (key) { efree(key); } efree(word); }
3.3 getword函数分析
关键解析函数php_ap_getword:
static char *php_ap_getword(const zend_encoding *encoding, char **line, char stop)
{
char *pos = *line, quote;
char *res;
while (*pos && *pos != stop) {
if ((quote = *pos) == '"' || quote == '\'') {
++pos;
while (*pos && *pos != quote) {
if (*pos == '\\' && pos[1] && pos[1] == quote) {
pos += 2;
} else {
++pos;
}
}
if (*pos) {
++pos;
}
} else
++pos;
}
if (*pos == '\0') {
res = estrdup(*line);
*line += strlen(*line);
return res;
}
res = estrndup(*line, pos - *line);
while (*pos == stop) {
++pos;
}
*line = pos;
return res;
}
3.4 解析逻辑
- 遇到引号(
"或')时,会检查是否有转义字符\ - 如果遇到
\",会跳过这两个字符 - 最终解析结果为:
name=name"; filename=";name='username'- 但由于PHP的特殊处理,最终会取最后一个
name=的值
四、WAF绕过原理总结
-
解析差异:
- WAF可能只解析到第一个
name=的值 - PHP会完整解析整个Content-Disposition头
- WAF可能只解析到第一个
-
转义字符利用:
- 反斜杠
\导致WAF和PHP解析不一致 - WAF可能认为
name="name\"是一个完整字段 - PHP会继续解析后面的
filename=";name='username'"
- 反斜杠
-
参数覆盖:
- 构造多个
name=参数 - PHP会取最后一个有效的
name=参数
- 构造多个
五、防御建议
-
WAF层面:
- 实现与PHP一致的解析逻辑
- 对转义字符进行特殊处理
- 检查整个Content-Disposition头
-
代码层面:
- 严格验证输入参数
- 使用白名单验证参数名
- 对multipart/form-data请求进行严格解析
-
配置层面:
- 限制上传文件类型
- 设置合理的
post_max_size和upload_max_filesize - 启用严格的HTTP头检查
六、扩展利用
-
其他转义字符利用:
- 尝试使用不同的转义组合
- 测试不同编码下的解析差异
-
多级参数构造:
Content-Disposition: form-data; name="param1\"; name=\"param2"; filename=";name='actual_param'" -
混合编码利用:
- 结合URL编码和转义字符
- 测试不同字符集的解析差异
通过深入理解PHP的RFC1867实现细节,可以发现WAF与后端解析器之间的差异,从而构造出有效的绕过Payload。防御方需要确保安全设备的解析逻辑与后端应用保持一致。