4

I have an Apache/2.4.6 (CentOS) server with multiple subdomains as ServerAlias in Apache VirtualHost.

something like:

<VirtualHost *:443> ServerName example.com ServerAlias a.example.com ServerAlias b.example.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.example.com' can access another subdomain 'b.example.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.example.com and not b.example.com:

curl 'https://a.example.com/users/login' \ -H 'Host: b.example.com' \ --data-raw $'{"email":"[email protected]","password":"*****"}' 

A normal call from the client side application looks like this:

curl 'https://a.example.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.

2
  • How do you decide which database to use? Is that based on SNI (i.e. at TLS level), on Host (i.e. at HTTP level), or something else? Is this consistent everywhere? Also if you can't get the Host header in PHP something is very wrong in your setup. Commented Dec 18, 2024 at 14:44
  • Also, what is the actual vulnerability here? If you use the Host header to device what database to use, and that's the only thing you use, the "attack" actually gives the exact same result as posting the request to b.mydomain.com directly. The only issue is if you mix up SNI-based and Host-based detection (or other methods) Commented Dec 18, 2024 at 14:49

2 Answers 2

15

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 like GET /users/login, i.e. only the path (and query string) is present – it is not GET https://a.example.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.example.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 the Host: header; PHP in fact receives all of the headers through HTTP_* 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.

5
  • Another caveat here is that it's possible to have a server that is not listed in an A record for the hostname, but is configured to process that hostname in a meaningful way. Then, the scenario similar to the one described in the Q is possible and it involves setting a Host header to a value that doesn't correspond to the IP address the connection was made. But the protection is obvious: don't configure servers to respond for hostnames they aren't supposed to serve. Commented Dec 17, 2024 at 9:49
  • 1
    Well, that's true, but I'd classify that under "failure of security-by-obscurity" – if httpd is configured to serve some sensitive website without sufficient authentication, merely by knowing the right hostname, then the absence of a DNS record is not a proper security measure in the first place... Although, I can see it being more of an issue if this is the first VHost in Apache, which becomes the default VHost for any non-matching request (e.g. just requesting by IP address) – on my servers I do make sure to place a dummy deny-all vhost at the top of configuration, for certain reasons... Commented Dec 17, 2024 at 9:59
  • 1
    Thank you @grawity for the excellent explanation, I now understand better how the HTTP protocol works. My application does isolate PHP sessions (or auth tokens) per domain, so no security issue there. Commented Dec 17, 2024 at 10:09
  • @grawity a dummy default vhost is truly good idea and helps in most cases, but there's still a wildcard servername feature (ServerAlias *.domain.org) , and that is as useful as it is dangerous, because dummy vhost won't help in this case. I encountered this once, there was a wildcard DNS and one name was declared separately and sent to another server, and the application happened to be susceptible. That wasn't "by obscurity", that was simple oversight. Commented Dec 17, 2024 at 10:38
  • 2
    The hostname is actually also provided in the SNI option of the TLS (SSL) exchange. IIRC the virtual host will be picked on the basis of the SNI (as the virtual host is usually needed to serve the right TLS certificate). Commented Dec 18, 2024 at 14:46
10

You are misunderstanding the vulnerability here, taking your example from the top post:

curl 'https://a.example.com/users/login' \ -H 'Host: b.example.com' \ --data-raw $'{"email":"[email protected]","password":"*****"}' 

This authenticates to the server via SNI as a.example.com, but then sends the host header b.example.com.

Apache by defaults detects this, logs an error to the log file and returns a 400 error.

Hostname ... provided via SNI and hostname ... provided via HTTP are different

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 to a.example.com and then sends a Host header of b.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

1
  • Thank you for this excellent answer, I do have a mechanism in the token to identify for which subdomain in belongs and I reject calls with a token that does not belong to the right subdomain. I wasn't aware of the SNI detection, thanks! Commented Dec 18, 2024 at 10:33

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.