gixy
gixy copied to clipboard
The hostspoofing recommended fix is wrong
The recommendation for remediating host spoofing in https://github.com/yandex/gixy/blob/master/docs/en/plugins/hostspoofing.md is wrong.
The recommendation is currently to use $host
instead of $http_host
, which does not remediate the issue and in certain cases makes it worse (vulnerable when otherwise would not be).
The value of the $host
variable is set like so:
in this order of precedence: host name from the request line, or host name from the “Host” request header field, or the server name matching a request
Which means that (after recommended fix), not only can an attacker inject a different host from the Host
header, but also from the request line. In cases where a filter/IDS is applied solely on the Host
header, the fix makes it vulnerable via the request line.
The proper fix to this issue should be to use validated $server_name
or a whitelist of accepted $host
values.
Example config:
server {
listen 80;
server_name example.com;
location / {
add_header x_host $host;
add_header x_http_host $http_host;
add_header x_server_name $server_name;
return 200 'hello';
}
}
Example request/response:
GET http://request-line/ HTTP/1.1
Host: host-header
HTTP/1.1 200 OK
Server: nginx/1.10.3 (Ubuntu)
Date: Fri, 11 Jan 2019 11:39:06 GMT
Content-Type: application/octet-stream
Content-Length: 5
Connection: keep-alive
x_host: request-line
x_http_host: host-header
x_server_name: example.com
hello
As you can see, using $host
instead of $http_host
does little (if anything) to mitigate the issue.
Something like this could work (using the $validated
variable):
map $host $validated {
default "example.com";
"cdn.example.com" "cdn.example.com";
"login.example.com" "login.example.com";
}
server {
listen 80;
server_name *.example.com;
location / {
add_header x_host $host;
add_header x_http_host $http_host;
add_header x_server_name $server_name;
add_header x_validated $validated;
return 200 'hello';
}
}
Example request responses:
GET http://request-line/ HTTP/1.1
Host: host-header
HTTP/1.1 200 OK
Server: nginx/1.10.3 (Ubuntu)
Date: Fri, 11 Jan 2019 11:58:36 GMT
Content-Type: application/octet-stream
Content-Length: 5
Connection: keep-alive
x_host: request-line
x_http_host: host-header
x_server_name: *.example.com
x_validated: example.com
hello
GET http://cdn.example.com/ HTTP/1.1
Host: host-header
HTTP/1.1 200 OK
Server: nginx/1.10.3 (Ubuntu)
Date: Fri, 11 Jan 2019 11:58:58 GMT
Content-Type: application/octet-stream
Content-Length: 5
Connection: keep-alive
x_host: cdn.example.com
x_http_host: host-header
x_server_name: *.example.com
x_validated: cdn.example.com
hello
GET http://notallowed.example.com/ HTTP/1.1
Host: host-header
HTTP/1.1 200 OK
Server: nginx/1.10.3 (Ubuntu)
Date: Fri, 11 Jan 2019 11:59:24 GMT
Content-Type: application/octet-stream
Content-Length: 5
Connection: keep-alive
x_host: notallowed.example.com
x_http_host: host-header
x_server_name: *.example.com
x_validated: example.com
hello
Hi! Actually, you're describing another problem - lack of server
default section (e.g. #80). Someday I'll do this check too.
For example:
- configuration:
server {
listen 80;
server_name example.com;
location / {
add_header x_host $host;
add_header x_http_host $http_host;
add_header x_server_name $server_name;
return 200 'hello';
}
}
server {
listen 80 default_server;
return 404 'go away';
}
- tests:
$ nc localhost 80
GET http://request-line/ HTTP/1.1
Host: host-header
HTTP/1.1 404 Not Found
Server: nginx/1.14.2
Date: Fri, 11 Jan 2019 12:02:24 GMT
Content-Type: application/octet-stream
Content-Length: 7
Connection: keep-alive
go away
GET http://example.com/ HTTP/1.1
Host: host-header
HTTP/1.1 200 OK
Server: nginx/1.14.2
Date: Fri, 11 Jan 2019 12:02:45 GMT
Content-Type: application/octet-stream
Content-Length: 5
Connection: keep-alive
x_host: example.com
x_http_host: host-header
x_server_name: example.com
hello
GET / HTTP/1.1
Host: example.com
HTTP/1.1 200 OK
Server: nginx/1.14.2
Date: Fri, 11 Jan 2019 12:03:03 GMT
Content-Type: application/octet-stream
Content-Length: 5
Connection: keep-alive
x_host: example.com
x_http_host: example.com
x_server_name: example.com
hello
@buglloc What about people using vuln code inside their default_server, or allow wildcard/regex for server names (https://stackoverflow.com/questions/9454764/nginx-server-name-wildcard-or-catch-all)?
Having default server + explicit server_name values is just another way of whitelisting it and fix the problem, but saying "just use $host" is misleading imo (as it wont fix anything unless the prerequisites are filled).
@buglloc any update on the issue? I agree with @avlidienbrunn and found an instance of SSRF that was exploitable because the author relied on $host. It might be best to update the verbiage so that it isn't misleading developers into believing that $host is the secure alternative of $http_host.
What about people using vuln code inside their default_server, or allow wildcard/regex for server names
They definitely shouldn’t do that or be aware that they actually accept any host.