In PHP, it is very easy to detect an IP address at a basic level:
echo 'You know, your IP address is' . $_SERVER['REMOTE_ADDR'] . '?';
Warning: Getting the IP address as the key of the
$_SERVER['REMOTE_ADDR']
field is only possible if PHP was called from the browser. In CLI mode (for example, running from Terminal with cron), the IP address is not available (this makes sense, since no network request is being made).
After many years of development, I finally stuck with this implementation:
function getIp(): string{if (isset($_SERVER['HTTP_CF_CONNECTING_IP'])) { // Cloudflare support$ip = $_SERVER['HTTP_CF_CONNECTING_IP'];} elseif (isset($_SERVER['REMOTE_ADDR']) === true) {$ip = $_SERVER['REMOTE_ADDR'];if (preg_match('/^(?:127|10)\.0\.0\.[12]?\d{1,2}$/', $ip)) {if (isset($_SERVER['HTTP_X_REAL_IP'])) {$ip = $_SERVER['HTTP_X_REAL_IP'];} elseif (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {$ip = $_SERVER['HTTP_X_FORWARDED_FOR'];}}} else {$ip = '127.0.0.1';}if (in_array($ip, ['::1', '0.0.0.0', 'localhost'], true)) {$ip = '127.0.0.1';}$filter = filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4);if ($filter === false) {$ip = '127.0.0.1';}return $ip;}
Much better, then:
echo 'You know, your IP address is' . getIp() . '?';
If the IP can be detected directly, or is only IPv6, or is in CLI mode (e.g. cron), it returns 127.0.0.1
(localhost).
Implementations that take into account the X-Forwarded-For
and X-Real-IP
headers are extremely dangerous directly in PHP, as data can easily be modified and an attacker can spoof a fake IP address to, for example, view the administration or activate the Debug mode of the site (Nette Tracy). On the other hand, we have to accept some proxied requests (for example, when proxing traffic through Cloudflare, or when running Apache and Ngnix on the same machine, when they are called locally right after each other).
In the case of direct user access to the server, there is only one correct solution, and that is to ensure on Apache (via the RemoteIP
extension) and on Nginx via the remote_ip
extension that X-Forwarded-For
is set from the actual IP address of the visitor, and that the IP address cannot be set with an HTTP header.
The $_SERVER['REMOTE_ADDR']
field automatically gets the correct IP address (that is, the IP address from which the request came directly to PHP) and we don't have to deal with it.
It often happens that a user accesses through a proxy. Then the actual IP address is stored in the variable $_SERVER['HTTP_X_FORWARDED_FOR']
.
This case can occur, for example, when routing on the server is resolved using the Ngnix -> Apache -> PHP
method, where Ngnix
serves as a reverse proxy before Apache
. In this case, PHP sees only the IP address within the internal network (usually of the form 127.0.0.*
).
For example, the Cloudflare service may behave in this way, and care should be taken whether we are working with the IP address of the actual user or the proxy. For me, the best way is to use the getIp()
function mentioned at the beginning of this article. We can ensure Cloudflare detection by verifying the existence of the $_SERVER['HTTP_CF_CONNECTING_IP']
key, which is automatically transmitted in every proxied request.
There is no reliable detection of proxy or VPN usage, but in a real environment we can filter out at least some of the traffic.
There are several ways to do this: Take a range of IP addresses and compare the IP address from which the request came.
From some VPN providers, lists of IP addresses are available unofficially (see e.g. https://gist.github.com/JamoCA…), in the case of Tor exit nodes officially (https://blog.torproject.org/changes-tor-exit-list-service, but Tor bridges are not there).
Another option is to make an online request somewhere, which can both delay page loading if the service is not working and also "leak" the IP addresses of visitors to a third party. From 2023 onwards, I would strongly advise against this approach, as it starts to become more about protecting and manipulating user data.
This online querying can be "naive" and just need to see who owns the range or if it's a proxy/VPN (some services may return this, but by default it's not part of the "IP info" e.g. from a whois service).
(Most) often some sort of reputation rating is used, where some IP addresses "trash" more than others. Statistically, there is more crap coming from various proxies, VPNs and Tor than from home IP addresses (except perhaps "infected" home IP addresses). Such a reputational assessment is offered by some DNS Block Lists, see some random list, https://en.m.wikipedia.org/wiki/Comparison_of_DNS… and the "Listing goal" column, or it's provided directly by companies like Cloudflare in the form of "bot management" etc.
A lot depends on what the detection goal is.
It depends on what IP address you have available.
ip2long
function is used for this,If your database server does not directly support a data type for the IP address, I recommend storing the IP address as varchar(39)
, where both versions will fit as a string and be human readable.
When storing the IP address, consider whether it makes sense to also store the domain name detected by the
gethostbyaddr
function. You can't find out the names when listing and searching, because it takes a very long time and they can change over time.
The ideal solution is to create a list of blocked IP addresses and compare this list with the current IP address on each request. If the addresses match, the request will be stopped immediately.
$blackList = ['first-ip','druha-ip',];if (\in_array(getIp(), $blackList, true) === true) {echo 'Unfortunately your ip address is blocked :-(';die; // Exit request}
The example assumes an implementation of the getIp()
function as in the example above.
A more powerful solution is to check for the occurrence of the index in the array:
$blackList = ['first-ip' => true,'druha-ip' => true,];if (isset($blackList[getIp()]) === true) {echo 'Unfortunately your ip address is blocked :-(';die; // Exit request}
The IP address of the server is usually stored in the $_SERVER['SERVER_ADDR']
field and its name can be obtained by the gethostbyaddr($_SERVER['SERVER_ADDR'])
construct.
However, if the Ngnix -> Apache -> PHP
concept is used and Ngnix
is in the role of a reverse proxy, the real IP address of the server is not displayed.
In this case, the name of the server can be found in the $_SERVER['SERVER_NAME']
field, or by using the php_uname('n')
function. Official uname function documentation.
We can then use this trick to find out the public IP address of the server: gethostbyname(php_uname('n'))
.
Jan Barášek Více o autorovi
Autor článku pracuje jako seniorní vývojář a software architekt v Praze. Navrhuje a spravuje velké webové aplikace, které znáte a používáte. Od roku 2009 nabral bohaté zkušenosti, které tímto webem předává dál.
Rád vám pomůžu:
Články píše Jan Barášek © 2009-2024 | Kontakt | Mapa webu
Status | Aktualizováno: ... | en