A: 

You pretty much answered your own question! :)

function getRealIpAddr() {
    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; }

Source

Alex
+2  A: 

Even then however, getting a user's real Ip address is going to be unreliable. All they need do is use an anonymous proxy server (one that doesn't honor the headers for http_x_forwarded_for or http_forwarded etc) and all you get is their proxy server's ip. You can then see if there is a list of proxy server ips that are anonymous, but there is no way to be sure that is 100% accurate as well and the most it'd do is let you know it is a proxy server. And if someone is being clever, they can spoof headers for http forwards. Let's say I don't like the local college. I figure out what IP's they registered, and get their ip banned on your site by doing bad things because I figure out you honor the http forwards. The list is endless. Then there is, as you guessed internal IPs such as the college network I metioned before. A lot use a 10.x.x.x format. So all you would know is that it was forwarded for a shared network. Then I won't start much into it, but dynamic ips are the way of broadband anymore. So. Even if you get a user IP, expect it to change in 2 - 3 months, longest.

Chisum
Thanks for the input. I'm currently utilizing the user's IP address to aid in session authentication by using their class C IP as a limiting factor to limit session hijacking but allow for dynamic IPs within reason. Spoofed IPs and anonymous proxy servers is just something I'll have to deal with for a select group of individuals.
cballou
Cool :) I am glad I can of help :)
Chisum
This help enough to be an answer? Appreciate a reward if so :)
Chisum
@cballou - Surely for this purpose REMOTE_ADDR is the correct one to use. Any approach relying on HTTP headers is vulnerable to header spoofing. How long is a session? Dynamic IPs don't change fast.
MZB
They do, especially if I want them to (change mac address which many drivers do support). Just REMOTE_ADDR by itself is enough to get what ever the last server it talked to was. So in a proxy situation you get the proxy IP.
Chisum
A: 

We use:

/**
 * Get the customer's IP address.
 *
 * @return string
 */
public function getIpAddress() {
    if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
        return $_SERVER['HTTP_CLIENT_IP'];
    } else if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
        $ips = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
        return trim($ips[count($ips) - 1]);
    } else {
        return $_SERVER['REMOTE_ADDR'];
    }
}

Edit: Just wanted to clarify, the explode on HTTP_X_FORWARDED_FOR is because of weird issues we had detecting IP's when Squid was used.

gabrielk
Whoops, I just realized you do basically the same thing with exploding on , and so forth. Plus a little extra. So I doubt my answer was much help. :)
gabrielk
A: 
/**
 * Sanitizes IPv4 address according to Ilia Alshanetsky's book
 * "php|architect?s Guide to PHP Security", chapter 2, page 67.
 *
 * @param string $ip An IPv4 address
 */
public static function sanitizeIpAddress($ip = '')
{
if ($ip == '')
    {
    $rtnStr = '0.0.0.0';
    }
else
    {
    $rtnStr = long2ip(ip2long($ip));
    }

return $rtnStr;
}

//---------------------------------------------------

/**
 * Returns the sanitized HTTP_X_FORWARDED_FOR server variable.
 *
 */
public static function getXForwardedFor()
{
if (isset($_SERVER['HTTP_X_FORWARDED_FOR']))
    {
    $rtnStr = $_SERVER['HTTP_X_FORWARDED_FOR'];
    }
elseif (isset($HTTP_SERVER_VARS['HTTP_X_FORWARDED_FOR']))
    {
    $rtnStr = $HTTP_SERVER_VARS['HTTP_X_FORWARDED_FOR'];
    }
elseif (getenv('HTTP_X_FORWARDED_FOR'))
    {
    $rtnStr = getenv('HTTP_X_FORWARDED_FOR');
    }
else
    {
    $rtnStr = '';
    }

// Sanitize IPv4 address (Ilia Alshanetsky):
if ($rtnStr != '')
    {
    $rtnStr = explode(', ', $rtnStr);
    $rtnStr = self::sanitizeIpAddress($rtnStr[0]);
    }

return $rtnStr;
}

//---------------------------------------------------

/**
 * Returns the sanitized REMOTE_ADDR server variable.
 *
 */
public static function getRemoteAddr()
{
if (isset($_SERVER['REMOTE_ADDR']))
    {
    $rtnStr = $_SERVER['REMOTE_ADDR'];
    }
elseif (isset($HTTP_SERVER_VARS['REMOTE_ADDR']))
    {
    $rtnStr = $HTTP_SERVER_VARS['REMOTE_ADDR'];
    }
elseif (getenv('REMOTE_ADDR'))
    {
    $rtnStr = getenv('REMOTE_ADDR');
    }
else
    {
    $rtnStr = '';
    }

// Sanitize IPv4 address (Ilia Alshanetsky):
if ($rtnStr != '')
    {
    $rtnStr = explode(', ', $rtnStr);
    $rtnStr = self::sanitizeIpAddress($rtnStr[0]);
    }

return $rtnStr;
}

//---------------------------------------------------

/**
 * Returns the sanitized remote user and proxy IP addresses.
 *
 */
public static function getIpAndProxy()
{
$xForwarded = self::getXForwardedFor();
$remoteAddr = self::getRemoteAddr();

if ($xForwarded != '')
    {
    $ip    = $xForwarded;
    $proxy = $remoteAddr;
    }
else
    {
    $ip    = $remoteAddr;
    $proxy = '';
    }

return array($ip, $proxy);
}
Meketrefe
+16  A: 

Here is a shorter, cleaner way to get the IP address:

function get_ip_address()
{
    foreach (array('HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_FORWARDED', 'HTTP_X_CLUSTER_CLIENT_IP', 'HTTP_FORWARDED_FOR', 'HTTP_FORWARDED', 'REMOTE_ADDR') as $key)
    {
        if (array_key_exists($key, $_SERVER) === true)
        {
            foreach (explode(',', $_SERVER[$key]) as $ip)
            {
                $ip = trim($ip); // just to be safe

                if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6 | FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) !== false)
                {
                    self::$ip = $ip; return $ip;
                }
            }
        }
    }
}

I hope it helps!


Your code seems to be pretty complete already, I cannot see any possible bugs in it (aside from the usual IP caveats), I would change the validate_ip() function to rely on the filter extension though:

public function validate_ip($ip)
{
    if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) === false)
    {
        return false;
    }

    self::$ip = sprintf('%u', ip2long($ip)); // you seem to want this

    return true;
}

Also your HTTP_X_FORWARDED_FOR snippet can be simplified from this:

// check for IPs passing through proxies
if (!empty($_SERVER['HTTP_X_FORWARDED_FOR']))
{
    // check if multiple ips exist in var
    if (strpos($_SERVER['HTTP_X_FORWARDED_FOR'], ',') !== false)
    {
        $iplist = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);

        foreach ($iplist as $ip)
        {
            if ($this->validate_ip($ip))
                return $ip;
        }
    }

    else
    {
        if ($this->validate_ip($_SERVER['HTTP_X_FORWARDED_FOR']))
            return $_SERVER['HTTP_X_FORWARDED_FOR'];
    }
}

To this:

// check for IPs passing through proxies
if (!empty($_SERVER['HTTP_X_FORWARDED_FOR']))
{
    $iplist = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);

    foreach ($iplist as $ip)
    {
        if ($this->validate_ip($ip))
            return $ip;
    }
}

You may also want to validate IPv6 addresses.

Alix Axel
I definitely appreciate the `filter_var` fix as it removes a bunch of hackish unsigned int checks on the IP address. I also like the fact it gives me the option of validating IPv6 addresses as well. The `HTTP_X_FORWARDED_FOR` optimization is also much appreciated. In a few minutes I will update the code.
cballou
filter_var didn't work for me until I fixed it by adding || between options params instead of | .. Here is what made it work: FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 || FILTER_FLAG_IPV6 || FILTER_FLAG_NO_PRIV_RANGE || FILTER_FLAG_NO_RES_RANGE
Alex L
It's worth noting that usage of filter_var will only work with PHP > 5.2
cballou
`if (array_key_exists($key, $_SERVER) === true) { }` could be shortened to `if (array_key_exists($key, $_SERVER)) { }`, since array_key_exists always returns `true` or `false`.
Mathias Bynens
Well well, such a wrong code and such upvoted.
Col. Shrapnel
@Col. Shrapnel: Why do you say it's wrong?
Alix Axel
@Mathias Bynens: Yes, I know. My coding style is way too explicit sometimes, old habits die hard. =\
Alix Axel
It records some crap instead of IP address. I suppose you had never used your too explicit code for any real purpose. Or you would have seen it's weakness yourself. Ask yourself a question: Why does Apache HTTPD do record REMOTE_ADDR in their logs, not something alike your complex venture devised to determine an IP address? Suppose Apache folks not as as smart as yourself? Or, may be, they have some reasons to write exactly REMOTE_ADDR, not something else? ;)
Col. Shrapnel
@Col. Shrapnel: I will just assume you're too stupid to read and understand the question AND my **entire** answer.
Alix Axel
@alix well I assume you fail to confirm practical use of your code :)
Col. Shrapnel
@Col. Shrapnel: You assume right, I never had the need to use anything other than `REMOTE_ADDR`, still I think you should pay some attention where I mention the usual IP caveats of relying on more than the `REMOTE_ADDR` index **and** the OP original revision (http://stackoverflow.com/revisions/e733f4af-69c0-40a4-b168-3a5b2782d92a/view-source). You arrived a couple of months late to this discussion buddy.
Alix Axel
For me, filter_var had to be formatted thusly before it would work: filter_var($ip, FILTER_VALIDATE_IP, array('flags' => 'FILTER_FLAG_IPV4|FILTER_FLAG_IPV6|FILTER_FLAG_NO_PRIV_RANGE|FILTER_FLAG_NO_RES_RANGE'))
vamin
+1  A: 

The biggest question is for what purpose?

Your code is nearly as comprehensive as it could be - but I see that if you spot what looks like a proxy added header, you use that INSTEAD of the CLIENT_IP, however if you want this information for audit purposes then be warned - its very easy to fake.

Certainly you should never use IP addresses for any sort of authentication - even these can be spoofed.

You could get a better measurement of the client ip address by pushing out a flash or java applet which connects back to the server via a non-http port (which would therefore reveal transparent proxies or cases where the proxy-injected headers are false - but bear in mind that, where the client can ONLY connect via a web proxy or the outgoing port is blocked, there will be no connection from the applet.

C.

symcbean
Taking into consideration I am looking for a PHP only solution, are you suggesting I add `$_SERVER['CLIENT_IP']` as the second else if statement?
cballou
No - just that if you want to place any significance on the data returned, then it would be a good idea to preserve the network end point address (client IP) as well as anything suggesting a different value in proxy added headers (e.g. you may see lots of 192.168.1.x addresses but coming from different client ips)C.
symcbean
A: 

I do wonder if perhaps you should iterate over the exploded HTTP_X_FORWARDED_FOR in reverse order, since my experience has been that the user's IP address ends up at the end of the comma-separated list, so starting at the start of the header, you're more likely to get the ip address of one of the proxies returned, which could potentially still allow session hijacking as many users may come through that proxy.

Chris Withers
Having read the wikipedia page on HTTP_X_FORWARDED_FOR:http://en.wikipedia.org/wiki/X-Forwarded-For...I see the suggested order is, indeed, left to right as your code has it. However, from our logs I can see there are a lot of cases where this is not respected by proxies in the wild and the ip address your want to be checking could be at either end of the list.
Chris Withers
A: 

I have modified @cballou's code to make it standalone (not in a class) and to fix one minor syntax error:

<?php
 /**
  * Ensures an ip address is both a valid IP and does not fall within
  * a private network range.
  *
  * @access public
  * @param string $ip
  */
 function validate_ip($ip) {
     if (filter_var($ip, FILTER_VALIDATE_IP,
                         FILTER_FLAG_IPV4 |
                         FILTER_FLAG_IPV6 |
                         FILTER_FLAG_NO_PRIV_RANGE |
                         FILTER_FLAG_NO_RES_RANGE) === false)
         return false;
     return true;
 }

 /**
  * Retrieves the best guess of the client's actual IP address.
  * Takes into account numerous HTTP proxy headers due to variations
  * in how different ISPs handle IP addresses in headers between hops.
  */
 function get_ip_address() {
  // check for shared internet/ISP IP
  if (!empty($_SERVER['HTTP_CLIENT_IP']) && validate_ip($_SERVER['HTTP_CLIENT_IP']))
   return $_SERVER['HTTP_CLIENT_IP'];

  // check for IPs passing through proxies
  if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
   // check if multiple ips exist in var
    $iplist = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
    foreach ($iplist as $ip) {
     if (validate_ip($ip))
      return $ip;
    }
  }
  if (!empty($_SERVER['HTTP_X_FORWARDED']) && validate_ip($_SERVER['HTTP_X_FORWARDED']))
   return $_SERVER['HTTP_X_FORWARDED'];
  if (!empty($_SERVER['HTTP_X_CLUSTER_CLIENT_IP']) && validate_ip($_SERVER['HTTP_X_CLUSTER_CLIENT_IP']))
   return $_SERVER['HTTP_X_CLUSTER_CLIENT_IP'];
  if (!empty($_SERVER['HTTP_FORWARDED_FOR']) && validate_ip($_SERVER['HTTP_FORWARDED_FOR']))
   return $_SERVER['HTTP_FORWARDED_FOR'];
  if (!empty($_SERVER['HTTP_FORWARDED']) && validate_ip($_SERVER['HTTP_FORWARDED']))
   return $_SERVER['HTTP_FORWARDED'];

  // return unreliable ip since all else failed
  return $_SERVER['REMOTE_ADDR'];
 }
?>
Joseph Turian
@Joseph - To drastically reduce the number of lines of code, see the revised solution provided by @Alix. You would no longer need the validate_ip() function.
cballou