TL;DR
Specific http server (apache/nginx) config or whitelist of hosts & $_SERVER['SERVER_NAME'].
gethostname() ? only for non-virtual host...
https://www.php.net/manual/en/function.gethostname.php
gets the standard host name for the local machine.
... but if you're on a virtual host, this will still return the system host, not the virtual one :-( ...
(typically e.g. shared web hosting)
So what about $_SERVER['SERVER_NAME']?
https://www.php.net/manual/en/reserved.variables.server.php
Well, this on the other hand can be spoofed if not configured properly,
but it does return the virtual host if on virtual host.
Note: Under Apache 2, UseCanonicalName = On and ServerName must be set. Otherwise, this value reflects the hostname supplied by the client, which can be spoofed. It is not safe to rely on this value in security-dependent contexts.
Nginx => the same thing, docs just don't mention it.
Solution
Basically you have 2 options:
If you can configure your http server, you can force it to be your value, not value from request, this differs per http server you use:
- Apache:
UseCanonicalName (to On) & ServerName directives have to be set - most likely in your <VirtualHost>
- Nginx: you have to set the
server_name directive in your server{} block.
If you can't configure your server or can't rely on its setting, you can at least whitelist the allowed hostnames
<?php
$allowedHosts = [
'localhost',
'example.com',
'www.example.com',
];
if (!\in_array($_SERVER['HTTP_HOST'], $allowedHosts)) {
exit('You trynna spoof me?');
}