diff --git a/README.md b/README.md index 0b6229e..0ca08d7 100644 --- a/README.md +++ b/README.md @@ -369,6 +369,7 @@ By default, `nginx-proxy` forwards all incoming request headers from the client * `Proxy`: Always removed if present. This prevents attackers from using the so-called [httpoxy attack](http://httpoxy.org). There is no legitimate reason for a client to send this header, and there are many vulnerable languages / platforms (`CVE-2016-5385`, `CVE-2016-5386`, `CVE-2016-5387`, `CVE-2016-5388`, `CVE-2016-1000109`, `CVE-2016-1000110`, `CERT-VU#797896`). * `X-Real-IP`: Set to the client's IP address. * `X-Forwarded-For`: The client's IP address is appended to the value provided by the client. (If the client did not provide this header, it is set to the client's IP address.) + * `X-Forwarded-Host`: If the client did not provide this header or if the `TRUST_DOWNSTREAM_PROXY` environment variable is set to `false` (see below), this is set to the value of the `Host` header provided by the client. Otherwise, the header is forwarded to the backend server unmodified. * `X-Forwarded-Proto`: If the client did not provide this header or if the `TRUST_DOWNSTREAM_PROXY` environment variable is set to `false` (see below), this is set to `http` for plain HTTP connections and `https` for TLS connections. Otherwise, the header is forwarded to the backend server unmodified. * `X-Forwarded-Ssl`: Set to `on` if the `X-Forwarded-Proto` header sent to the backend server is `https`, otherwise set to `off`. * `X-Forwarded-Port`: If the client did not provide this header or if the `TRUST_DOWNSTREAM_PROXY` environment variable is set to `false` (see below), this is set to the port of the server that accepted the client's request. Otherwise, the header is forwarded to the backend server unmodified. @@ -376,7 +377,7 @@ By default, `nginx-proxy` forwards all incoming request headers from the client #### Trusting Downstream Proxy Headers -For legacy compatibility reasons, `nginx-proxy` forwards any client-supplied `X-Forwarded-Proto` (which affects the value of `X-Forwarded-Ssl`) and `X-Forwarded-Port` headers unchecked and unmodified. To prevent malicious clients from spoofing the protocol or port that is perceived by your backend server, you are encouraged to set the `TRUST_DOWNSTREAM_PROXY` value to `false` if: +For legacy compatibility reasons, `nginx-proxy` forwards any client-supplied `X-Forwarded-Proto` (which affects the value of `X-Forwarded-Ssl`), `X-Forwarded-Host`, and `X-Forwarded-Port` headers unchecked and unmodified. To prevent malicious clients from spoofing the protocol, hostname, or port that is perceived by your backend server, you are encouraged to set the `TRUST_DOWNSTREAM_PROXY` value to `false` if: * you do not operate a second reverse proxy downstream of `nginx-proxy`, or * you do operate a second reverse proxy downstream of `nginx-proxy` but that proxy forwards those headers unchecked from untrusted clients. @@ -400,6 +401,7 @@ proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $proxy_connection; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; +proxy_set_header X-Forwarded-Host $proxy_x_forwarded_host; proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto; proxy_set_header X-Forwarded-Ssl $proxy_x_forwarded_ssl; proxy_set_header X-Forwarded-Port $proxy_x_forwarded_port; diff --git a/nginx.tmpl b/nginx.tmpl index eaf4121..e7d77c9 100644 --- a/nginx.tmpl +++ b/nginx.tmpl @@ -155,6 +155,11 @@ map $http_x_forwarded_proto $proxy_x_forwarded_proto { '' $scheme; } +map $http_x_forwarded_host $proxy_x_forwarded_host { + default {{ if $trust_downstream_proxy }}$http_x_forwarded_host{{ else }}$http_host{{ end }}; + '' $http_host; +} + # If we receive X-Forwarded-Port, pass it through; otherwise, pass along the # server port the client connected to map $http_x_forwarded_port $proxy_x_forwarded_port { @@ -212,6 +217,7 @@ proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $proxy_connection; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; +proxy_set_header X-Forwarded-Host $proxy_x_forwarded_host; proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto; proxy_set_header X-Forwarded-Ssl $proxy_x_forwarded_ssl; proxy_set_header X-Forwarded-Port $proxy_x_forwarded_port; diff --git a/test/test_headers/test_http.py b/test/test_headers/test_http.py index 5983a10..739b455 100644 --- a/test/test_headers/test_http.py +++ b/test/test_headers/test_http.py @@ -30,6 +30,19 @@ def test_X_Forwarded_Proto_is_passed_on(docker_compose, nginxproxy): assert "X-Forwarded-Proto: f00\n" in r.text +##### Testing the handling of X-Forwarded-Host ##### + +def test_X_Forwarded_Host_is_generated(docker_compose, nginxproxy): + r = nginxproxy.get("http://web.nginx-proxy.tld/headers") + assert r.status_code == 200 + assert "X-Forwarded-Host: web.nginx-proxy.tld\n" in r.text + +def test_X_Forwarded_Host_is_passed_on(docker_compose, nginxproxy): + r = nginxproxy.get("http://web.nginx-proxy.tld/headers", headers={'X-Forwarded-Host': 'example.com'}) + assert r.status_code == 200 + assert "X-Forwarded-Host: example.com\n" in r.text + + ##### Testing the handling of X-Forwarded-Port ##### def test_X_Forwarded_Port_is_generated(docker_compose, nginxproxy): diff --git a/test/test_headers/test_https.py b/test/test_headers/test_https.py index c5457c4..7a428ae 100644 --- a/test/test_headers/test_https.py +++ b/test/test_headers/test_https.py @@ -33,6 +33,19 @@ def test_X_Forwarded_Proto_is_passed_on(docker_compose, nginxproxy): assert "X-Forwarded-Proto: f00\n" in r.text +##### Testing the handling of X-Forwarded-Host ##### + +def test_X_Forwarded_Host_is_generated(docker_compose, nginxproxy): + r = nginxproxy.get("https://web.nginx-proxy.tld/headers") + assert r.status_code == 200 + assert "X-Forwarded-Host: web.nginx-proxy.tld\n" in r.text + +def test_X_Forwarded_Host_is_passed_on(docker_compose, nginxproxy): + r = nginxproxy.get("https://web.nginx-proxy.tld/headers", headers={'X-Forwarded-Host': 'example.com'}) + assert r.status_code == 200 + assert "X-Forwarded-Host: example.com\n" in r.text + + ##### Testing the handling of X-Forwarded-Port ##### def test_X_Forwarded_Port_is_generated(docker_compose, nginxproxy): diff --git a/test/test_trust-downstream-proxy/test_default.py b/test/test_trust-downstream-proxy/test_default.py index 456d07a..f56c406 100644 --- a/test/test_trust-downstream-proxy/test_default.py +++ b/test/test_trust-downstream-proxy/test_default.py @@ -8,6 +8,11 @@ import re ('https://web.nginx-proxy.tld/headers', 'X-Forwarded-Proto', None, 'https'), ('https://web.nginx-proxy.tld/headers', 'X-Forwarded-Proto', 'f00', 'f00'), + ('http://web.nginx-proxy.tld/headers', 'X-Forwarded-Host', None, 'web.nginx-proxy.tld'), + ('http://web.nginx-proxy.tld/headers', 'X-Forwarded-Host', 'example.com', 'example.com'), + ('https://web.nginx-proxy.tld/headers', 'X-Forwarded-Host', None, 'web.nginx-proxy.tld'), + ('https://web.nginx-proxy.tld/headers', 'X-Forwarded-Host', 'example.com', 'example.com'), + ('http://web.nginx-proxy.tld/headers', 'X-Forwarded-Port', None, '80'), ('http://web.nginx-proxy.tld/headers', 'X-Forwarded-Port', '1234', '1234'), ('https://web.nginx-proxy.tld/headers', 'X-Forwarded-Port', None, '443'), diff --git a/test/test_trust-downstream-proxy/test_disabled.py b/test/test_trust-downstream-proxy/test_disabled.py index bc9684f..88c8054 100644 --- a/test/test_trust-downstream-proxy/test_disabled.py +++ b/test/test_trust-downstream-proxy/test_disabled.py @@ -8,6 +8,11 @@ import re ('https://web.nginx-proxy.tld/headers', 'X-Forwarded-Proto', None, 'https'), ('https://web.nginx-proxy.tld/headers', 'X-Forwarded-Proto', 'f00', 'https'), + ('http://web.nginx-proxy.tld/headers', 'X-Forwarded-Host', None, 'web.nginx-proxy.tld'), + ('http://web.nginx-proxy.tld/headers', 'X-Forwarded-Host', 'example.com', 'web.nginx-proxy.tld'), + ('https://web.nginx-proxy.tld/headers', 'X-Forwarded-Host', None, 'web.nginx-proxy.tld'), + ('https://web.nginx-proxy.tld/headers', 'X-Forwarded-Host', 'example.com', 'web.nginx-proxy.tld'), + ('http://web.nginx-proxy.tld/headers', 'X-Forwarded-Port', None, '80'), ('http://web.nginx-proxy.tld/headers', 'X-Forwarded-Port', '1234', '80'), ('https://web.nginx-proxy.tld/headers', 'X-Forwarded-Port', None, '443'), diff --git a/test/test_trust-downstream-proxy/test_enabled.py b/test/test_trust-downstream-proxy/test_enabled.py index 456d07a..f56c406 100644 --- a/test/test_trust-downstream-proxy/test_enabled.py +++ b/test/test_trust-downstream-proxy/test_enabled.py @@ -8,6 +8,11 @@ import re ('https://web.nginx-proxy.tld/headers', 'X-Forwarded-Proto', None, 'https'), ('https://web.nginx-proxy.tld/headers', 'X-Forwarded-Proto', 'f00', 'f00'), + ('http://web.nginx-proxy.tld/headers', 'X-Forwarded-Host', None, 'web.nginx-proxy.tld'), + ('http://web.nginx-proxy.tld/headers', 'X-Forwarded-Host', 'example.com', 'example.com'), + ('https://web.nginx-proxy.tld/headers', 'X-Forwarded-Host', None, 'web.nginx-proxy.tld'), + ('https://web.nginx-proxy.tld/headers', 'X-Forwarded-Host', 'example.com', 'example.com'), + ('http://web.nginx-proxy.tld/headers', 'X-Forwarded-Port', None, '80'), ('http://web.nginx-proxy.tld/headers', 'X-Forwarded-Port', '1234', '1234'), ('https://web.nginx-proxy.tld/headers', 'X-Forwarded-Port', None, '443'),