Host ambiguity could be a problem as well when using FastCGI,
SCGI or uWSGI before NGINX v1.29.5. This is fixed in
NGINX v1.29.5 and mitigated in Debian packages
(eg. 1.26.3-3+deb13u5).

For reference, here is official NGINX fastcgi_params before v1.29.5:

    fastcgi_param  QUERY_STRING       $query_string;
    fastcgi_param  REQUEST_METHOD     $request_method;
    fastcgi_param  CONTENT_TYPE       $content_type;
    fastcgi_param  CONTENT_LENGTH     $content_length;

    fastcgi_param  SCRIPT_NAME        $fastcgi_script_name;
    fastcgi_param  REQUEST_URI        $request_uri;
    fastcgi_param  DOCUMENT_URI       $document_uri;
    fastcgi_param  DOCUMENT_ROOT      $document_root;
    fastcgi_param  SERVER_PROTOCOL    $server_protocol;
    fastcgi_param  REQUEST_SCHEME     $scheme;
    fastcgi_param  HTTPS              $https if_not_empty;

    fastcgi_param  GATEWAY_INTERFACE  CGI/1.1;
    fastcgi_param  SERVER_SOFTWARE    nginx/$nginx_version;

    fastcgi_param  REMOTE_ADDR        $remote_addr;
    fastcgi_param  REMOTE_PORT        $remote_port;
    fastcgi_param  REMOTE_USER        $remote_user;
    fastcgi_param  SERVER_ADDR        $server_addr;
    fastcgi_param  SERVER_PORT        $server_port;
    fastcgi_param  SERVER_NAME        $server_name;

    # PHP only, required if PHP was built with --enable-force-cgi-redirect
    fastcgi_param  REDIRECT_STATUS    200

In this case as well, an attacker can set a HTTP_HOST parameter
which is not consistent with the value which was used for virtual
host routing.

For the following host-ambiguous HTTP request:

    GET http://foo/ HTTP/1.1
    User-Agent: UA
    Host: bar

We have the following FastCGI variables before NGINX v1.29.5:

    SERVER_NAME       = foo
    HTTP_HOST         = bar <- from Host header field

We have the following FastCGI variables since NGINX v1.29.5:

    SERVER_NAME       = foo
    HTTP_HOST         = foo <- from request line

See this change:

Changes with nginx 1.29.5 04 Feb 2026
  [...]

  *) Bugfix: fixed setting HTTP_HOST when proxying to FastCGI, SCGI, and
      uwsgi backends.

  [...]

Relevant commit:

commit 71b18973b2b5ea29ed27b47fc0e619b4df533b60
Author: Andrew Clayton <[email protected]>
Date:   Sat Dec 13 07:05:27 2025 +0000

FastCGI: ensure HTTP_HOST is set to the requested target host.

Previously, the HTTP_HOST environment variable was constructed from the
Host request header field, which doesn't work well with HTTP/2 and
HTTP/3 where Host may be supplanted by the ":authority" pseudo-header
field per RFC 9110, section 7.2. Also, it might give an incorrect
HTTP_HOST value from HTTP/1.x requests given in the absolute form, in
which case the Host header must be ignored by the server, per RFC 9112,
section 3.2.2.

The fix is to redefine the HTTP_HOST default from a protocol-specific
value given in the $host variable. This will now use the Host request
header field, ":authority" pseudo-header field, or request line target
URI depending on request HTTP version.

Also the CGI specification (RFC 3875, 4.1.18) notes

    The server SHOULD set meta-variables specific to the protocol and
    scheme for the request. Interpretation of protocol-specific
    variables depends on the protocol version in SERVER_PROTOCOL.

Closes: https://github.com/nginx/nginx/issues/256
Closes: https://github.com/nginx/nginx/issues/455
Closes: https://github.com/nginx/nginx/issues/912
    Closes: https://github.com/nginx/nginx/issues/912

Similar commits exist for uWSGI and SCGI (but bot for
ngx_http_proxy_module).

We can consider that the application code should rely on
SERVER_NAME (not HTTP_HOST according) to the CGI
specification [3]. However,

* the usage of HTTP_HOST in application code is quite prevalent
  as well;
* SERVER_NAME/$server_name does not really do what we want
  when using wildcard server names.

For example the URL reconstruction algorithm documented in
WSGI [1] is:

    from urllib.parse import quote
    url = environ['wsgi.url_scheme']+'://'

    if environ.get('HTTP_HOST'):
        url += environ['HTTP_HOST']
    else:
        url += environ['SERVER_NAME']

        if environ['wsgi.url_scheme'] == 'https':
            if environ['SERVER_PORT'] != '443':
               url += ':' + environ['SERVER_PORT']
        else:
            if environ['SERVER_PORT'] != '80':
               url += ':' + environ['SERVER_PORT']

    url += quote(environ.get('SCRIPT_NAME', ''))
    url += quote(environ.get('PATH_INFO', ''))
    if environ.get('QUERY_STRING'):
        url += '?' + environ['QUERY_STRING']

With this algorithm, HTTP_HOST takes precedence over SERVER_NAME.
The reconstructed URL is therefore vulnerable to host ambiguity
requests.

Similarly PSGI [2] says:

> SERVER_NAME, SERVER_PORT: When combined with SCRIPT_NAME and
> PATH_INFO, these keys can be used to complete the URL.
> Note, however, that HTTP_HOST, if present, should be used in
> preference to SERVER_NAME for reconstructing the request URL.
> SERVER_NAME and SERVER_PORT MUST NOT be empty strings, and are
> always required.

Debian uses the following workaround for older versions of NGINX:

    fastcgi_param  HTTP_HOST        $host;

[1] https://peps.python.org/pep-3333/#url-reconstruction
[2] https://github.com/plack/psgi-specs/blob/master/PSGI.pod
[3] https://datatracker.ietf.org/doc/html/rfc3875#section-4.1.14

Regards,

--
Gabriel Corona

Attachment: OpenPGP_signature.asc
Description: OpenPGP digital signature

Reply via email to