Opened 4 years ago
Closed 12 months ago
#1724 closed enhancement (fixed)
Nginx doesn't sanitize and is inconsistent with multiple, repeated input headers
|Reported by:||Owned by:|
|uname -a:||Linux 186b85fdedd6 4.20.6-100.fc28.x86_64 #1 SMP Thu Jan 31 15:51:26 UTC 2019 x86_64 Linux|
nginx version: nginx/1.15.8
built by gcc 8.2.0 (Alpine 8.2.0)
built with OpenSSL 1.1.1a 20 Nov 2018
TLS SNI support enabled
configure arguments: --prefix=/etc/nginx --sbin-path=/usr/sbin/nginx --modules-path=/usr/lib/nginx/modules --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --pid-path=/var/run/nginx.pid --lock-path=/var/run/nginx.lock --http-client-body-temp-path=/var/cache/nginx/client_temp --http-proxy-temp-path=/var/cache/nginx/proxy_temp --http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp --http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp --http-scgi-temp-path=/var/cache/nginx/scgi_temp --user=nginx --group=nginx --with-http_ssl_module --with-http_realip_module --with-http_addition_module --with-http_sub_module --with-http_dav_module --with-http_flv_module --with-http_mp4_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_random_index_module --with-http_secure_link_module --with-http_stub_status_module --with-http_auth_request_module --with-http_xslt_module=dynamic --with-http_image_filter_module=dynamic --with-http_geoip_module=dynamic --with-threads --with-stream --with-stream_ssl_module --with-stream_ssl_preread_module --with-stream_realip_module --with-stream_geoip_module=dynamic --with-http_slice_module --with-mail --with-mail_ssl_module --with-compat --with-file-aio --with-http_v2_module
I understand this is not supported by HTTP specification, but nginx behavior on multiple, repeated headers is inconsistent, possibly leading to errors and attack vectors.
If a client sends a repeated HTTP header, internally nginx uses the first incoming header, but forwards the last one to the backend. For example, if a client sends a request like this:
GET / HTTP/1.1 Host: myserver.com Host: notmyserver.com
The myserver.com will be accepted and parsed as the $server_name, but backend servers (tested with php-fpm on fgci_pass) actually receive notmyserver.com.
Still, if I manually set it:
fastcgi_param HOST_NAME $host; # or fastcgi_param HOST_NAME $http_host;
The correct host (first one) will be sent to backend.
The same odd behavior is found on other repeated http headers (like content-length), possibly leading to other attack vectors.
Tested on docker nginx-alpine:latest
Change History (7)
comment:1 by , 4 years ago
|Type:||defect → enhancement|
comment:2 by , 4 years ago
For the record, multiple
Host headers are allowed since nginx 0.7.0 (revision b9de93d804ea):
*) Change: now nginx allows several "Host" request header line.
These were allowed as a workaround for broken clients, specifically some Motorola phones, which used to sent two
Host headers, with
localhost in the second one (see here, in Russian).
comment:3 by , 20 months ago
For the record, multiple
Host headers were disabled in 4f18393a1d51 (nginx 1.17.9).
comment:7 by , 12 months ago
|Status:||new → closed|
Fixed, thanks for prodding.
Internally, nginx will always use the first Host header field received (much like it will prefer host from the request line, if available). When passing HTTP headers to a FastCGI application it will, however, pass all the headers it received. It is up to the application to handle these headers.
To improve things, we may consider rejecting requests with duplicate
Hostheaders. This is also explicitly required by RFC 7230:
Also, we probably also need to reject requests with Host header which does not match host from the request line, since without such a check it will be trivial to provide arbitrary HTTP_HOST to an application anyway.
As for other repeated HTTP headers, this actually depends on the header. In particular, duplicate Content-Length headers are explicitly rejected.