Opened 6 months ago
Last modified 9 days ago
#2650 new defect
Uncovered edge case in host header validation
Reported by: | Daniil Lemenkov | Owned by: | |
---|---|---|---|
Priority: | minor | Milestone: | |
Component: | nginx-core | Version: | 1.25.x |
Keywords: | host, patch | Cc: | |
uname -a: | Linux Blue 5.19.0-46-generic #47-Ubuntu SMP PREEMPT_DYNAMIC Fri Jun 16 13:30:11 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux | ||
nginx -V: |
nginx version: nginx/1.27.0
built by gcc 12.2.0 (Ubuntu 12.2.0-3ubuntu1) configure arguments: |
Description
Hello to maintainers, developers and anybody interested!
I suppose there is an uncovered edge case during host header validation procedure. It is likely to be caused by a code in ngx_http_validate_host
.
Consider the following nginx.conf
file:
events {} http { server { listen 8012 default_server; server_name _; return 200 "default_server, host=$host, server_name=$server_name\n"; } server { listen 8012; server_name example.com; return 200 "example.com, host=$host, server_name=$server_name\n"; } server { listen 8012; server_name example.com.; return 200 "example.com. (with dot at the end), host=$host, server_name=$server_name\n"; } }
... and responses of the server running the config:
$ curl localhost:8012 -H 'Host: example.com' example.com, host=example.com, server_name=example.com # As expected: dot-ended domains are unified $ curl localhost:8012 -H 'Host: example.com.' example.com, host=example.com, server_name=example.com # Unexpected: request with dot-ended domain will not be processed in usual virtual host $ curl localhost:8012 -H 'Host: example.com.:1234.' example.com. (with dot at the end), host=example.com., server_name=example.com.
There are unexpected header validation results in cases when a port part of the host header contains a dot.
Although the last request is probably wrong, I suppose Nginx should provide consistent behavior in the case too to allow users to rely on it.
You may evolve these requests to a more strange one (within the same server with the same config):
# As expected: '.' is incorrect host $ curl localhost:8012 -H 'Host: .' <html> <head><title>400 Bad Request</title></head> <body> <center><h1>400 Bad Request</h1></center> <hr><center>nginx/1.27.0</center> </body> </html> # Unexpected: the server handle and process a bad request successfully $ curl localhost:8012 -H 'Host: .:12.34' default_server, host=., server_name=_
In this case a request with a single-dot host header, which is forbidden usually, succeeds now.
This may have some negative impact on configurations with an authorization based on use of $host
variable. Consider another nginx.conf
file:
events {} http { server { listen 8013 default_server; server_name _; root "html/$host"; } server { listen 8013; server_name secret.example.com; root "html/secret.example.com"; return 401 "Unauthorized\n"; } }
... and responses of the server running this config:
$ cat html/secret.example.com/secret SomeSecret # As expected: an access is unauthorized $ curl localhost:8013/secret -H 'Host: secret.example.com' Unauthorized # Unexpected: one may gain an unauthorized access $ curl localhost:8013/secret.example.com/secret -H 'Host: .:.1234' SomeSecret
Using this configuration looks like a bad approach for an authorization, nevertheless, I think the issue may cause some other unexpected cases in other applications.
I wish you consider the issue important enough to be acknowledged. To increase your interest for fixing it, I will try to prepare a patch. Hope this helps.
Thank you all a lot!
There is one small and simple patch that fixes the issue and might satisfy code owners:
Roughly speaking, it disables special dot processing after
:
or]
(insw_rest
state).Pros: small, simple.
Cons: disables some extra checks for rest part of a host header (for instance,
example.com.:1234..
now will be correct host header forexample.com
), and therefore breaks two tests in nginx-tests.I also see another option to fix the issue — to rewrite a bit the whole
ngx_http_validate_host
— however I don't know your preferences on such a big patches, so I tried to start with the simplest one.