记一次刨根问底的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 关键点分析

  1. Content-Disposition字段的特殊构造

    • name="name\"; filename=";name='username'"
    • 使用反斜杠\转义了第一个双引号
    • 构造了看似文件上传但实际上传递参数的格式
  2. 参数传递机制

    • 表面上是文件上传(multipart/form-data)
    • 实际上是参数传递,最终PHP会解析为$_POST['username'] = 'admin'
  3. WAF绕过原理

    • WAF可能只检查name=后面的值,而PHP会解析整个字符串
    • 反斜杠转义导致WAF和PHP解析不一致

二、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处理流程

  1. Nginx接收HTTP请求
  2. 通过FastCGI协议转发给PHP-FPM处理
  3. PHP解析FastCGI数据包
  4. 最终PHP处理结果可能与WAF解析不一致

三、PHP源码级分析

3.1 RFC1867处理函数

PHP处理multipart/form-data的核心函数是rfc1867_post_handler,位于main/rfc1867.c中。

3.2 关键处理流程

  1. 边界解析

    boundary = strstr(content_type_dup, "boundary");
    boundary++;
    
  2. 内容解析

    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;
        }
    
  3. 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 解析逻辑

  1. 遇到引号("')时,会检查是否有转义字符\
  2. 如果遇到\",会跳过这两个字符
  3. 最终解析结果为:
    • name = name"; filename=";name='username'
    • 但由于PHP的特殊处理,最终会取最后一个name=的值

四、WAF绕过原理总结

  1. 解析差异

    • WAF可能只解析到第一个name=的值
    • PHP会完整解析整个Content-Disposition头
  2. 转义字符利用

    • 反斜杠\导致WAF和PHP解析不一致
    • WAF可能认为name="name\"是一个完整字段
    • PHP会继续解析后面的filename=";name='username'"
  3. 参数覆盖

    • 构造多个name=参数
    • PHP会取最后一个有效的name=参数

五、防御建议

  1. WAF层面

    • 实现与PHP一致的解析逻辑
    • 对转义字符进行特殊处理
    • 检查整个Content-Disposition头
  2. 代码层面

    • 严格验证输入参数
    • 使用白名单验证参数名
    • 对multipart/form-data请求进行严格解析
  3. 配置层面

    • 限制上传文件类型
    • 设置合理的post_max_sizeupload_max_filesize
    • 启用严格的HTTP头检查

六、扩展利用

  1. 其他转义字符利用

    • 尝试使用不同的转义组合
    • 测试不同编码下的解析差异
  2. 多级参数构造

    Content-Disposition: form-data; name="param1\"; name=\"param2"; filename=";name='actual_param'"
    
  3. 混合编码利用

    • 结合URL编码和转义字符
    • 测试不同字符集的解析差异

通过深入理解PHP的RFC1867实现细节,可以发现WAF与后端解析器之间的差异,从而构造出有效的绕过Payload。防御方需要确保安全设备的解析逻辑与后端应用保持一致。

HTTP包WAF绕过技术深度分析 一、HTTP请求包分析 1.1 原始HTTP请求包 1.2 关键点分析 Content-Disposition字段的特殊构造 : name="name\"; filename=";name='username'" 使用反斜杠 \ 转义了第一个双引号 构造了看似文件上传但实际上传递参数的格式 参数传递机制 : 表面上是文件上传( multipart/form-data ) 实际上是参数传递,最终PHP会解析为 $_POST['username'] = 'admin' WAF绕过原理 : WAF可能只检查 name= 后面的值,而PHP会解析整个字符串 反斜杠转义导致WAF和PHP解析不一致 二、FastCGI协议分析 2.1 FastCGI数据流 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 关键处理流程 边界解析 : 内容解析 : Content-Disposition解析 : 3.3 getword函数分析 关键解析函数 php_ap_getword : 3.4 解析逻辑 遇到引号( " 或 ' )时,会检查是否有转义字符 \ 如果遇到 \" ,会跳过这两个字符 最终解析结果为: name = name"; filename=";name='username' 但由于PHP的特殊处理,最终会取最后一个 name= 的值 四、WAF绕过原理总结 解析差异 : WAF可能只解析到第一个 name= 的值 PHP会完整解析整个Content-Disposition头 转义字符利用 : 反斜杠 \ 导致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头检查 六、扩展利用 其他转义字符利用 : 尝试使用不同的转义组合 测试不同编码下的解析差异 多级参数构造 : 混合编码利用 : 结合URL编码和转义字符 测试不同字符集的解析差异 通过深入理解PHP的RFC1867实现细节,可以发现WAF与后端解析器之间的差异,从而构造出有效的绕过Payload。防御方需要确保安全设备的解析逻辑与后端应用保持一致。