Related
I know there are a plethora of $_SERVER variables headers available for IP address retrieval. I was wondering if there is a general consensus as to how to most accurately retrieve a user's real IP address (well knowing no method is perfect) using said variables?
I spent some time trying to find an in depth solution and came up with the following code based on a number of sources. I would love it if somebody could please poke holes in the answer or shed some light on something perhaps more accurate.
edit includes optimizations from #Alix
/**
* 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.
*/
public function get_ip_address() {
// Check for shared internet/ISP IP
if (!empty($_SERVER['HTTP_CLIENT_IP']) && $this->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 IP addresses exist in var
$iplist = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
foreach ($iplist as $ip) {
if ($this->validate_ip($ip))
return $ip;
}
}
}
if (!empty($_SERVER['HTTP_X_FORWARDED']) && $this->validate_ip($_SERVER['HTTP_X_FORWARDED']))
return $_SERVER['HTTP_X_FORWARDED'];
if (!empty($_SERVER['HTTP_X_CLUSTER_CLIENT_IP']) && $this->validate_ip($_SERVER['HTTP_X_CLUSTER_CLIENT_IP']))
return $_SERVER['HTTP_X_CLUSTER_CLIENT_IP'];
if (!empty($_SERVER['HTTP_FORWARDED_FOR']) && $this->validate_ip($_SERVER['HTTP_FORWARDED_FOR']))
return $_SERVER['HTTP_FORWARDED_FOR'];
if (!empty($_SERVER['HTTP_FORWARDED']) && $this->validate_ip($_SERVER['HTTP_FORWARDED']))
return $_SERVER['HTTP_FORWARDED'];
// Return unreliable IP address since all else failed
return $_SERVER['REMOTE_ADDR'];
}
/**
* Ensures an IP address is both a valid IP address and does not fall within
* a private network range.
*
* #access public
* #param string $ip
*/
public 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;
self::$ip = $ip;
return true;
}
Words of Warning (update)
REMOTE_ADDR still represents the most reliable source of an IP address. The other $_SERVER variables mentioned here can be spoofed by a remote client very easily. The purpose of this solution is to attempt to determine the IP address of a client sitting behind a proxy. For your general purposes, you might consider using this in combination with the IP address returned directly from $_SERVER['REMOTE_ADDR'] and storing both.
For 99.9% of users this solution will suit your needs perfectly. It will not protect you from the 0.1% of malicious users looking to abuse your system by injecting their own request headers. If relying on IP addresses for something mission critical, resort to REMOTE_ADDR and don't bother catering to those behind a proxy.
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_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) !== false){
return $ip;
}
}
}
}
}
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_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.
Even then however, getting a user's real IP address is going to be unreliable. All they need to do is use an anonymous proxy server (one that doesn't honor the headers for http_x_forwarded_for, http_forwarded, etc) and all you get is their proxy server's IP address.
You can then see if there is a list of proxy server IP addresses 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 addresses they registered, and get their IP address 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 IP addresses 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 IP addresses are the way of broadband anymore. So. Even if you get a user IP address, expect it to change in 2 - 3 months, at the longest.
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'];
}
}
The explode on HTTP_X_FORWARDED_FOR is because of weird issues we had detecting IP addresses when Squid was used.
My answer is basically just a polished, fully-validated, and fully-packaged, version of #AlixAxel's answer:
<?php
/* Get the 'best known' client IP. */
if (!function_exists('getClientIP'))
{
function getClientIP()
{
if (isset($_SERVER["HTTP_CF_CONNECTING_IP"]))
{
$_SERVER['REMOTE_ADDR'] = $_SERVER["HTTP_CF_CONNECTING_IP"];
};
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))
{
foreach (explode(',', $_SERVER[$key]) as $ip)
{
$ip = trim($ip);
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) !== false)
{
return $ip;
};
};
};
};
return false;
};
};
$best_known_ip = getClientIP();
if(!empty($best_known_ip))
{
$ip = $clients_ip = $client_ip = $client_IP = $best_known_ip;
}
else
{
$ip = $clients_ip = $client_ip = $client_IP = $best_known_ip = '';
};
?>
Changes:
It simplifies the function name (with 'camelCase' formatting style).
It includes a check to make sure the function isn't already declared in another part of your code.
It takes into account 'CloudFlare' compatibility.
It initializes multiple "IP-related" variable names to the returned value, of the 'getClientIP' function.
It ensures that if the function doesn't return a valid IP address, all the variables are set to a empty string, instead of null.
It's only (45) lines of code.
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.
Thanks for this, very useful.
It would help though if the code were syntactically correct. As it is there's a { too many around line 20. Which I'm afraid means nobody actually tried this out.
I may be crazy, but after trying it on a few valid and invalid addresses, the only version of validate_ip() that worked was this:
public function validate_ip($ip)
{
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE) === false)
return false;
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_RES_RANGE) === false)
return false;
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) === false && filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) === false)
return false;
return true;
}
Here's a modified version if you use CloudFlare caching layer Services
function getIP()
{
$fields = array('HTTP_X_FORWARDED_FOR',
'REMOTE_ADDR',
'HTTP_CF_CONNECTING_IP',
'HTTP_X_CLUSTER_CLIENT_IP');
foreach($fields as $f)
{
$tries = $_SERVER[$f];
if (empty($tries))
continue;
$tries = explode(',',$tries);
foreach($tries as $try)
{
$r = filter_var($try,
FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 |
FILTER_FLAG_NO_PRIV_RANGE |
FILTER_FLAG_NO_RES_RANGE);
if ($r !== false)
{
return $try;
}
}
}
return false;
}
Just another clean way:
function validateIp($var_ip){
$ip = trim($var_ip);
return (!empty($ip) &&
$ip != '::1' &&
$ip != '127.0.0.1' &&
filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) !== false)
? $ip : false;
}
function getClientIp() {
$ip = #$this->validateIp($_SERVER['HTTP_CLIENT_IP']) ?:
#$this->validateIp($_SERVER['HTTP_X_FORWARDED_FOR']) ?:
#$this->validateIp($_SERVER['HTTP_X_FORWARDED']) ?:
#$this->validateIp($_SERVER['HTTP_FORWARDED_FOR']) ?:
#$this->validateIp($_SERVER['HTTP_FORWARDED']) ?:
#$this->validateIp($_SERVER['REMOTE_ADDR']) ?:
'LOCAL OR UNKNOWN ACCESS';
return $ip;
}
i realize there are much better and more concise answers above, and this isnt a function nor the most graceful script around. In our case we needed to output both the spoofable x_forwarded_for and the more reliable remote_addr in a simplistic switch per-say. It needed to allow blanks for injecting into other functions if-none or if-singular (rather than just returning the preformatted function). It needed an "on or off" var with a per-switch customized label(s) for platform settings. It also needed a way for $ip to be dynamic depending on request so that it would take form of forwarded_for.
Also i didnt see anyone address isset() vs !empty() -- its possible to enter nothing for x_forwarded_for yet still trigger isset() truth resulting in blank var, a way to get around is to use && and combine both as conditions. Keep in mind you can spoof words like "PWNED" as x_forwarded_for so make sure you sterilize to a real ip syntax if your outputting somewhere protected or into DB.
Also also, you can test using google translate if you need a multi-proxy to see the array in x_forwarder_for. If you wanna spoof headers to test, check this out Chrome Client Header Spoof extension. This will default to just standard remote_addr while behind anon proxy.
I dunno any case where remote_addr could be empty, but its there as fallback just in case.
// proxybuster - attempts to un-hide originating IP if [reverse]proxy provides methods to do so
$enableProxyBust = true;
if (($enableProxyBust == true) && (isset($_SERVER['REMOTE_ADDR'])) && (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) && (!empty($_SERVER['HTTP_X_FORWARDED_FOR']))) {
$ip = end(array_values(array_filter(explode(',',$_SERVER['HTTP_X_FORWARDED_FOR']))));
$ipProxy = $_SERVER['REMOTE_ADDR'];
$ipProxy_label = ' behind proxy ';
} elseif (($enableProxyBust == true) && (isset($_SERVER['REMOTE_ADDR']))) {
$ip = $_SERVER['REMOTE_ADDR'];
$ipProxy = '';
$ipProxy_label = ' no proxy ';
} elseif (($enableProxyBust == false) && (isset($_SERVER['REMOTE_ADDR']))) {
$ip = $_SERVER['REMOTE_ADDR'];
$ipProxy = '';
$ipProxy_label = '';
} else {
$ip = '';
$ipProxy = '';
$ipProxy_label = '';
}
To make these dynamic for use in function(s) or query/echo/views below, say for log gen or error reporting, use globals or just echo em in wherever you desire without making a ton of other conditions or static-schema-output functions.
function fooNow() {
global $ip, $ipProxy, $ipProxy_label;
// begin this actions such as log, error, query, or report
}
Thank you for all your great thoughts. Please let me know if this could be better, still kinda new to these headers :)
I came up with this function that does not simply return the IP address but an array with IP information.
// Example usage:
$info = ip_info();
if ( $info->proxy ) {
echo 'Your IP is ' . $info->ip;
} else {
echo 'Your IP is ' . $info->ip . ' and your proxy is ' . $info->proxy_ip;
}
Here's the function:
/**
* 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.
*
* #since 1.1.3
*
* #return object {
* IP Address details
*
* string $ip The users IP address (might be spoofed, if $proxy is true)
* bool $proxy True, if a proxy was detected
* string $proxy_id The proxy-server IP address
* }
*/
function ip_info() {
$result = (object) array(
'ip' => $_SERVER['REMOTE_ADDR'],
'proxy' => false,
'proxy_ip' => '',
);
/*
* This code tries to bypass a proxy and get the actual IP address of
* the visitor behind the proxy.
* Warning: These values might be spoofed!
*/
$ip_fields = array(
'HTTP_CLIENT_IP',
'HTTP_X_FORWARDED_FOR',
'HTTP_X_FORWARDED',
'HTTP_X_CLUSTER_CLIENT_IP',
'HTTP_FORWARDED_FOR',
'HTTP_FORWARDED',
'REMOTE_ADDR',
);
foreach ( $ip_fields as $key ) {
if ( array_key_exists( $key, $_SERVER ) === true ) {
foreach ( explode( ',', $_SERVER[$key] ) as $ip ) {
$ip = trim( $ip );
if ( filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE ) !== false ) {
$forwarded = $ip;
break 2;
}
}
}
}
// If we found a different IP address then REMOTE_ADDR then it's a proxy!
if ( $forwarded != $result->ip ) {
$result->proxy = true;
$result->proxy_ip = $result->ip;
$result->ip = $forwarded;
}
return $result;
}
As someone said previously, the key here is for what reason you want to store user's ips.
I'll give an example from a registration system I work on and of course the solution just to contribute sth in this old discussion that comes frequently in my searches.
Many php registration libraries use ip to throttle/lock out failed attempts based on user's ip.
Consider this table:
-- mysql
DROP TABLE IF EXISTS `attempts`;
CREATE TABLE `attempts` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`ip` varchar(39) NOT NULL, /*<<=====*/
`expiredate` datetime NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- sqlite
...
Then, when a user tries to do a login or anything related with servicing like a password reset, a function is called at the start:
public function isBlocked() {
/*
* used one of the above methods to capture user's ip!!!
*/
$ip = $this->ip;
// delete attempts from this ip with 'expiredate' in the past
$this->deleteAttempts($ip, false);
$query = $this->dbh->prepare("SELECT count(*) FROM {$this->token->get('table_attempts')} WHERE ip = ?");
$query->execute(array($ip));
$attempts = $query->fetchColumn();
if ($attempts < intval($this->token->get('attempts_before_verify'))) {
return "allow";
}
if ($attempts < intval($this->token->get('attempts_before_ban'))) {
return "captcha";
}
return "block";
}
Say, for example, $this->token->get('attempts_before_ban') === 10 and 2 users come for the same ips as is the case in the previous codes where headers can be spoofed, then after 5 attempts each both are banned!
Even worst, if all come from the same proxy then only the first 10 users will be logged and all the rest will be banned!
The critical here is that we need a unique index on table attempts and we can get it from a combination like:
`ip` varchar(39) NOT NULL,
`jwt_load varchar(100) NOT NULL
where jwt_load comes from a http cookie that follows the json web token technology where we store only the encrypted payload that should contain an arbitrary/unique value for every user.
Of course the request should be modified to: "SELECT count(*) FROM {$this->token->get('table_attempts')} WHERE ip = ? AND jwt_load = ?" and the class should also initiate a private $jwt.
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.
From Symfony's Request class
https://github.com/symfony/symfony/blob/1bd125ec4a01220878b3dbc3ec3156b073996af9/src/Symfony/Component/HttpFoundation/Request.php
const HEADER_FORWARDED = 'forwarded';
const HEADER_CLIENT_IP = 'client_ip';
const HEADER_CLIENT_HOST = 'client_host';
const HEADER_CLIENT_PROTO = 'client_proto';
const HEADER_CLIENT_PORT = 'client_port';
/**
* Names for headers that can be trusted when
* using trusted proxies.
*
* The FORWARDED header is the standard as of rfc7239.
*
* The other headers are non-standard, but widely used
* by popular reverse proxies (like Apache mod_proxy or Amazon EC2).
*/
protected static $trustedHeaders = array(
self::HEADER_FORWARDED => 'FORWARDED',
self::HEADER_CLIENT_IP => 'X_FORWARDED_FOR',
self::HEADER_CLIENT_HOST => 'X_FORWARDED_HOST',
self::HEADER_CLIENT_PROTO => 'X_FORWARDED_PROTO',
self::HEADER_CLIENT_PORT => 'X_FORWARDED_PORT',
);
/**
* Returns the client IP addresses.
*
* In the returned array the most trusted IP address is first, and the
* least trusted one last. The "real" client IP address is the last one,
* but this is also the least trusted one. Trusted proxies are stripped.
*
* Use this method carefully; you should use getClientIp() instead.
*
* #return array The client IP addresses
*
* #see getClientIp()
*/
public function getClientIps()
{
$clientIps = array();
$ip = $this->server->get('REMOTE_ADDR');
if (!$this->isFromTrustedProxy()) {
return array($ip);
}
if (self::$trustedHeaders[self::HEADER_FORWARDED] && $this->headers->has(self::$trustedHeaders[self::HEADER_FORWARDED])) {
$forwardedHeader = $this->headers->get(self::$trustedHeaders[self::HEADER_FORWARDED]);
preg_match_all('{(for)=("?\[?)([a-z0-9\.:_\-/]*)}', $forwardedHeader, $matches);
$clientIps = $matches[3];
} elseif (self::$trustedHeaders[self::HEADER_CLIENT_IP] && $this->headers->has(self::$trustedHeaders[self::HEADER_CLIENT_IP])) {
$clientIps = array_map('trim', explode(',', $this->headers->get(self::$trustedHeaders[self::HEADER_CLIENT_IP])));
}
$clientIps[] = $ip; // Complete the IP chain with the IP the request actually came from
$firstTrustedIp = null;
foreach ($clientIps as $key => $clientIp) {
// Remove port (unfortunately, it does happen)
if (preg_match('{((?:\d+\.){3}\d+)\:\d+}', $clientIp, $match)) {
$clientIps[$key] = $clientIp = $match[1];
}
if (!filter_var($clientIp, FILTER_VALIDATE_IP)) {
unset($clientIps[$key]);
}
if (IpUtils::checkIp($clientIp, self::$trustedProxies)) {
unset($clientIps[$key]);
// Fallback to this when the client IP falls into the range of trusted proxies
if (null === $firstTrustedIp) {
$firstTrustedIp = $clientIp;
}
}
}
// Now the IP chain contains only untrusted proxies and the client IP
return $clientIps ? array_reverse($clientIps) : array($firstTrustedIp);
}
I'm surprised no one has mentioned filter_input, so here is Alix Axel's answer condensed to one-line:
function get_ip_address(&$keys = ['HTTP_X_FORWARDED_FOR', 'HTTP_X_FORWARDED', 'HTTP_X_CLUSTER_CLIENT_IP', 'HTTP_FORWARDED_FOR', 'HTTP_FORWARDED', 'HTTP_CLIENT_IP', 'REMOTE_ADDR'])
{
return empty($keys) || ($ip = filter_input(INPUT_SERVER, array_pop($keys), FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE))? $ip : get_ip_address($keys);
}
I know this is too late to answer. But you may try these options:
Option 1: (Using curl)
$ch = curl_init();
// set URL and other appropriate options
curl_setopt($ch, CURLOPT_URL, "https://ifconfig.me/");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
// grab URL and pass it to the browser
$ip = curl_exec($ch);
// close cURL resource, and free up system resources
curl_close($ch);
return $ip;
Option 2: (Works good on mac)
return trim(shell_exec("dig +short myip.opendns.com #resolver1.opendns.com"));
Option 3: (Just used a trick)
return str_replace('Current IP CheckCurrent IP Address: ', '', strip_tags(file_get_contents('http://checkip.dyndns.com')));
Might be a reference:
https://www.tecmint.com/find-linux-server-public-ip-address/
Though this post very old but the topic still demands attention. So here I come with another solution I used in my project. I found other solutions here either incomplete or too complex to understand.
if (! function_exists('get_visitor_IP'))
{
/**
* Get the real IP address from visitors proxy. e.g. Cloudflare
*
* #return string IP
*/
function get_visitor_IP()
{
// Get real visitor IP behind CloudFlare network
if (isset($_SERVER["HTTP_CF_CONNECTING_IP"])) {
$_SERVER['REMOTE_ADDR'] = $_SERVER["HTTP_CF_CONNECTING_IP"];
$_SERVER['HTTP_CLIENT_IP'] = $_SERVER["HTTP_CF_CONNECTING_IP"];
}
// Sometimes the `HTTP_CLIENT_IP` can be used by proxy servers
$ip = #$_SERVER['HTTP_CLIENT_IP'];
if (filter_var($ip, FILTER_VALIDATE_IP)) {
return $ip;
}
// Sometimes the `HTTP_X_FORWARDED_FOR` can contain more than IPs
$forward_ips = #$_SERVER['HTTP_X_FORWARDED_FOR'];
if ($forward_ips) {
$all_ips = explode(',', $forward_ips);
foreach ($all_ips as $ip) {
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)){
return $ip;
}
}
}
return $_SERVER['REMOTE_ADDR'];
}
}
You pretty much answered your own question! :)
function getRealIpAddr() {
if(!empty($_SERVER['HTTP_CLIENT_IP'])) //Check IP address from shared Internet
{
$IPaddress = $_SERVER['HTTP_CLIENT_IP'];
}
elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) //To check IP address is passed from the proxy
{
$IPaddress = $_SERVER['HTTP_X_FORWARDED_FOR'];
}
else
{
$IPaddress = $_SERVER['REMOTE_ADDR'];
}
return $IPaddress;
}
Source
Just a VB.NET version of the answer:
Private Function GetRequestIpAddress() As IPAddress
Dim serverVariables = HttpContext.Current.Request.ServerVariables
Dim headersKeysToCheck = {"HTTP_CLIENT_IP", _
"HTTP_X_FORWARDED_FOR", _
"HTTP_X_FORWARDED", _
"HTTP_X_CLUSTER_CLIENT_IP", _
"HTTP_FORWARDED_FOR", _
"HTTP_FORWARDED", _
"REMOTE_ADDR"}
For Each thisHeaderKey In headersKeysToCheck
Dim thisValue = serverVariables.Item(thisHeaderKey)
If thisValue IsNot Nothing Then
Dim validAddress As IPAddress = Nothing
If IPAddress.TryParse(thisValue, validAddress) Then
Return validAddress
End If
End If
Next
Return Nothing
End Function
/**
* 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);
}
I have to upload a website that uses Laravel.
The server I must use are using a reverse proxy and when I put the files i developped on my computer, I'm getting a DNS error.
I do not have access to the server configuration, I can only upload/download files on the website's server partition.
I searched to find a solution but anything I can find was sort of related to this question.
So, this is not Laravel version, but this can help you, I hope !
Here is some code I wrote in cakePHP 2.X because I have some issues with a reverse proxy too
Cache class is the one from cakePHP, it's very simple (60 sec expiration, automatically serialized data).
LogError function is a simple log function
The code is the following
// Constants
define('REVERSE_PROXY_ADDR' , 'r-proxy.internal-domain.com');
define('REVERSE_PROXY_CACHE', 'r-proxy');
// Cache Config
Cache::config(REVERSE_PROXY_CACHE, array(
'engine' => 'File',
'prefix' => REVERSE_PROXY_CACHE . '_',
'path' => CACHE . REVERSE_PROXY_CACHE . '/',
'serialize' => true,
'duration' => 60,
));
function clientIp()
{
// Originaly from CakePHP 2.X
// ------------------------------
if ( isset($_SERVER['HTTP_CLIENT_IP']) )
{
$ipaddr = $_SERVER['HTTP_CLIENT_IP'];
}
else
{
$ipaddr = $_SERVER['REMOTE_ADDR'];
}
if ( isset($_SERVER['HTTP_CLIENTADDRESS']) )
{
$tmpipaddr = $_SERVER['HTTP_CLIENTADDRESS'];
if ( !empty( $tmpipaddr ) )
{
$ipaddr = preg_replace('/(?:,.*)/', '', $tmpipaddr);
}
}
$ip = trim($ipaddr);
// ------------------------------
// Reverse proxy stuff
if ( defined('REVERSE_PROXY_ADDR') && defined('REVERSE_PROXY_CACHE') )
{
$xfor = preg_replace('/(?:,.*)/', '', $_SERVER['HTTP_X_FORWARDED_FOR']);
$list = Cache::read('iplist', REVERSE_PROXY_CACHE);
if ( $list === false )
{
$list = gethostbynamel(REVERSE_PROXY_ADDR);
Cache::write('iplist', $list, REVERSE_PROXY_CACHE);
}
// empty or unreadable
if ( empty( $list ) )
{
logError('Unable to gather reverse proxy ip list, or empty list');
logError('Type : ' . gettype($list));
logError('IP : ' . $ip . ' - X-FORWARDED-FOR : ' . $xfor);
return $ip;
}
// not array ?!?!
if ( !is_array($list) )
{
logError('Given list was not an array');
logError('Type : ' . gettype($list));
logError($list);
return $ip;
}
// if in array, give forwarded for header
if ( in_array($ip, $list, true) )
{
return $xfor;
}
}
return $ip;
}
Then you just have to call the clientIp(); function.
If you have a static IP Address for your reverse proxy, you can just set it manually in the code, but that's not a good practice. But you will not need to use cache, and it will simplify a lot the code.
If you use a dynamic reverse proxy, you will have to query it on its hostname like this (what I did in the posted code) :
gethostbynamel('reverse-proxy-addr') to get a list of possible rproxy IPs
OR
gethostbyname('reverse-proxy-addr') to get one IP for rproxy
In other case you just have to check that REMOTE_ADDR is in a list of IPs marked as Reverse-proxy IPs
Hope it helps !
I have a xampp server installed in a windows cpu which I use as a public server for my clients. Now I have made a request box for my clients to drop some request on it, and I am saving their IP in db while sending data through php. I use this php script to get the clients IP address
function get_ip() {
//Just get the headers if we can or else use the SERVER global
if ( function_exists( 'apache_request_headers' ) ) {
$headers = apache_request_headers();
} else {
$headers = $_SERVER;
}
//Get the forwarded IP if it exists
if ( array_key_exists( 'X-Forwarded-For', $headers ) && filter_var( $headers['X-Forwarded-For'], FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 ) ) {
$the_ip = $headers['X-Forwarded-For'];
} elseif ( array_key_exists( 'HTTP_X_FORWARDED_FOR', $headers ) && filter_var( $headers['HTTP_X_FORWARDED_FOR'], FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 )
) {
$the_ip = $headers['HTTP_X_FORWARDED_FOR'];
} else {
$the_ip = filter_var( $_SERVER['REMOTE_ADDR'], FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 );
}
return $the_ip;
}
echo get_ip();
But XAMPP is showing the machine's default gateway instead of showing the visitor's IP.
How can I fix that?
Yesterday, i post an 'ad' on an ebay site (marktplaats.nl) and the ad was immediately visible in google when i search for this ad on Google.
How do they do this?
Because it is a very large site, i don't think they repost the sitemap. I have made such function to update the sitemap but don't know this is a good method. What i have is this (part of my library):
// returns false when input invalid, returns a number (array index) when url is invalid or // request fails, returns true when complete:
function suSubmitSitemap( $sXmlUrl, $asSearchEnginePingUrl = 'http://www.google.com/ping?sitemap=%s', $bContinueOnError = false )
{
if( !suIsValidString( $sXmlUrl ) || ( !suIsValidString( $asSearchEnginePingUrl ) && !suIsValidArray( $asSearchEnginePingUrl )))
{ return false; }
$a = (is_array($asSearchEnginePingUrl))?$asSearchEnginePingUrl:explode(',', $asSearchEnginePingUrl );
$sXmlUrl = urlencode( $sXmlUrl );
$ret = false;
foreach( $a as $i=>$sUrl )
{
$sUri = str_replace( '%s', $sXmlUrl, $sUrl );
$bValid = (!is_bool( strpos( $sUrl, '%s' )) && suGetUrlContent( $sUri ));
if( !$bValid )
{
if( !$bContinueOnError )
{ return $i; }
if( !is_array( $ret ))
{ $ret = array(); }
$ret[$i] = $sUri;
}
}
return ret;
}
Is this a safe way to do this (Google will not ban you when frequently called), when this is not the way to do it, does anyone know how implement such index update functionality in PHP?
Google will constantly crawl frequently updated/popular sites and update their search results. This has the benefit of making Google more relevant.
Resubmitting your site map will not help you get crawled as fast as Ebay. Get more visitors and post content more frequently to get Google to visit your site more often.
Also, check out:
http://www.google.com/support/webmasters/bin/answer.py?answer=48620
http://www.searchenginejournal.com/10-ways-to-increase-your-site-crawl-rate/7159/
I know there are a plethora of $_SERVER variables headers available for IP address retrieval. I was wondering if there is a general consensus as to how to most accurately retrieve a user's real IP address (well knowing no method is perfect) using said variables?
I spent some time trying to find an in depth solution and came up with the following code based on a number of sources. I would love it if somebody could please poke holes in the answer or shed some light on something perhaps more accurate.
edit includes optimizations from #Alix
/**
* 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.
*/
public function get_ip_address() {
// Check for shared internet/ISP IP
if (!empty($_SERVER['HTTP_CLIENT_IP']) && $this->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 IP addresses exist in var
$iplist = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
foreach ($iplist as $ip) {
if ($this->validate_ip($ip))
return $ip;
}
}
}
if (!empty($_SERVER['HTTP_X_FORWARDED']) && $this->validate_ip($_SERVER['HTTP_X_FORWARDED']))
return $_SERVER['HTTP_X_FORWARDED'];
if (!empty($_SERVER['HTTP_X_CLUSTER_CLIENT_IP']) && $this->validate_ip($_SERVER['HTTP_X_CLUSTER_CLIENT_IP']))
return $_SERVER['HTTP_X_CLUSTER_CLIENT_IP'];
if (!empty($_SERVER['HTTP_FORWARDED_FOR']) && $this->validate_ip($_SERVER['HTTP_FORWARDED_FOR']))
return $_SERVER['HTTP_FORWARDED_FOR'];
if (!empty($_SERVER['HTTP_FORWARDED']) && $this->validate_ip($_SERVER['HTTP_FORWARDED']))
return $_SERVER['HTTP_FORWARDED'];
// Return unreliable IP address since all else failed
return $_SERVER['REMOTE_ADDR'];
}
/**
* Ensures an IP address is both a valid IP address and does not fall within
* a private network range.
*
* #access public
* #param string $ip
*/
public 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;
self::$ip = $ip;
return true;
}
Words of Warning (update)
REMOTE_ADDR still represents the most reliable source of an IP address. The other $_SERVER variables mentioned here can be spoofed by a remote client very easily. The purpose of this solution is to attempt to determine the IP address of a client sitting behind a proxy. For your general purposes, you might consider using this in combination with the IP address returned directly from $_SERVER['REMOTE_ADDR'] and storing both.
For 99.9% of users this solution will suit your needs perfectly. It will not protect you from the 0.1% of malicious users looking to abuse your system by injecting their own request headers. If relying on IP addresses for something mission critical, resort to REMOTE_ADDR and don't bother catering to those behind a proxy.
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_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) !== false){
return $ip;
}
}
}
}
}
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_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.
Even then however, getting a user's real IP address is going to be unreliable. All they need to do is use an anonymous proxy server (one that doesn't honor the headers for http_x_forwarded_for, http_forwarded, etc) and all you get is their proxy server's IP address.
You can then see if there is a list of proxy server IP addresses 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 addresses they registered, and get their IP address 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 IP addresses 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 IP addresses are the way of broadband anymore. So. Even if you get a user IP address, expect it to change in 2 - 3 months, at the longest.
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'];
}
}
The explode on HTTP_X_FORWARDED_FOR is because of weird issues we had detecting IP addresses when Squid was used.
My answer is basically just a polished, fully-validated, and fully-packaged, version of #AlixAxel's answer:
<?php
/* Get the 'best known' client IP. */
if (!function_exists('getClientIP'))
{
function getClientIP()
{
if (isset($_SERVER["HTTP_CF_CONNECTING_IP"]))
{
$_SERVER['REMOTE_ADDR'] = $_SERVER["HTTP_CF_CONNECTING_IP"];
};
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))
{
foreach (explode(',', $_SERVER[$key]) as $ip)
{
$ip = trim($ip);
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) !== false)
{
return $ip;
};
};
};
};
return false;
};
};
$best_known_ip = getClientIP();
if(!empty($best_known_ip))
{
$ip = $clients_ip = $client_ip = $client_IP = $best_known_ip;
}
else
{
$ip = $clients_ip = $client_ip = $client_IP = $best_known_ip = '';
};
?>
Changes:
It simplifies the function name (with 'camelCase' formatting style).
It includes a check to make sure the function isn't already declared in another part of your code.
It takes into account 'CloudFlare' compatibility.
It initializes multiple "IP-related" variable names to the returned value, of the 'getClientIP' function.
It ensures that if the function doesn't return a valid IP address, all the variables are set to a empty string, instead of null.
It's only (45) lines of code.
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.
Thanks for this, very useful.
It would help though if the code were syntactically correct. As it is there's a { too many around line 20. Which I'm afraid means nobody actually tried this out.
I may be crazy, but after trying it on a few valid and invalid addresses, the only version of validate_ip() that worked was this:
public function validate_ip($ip)
{
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE) === false)
return false;
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_RES_RANGE) === false)
return false;
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) === false && filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) === false)
return false;
return true;
}
Here's a modified version if you use CloudFlare caching layer Services
function getIP()
{
$fields = array('HTTP_X_FORWARDED_FOR',
'REMOTE_ADDR',
'HTTP_CF_CONNECTING_IP',
'HTTP_X_CLUSTER_CLIENT_IP');
foreach($fields as $f)
{
$tries = $_SERVER[$f];
if (empty($tries))
continue;
$tries = explode(',',$tries);
foreach($tries as $try)
{
$r = filter_var($try,
FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 |
FILTER_FLAG_NO_PRIV_RANGE |
FILTER_FLAG_NO_RES_RANGE);
if ($r !== false)
{
return $try;
}
}
}
return false;
}
Just another clean way:
function validateIp($var_ip){
$ip = trim($var_ip);
return (!empty($ip) &&
$ip != '::1' &&
$ip != '127.0.0.1' &&
filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) !== false)
? $ip : false;
}
function getClientIp() {
$ip = #$this->validateIp($_SERVER['HTTP_CLIENT_IP']) ?:
#$this->validateIp($_SERVER['HTTP_X_FORWARDED_FOR']) ?:
#$this->validateIp($_SERVER['HTTP_X_FORWARDED']) ?:
#$this->validateIp($_SERVER['HTTP_FORWARDED_FOR']) ?:
#$this->validateIp($_SERVER['HTTP_FORWARDED']) ?:
#$this->validateIp($_SERVER['REMOTE_ADDR']) ?:
'LOCAL OR UNKNOWN ACCESS';
return $ip;
}
i realize there are much better and more concise answers above, and this isnt a function nor the most graceful script around. In our case we needed to output both the spoofable x_forwarded_for and the more reliable remote_addr in a simplistic switch per-say. It needed to allow blanks for injecting into other functions if-none or if-singular (rather than just returning the preformatted function). It needed an "on or off" var with a per-switch customized label(s) for platform settings. It also needed a way for $ip to be dynamic depending on request so that it would take form of forwarded_for.
Also i didnt see anyone address isset() vs !empty() -- its possible to enter nothing for x_forwarded_for yet still trigger isset() truth resulting in blank var, a way to get around is to use && and combine both as conditions. Keep in mind you can spoof words like "PWNED" as x_forwarded_for so make sure you sterilize to a real ip syntax if your outputting somewhere protected or into DB.
Also also, you can test using google translate if you need a multi-proxy to see the array in x_forwarder_for. If you wanna spoof headers to test, check this out Chrome Client Header Spoof extension. This will default to just standard remote_addr while behind anon proxy.
I dunno any case where remote_addr could be empty, but its there as fallback just in case.
// proxybuster - attempts to un-hide originating IP if [reverse]proxy provides methods to do so
$enableProxyBust = true;
if (($enableProxyBust == true) && (isset($_SERVER['REMOTE_ADDR'])) && (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) && (!empty($_SERVER['HTTP_X_FORWARDED_FOR']))) {
$ip = end(array_values(array_filter(explode(',',$_SERVER['HTTP_X_FORWARDED_FOR']))));
$ipProxy = $_SERVER['REMOTE_ADDR'];
$ipProxy_label = ' behind proxy ';
} elseif (($enableProxyBust == true) && (isset($_SERVER['REMOTE_ADDR']))) {
$ip = $_SERVER['REMOTE_ADDR'];
$ipProxy = '';
$ipProxy_label = ' no proxy ';
} elseif (($enableProxyBust == false) && (isset($_SERVER['REMOTE_ADDR']))) {
$ip = $_SERVER['REMOTE_ADDR'];
$ipProxy = '';
$ipProxy_label = '';
} else {
$ip = '';
$ipProxy = '';
$ipProxy_label = '';
}
To make these dynamic for use in function(s) or query/echo/views below, say for log gen or error reporting, use globals or just echo em in wherever you desire without making a ton of other conditions or static-schema-output functions.
function fooNow() {
global $ip, $ipProxy, $ipProxy_label;
// begin this actions such as log, error, query, or report
}
Thank you for all your great thoughts. Please let me know if this could be better, still kinda new to these headers :)
I came up with this function that does not simply return the IP address but an array with IP information.
// Example usage:
$info = ip_info();
if ( $info->proxy ) {
echo 'Your IP is ' . $info->ip;
} else {
echo 'Your IP is ' . $info->ip . ' and your proxy is ' . $info->proxy_ip;
}
Here's the function:
/**
* 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.
*
* #since 1.1.3
*
* #return object {
* IP Address details
*
* string $ip The users IP address (might be spoofed, if $proxy is true)
* bool $proxy True, if a proxy was detected
* string $proxy_id The proxy-server IP address
* }
*/
function ip_info() {
$result = (object) array(
'ip' => $_SERVER['REMOTE_ADDR'],
'proxy' => false,
'proxy_ip' => '',
);
/*
* This code tries to bypass a proxy and get the actual IP address of
* the visitor behind the proxy.
* Warning: These values might be spoofed!
*/
$ip_fields = array(
'HTTP_CLIENT_IP',
'HTTP_X_FORWARDED_FOR',
'HTTP_X_FORWARDED',
'HTTP_X_CLUSTER_CLIENT_IP',
'HTTP_FORWARDED_FOR',
'HTTP_FORWARDED',
'REMOTE_ADDR',
);
foreach ( $ip_fields as $key ) {
if ( array_key_exists( $key, $_SERVER ) === true ) {
foreach ( explode( ',', $_SERVER[$key] ) as $ip ) {
$ip = trim( $ip );
if ( filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE ) !== false ) {
$forwarded = $ip;
break 2;
}
}
}
}
// If we found a different IP address then REMOTE_ADDR then it's a proxy!
if ( $forwarded != $result->ip ) {
$result->proxy = true;
$result->proxy_ip = $result->ip;
$result->ip = $forwarded;
}
return $result;
}
As someone said previously, the key here is for what reason you want to store user's ips.
I'll give an example from a registration system I work on and of course the solution just to contribute sth in this old discussion that comes frequently in my searches.
Many php registration libraries use ip to throttle/lock out failed attempts based on user's ip.
Consider this table:
-- mysql
DROP TABLE IF EXISTS `attempts`;
CREATE TABLE `attempts` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`ip` varchar(39) NOT NULL, /*<<=====*/
`expiredate` datetime NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- sqlite
...
Then, when a user tries to do a login or anything related with servicing like a password reset, a function is called at the start:
public function isBlocked() {
/*
* used one of the above methods to capture user's ip!!!
*/
$ip = $this->ip;
// delete attempts from this ip with 'expiredate' in the past
$this->deleteAttempts($ip, false);
$query = $this->dbh->prepare("SELECT count(*) FROM {$this->token->get('table_attempts')} WHERE ip = ?");
$query->execute(array($ip));
$attempts = $query->fetchColumn();
if ($attempts < intval($this->token->get('attempts_before_verify'))) {
return "allow";
}
if ($attempts < intval($this->token->get('attempts_before_ban'))) {
return "captcha";
}
return "block";
}
Say, for example, $this->token->get('attempts_before_ban') === 10 and 2 users come for the same ips as is the case in the previous codes where headers can be spoofed, then after 5 attempts each both are banned!
Even worst, if all come from the same proxy then only the first 10 users will be logged and all the rest will be banned!
The critical here is that we need a unique index on table attempts and we can get it from a combination like:
`ip` varchar(39) NOT NULL,
`jwt_load varchar(100) NOT NULL
where jwt_load comes from a http cookie that follows the json web token technology where we store only the encrypted payload that should contain an arbitrary/unique value for every user.
Of course the request should be modified to: "SELECT count(*) FROM {$this->token->get('table_attempts')} WHERE ip = ? AND jwt_load = ?" and the class should also initiate a private $jwt.
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.
From Symfony's Request class
https://github.com/symfony/symfony/blob/1bd125ec4a01220878b3dbc3ec3156b073996af9/src/Symfony/Component/HttpFoundation/Request.php
const HEADER_FORWARDED = 'forwarded';
const HEADER_CLIENT_IP = 'client_ip';
const HEADER_CLIENT_HOST = 'client_host';
const HEADER_CLIENT_PROTO = 'client_proto';
const HEADER_CLIENT_PORT = 'client_port';
/**
* Names for headers that can be trusted when
* using trusted proxies.
*
* The FORWARDED header is the standard as of rfc7239.
*
* The other headers are non-standard, but widely used
* by popular reverse proxies (like Apache mod_proxy or Amazon EC2).
*/
protected static $trustedHeaders = array(
self::HEADER_FORWARDED => 'FORWARDED',
self::HEADER_CLIENT_IP => 'X_FORWARDED_FOR',
self::HEADER_CLIENT_HOST => 'X_FORWARDED_HOST',
self::HEADER_CLIENT_PROTO => 'X_FORWARDED_PROTO',
self::HEADER_CLIENT_PORT => 'X_FORWARDED_PORT',
);
/**
* Returns the client IP addresses.
*
* In the returned array the most trusted IP address is first, and the
* least trusted one last. The "real" client IP address is the last one,
* but this is also the least trusted one. Trusted proxies are stripped.
*
* Use this method carefully; you should use getClientIp() instead.
*
* #return array The client IP addresses
*
* #see getClientIp()
*/
public function getClientIps()
{
$clientIps = array();
$ip = $this->server->get('REMOTE_ADDR');
if (!$this->isFromTrustedProxy()) {
return array($ip);
}
if (self::$trustedHeaders[self::HEADER_FORWARDED] && $this->headers->has(self::$trustedHeaders[self::HEADER_FORWARDED])) {
$forwardedHeader = $this->headers->get(self::$trustedHeaders[self::HEADER_FORWARDED]);
preg_match_all('{(for)=("?\[?)([a-z0-9\.:_\-/]*)}', $forwardedHeader, $matches);
$clientIps = $matches[3];
} elseif (self::$trustedHeaders[self::HEADER_CLIENT_IP] && $this->headers->has(self::$trustedHeaders[self::HEADER_CLIENT_IP])) {
$clientIps = array_map('trim', explode(',', $this->headers->get(self::$trustedHeaders[self::HEADER_CLIENT_IP])));
}
$clientIps[] = $ip; // Complete the IP chain with the IP the request actually came from
$firstTrustedIp = null;
foreach ($clientIps as $key => $clientIp) {
// Remove port (unfortunately, it does happen)
if (preg_match('{((?:\d+\.){3}\d+)\:\d+}', $clientIp, $match)) {
$clientIps[$key] = $clientIp = $match[1];
}
if (!filter_var($clientIp, FILTER_VALIDATE_IP)) {
unset($clientIps[$key]);
}
if (IpUtils::checkIp($clientIp, self::$trustedProxies)) {
unset($clientIps[$key]);
// Fallback to this when the client IP falls into the range of trusted proxies
if (null === $firstTrustedIp) {
$firstTrustedIp = $clientIp;
}
}
}
// Now the IP chain contains only untrusted proxies and the client IP
return $clientIps ? array_reverse($clientIps) : array($firstTrustedIp);
}
I'm surprised no one has mentioned filter_input, so here is Alix Axel's answer condensed to one-line:
function get_ip_address(&$keys = ['HTTP_X_FORWARDED_FOR', 'HTTP_X_FORWARDED', 'HTTP_X_CLUSTER_CLIENT_IP', 'HTTP_FORWARDED_FOR', 'HTTP_FORWARDED', 'HTTP_CLIENT_IP', 'REMOTE_ADDR'])
{
return empty($keys) || ($ip = filter_input(INPUT_SERVER, array_pop($keys), FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE))? $ip : get_ip_address($keys);
}
I know this is too late to answer. But you may try these options:
Option 1: (Using curl)
$ch = curl_init();
// set URL and other appropriate options
curl_setopt($ch, CURLOPT_URL, "https://ifconfig.me/");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
// grab URL and pass it to the browser
$ip = curl_exec($ch);
// close cURL resource, and free up system resources
curl_close($ch);
return $ip;
Option 2: (Works good on mac)
return trim(shell_exec("dig +short myip.opendns.com #resolver1.opendns.com"));
Option 3: (Just used a trick)
return str_replace('Current IP CheckCurrent IP Address: ', '', strip_tags(file_get_contents('http://checkip.dyndns.com')));
Might be a reference:
https://www.tecmint.com/find-linux-server-public-ip-address/
Though this post very old but the topic still demands attention. So here I come with another solution I used in my project. I found other solutions here either incomplete or too complex to understand.
if (! function_exists('get_visitor_IP'))
{
/**
* Get the real IP address from visitors proxy. e.g. Cloudflare
*
* #return string IP
*/
function get_visitor_IP()
{
// Get real visitor IP behind CloudFlare network
if (isset($_SERVER["HTTP_CF_CONNECTING_IP"])) {
$_SERVER['REMOTE_ADDR'] = $_SERVER["HTTP_CF_CONNECTING_IP"];
$_SERVER['HTTP_CLIENT_IP'] = $_SERVER["HTTP_CF_CONNECTING_IP"];
}
// Sometimes the `HTTP_CLIENT_IP` can be used by proxy servers
$ip = #$_SERVER['HTTP_CLIENT_IP'];
if (filter_var($ip, FILTER_VALIDATE_IP)) {
return $ip;
}
// Sometimes the `HTTP_X_FORWARDED_FOR` can contain more than IPs
$forward_ips = #$_SERVER['HTTP_X_FORWARDED_FOR'];
if ($forward_ips) {
$all_ips = explode(',', $forward_ips);
foreach ($all_ips as $ip) {
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)){
return $ip;
}
}
}
return $_SERVER['REMOTE_ADDR'];
}
}
You pretty much answered your own question! :)
function getRealIpAddr() {
if(!empty($_SERVER['HTTP_CLIENT_IP'])) //Check IP address from shared Internet
{
$IPaddress = $_SERVER['HTTP_CLIENT_IP'];
}
elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) //To check IP address is passed from the proxy
{
$IPaddress = $_SERVER['HTTP_X_FORWARDED_FOR'];
}
else
{
$IPaddress = $_SERVER['REMOTE_ADDR'];
}
return $IPaddress;
}
Source
Just a VB.NET version of the answer:
Private Function GetRequestIpAddress() As IPAddress
Dim serverVariables = HttpContext.Current.Request.ServerVariables
Dim headersKeysToCheck = {"HTTP_CLIENT_IP", _
"HTTP_X_FORWARDED_FOR", _
"HTTP_X_FORWARDED", _
"HTTP_X_CLUSTER_CLIENT_IP", _
"HTTP_FORWARDED_FOR", _
"HTTP_FORWARDED", _
"REMOTE_ADDR"}
For Each thisHeaderKey In headersKeysToCheck
Dim thisValue = serverVariables.Item(thisHeaderKey)
If thisValue IsNot Nothing Then
Dim validAddress As IPAddress = Nothing
If IPAddress.TryParse(thisValue, validAddress) Then
Return validAddress
End If
End If
Next
Return Nothing
End Function
/**
* 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);
}