Score:0

NGINX routes to the wrong virtual host when Host header contains colon character

cn flag

My nginx.conf has several "server" sections, and a catch-all server section. Here is an example nginx.conf to give you an idea:

user www-data;
worker_processes auto;
worker_cpu_affinity auto;
pid /run/nginx.pid;

events {
    worker_connections 4000;
    use epoll;
    accept_mutex off;
}

http {
    include /etc/nginx/mime.types;
    default_type application/octet-stream;
    error_log /var/log/nginx/error.log;

    server {
        listen 80;

        server_name foo.com;

        location / {
            default_type text/plain;
            return 200 "hello from foo.com";
        }

        error_page 500 502 503 504 /500.html;
    }

    server {
        listen 80 default_server;
        server_name _;

        location / {
            return 403 "sorry";
        }
    }

}

I'm expecting the server to return 403 if the "Host" header is anything but "foo.com".

Somebody's apparently running Burp Suite on my server, and I noticed and interesting behavior when they send a "Host: foo.com:more-stuff-here" header: NGINX routes the request to the first "server" section. It looks as if it ignores the colon and everything after it in the header value.

I can reproduce it locally with the above nginx.conf:

$ curl -H "Host: foo.com" http://127.0.0.1
hello from foo.com

$ curl -H "Host: foo.com:unexpected-content" http://127.0.0.1
hello from foo.com

$ curl -H "Host: bar.com" http://127.0.0.1
sorry

Why does NGINX do this? Is this an expected behavior? What should I change in nginx.conf to ensure requests with "Host: foo.com:more-stuff-here" header go to the default block?

Update: For anybody researching the same issue, I also created a ticket in NGINX issue tracker.

djdomi avatar
za flag
underscore invalidate the host name and makes it to the default
Pēteris Caune avatar
cn flag
@djdomi – nginx docs say underscore has no special meaning, it's just an invalid hostname. The important bit is "default_server" in the listen directive. I'm confused why nginx treats the Host value "foo.com:unexpected-content" as a direct match for the server name "foo.com".
Score:3
in flag

The definition for the host header in the HTTP RFC indicates that the Host header should have the form host:port, with the :port being optional.

nginx sees everything after the colon as the port for the host, but it is irrelevant in your context since you have not specified a server block that way. So it uses the closest match it can find, the host without the "port".

Pēteris Caune avatar
cn flag
Ah, I see. What can I do to fix this? Is it possible to make nginx ignore requests with invalid port values?
in flag
Is it your problem that an application which sends invalid requests doesn't work properly? If yes I'd fix the application, if no I'd just ignore it.
in flag
The only option I can think of would be to try to add `foo.com:*` as an server_alias to the default host and hope that nginx is intelligent enough to distinguish it.
Pēteris Caune avatar
cn flag
My application generates a warning about an invalid Host header. I can silence the warning, but I would prefer if NGINX could catch the invalid requests before they make to the application.
Pēteris Caune avatar
cn flag
I tested `foo.com:*`, NGINX doesn't start and prints an error: `invalid server name or wildcard "foo.com:*"`
in flag
Thought so much. Well, then I don't think there is anything you can do on the nginx side. This should be fixed in the application that makes the invalid requests.
Score:0
us flag

The following might work for default_server:

server {
    listen 80 default_server;

    server_name _ ~example\.com:;

    location / {
        return 403 "sorry";
    }
}

The important part is the tilde ~, which indicates a regular expression match.

Pēteris Caune avatar
cn flag
Good idea, but doesn't seem to work for me – the request still gets routed to the other server section.
Score:0
tl flag

The only alternative thought I could think of would be to code something in at the server side application level to check the host value and return the 403 response in code.

This however is not really the right way to do it, because the issue is a client issue to fix and not for your application.

for example, taking from this SO answer given a PHP application you could easily amend this logic to achieve the desired effect, note specifically the check for the SERVER_PORT as per Gerald's answer

$allowed_hosts = array('foo.example.com', 'bar.example.com');
$allowed_ports = array(80,443);
if (!isset($_SERVER['HTTP_HOST']) || !in_array($_SERVER['HTTP_HOST'], $allowed_hosts) || !in_array($_SERVER['SERVER_PORT'], $allowed_ports )) {
    header($_SERVER['SERVER_PROTOCOL'].' 400 Bad Request');
    exit;
}
Pēteris Caune avatar
cn flag
Thanks for the answer. That's how it's working currently for me here (the backend application uses Django web framework, which checks the host header against an allowlist. This is fine, but I was hoping to get NGINX to act as a "shield" in front of the backend app, making sure only valid, well-formed requests go through to the backend app)
mangohost

Post an answer

Most people don’t grasp that asking a lot of questions unlocks learning and improves interpersonal bonding. In Alison’s studies, for example, though people could accurately recall how many questions had been asked in their conversations, they didn’t intuit the link between questions and liking. Across four studies, in which participants were engaged in conversations themselves or read transcripts of others’ conversations, people tended not to realize that question asking would influence—or had influenced—the level of amity between the conversationalists.