Host header injection when redirecting from http to https

Hello!

I have a site with a custom domain and https enabled. My issue is that when a malicious user enters the http site and sets the host header to something other than the custom domain (for example evildomain.net), netlify returns a 301 with the Location: evildomain.net. I attempted to add the proper redirects with the proper headers in the _redirect file as well as the netlify.toml file, but that didn’t happen as I believe this redirect is happening before the request reaches the site.

To reproduce:
curl -vvv 'http://jovial-blackwell-e78188.netlify.com' -H 'Host: evildomain.net' -H 'Accept: */*' -H 'Accept-Language: en-US,en;q=0.5' -H 'Accept-Encodeing: gzip, deflate' -H 'Connection: close' -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36' --compressed

Is there anything I can do from a configuration standpoint to change how this site redirects from http to https?

Hi, @zak_matik, the curl command above doesn’t connect to your site at this URL http://jovial-blackwell-e78188.netlify.com.

Also note that this is not a valid URL:

http://jovial-blackwell-e78188.netlify.com

All HTTP URLs must include a path to be valid and the minimum possible path is /. This is the shortest valid URL for that site:

http://jovial-blackwell-e78188.netlify.com/

This is actually not particularly pertinent but I wanted to mention it.

So, what does that curl command do? It looks up the IP address for jovial-blackwell-e78188.netlify.com. Then it connects to that IP address and asks for this URL:

http://evildomain.net/

This is shown in the headers that curl sends:

$ curl -vvv 'http://jovial-blackwell-e78188.netlify.com' -H 'Host: evildomain.net' -H 'Accept: */*' -H 'Accept-Language: en-US,en;q=0.5' -H 'Accept-Encodeing: gzip, deflate' -H 'Connection: close' -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36' --compressed
*   Trying 206.189.73.52...
* TCP_NODELAY set
* Connected to jovial-blackwell-e78188.netlify.com (206.189.73.52) port 80 (#0)
> GET / HTTP/1.1
> Host: evildomain.net
> Accept-Encoding: deflate, gzip
> Accept: */*
> Accept-Language: en-US,en;q=0.5
> Accept-Encodeing: gzip, deflate
> Connection: close
> User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36
>

It connected to the IP address 206.189.73.52 (not to “your site” but an IP address which handles millions of sites) and requested the HTTP response (not HTTPS) for the host evildomain.net at the path of / (and the path is in the GET line below):

> GET / HTTP/1.1

In other words, it didn’t make an HTTP request to your site at all, even if the curl command included your sites domain name due to how curl and HTTP both work.

Note, if you make this an HTTPS request, the response is a 404 status and not a 301 redirect:

$ curl -vvv 'https://jovial-blackwell-e78188.netlify.com' -H 'Host: evildomain.net' -H 'Accept: */*' -H 'Accept-Language: en-US,en;q=0.5' -H 'Accept-Encodeing: gzip, deflate' -H 'Connection: close' -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36' --compressed  2>&1 | egrep '< HTTP'
< HTTP/2 404

To summarize, that curl command is making an HTTP request for a domain name via an IP address of our CDN but for a domain which isn’t actually hosted on our CDN.

If you want rules for your site to apply, you must include your site’s domain name in the Host: header. If you include some other domain name, the HTTP request is no longer happening to your site and Netlify will handle those invalid requests in a general (not site specific) way. (That “way” being we force a redirect to HTTPS and then return a 404 for that URL.)

If there are other questions about this, please let us know.

Hi Luke,

Thanks so much for the detailed reply!

The issue I’m trying to mitigate is Host Header Injection. Unfortunately, with how the http -> https redirect currently works, the headers returned to the requester are

< HTTP/1.1 301 Moved Permanently
< Content-Length: 0
< Content-Type: text/html
< Date: Thu, 11 Jun 2020 16:53:40 GMT
< Location: https://evildomain.net/
< Age: 2
< Connection: close
< Server: Netlify
< X-NF-Request-ID: e0b5ccb8-9f4c-4db7-b155-441dad971dd4-3766499

Since the status code a 301 and the location is the injected host header, this can lead to web cache poisoning or password reset poisoning.

From your response, it seems like there’s nothing I can do from a configuration standpoint to mitigate this issue specifically for my site. Is there some way I could open a ticket or bug report to ask Netlify to change the redirect behavior for the CDN?

Sorry, could you be a bit clearer about what the problem is? From your description, I think we are performing an intended and legitimate redirect, there is no possibility of any injection, but I think I am probably not understanding what you are trying to report, rather than your report being dumb or wrong :slight_smile:
Thanks for your help in improving my understanding!

Hi, no problem, I’ll try to be a little bit clearer with my problem. For some background, I’m looking into the security of our site hosted by Netlify and so I am acting as a malicious user by purposefully setting the host header to something other than the host of my site. The purpose of this is to test to make sure we’re not susceptible to host header injection, as mentioned above.

The issue I have is that Netlify is not behaving in the way that Luke describes above. He says:

If you include some other domain name, the HTTP request is no longer happening to your site and Netlify will handle those invalid requests in a general (not site specific) way. (That “way” being we force a redirect to HTTPS and then return a 404 for that URL.)

Unfortunately, as I mentioned in my last message, netlify doesn’t return a 404 for that URL. Instead, the Netlify server issues a 301 with the location as the injected host header. Because Netlify issues a 301 with the location as the injected host header, it is possible for a malicious user to poison the cache of a web browser so that all further requests to that host are redirected to the malicious domain injected as the host header and returned as the location header by the 301.

The full curl request with request and response headers:

curl -vvv 'http://jovial-blackwell-e78188.netlify.com' -H 'Host: evildomain.net' -H 'Accept: */*' -H 'Accept-Language: en-US,en;q=0.5' -H 'Accept-Encodeing: gzip, deflate' -H 'Connection: close' -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36' --compressed
*   Trying 2604:a880:2:d0::ddf:c001...
* TCP_NODELAY set
* Connected to jovial-blackwell-e78188.netlify.com (2604:a880:2:d0::ddf:c001) port 80 (#0)
> GET / HTTP/1.1
> Host: evildomain.net
> Accept-Encoding: deflate, gzip
> Accept: */*
> Accept-Language: en-US,en;q=0.5
> Accept-Encodeing: gzip, deflate
> Connection: close
> User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36
> 
< HTTP/1.1 301 Moved Permanently
< Content-Length: 0
< Content-Type: text/html
< Date: Sat, 13 Jun 2020 04:18:25 GMT
< Location: https://evildomain.net/
< Age: 0
< Connection: close
< Server: Netlify
< X-NF-Request-ID: 0aa0ef4d-7cc5-4c5d-931c-85e7b6c97d15-5698185
< 
* Closing connection 0

As you can see, the combination of the 301 Moved Permanently status and the location: https://evildomain.net means that the browser will cache this location and cache poisoning is possible.

Please let me know if anything is still unclear or if I’m misunderstanding something about Netlify’s setup.

Thanks!

Hi. @zak_matik, I know you are seeing Netlify return a redirect for a domain which isn’t hosted on our service but that is a side effect of you having already taken control of the local system.

I don’t see any way for this 301 to cause any issues to the local browser cache. Even if it could this would be a non-issue because that system is already compromised. I definitely don’t see any way for this to affect the caching at Netlify or any other computer’s local browser cache. Again though, even for the local system which has been hacked to take control of the Host header, I don’t see the local browsers cache as being affected by this.

First, I want to clarify what I said here:

That “way” being we force a redirect to HTTPS and then return a 404 for that URL.

There is a two step process:

  1. Redirect using a 301 status to HTTPS.
  2. Return a 404 if the domain is not hosted on Netlify.

Your curl command above shows the 301 redirect to HTTPS. Now just change the http in the curl command to https and you will see the 404, like so:

$ curl -vvv 'https://jovial-blackwell-e78188.netlify.com' -H 'Host: evildomain.net' -H 'Accept: */*' -H 'Accept-Language: en-US,en;q=0.5' -H 'Accept-Encodeing: gzip, deflate' -H 'Connection: close' -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36' --compressed
*   Trying 165.227.0.164...
* TCP_NODELAY set
* Connected to jovial-blackwell-e78188.netlify.com (165.227.0.164) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*   CAfile: /etc/ssl/cert.pem
  CApath: none
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES256-GCM-SHA384
* ALPN, server accepted to use h2
* Server certificate:
*  subject: C=US; ST=ca; L=San Francisco; O=Netlify, Inc; CN=*.netlify.com
*  start date: Jul  3 00:00:00 2019 GMT
*  expire date: Jul  7 12:00:00 2020 GMT
*  subjectAltName: host "jovial-blackwell-e78188.netlify.com" matched cert's "*.netlify.com"
*  issuer: C=US; O=DigiCert Inc; CN=DigiCert SHA2 Secure Server CA
*  SSL certificate verify ok.
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x7fc78580f600)
> GET / HTTP/2
> Host: evildomain.net
> Accept-Encoding: deflate, gzip
> Accept: */*
> Accept-Language: en-US,en;q=0.5
> Accept-Encodeing: gzip, deflate
> Connection: close
> User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36
>
* Connection state changed (MAX_CONCURRENT_STREAMS == 150)!
< HTTP/2 404
< cache-control: no-cache
< content-length: 9
< content-type: text/plain; charset=utf-8
< date: Mon, 15 Jun 2020 02:27:15 GMT
< strict-transport-security: max-age=31536000
< x-content-type-options: nosniff
< x-frame-options: ALLOWALL
< x-request-id: f96f6023-505d-4e1e-be07-c3812919e2ed
< x-runtime: 0.003430
< age: 0
< server: Netlify
< x-nf-request-id: f9166d6f-9b96-4314-9559-aec1161128e9-3844101
<
* Connection #0 to host jovial-blackwell-e78188.netlify.com left intact
Not Found* Closing connection 0

I’m still not clear how this 301 and 404 can be used to poison the local browser cache. If you are able to demonstrate a security issue, we will take it quite seriously. So far, though, I’m not seeing any vulnerability.

Let’s say that there is a malicious program running on the end user’s system which did change the Host header to point to some other domain name. The user tries to visit good-domain.net and instead gets a 301 redirect to evildomain.net. This would then change the URL in the address bar to evildomain.net. If you somehow directed the HTTPS request for evildomain.net to Netlify, we would show the content for that site if we host it or a 404 if we don’t.

Is your concern that the 301 redirect from good-domain.net to evildomain.net will get cached by the browser? If so, that doesn’t happen. You can test this by assigning a custom domain to a site and 301 redirecting the site-subdomain-here.netlify.app domain to the custom domain. You will get 301 redirected each time you try to visit the netlify.app subdomain. The browser won’t cache that.

Are you concerned about something else being cached? If so, would you please explain what that is. Note, this won’t cause Netlify to render something incorrectly because there is no server-side rendering at Netlify.

In other words, let’s say we have returned the 301 redirect to the HTTPS URL. What happens next to poison the cache? Also, would you please clarify exactly which cache you are concerned about becoming poisoned? The local browser cache or the cache at Netlify? (I assure you this won’t affect the caching at Netlify but, if you are concerned that, will please explain exactly how believe this will work.)

To summarize, I don’t believe you are going to be able to demonstrate cache poisoning using this method because I don’t think it is possible. However, if you do find a vulnerability, please private message one of our team to let us know. If you want to discuss the hypotheticals here publicly, we welcome that conversation as well.