Opened 6 years ago

Closed 3 years ago

#1724 closed enhancement (fixed)

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

comment:1 by Maxim Dounin, 6 years ago

Type: defectenhancement

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 by Maxim Dounin, 6 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 Maxim Dounin, 3 years ago

For the record, multiple Host headers were disabled in 4f18393a1d51 (nginx 1.17.9).

comment:4 by Maxim Dounin <mdounin@…>, 3 years ago

In 8020:f8f6b9fee66a/nginx:

FastCGI: combining headers with identical names (ticket #1724).

FastCGI responder is expected to receive CGI/1.1 environment variables
in the parameters (see section "6.2 Responder" of the FastCGI specification).
Obviously enough, there cannot be multiple environment variables with
the same name.

Further, CGI specification (RFC 3875, section "4.1.18. Protocol-Specific
Meta-Variables") explicitly requires to combine headers: "If multiple
header fields with the same field-name are received then the server MUST
rewrite them as a single value having the same semantics".

comment:5 by Maxim Dounin <mdounin@…>, 3 years ago

In 8021:75af96daee97/nginx:

SCGI: combining headers with identical names (ticket #1724).

SCGI specification explicitly forbids headers with duplicate names
(section "3. Request Format"): "Duplicate names are not allowed in
the headers".

Further, provided headers are expected to follow CGI specification,
which also requires to combine headers (RFC 3875, section "4.1.18.
Protocol-Specific Meta-Variables"): "If multiple header fields with
the same field-name are received then the server MUST rewrite them
as a single value having the same semantics".

comment:6 by Maxim Dounin <mdounin@…>, 3 years ago

In 8022:8b7a96fdd54c/nginx:

Uwsgi: combining headers with identical names (ticket #1724).

The uwsgi specification states that "The uwsgi block vars represent a
dictionary/hash". This implies that no duplicate headers are expected.

Further, provided headers are expected to follow CGI specification,
which also requires to combine headers (RFC 3875, section "4.1.18.
Protocol-Specific Meta-Variables"): "If multiple header fields with
the same field-name are received then the server MUST rewrite them
as a single value having the same semantics".

comment:7 by Maxim Dounin, 3 years ago

Resolution: fixed
Status: newclosed

Fixed, thanks for prodding.

Note: See TracTickets for help on using tickets.