参考文章:

URL 特殊字符处理的不一致

Nginx 在检查 URI-based 规则前会执行 path normalization 以防止安全问题。Path normalization 是将请求的 URL 转换和标准化为一致、规范格式的过程。

它包括移除 URL 路径中的冗余或不必要元素,例如 extra slashes、dot segments,处理 path traversal 和 URL-encoded characters,以确保 web 服务器内的统一性和正确路由。

Nginx 负载均衡与后端技术一样,解析 URL 前会执行 Normalization 操作,这包含 trim() 函数操作,但不同语言下的 trim() 函数移除的特殊字符不同,这就导致了 URL 解析不一致性:传入相同的 URL,Normalization 后欲解析的 URL 可能不同。

trim 函数的核心作用:移除字符串首尾(开头和/或结尾)的指定字符,最常见的是去除空白字符(如空格、制表符 \t、换行符 \n 等)。它的存在是为了标准化和清理字符串数据,提高程序处理数据的准确性与一致性。

利用这种不同性,就可以绕过 Nginx 的路由规则。

Flask

Flask removes the characters \x85, \xA0, \x1F, \x1E, \x1D, \x1C, \x0C, \x0B, and \x09 from the URL path, but NGINX doesn’t.

Take the following nginx configuration/API source code as a reference:

location = /admin {
    deny all;
}

location = /admin/ {
    deny all;
}
@app.route('/admin', methods = ['GET'])
def admin():
    data = {"url":request.url, "admin":"True"}

    return Response(str(data), mimetype="application/json")

由于 Nginx 并不会在解析时将末尾的 \x85, \xA0, \x1F, \x1E, \x1D, \x1C, \x0C, \x0B, \x09 移除,所以例如:当传入 /admin\x85 (后接不可见的 hex 字符,此处仅演示,下同)并不会匹配 /admin 的 deny all 规则,路由就会被传递给 Flask 后端;但 Flask 会移除 \x85 字符,从而按 /admin 路由,进入到 admin 页面。由此,URL 解析的不一致性就被利用来绕过 Nginx 路由配置的 WAF。

具体 Payload:

Flask URL 解析不一致示例

点击图片可查看完整电子表格

Node.JS

Node.js URL 解析不一致示例

点击图片可查看完整电子表格

Spring Boot

Spring Boot URL 解析不一致示例

点击图片可查看完整电子表格

非法路由处理的不一致

When two .php files are in the same pathname of the HTTP request, PHP will match the first one, ignoring everything after the slash. Since the Nginx is configured to block requests to the specific endpoint /admin.php, it’s possible to access the admin.php file by doing the following request:

PHP 非法路由处理示例

源笔记:结构化笔记/不一致性/URL 解析不一致性.md