#2329 closed defect (wontfix)
Unexpected request routing when Host header value contains colon
Reported by: | Pēteris Caune | Owned by: | |
---|---|---|---|
Priority: | minor | Milestone: | |
Component: | nginx-core | Version: | 1.18.x |
Keywords: | Cc: | ||
uname -a: | Linux ubuntu-impish 5.13.0-28-generic #31-Ubuntu SMP Thu Jan 13 17:41:06 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux | ||
nginx -V: |
nginx version: nginx/1.18.0 (Ubuntu)
built with OpenSSL 1.1.1l 24 Aug 2021 TLS SNI support enabled configure arguments: --with-cc-opt='-g -O2 -ffile-prefix-map=/build/nginx-wlVHrx/nginx-1.18.0=. -flto=auto -ffat-lto-objects -fstack-protector-strong -Wformat -Werror=format-security -fPIC -Wdate-time -D_FORTIFY_SOURCE=2' --with-ld-opt='-Wl,-Bsymbolic-functions -flto=auto -Wl,-z,relro -Wl,-z,now -fPIC' --prefix=/usr/share/nginx --conf-path=/etc/nginx/nginx.conf --http-log-path=/var/log/nginx/access.log --error-log-path=/var/log/nginx/error.log --lock-path=/var/lock/nginx.lock --pid-path=/run/nginx.pid --modules-path=/usr/lib/nginx/modules --http-client-body-temp-path=/var/lib/nginx/body --http-fastcgi-temp-path=/var/lib/nginx/fastcgi --http-proxy-temp-path=/var/lib/nginx/proxy --http-scgi-temp-path=/var/lib/nginx/scgi --http-uwsgi-temp-path=/var/lib/nginx/uwsgi --with-compat --with-debug --with-pcre-jit --with-http_ssl_module --with-http_stub_status_module --with-http_realip_module --with-http_auth_request_module --with-http_v2_module --with-http_dav_module --with-http_slice_module --with-threads --add-dynamic-module=/build/nginx-wlVHrx/nginx-1.18.0/debian/modules/http-geoip2 --with-http_addition_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_sub_module |
Description
Here's my nginx.conf:
user www-data; worker_processes auto; pid /run/nginx.pid; include /etc/nginx/modules-enabled/*.conf; events { worker_connections 768; } http { include /etc/nginx/mime.types; default_type application/octet-stream; error_log /var/log/nginx/error.log; server { listen 80; server_name foo.com; location / { default_type text/plain; return 200 "hello from foo.com"; } error_page 500 502 503 504 /500.html; } server { listen 80 default_server; server_name _; location / { return 403 "sorry"; } } }
I'm expecting the server to return "sorry" if the "Host" header is anything but "foo.com".
Somebody's apparently running Burp Suite on my server, and I noticed and interesting behavior when they send a "Host: foo.com:more-stuff-here" header: NGINX routes the request to the first "server" section. It looks as if it ignores the colon and everything after it in the header value.
I can reproduce it locally with the above nginx.conf:
$ curl -H "Host: foo.com" http://127.0.0.1 hello from foo.com $ curl -H "Host: foo.com:z" http://127.0.0.1 hello from foo.com $ curl -H "Host: foo.comz" http://127.0.0.1 sorry
Why does NGINX do this? Is this an expected behavior? What should I change in nginx.conf to ensure requests with "Host: foo.com:more-stuff-here" header go to the default block?
Change History (3)
comment:1 by , 3 years ago
Resolution: | → wontfix |
---|---|
Status: | new → closed |
comment:2 by , 3 years ago
If you think this can be beneficial, please elaborate why do you think so.
I'll add a little context about my particular situation. I'm using NGINX as a reverse proxy in front of a Django application. In production mode (DEBUG=False), Django checks the "Host" header against an allowlist (the ALLOWED_HOSTS
setting). It matches the hostname only, and ignores the port part. *But*, it looks like it still requires the port to be a numeric value. So, with ALLOWED_HOSTS=["foo.com"]
, Django will accept Host: foo.com:123
and Host: foo.com:456
but it will reject Host: foo.com:abc
.
When a client makes a request with a "Host: foo.com:abc" header, NGINX passes the request to the Django app, and the Django app returns HTTP 400 (and logs a warning). This is fine, but my intuition was NGINX would act as a "shield" in front of the backend app, making sure only valid, well-formed requests go through to the backend app. And I expected a Host header value "foo.com:abc" would not be counted as valid.
ALLOWED_HOSTS documentation: https://docs.djangoproject.com/en/4.0/ref/settings/#allowed-hosts
comment:3 by , 3 years ago
Thanks for the details. In this particular case Django's syntax checking is more strict than nginx one, though it does not seem to be something important.
Anything after the
:
is considered to be the optional TCP port (see RFC 7230, section "5.4. Host") and ignored by nginx, as server_name is only used to match names, and not ports.Note that accepting only
Host: foo.com
is incorrect, as at leastHost: foo.com:80
is perfectly valid for your particular configuration and can be used by legitimate clients. Further, other port numbers might be valid as well, depending on the TCP port forwarding being used.If you nevertheless want to additionally limit requests accepted, you can do so with additional checking of the
$http_host
variable with the rewrite module. Note though that there can be requests without theHost
header at all, assuming the host name is provided in the request line, following the absolute form of the request line (or without theHost
header at all in HTTP/1.0, but these will end up in the default server block).In theory, the code can be changed to perform stricter syntax checking and only allow digits and not arbitrary characters in the port, it is not clear why it can be needed/useful (as syntactically valid port numbers have to be accepted anyway, see above). If you think this can be beneficial, please elaborate why do you think so.