HTTP message structure has 3 sections, the Start Line and then optional Header and Body.

The Start Line contains the information about the type of message ( GET, POST, etc), the destination and HTTP version. The Header contains message metadata and the Body contains the message data.

Header information can be broken down further into 3 types.

  • General Headers that apply to the message as a whole.
  • Request/Response Headers, depending on whether the message is a Request or a Response, modifies the message
  • Entity Headers apply to the body of the message

In an example of a General Header in a response from this site we can see that the request is a GET to htttps://daviddever.net/ and that it returned a successful response.

Request URL: https://daviddever.net/
Request Method: GET
Status Code: 200 
Remote Address: 165.227.76.174:443
Referrer Policy: no-referrer-when-downgrade

Further information in the example Response Headers we can see the content is encoded in gzip format and that it includes text and html in UTF-8 character set, the time of the response and information about the webserver.

cache-control: public, max-age=0
content-encoding: gzip
content-type: text/html; charset=utf-8
date: Mon, 17 Dec 2018 6:23:11 GMT
server: nginx/1.14.0 (Ubuntu)
status: 200
strict-transport-security: max-age=63072000; includeSubDomains; preload
vary: Accept-Encoding
x-content-type-options: nosniff
x-frame-options: SAMEORIGIN
x-powered-by: Express

From the example we can see that these headers can contain lots of useful information to help applications understand and decoded the information in the HTTP message.

These messages can be abused, stuff with incorrect information, or malformed to try and exploit vulnerabilities web servers. For instance, in the past, a malformed HTTP 1.0 Request with an empty Host Header could cause some web servers to expose internal IP addresses.

A request with a malicious Host header can be used to take advantage of how applications concatenate the Host with the web root to generate links to another host as described in full in this article. Password reset emails are one example of a channel this can be used with this, the link in the email being generated from the Host to redirect the user to a malicious site.

In the example below, we are using curl to send a request with a different Host is still accepted as valid by Nginx and receives a 200 response.

curl -I https://daviddever.net -H "Host:differenthost.com"
HTTP/2 200
server: nginx/1.14.0 (Ubuntu)
date: Mon, 17 Dec 2018 7:04:56 GMT
content-type: text/html; charset=utf-8
content-length: 42449
x-powered-by: Express
cache-control: public, max-age=0
vary: Accept-Encoding
strict-transport-security: max-age=63072000; includeSubDomains; preload
x-frame-options: SAMEORIGIN
x-content-type-options: nosniff

To resolve this we can whitelist hosts in our configuration and refuse everything else. Set the server_namein each server block in the Nginx configuration to allow the speciffic valid hosts. If there are several for a single site you may need to increase server_names_hash_bucket_size value to fit the extra information. Then add a final server block including default_server in the listen and server_name _; and set location to return a 404 to deny requests from anything not specifically allowed earlier.

server {
    listen 80;
    server_name daviddever.net localhost;

    location / {
        root /var/www/examplesite
    }
}

server {
    listen 80 default_server;
    server_name _;

    location / {
        return 404;
    }
}

With the new configuration in place our previous curl request returns a 400

curl -I https://daviddever.net -H "Host:differenthost.com"
HTTP/2 404
server: nginx/1.14.0 (Ubuntu)
date: Mon, 17 Dec 2018 7:34:03 GMT
content-type: text/html
content-length: 178

A X-Forwarded-Host header is used set when Nginx is running as a reverse proxy and can be exploited in the same fashion.

If you are not using Nginx as a reverse proxy then it is safe to drop anything with an X-Forwarded-Host using an if statement, which using a return statements ensures non-evil usage.

if ($http_x_forwarded_host) { return 404; }

Things can get a little more complicated if you are using Nginx as a reverse proxy which means you will need to determine how exactly the X-Forwarded-Host header is being used, but one solution may be to rewrite the header in the proxy to ensure that it is always set to the correct value.