Follow @Openwall on Twitter for new release announcements and other news
[<prev] [next>] [day] [month] [year] [list]
Message-ID: <057cf50f332e77a59bd38bcb459f7104@free.fr>
Date: Thu, 21 May 2026 13:59:20 +0200
From: gabriel.corona@...e.fr
To: oss-security@...ts.openwall.com
Subject: Host ambiguous requests through NGINX $host and Debian's proxy_params

When receiving a HTTP/1.x request, NGINX does not check that the 
authority
coming from the request line (if present) is consistent with the 
authority
from the Host header field. In some deployments (when $http_host is 
used)
an attacker could exploit this ambiguity to bypass security restrictions
configured in NGINX (of have other security impacts).

Example:

    GET http://foo/ HTTP/1.1 <- Used for virtual host routing
    User-Agent: UA
    Host: bar                <- Used for $http_host

Debian's proxy_param file used to use $http_host:

    proxy_set_header Host $http_host;

This has been fixed as part of Debian bug #1126960 [1].

In this case, "foo" is used for virtual host routing but the request is
forwarded as:

    GET / HTTP/1.1
    User-Agent: UA
    Host: bar
    X-Real-IP: ...
    X-Forwarded-For: ...
    X-Forwarded-Proto: ...

If the backend application actually makes use of the Host value, this
might have a security impact. For example,

* if the backend application uses the Host value for tenant dispatching;
* and NGINX is configured with per-tenant configuration (eg. 
authorization,
  logging, etc),

then this might have a security impact.

Example NGINX configuration:

  server {
      listen 443 ssl;
      server_name tenant1;
      ssl_certificate           /etc/nginx/ssl/tenant1.crt;
      ssl_certificate_key       /etc/nginx/ssl/tenant1.key;
      location / {
        proxy_pass http://backend;
        include proxy_params;
      }
  }
  server {
      listen 443 ssl;
      server_name host2;
      ssl_certificate           /etc/nginx/ssl/tenant2.crt;
      ssl_certificate_key       /etc/nginx/ssl/tenant2.key;
      location / {
        allow 10.0.0.0/8;
        allow 192.168.0.0/16;
        allow 127.0.0.1/8;
        deny all;
        proxy_pass http://backend;
        include proxy_params;
      }
  }

In this example, tenant2 is expected to be only available from
private IP addresses. However, an attacker could target tenant2
with:

    GET http://tenant1/ HTTP/1.1 <- Used for virtual host routing
    User-Agent: UA
    Host: tenant2                <- Used for $http_host

Another potential application, would be to send access logs
of an attack to the log files of the wrong tenant.

You might be impacted if:

* NGINX is directly exposed;
* you use $http_host (eg. through Debian's proxy_params).

NGINX's position
----------------

NGINX's position is that the bug is to use $http host. NGINX's
documentation recommends using $host [2]:

    proxy_set_header Host $host;

I would claim that NGINX could (should?):

1. either reject host ambiguous requests altogether
  (because they are attacks right?);
2. or override the HTTP header with the value from the request line.

These behaviors are consistent with what other (open-soruce)
HTTP server do:

1. NGINX when using HTTP/2, HA proxy for solution 1;
2. Traefik, Caddy, Apache HTTPD for solution 2.

At the very least, the security impact of using $host vs
$http_host should be better documented.

Mitigations
-----------

Mitigation 1: always use `$host` instead of `$http_host`:

    proxy_set_header Host             $host;
    proxy_set_header X-Forwarded-Host $host;

Mitigation 1b: always use `$server_name` instead of `$http_host`:

    proxy_set_header Host             $server_name;
    proxy_set_header X-Forwarded-Host $server_name;

Mitigation 1c: always the hardcoded expected hostname instead of 
`$http_host`.

    proxy_set_header Host             "www.example.com";
    proxy_set_header X-Forwarded-Host "www.example.com";

Mitigation 2: use NGINX directive to reject ambiguous (malicious) 
requests.

    # Does not work when using ports:
    if ($host != $http_host) {
        return 421 "Ambiguous host";
    }

    # Since nginx 1.29.3:
    if ($http_host != "$host$is_request_port$request_port") {
        return 421 "Ambiguous host";
    }

Debian info
-----------

Snippet from Debian changelogs:

    nginx (1.26.3-3+deb13u4) trixie; urgency=medium
    
      * d/conf/*_params: use "$host" instead of "$http_host"
        * "$http_host" forwards the Host header exactly as supplied by 
the client
          and may not match the effective request target (e.g. 
absolute-form
          requests with a conflicting Host header)
          this can expose inconsistent or attacker-controlled host 
values to
          backend applications (uwsgi, fastcgi, scgi, proxy)
        * switch to "$host" as a safer, normalized alternative
        * note: this changes behaviour, as "$host" does not preserve the
          client-supplied port; deployments relying on "$http_host" 
including
          a port number may be affected
        * it is workaround for Debian bug #1126960 for stable/oldstable 
release
    
     -- Jan Mojžíš <janmojzis@...ian.org>  Mon, 20 Apr 2026 17:52:06 
+0000

New proxy_params file:

    # !!! Security workaround !!!
    # Do not set the `Host` header as "$http_host".
    #
    # "$http_host" is the Host header exactly as supplied by the client.
    # This is unsafe when a client sends an absolute-form request target 
together
    # with a different Host header, for example:
    #
    #     GET https://example.com/ HTTP/1.1
    #     Host: malformedhost
    #
    # In such a case, passing "$http_host" upstream exposes the raw 
client-supplied
    # Host value ("malformedhost") to the backend application, even 
though it does
    # not match the effective request target. Applications often use 
HTTP_HOST for
    # redirects, absolute URL generation, virtual host routing, or 
security checks;
    # forwarding the raw Host header can therefore lead to incorrect or 
unsafe
    # behaviour.
    #
    # Newer nginx versions (since 1.30.0) introduce variables 
"$is_request_port" and
    # "$request_port", allowing `Host` to be constructed as:
    #     $host$is_request_port$request_port
    #
    # In stable/oldstable packages we use "$host" as a security 
workaround.
    # It avoids forwarding an untrusted raw Host header to the backend.
    #
    # Note: this changes behaviour compared to previous versions, 
because "$host"
    # does not preserve the client-supplied port, while "$http_host" 
typically
    # does. Existing deployments that rely on "$http_host" containing a 
port number
    # may therefore break or behave differently after this change.
    
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;

[1] https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1126960
[2] https://docs.nginx.com/nginx/admin-guide/web-server/reverse-proxy/
[3] 
https://metadata.ftp-master.debian.org/changelogs//main/n/nginx/nginx_1.26.3-3+deb13u5_changelog

Regard,

-- 
Gabriel Corona

Powered by blists - more mailing lists

Please check out the Open Source Software Security Wiki, which is counterpart to this mailing list.

Confused about mailing lists and their use? Read about mailing lists on Wikipedia and check out these guidelines on proper formatting of your messages.