gixy icon indicating copy to clipboard operation
gixy copied to clipboard

The hostspoofing recommended fix is wrong

Open avlidienbrunn opened this issue 6 years ago • 6 comments

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.

avlidienbrunn avatar Jan 11 '19 11:01 avlidienbrunn

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.

avlidienbrunn avatar Jan 11 '19 11:01 avlidienbrunn

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

avlidienbrunn avatar Jan 11 '19 11:01 avlidienbrunn

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 avatar Jan 11 '19 12:01 buglloc

@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).

avlidienbrunn avatar Jan 11 '19 12:01 avlidienbrunn

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

rickolous avatar Feb 23 '20 17:02 rickolous

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.

alexeyten avatar Feb 23 '20 17:02 alexeyten