0

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.

2
  • underscore invalidate the host name and makes it to the default Commented Mar 2, 2022 at 18:11
  • @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". Commented Mar 3, 2022 at 11:17

3 Answers 3

3
+100

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".

6
  • Ah, I see. What can I do to fix this? Is it possible to make nginx ignore requests with invalid port values? Commented Mar 3, 2022 at 11:50
  • 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. Commented Mar 3, 2022 at 11:52
  • 1
    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. Commented Mar 3, 2022 at 11:55
  • 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. Commented Mar 3, 2022 at 12:18
  • 2
    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. Commented Mar 3, 2022 at 12:24
0

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.

1
  • Good idea, but doesn't seem to work for me – the request still gets routed to the other server section. Commented Mar 5, 2022 at 16:37
0

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; } 
1
  • 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) Commented Mar 11, 2022 at 7:59

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.