I have an Apache/2.4.6 (CentOS) server with multiple subdomains as ServerAlias in Apache VirtualHost.
something like:
<VirtualHost *:443>
ServerName mydomain.com
ServerAlias a.mydomain.com
ServerAlias b.mydomain.com
Each client company should access through its subdomain and there are different databases for each client company for security, there is a separation of data.
I was alerted by a cyber security expert that there is a vulnerability where a user of one subdomain 'a.mydomain.com' can access another subdomain 'b.mydomain.com' by adding a Host header to the calls from the client to the web server.
At first I tried to get the information in PHP but failed, the PHP doesn't get the headers information. Then I switched to looking for a solution to this situation at the web server level - Apache.
I want to detect and reject when a malicious user tries to fool the server and send the request to another subdomain using a Host Header, in this example, the user should be served by a.mydomain.com and not b.mydomain.com:
curl 'https://a.mydomain.com/users/login' \
-H 'Host: b.mydomain.com' \
--data-raw $'{"email":"[email protected]","password":"*****"}'
A normal call from the client side application looks like this:
curl 'https://a.mydomain.com/users/login' \
--data-raw $'{"email":"[email protected]","password":"*****"}'
I tried RequestHeader unset host
but it doesn't work as I expected.
My expectation was that if the malicious user sent a "Host" header, the server should ignore it. This would cause both culr calls above to be effectively the same.
I think that what happens is that Apache is using the URL in the call, but if there is a "Host" header, it takes precedent and that is what is used and the original domain from the URL is discarded.
If that is the case, then RequestHeader unset host
doesn't send any host to my PHP code, which causes my code to break, as it needs to know which client company is calling it.
This doesn't make sense, because the
Host
header is how Apache knows the URL in the first place.Regular HTTP requests do not provide the literal URL to a web server. If you run curl with the
-v
option, you'll see that the request looks likeGET /users/login
, i.e. only the path (and query string) is present – it is notGET https://a.mydomain.com/users/login
as you probably imagine. (Proxies use the latter, normal requests don't.)So where does the hostname part of the URL go when an HTTP request is made? The only place it goes, as far as HTTP is concerned, is the
Host:
header. It is therefore impossible for the header to be "different than URL of request", because it is the URL of request.In other words, changing the Host header isn't really "fooling the server" any more than just changing the URL; just doing
https://b.mydomain.com/users/login
would have the same effect.There may be some other issue that the "expert" was trying to warn you about (e.g. maybe they were talking about a request that attempts to send two Host headers, which I believe both Apache and PHP already guard against; or maybe they meant the server accepting mismatching TLS SNI and HTTP Host, which also isn't much of an issue in itself), but you will need to ask them for clarification.
My wild guess is that your application does not isolate PHP sessions (or auth tokens) per domain, so that if a user logs in and receives a session cookie (or access token) for domain A, but then attempts to send the same token for domain B, the application accepts it as valid authentication for domain B. That would be a major security issue, but nothing to do with spoofing the Host header as such.
Now, PHP does know which domain it was called for (either
$_SERVER["SERVER_NAME"]
and/or$_SERVER["HTTP_HOST"]
– the former comes from Apache, the latter is literally theHost:
header; PHP in fact receives all of the headers throughHTTP_*
fields), so if I happened to guess right, then it might be that those fields need to be stored and compared against the PHP session (or against the audience of any JWT that you're issuing, or whatever else you use for authentication).But again, this is only a wild guess, and you should get the expert to provide you with a more specific explanation of what they really meant.
You are misunderstanding the vulnerability here, taking your example from the top post:
This authenticates to the server via SNI as
a.mydomain.com
, but then sends the host headerb.mydomain.com
.Apache by defaults detects this, logs an error to the log file and returns a 400 error.
See also: https://security.stackexchange.com/questions/134021/what-kind-of-attack-is-prevented-by-apache2s-error-code-ah02032-hostname-prov
Apache exposes the results of the detected SNI inside the variable
SSL_TLS_SNI
Note that you are using
ServerAlias
instead of making seperate blocks per server. If the SNI tells that it connected toa.example.com
and then sends a Host header ofb.example.com
, because from apaches perspective, it is the same server it allows the request.You want to split up each of your servers to make sure apache detects the mismatch of the SNI and the host header. You can use an
Include
block to load a common configuration file so you do not have to repeat yourself