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
OpenPGP_signature.asc
Description: OpenPGP digital signature
