How to use HTTP_X_FORWARDED_FOR properly?
Solution 1
You can use this function to get proper client IP:
public function getClientIP(){
if (array_key_exists('HTTP_X_FORWARDED_FOR', $_SERVER)){
return $_SERVER["HTTP_X_FORWARDED_FOR"];
}else if (array_key_exists('REMOTE_ADDR', $_SERVER)) {
return $_SERVER["REMOTE_ADDR"];
}else if (array_key_exists('HTTP_CLIENT_IP', $_SERVER)) {
return $_SERVER["HTTP_CLIENT_IP"];
}
return '';
}
Solution 2
I like Hrishikesh's answer, to which I only have this to add...because we saw a comma-delimited string coming across when multiple proxies along the way were used, we found it necessary to add an explode and grab the final value, like this:
$IParray=array_values(array_filter(explode(',',$_SERVER['HTTP_X_FORWARDED_FOR'])));
return reset($IParray);
the array_filter is in there to remove empty entries.
Solution 3
In the light of the latest httpoxy vulnerabilities, there is really a need for a full example, how to use HTTP_X_FORWARDED_FOR
properly.
So here is an example written in PHP, how to detect a client IP address, if you know that client may be behind a proxy and you know this proxy can be trusted. If you don't known any trusted proxies, just use REMOTE_ADDR
<?php
function get_client_ip ()
{
// Nothing to do without any reliable information
if (!isset ($_SERVER['REMOTE_ADDR'])) {
return NULL;
}
// Header that is used by the trusted proxy to refer to
// the original IP
$proxy_header = "HTTP_X_FORWARDED_FOR";
// List of all the proxies that are known to handle 'proxy_header'
// in known, safe manner
$trusted_proxies = array ("2001:db8::1", "192.168.50.1");
if (in_array ($_SERVER['REMOTE_ADDR'], $trusted_proxies)) {
// Get the IP address of the client behind trusted proxy
if (array_key_exists ($proxy_header, $_SERVER)) {
// Header can contain multiple IP-s of proxies that are passed through.
// Only the IP added by the last proxy (last IP in the list) can be trusted.
$proxy_list = explode (",", $_SERVER[$proxy_header]);
$client_ip = trim (end ($proxy_list));
// Validate just in case
if (filter_var ($client_ip, FILTER_VALIDATE_IP)) {
return $client_ip;
} else {
// Validation failed - beat the guy who configured the proxy or
// the guy who created the trusted proxy list?
// TODO: some error handling to notify about the need of punishment
}
}
}
// In all other cases, REMOTE_ADDR is the ONLY IP we can trust.
return $_SERVER['REMOTE_ADDR'];
}
print get_client_ip ();
?>
Solution 4
You can also solve this problem via Apache configuration using mod_remoteip, by adding the following to a conf.d file:
RemoteIPHeader X-Forwarded-For
RemoteIPInternalProxy 172.16.0.0/12
LogFormat "%a %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
kingmaple
I am natural born Aries, I went through military service and obtained a degree in the University of Tallinn, in Estonia. I am working as an architect in the fields of internet media, creating applications like the one you are using right now. I also consult developers and startups, so if you need help with anything, let me know.
Updated on July 09, 2022Comments
-
kingmaple almost 2 years
Alright, I have an small authentication issue. My web service allows to connect to my API over HTTP with a username and password, but this connection can also be restricted to a specific IP address.
This means that the
$_SERVER['REMOTE_ADDR']
can be incorrect. I already know that any IP information can never truly be relied upon - I have the restriction only in an attempt to add another layer of security.If this is the general overview of a request to my web server:
clientSERVER => clientPROXY => myPROXY => mySERVER
Then this means that mySERVER shows
REMOTE_ADDR
of myPROXY instead of that of the client and sends the actual IP of the client asHTTP_X_FORWARDED_FOR
.To overcome this, my web service has a list of 'trusted proxy' IP addresses and if
REMOTE_ADDR
is from one of those trusted IP addresses, then it tells my web service that the actual IP address is the value ofHTTP_X_FORWARDED_FOR
.Now the problem is with clientPROXY. This means that (quite often) mySERVER gets
HTTP_X_FORWARDED_FOR
value that has multiple IP addresses. According toHTTP_X_FORWARDED_FOR
documentation, the value is a comma-separated list of IP addresses where the first IP is that of the actual true client and every other IP address is that of a proxy.So, if
HTTP_X_FORWARDED_FOR
has multiple values and my service is IP restricted, do I have to check the 'last' value ofHTTP_X_FORWARDED_FOR
against my allowed IP list and just ignore the actual client IP?I assume that in a system, where I have to set the list of allowed IP addresses, the whitelisted IP address should be that of a proxy and not an IP that is behind the proxy (since that could be some localhost IP and change frequently).
And what of
HTTP_CLIENT_IP
? -
BenMorel over 10 yearsNone of these headers is "more reliable" than the others. They can all be forged by the client, and your applications needs to know what's trusted and what's not: you normally know your infrastructure and the IP address of any proxy / load balancer in front of your HTTP server(s).
-
TheEnvironmentalist over 10 yearsTrue, but generally, if an order of preference is to be established based on assumed reliability, this is it. I realize that they can all be manipulated, but if these must be used, this is how to do it.
-
Admin over 9 yearsThis answer doesn't address the specific situation described in the question, where not all requests are passed through a proxy. As a result, requests received directly from clients may contain inaccurate IP addresses in headers.
-
Matthew Kolb over 9 yearsNote that it appears that using the last value in the list is still probably using a proxy's IP. According to the link below, the originating client is the FIRST IP. en.wikipedia.org/wiki/X-Forwarded-For
-
Vipul Hadiya about 9 yearsNice answer, you can find even accurate from Prestashop's Tools class :) . Find getRemoteAddr() function
-
Rob Brandt about 9 yearsAlso note that the same source says that this is easy to forge, so the last one is more reliable. So each use case may make different choices. If the use case for getting the IP is combating fraud or spam, the first IP may be meaningless and the most reliable address - the last one - is most useful. If the use case for getting the IP is less nefarious activities, the first one would be most useful.
-
transistor09 about 9 yearsWhy is there so much disinformation on this topic?
$_SERVER['REMOTE_ADDR']
is the only reliable field that's not influenced by the remote user, all others are parsed from headers. -
TheEnvironmentalist almost 9 years@transistor09 Care to provide your own answer then?
-
Ben Dyer over 6 yearsThis is an excellent answer, but there is one minor problem. If strict error reporting is on, trying to
trim(end(explode()))
on one line will return "Only variables should be passed by reference." To get around that, set the exploded proxy header to a variable first, thentrim(end())
that instead. -
Erki Aring over 6 years@BenDyer Thanks, fixed it.
-
frodeborli almost 6 yearsThis is a possible security hole. Anybody can add the X-Forwarded-For header to their request.
-
tisc0 almost 6 years@frodeborli : You're right ; that's why one should use the reqidel+reqadd directives in haproxy.
-
Lucas Bustamante over 4 yearsI believe this is incorrect. According to Mozilla, the original IP is the first, not the last, so it should be
trim(array_shift())
developer.mozilla.org/en-US/docs/Web/HTTP/Headers/… -
CragMonkey over 2 yearsOn my site,
$_SERVER['HTTP_CLIENT_IP']
is not set and$_SERVER['REMOTE_ADDR']
changes on every page load, while$_SERVER['HTTP_X_FORWARDED_FOR']
is constant and set to the expected value.