Opened 4 months ago

Last modified 4 months ago

#1724 new enhancement

Nginx doesn't sanitize and is inconsistent with multiple, repeated input headers

Reported by: capile@… Owned by:
Priority: minor Milestone:
Component: nginx-core Version: 1.15.x
Keywords: http header Cc:
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 -V: 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

Description

Ola,

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

Cheers,

Guilherme Capilé

Change History (2)

comment:1 Changed 4 months ago by mdounin

  • Type changed from defect to enhancement

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.

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 Host headers. This is also explicitly required by RFC 7230:

   A server MUST respond with a 400 (Bad Request) status code to any
   HTTP/1.1 request message that lacks a Host header field and to any
   request message that contains more than one Host header field or a
   Host header field with an invalid field-value.

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.

The same odd behavior is found on other repeated http headers (like content-length), possibly leading to other attack vectors.

As for other repeated HTTP headers, this actually depends on the header. In particular, duplicate Content-Length headers are explicitly rejected.

comment:2 Changed 4 months ago by mdounin

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).

Note: See TracTickets for help on using tickets.