11

I have a simple script which determines the user's IP address:

function GetIp(){
      if (!empty($_SERVER['HTTP_CLIENT_IP']))
      //check ip from share internet
      {
        $ip=$_SERVER['HTTP_CLIENT_IP'];
      }
      elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR']))
      //to check ip is pass from proxy
      {
        $ip=$_SERVER['HTTP_X_FORWARDED_FOR'];
      }
      else
      {
        $ip=$_SERVER['REMOTE_ADDR'];
      }
      return $ip;
}

Now on the Net somewhere I saw someone using this script:

if (isset($_SERVER['HTTP_CLIENT_IP']) && $_SERVER['HTTP_CLIENT_IP'] != '')
        $Ip = $_SERVER['HTTP_CLIENT_IP'];
    elseif (isset($_SERVER['HTTP_X_FORWARDED_FOR']) && $_SERVER['HTTP_X_FORWARDED_FOR'] != '')
        $Ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
    elseif (isset($_SERVER['REMOTE_ADDR']) && $_SERVER['REMOTE_ADDR'] != '')
        $Ip = $_SERVER['REMOTE_ADDR'];

I was wondering if my implementation is broken.. Do I need to check if the value of $_SERVER['HTTP_CLIENT_IP'], $_SERVER['HTTP_X_FORWARDED_FOR'], or $_SERVER['REMOTE_ADDR'] is empty? Or is it actually unnecessary to do so?

Pacerier
  • 86,231
  • 106
  • 366
  • 634
  • 1
    What is the purpose of this? Is it in any way relevant to security? Beacause if it is, `REMOTE_ADDR` is the only one worthwhile. – Pekka Oct 01 '11 at 21:51
  • Depends. I remember some hosting companies being out there that have a proxy in front of their servers and they push in the proxies *local* IP which lead to `REMOTE_ADDR` always being something like `10.0.0.1` ... So if you wanted to get the clients IP you had to go with `X_FORWARDED_FOR` – klaustopher Oct 01 '11 at 21:57

4 Answers4

20

If the reason why you want to find out the client's IP address is really important, screw all this stuff.

Any one of these header values can be freely spoofed.

REMOTE_ADDR is the only really reliable information, as it is transmitted to you by your web server that is handling the request. It can be theoretically falsified as well, but that is much, much harder than spoofing a header value, and an entirely different class of attack.

There are exceptions in very, very specific hosting environments behind reverse proxies. In those cases the person administering that proxy will be able to tell what header value you need to test for.

Pekka
  • 442,112
  • 142
  • 972
  • 1,088
  • Could you elaborate on the words "freely spoofed".. Do you mean a layman like myself could spoof a website that uses this kind of script? – Pacerier Oct 01 '11 at 22:09
  • @Pacerier yup - it means that when I make a request to your web site, I could send along a `X_HTTP_FORWARDED_FOR` header with any value (e.g. somebody else's IP address). That's easily doable using curl or a browser plugin. In a normal setup, `REMOTE_ADDR` is the only reliable IP address there is. @kemo's snippet takes that into consideration by trusting the header values only when they come from a trusted proxy – Pekka Oct 01 '11 at 22:11
  • thanks for the info! and these guys here http://stackoverflow.com/questions/444966/working-with-ipv6-addresses-in-php are naming the functions as **GetRealRemoteIp** like as if it's *stronger* than simply doing $_SERVER["REMOTE_ADDR"] – Pacerier Oct 01 '11 at 22:13
  • @Pacerier yeah, that looks dangerously wrong. (As said above, Kemo's script is better, it doesn't just trust *any* header value that comes in) – Pekka Oct 01 '11 at 22:16
  • Cool =D Just one last question before I signoff, do you happen to know if $_SERVER["REMOTE_ADDR"] will return something like `""` (zero-length) or `"unknown"` or something else other than an IP altogether? – Pacerier Oct 01 '11 at 22:18
  • @Pacerier I don't know - I can't think of a situation where it would be empty! But you could test whether it's a real IP address. See [How can I simply validate whether a string is a valid IP in PHP?](http://stackoverflow.com/q/5827435) – Pekka Oct 01 '11 at 22:19
  • I was trying to spoof HTTP_X_REAL_IP with browser plugin but had no success. But spoofing HTTP_X_FORWARDED_FOR worked, - it showed 2 IP adreses then - one which I send with header and one which was original. So how to spoof X-Real-IP ? – Dariux Feb 04 '15 at 14:16
  • Yes, but not always bring the desired IP. If you use Nginx, you can receive the IP 127.0.0.1 on REMOTE_ADDR. Sometimes, is necessary to read the HTTP_CLIENT_IP and HTTP_X_FORWARDED_FOR, even if is easily spoofed. – Dherik Apr 22 '15 at 11:51
  • I agree, had to checks these in my ShowMyIP project page: I tried to point out the risks in my code comments to the source code ([here's the post containing the relevant excerpt](http://wp.me/p5e08M-Ff) ). – Darkseal Sep 28 '15 at 22:02
8

From Kohanas' Request class:

if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])
    AND isset($_SERVER['REMOTE_ADDR'])
    AND in_array($_SERVER['REMOTE_ADDR'], Request::$trusted_proxies))
{
    // Use the forwarded IP address, typically set when the
    // client is using a proxy server.
    // Format: "X-Forwarded-For: client1, proxy1, proxy2"
    $client_ips = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);

    Request::$client_ip = array_shift($client_ips);

    unset($client_ips);
}
elseif (isset($_SERVER['HTTP_CLIENT_IP'])
    AND isset($_SERVER['REMOTE_ADDR'])
    AND in_array($_SERVER['REMOTE_ADDR'], Request::$trusted_proxies))
{
    // Use the forwarded IP address, typically set when the
    // client is using a proxy server.
    $client_ips = explode(',', $_SERVER['HTTP_CLIENT_IP']);

    Request::$client_ip = array_shift($client_ips);

    unset($client_ips);
}
elseif (isset($_SERVER['REMOTE_ADDR']))
{
    // The remote IP address
    Request::$client_ip = $_SERVER['REMOTE_ADDR'];
}

This is pretty much as good as it gets. Please note the Request::$trusted_proxies array and that your $ip var is Request::$client_ip in this case.

Kemo
  • 6,942
  • 3
  • 32
  • 39
7

Do not check any HTTP_* headers for the client IP unless you specifically know your application is configured behind a reverse proxy. Trusting the values of these headers unconditionally will allow users to spoof their IP address.

The only $_SERVER field containing a reliable value is REMOTE_ADDR.

  • Ic.. I'd actually got the script here http://stackoverflow.com/questions/444966/working-with-ipv6-addresses-in-php could you explain why is everyone using this kind of IP detect scripts and putting it in a function named *GetRealIp* if the *real ip* is stored in $_SERVER["REMOTE_ADDR"] ? – Pacerier Oct 01 '11 at 22:11
1

The two things are practically identical.. In the script you found, the author is just doing a check if the element in the array is set before checking that it is non-empty.

In regards to using the empty()-function instead of the comparison, check http://php.net/empty. Since you are dealing with a variable that is set by the environment and not a user input it doesn't matter which of the two options you choose. So your script should be perfectly fine

klaustopher
  • 6,702
  • 1
  • 22
  • 25
  • I mean I'm aware that it's doing an empty check, I'm wondering if it's necessary to do such a thing. In other words.. will I even ever get a result that is zero-length? – Pacerier Oct 01 '11 at 22:07