Retrieve real IP of user using codeigniter - php

My app tracks the IP address of users logging into the site. The tracking was working fine on a regular web server (we were on hostgator) but seemed to start tracking odd IP addresses when we switch to a PaaS platform (pagodabox) After speaking to pagodabox support they informed me that the IPs codeigniter was picking up was the IPs of the load balancers/routers of pagodabox and to get a user's actual IP address I would have to utilize HTTP_X_FORWARDED_FOR
I was using codeigniter's input class function $this->input->ip_address() to retreive the user's IP. I looked at the function and noticed they had some sort of features to retreive the HTTP_X_FORWARDED_FOR IP value but I am not sure how to use it. Do i have to change/add something in the config?
EDIT: After a few users have pointed out where I should add in the list of IP addresses of load balancers a new question came up: What would I do if the list of IP's change frequently? (ie no static IP, all dynamic)

I'm sure you've resolved this by now, but I thought I would post the correct answer for future reference. I came across this same problem (Using load balancers on AWS with a CodeIgniter app.) As you pointed out, it's easy enough to get the correct IP behind a load balancer or other distributed environment using the HTTP_X_FORWARDED_FOR header. The problem is how do we correctly implement this solution in CodeIgniter? As the previous answer points out: Write your own IP function. The problem with this, is what if ip_address() is called throughout your app? Wouldn't it be better to override that function (With one that looks at the correct header)? CodeIgniter has a convenient mechanism for this, which is handy:
The solution is to extend the CodeIgniter Input class, by creating a new class file in /application/core called MY_Input.php (MY_ is a configurable prefix for extensions, you can change it in your config file). With extensions, you can create a function of the SAME name as the original class method without breaking anything, and without editing core files. CodeIgniter will just use your new method instead. Your extended input class will look something like this:
class MY_Input extends CI_Input {
function __construct()
{
parent::__construct();
}
//Overide ip_address() with your own function
function ip_address()
{
//Obtain the IP address however you'd like, you may want to do additional validation, etc..
$correct_ip_address = $_SERVER['HTTP_X_FORWARDED_FOR'];
return $correct_ip_address;
}
}
This way we've changed the core behavior without hacking the framework, and existing calls to ip_address() throughout your app will now be using your method.
With regards to dealing with other IP's in the chain, if you're only interested in the client IP, it shouldn't matter. With AWS load balancers at least, the HTTP_X_FORWARDED_FOR header seems to always contain the correct client IP.

Oliver's solution works, but in some circumstances, it is better to use the following if you know the proxy IP addresses that are in use. Edit your application/config/config.php file to include the following:
$config['proxy_ips'] = '1.2.3.4, 2.3.4.5';
Also be aware of the fact that the header information is usually unreliable and should not be used for security sensitive purposes. For example, it is not uncommon to restrict admin users to use only some white listed IP addresses.

<?php
function getIPfromXForwarded()
{
$ipString = #getenv("HTTP_X_FORWARDED_FOR");
$addr = explode(",",$ipString);
return $addr[sizeof($addr)-1];
}
?>
Try something like that. See if it works. Usage:
<? echo getIPfromXForwarded(); ?>

In your case, you can add specified Load-Balancer IP into $config['proxy_ips'] (application/config/config.php), for example:
$config['proxy_ips'] = ['192.168.1.2'];
Dynamic proxy IP:
According to your dynamic IP problem, you can mask the IP range for the Load-Balancer network, for example:
$config['proxy_ips'] = '192.168.1.0/24';
The mask function is able in Codeigniter 3
Get IP method:
$this->input->ip_address();
While $this refers to CI instance.
This method takes into account the $config['proxy_ips'] setting and will return the reported HTTP_X_FORWARDED_FOR, HTTP_CLIENT_IP, HTTP_X_CLIENT_IP or HTTP_X_CLUSTER_CLIENT_IP address for the allowed IP addresses.

I came across a version of Thava's solution that works great for situations where the load balancer IPs can change (such as AWS) and still leverages CIs configuration files natively. When you know you are running behind an LB you can modify config.php to be:
$config['proxy_ips'] = isset($_SERVER["REMOTE_ADDR"]) ? $_SERVER["REMOTE_ADDR"] : '';
Knowing that REMOTE_ADDR will always be the current LB.
Thanks to Eric Brown here https://expressionengine.com/forums/archive/topic/185751/amazon-load-balancing-and-codeigniter-configproxy_ips#925678

I know there is a good answer which is relevant to your question and is accepted by you but for future users I am sharing a function which is working perfectly for me in all situations.
public function ip()
{
$ipaddress = '';
if ($_SERVER['HTTP_CLIENT_IP'])
$ipaddress = $_SERVER['HTTP_CLIENT_IP'];
else if($_SERVER['HTTP_X_FORWARDED_FOR'])
$ipaddress = $_SERVER['HTTP_X_FORWARDED_FOR'];
else if($_SERVER['HTTP_X_FORWARDED'])
$ipaddress = $_SERVER['HTTP_X_FORWARDED'];
else if($_SERVER['HTTP_FORWARDED_FOR'])
$ipaddress = $_SERVER['HTTP_FORWARDED_FOR'];
else if($_SERVER['HTTP_FORWARDED'])
$ipaddress = $_SERVER['HTTP_FORWARDED'];
else if($_SERVER['REMOTE_ADDR'])
$ipaddress = $_SERVER['REMOTE_ADDR'];
else
$ipaddress = 'UNKNOWN';
echo $ipaddress ;
}

I had the same kind of situation at work, except that the IPs were not really 'dynamic', but the the 'infrastructure people' managing proxyies and load balancers used to change them for undisclosed reasons. So we had to negotiate and we came up with a solution setting up a hook in their configuration/provision management tool to write a config file somewhere (in a folder accessible to the user running our Apache/PHP).
So I used a CI hook to read that file on system bootstrap in order to change my apps config, updating values like the proxy IP list, cache path, cookie domain etc.

Related

Block users using their ip addresses in php

Actually I have no any practical experience using php other than in locally hosted server. I am following a video tutorial to learn php, and there was this code to block specific ip from a web site. The thing I want to know here if this is works for users who have dynamic ip addresses also or not? And, is this a proper way to block a user? I'm so glad if you can explain more about practical side of blocking users. Thank you!
<?php
$http_client_ip = $_SERVER['HTTP_CLIENT_IP'];
$http_x_forwarded_for = $SERVER['HTTP_X_FORWARDED_FOR'];
$remote_address = $SERVER['REMOTE_ADDR'];
if (!empty($http_client_ip)){
$ip_address = $http_client_ip;
} elseif (!empty($http_x_forwarded_for)) {
$ip_address = $http_x_forwarded_for;
} else {
$ip_address = $remote_address;
}
//block list contains all the IPs that should be blocked.
foreach ($block_list as $block) {
if ($block == $ip_address){
die();
}
}
?>
Visitors can be restricted from accessing your site by using the IP deny manager in your cPanel or by adding the Allow or Deny code in your .htaccess file.
The syntax is as follows:
Allows IP 122.102.1.2 access to your website.
Allow from 122.102.1.2
Denys IP 25.122.6.3 access to your website.
Deny from 25.122.6.3
both combined as,
Order deny,allow
Deny from all
Allow from 203.25.45.2
Deny from unwanted-domain.com
No it does not. It takes his IP and check if it is in the block list. As soon as it will change, he won't be blocked anymore.
You could try blocking a larger range of IP.
By example:
$targetAddr = "123.123..*..*"; //yes is two dots
//this code will match any class of 123.123.x.x,
//you can also do "123.123.123..*" to do anything that is 123.123.123.x
if (ereg($targetAddr, $_SERVER['REMOTE_ADDR'])) {
//remote address match, do something or don't do anything
} else {
//do whatever you want to address that doesn't match
}
To be honest I don't think this is the best way to block users. This could block other users than the one you are trying.
I guess this would be best for regional block, when you want to block your site from a certain geographical area, using the IP range assigned to that area. as goes for certain users with dynamic IP addresses, it won't work well.

Get current domain

I have my site on the server http://www.myserver.uk.com.
On this server I have two domains:
one.com and two.com
I would like to get the current domain using PHP, but if I use $_SERVER['HTTP_HOST'] then it is showing me
myserver.uk.com
instead of:
one.com or two.com
How can I get the domain, and not the server name?
Try using this:
$_SERVER['SERVER_NAME']
Or parse:
$_SERVER['REQUEST_URI']
Reference: apache_request_headers()
The best use would be
echo $_SERVER['HTTP_HOST'];
And it can be used like this:
if (strpos($_SERVER['HTTP_HOST'], 'banana.com') !== false) {
echo "Yes this is indeed the banana.com domain";
}
This code below is a good way to see all the variables in $_SERVER in a structured HTML output with your keywords highlighted that halts directly after execution. Since I do sometimes forget which one to use myself - I think this can be nifty.
<?php
// Change banana.com to the domain you were looking for..
$wordToHighlight = "banana.com";
$serverVarHighlighted = str_replace( $wordToHighlight, '<span style=\'background-color:#883399; color: #FFFFFF;\'>'. $wordToHighlight .'</span>', $_SERVER );
echo "<pre>";
print_r($serverVarHighlighted);
echo "</pre>";
exit();
?>
The only secure way of doing this
The only guaranteed secure method of retrieving the current domain is to store it in a secure location yourself.
Most frameworks take care of storing the domain for you, so you will want to consult the documentation for your particular framework. If you're not using a framework, consider storing the domain in one of the following places:
   Secure methods of storing the domain   
  Used By
A configuration file  
Joomla, Drupal/Symfony
The database  
WordPress
An environmental variable
Laravel  
A service registry  
Kubernetes DNS
The following work... but they're not secure
Hackers can make the following variables output whatever domain they want. This can lead to cache poisoning and barely noticeable phishing attacks.
$_SERVER['HTTP_HOST']
This gets the domain from the request headers which are open to manipulation by hackers. Same with:
$_SERVER['SERVER_NAME']
This one can be made better if the Apache setting usecanonicalname is turned off; in which case $_SERVER['SERVER_NAME'] will no longer be allowed to be populated with arbitrary values and will be secure. This is, however, non-default and not as common of a setup.
In popular systems
Below is how you can get the current domain in the following frameworks/systems:
WordPress
$urlparts = parse_url(home_url());
$domain = $urlparts['host'];
If you're constructing a URL in WordPress, just use home_url or site_url, or any of the other URL functions.
Laravel
request()->getHost()
The request()->getHost function is inherited from Symfony, and has been secure since the 2013 CVE-2013-4752 was patched.
Drupal
The installer does not yet take care of making this secure (issue #2404259). But in Drupal 8 there is documentation you can you can follow at Trusted Host Settings to secure your Drupal installation after which the following can be used:
\Drupal::request()->getHost();
Other frameworks
Feel free to edit this answer to include how to get the current domain in your favorite framework. When doing so, please include a link to the relevant source code or to anything else that would help me verify that the framework is doing things securely.
Addendum
Exploitation examples:
Cache poisoning can happen if a botnet continuously requests a page using the wrong hosts header. The resulting HTML will then include links to the attackers website where they can phish your users. At first the malicious links will only be sent back to the hacker, but if the hacker does enough requests, the malicious version of the page will end up in your cache where it will be distributed to other users.
A phishing attack can happen if you store links in the database based on the hosts header. For example, let say you store the absolute URL to a user's profiles on a forum. By using the wrong header, a hacker could get anyone who clicks on their profile link to be sent a phishing site.
Password reset poisoning can happen if a hacker uses a malicious hosts header when filling out the password reset form for a different user. That user will then get an email containing a password reset link that leads to a phishing site. Another more complex form of this skips the user having to do anything by getting the email to bounce and resend to one of the hacker's SMTP servers (for example CVE-2017-8295.)
Here are some more malicious examples
Additional Caveats and Notes:
When usecanonicalname is turned off the $_SERVER['SERVER_NAME'] is populated with the same header $_SERVER['HTTP_HOST'] would have used anyway (plus the port). This is Apache's default setup. If you or DevOps turns this on then you're okay -- ish -- but do you really want to rely on a separate team, or yourself three years in the future, to keep what would appear to be a minor configuration at a non-default value? Even though this makes things secure, I would caution against relying on this setup.
Red Hat, however, does turn usecanonical on by default [source].
If serverAlias is used in the virtual hosts entry, and the aliased domain is requested, $_SERVER['SERVER_NAME'] will not return the current domain, but will return the value of the serverName directive.
If the serverName cannot be resolved, the operating system's hostname command is used in its place [source].
If the host header is left out, the server will behave as if usecanonical
was on [source].
Lastly, I just tried exploiting this on my local server, and was unable to spoof the hosts header. I'm not sure if there was an update to Apache that addressed this, or if I was just doing something wrong. Regardless, this header would still be exploitable in environments where virtual hosts are not being used.
A Little Rant:
     This question received hundreds of thousands of views without a single mention of the security problems at hand! It shouldn't be this way, but just because a Stack Overflow answer is popular, that doesn't mean it is secure.
Using $_SERVER['HTTP_HOST'] gets me (subdomain.)maindomain.extension. It seems like the easiest solution to me.
If you're actually 'redirecting' through an iFrame, you could add a GET parameter which states the domain.
<iframe src="myserver.uk.com?domain=one.com"/>
And then you could set a session variable that persists this data throughout your application.
Try $_SERVER['SERVER_NAME'].
Tips: Create a PHP file that calls the function phpinfo() and see the "PHP Variables" section. There are a bunch of useful variables we never think of there.
To get the domain:
$_SERVER['HTTP_HOST']
Domain with protocol:
$protocol = strpos(strtolower($_SERVER['SERVER_PROTOCOL']), 'https') === FALSE ? 'http' : 'https';
$domainLink = $protocol . '://' . $_SERVER['HTTP_HOST'];
Protocol, domain, and queryString total:
$url = $protocol . '://' . $_SERVER['HTTP_HOST'] . '?' . $_SERVER['QUERY_STRING'];
**As the $_SERVER['SERVER_NAME'] is not reliable for multi-domain hosting!
I know this might not be entirely on the subject, but in my experience, I find storing the WWW-ness of the current URL in a variable useful.
In addition, please see my comment below, to see what this is getting at.
This is important when determining whether to dispatch Ajax calls with "www", or without:
$.ajax("url" : "www.site.com/script.php", ...
$.ajax("url" : "site.com/script.php", ...
When dispatching an Ajax call the domain name must match that of in the browser's address bar, and otherwise you will have an Uncaught SecurityError in the console.
So I came up with this solution to address the issue:
<?php
substr($_SERVER['SERVER_NAME'], 0, 3) == "www" ? $WWW = true : $WWW = false;
if ($WWW) {
/* We have www.example.com */
} else {
/* We have example.com */
}
?>
Then, based on whether $WWW is true, or false run the proper Ajax call.
I know this might sound trivial, but this is such a common problem that is easy to trip over.
Everybody is using the parse_url function, but sometimes a user may pass the argument in different formats.
So as to fix that, I have created a function. Check this out:
function fixDomainName($url='')
{
$strToLower = strtolower(trim($url));
$httpPregReplace = preg_replace('/^http:\/\//i', '', $strToLower);
$httpsPregReplace = preg_replace('/^https:\/\//i', '', $httpPregReplace);
$wwwPregReplace = preg_replace('/^www\./i', '', $httpsPregReplace);
$explodeToArray = explode('/', $wwwPregReplace);
$finalDomainName = trim($explodeToArray[0]);
return $finalDomainName;
}
Just pass the URL and get the domain.
For example,
echo fixDomainName('https://stackoverflow.com');
will return:
stackoverflow.com
And in some situation:
echo fixDomainName('stackoverflow.com/questions/id/slug');
And it will also return stackoverflow.com.
This quick & dirty works for me.
Whichever way you get the string containing the domain you want to extract, i.e. using a super global -$_SERVER['SERVER_NAME']- or, say, in Drupal: global $base_url, regex is your friend:
global $base_url;
preg_match("/\w+\.\w+$/", $base_url, $matches);
$domain = $matches[0];
The particular regex string I am using in the example will only capture the last two components of the $base_url string, of course, but you can add as many "\w+." as desired.
Hope it helps.

why cant read ip address sometimes, this function

I am using this function to get ip address of the user in my site but it cant read ipaddress sometimes. I dont know that a user can hide the ipaddress or not ? If a user able to do so then how can i get the ipaddress or any other solution to identify the local computer of the user so i can prevent that computer to open my site.
Any suggestion would be greatly appreciated.
function GetIP()
{
if (getenv("HTTP_CLIENT_IP") && strcasecmp(getenv("HTTP_CLIENT_IP"), "unknown"))
$ip = getenv("HTTP_CLIENT_IP");
else if (getenv("HTTP_X_FORWARDED_FOR") && strcasecmp(getenv("HTTP_X_FORWARDED_FOR"), "unknown"))
$ip = getenv("HTTP_X_FORWARDED_FOR");
else if (getenv("REMOTE_ADDR") && strcasecmp(getenv("REMOTE_ADDR"), "unknown"))
$ip = getenv("REMOTE_ADDR");
else if (isset($_SERVER['REMOTE_ADDR']) && $_SERVER['REMOTE_ADDR'] && strcasecmp($_SERVER['REMOTE_ADDR'], "unknown"))
$ip = $_SERVER['REMOTE_ADDR'];
else
$ip = "unknown";
return($ip);
}
Thanks a lot.
Normally just echo $_SERVER['REMOTE_ADDR']; should do the job. Otherwise, explain us, what you mean with "it cant read ipaddress sometimes"
Ip addresses aren't a sure-fire way of banning users, since they can use Proxies, or can have a dynamic IP. For instance, for some users, banning the IP would just mean they'd have to restart their router and they can access your site again.
Depending on your site, a better way to deny someone access is to use user authentication (user login).
The short answer is that there is no 100% guaranteed way of getting a user's IP address - $_SERVER['REMOTE_ADDR'] is your best bet but even that's not 100% reliable - especially for users on networks.
Anything that's passed over HTTP from the client could be blocked/spoofed.
It's easier to work your problem the other way around - it's much easier (and more secure) to whitelist access rather than blacklist it - if you can work that way around, I'd go for it. It only requires something like a simple (in an Apache .htaccess or vhost configuration):
Deny from all
Allow from mydomain.com
the only IP address you can get from the environment is REMOTE_ADDR one.
The other silly strings in your code is no more than HTTP headers, optional ones. Can be faked, omitted, have wrong format, be empty etc.
I leave a conclusion for you to make.

Allowing website access only from a specific network

I'm making a website that appeals to students at my school. I want to only allow access if the user is on the campus wifi or hardwire. Using PHP, how can I restrict access to people I am sure are on the campus internet?
You would need to get a range of IP addresses and put them in a while list. You could then use the $_SERVER['REMOTE_ADDR'] variable to check against the white list for access. Do it at the beginning of the page with something like this:
if(in_array($_SERVER['REMOTE_ADDR'],$white_list)) {
//allow execution code?
} else {
exit;
}
This is usually done in the webserver configuration, which has the advantage of also working for images, but in theory you could put
if ($_SERVER['REMOTE_ADDR'] != '...')
die();
in every of your PHP pages.
At first, you need to get the range of IPs from your school's network admin.
Then from PHP:
$ip=$_SERVER['REMOTE_ADDR'];
if(inRange($ip)) die();
else { ....
Now write inRange($ip) function to return true if the given ip is in the range. you can use explode function to get pieces of the ip to compare.. :)
It's already been mentioned, but the 'right' way to do it is to specify the IP range in the setup of your webserver (IOW, don't do it in PHP)

How to get real host or server name in PHP

How can I get real host name by not using $_SERVER['SERVER_NAME'] in PHP? Is there other more reliable way to get it ?
I have created a function which gets host name from the path to the domain.
I would like to avoid using $_SERVER['SERVER_NAME'] variable, because it can be faked by sending modified headers in the HTTP request.
This is my current implementation (this works if the path has an actual domain name in it. For instance: /vhosts/website.com/public_html):
function getServerName() {
$path = realpath(__FILE__);
$url = array();
preg_match_all("/\/[a-z0-9-]+(\.[a-z0-9-]+)+/i", $path, $url);
// 4 is minimum requirement for the address (e.g: http://www.in.tv)
if (strlen($url[0][0]) > 4) {
$result = str_replace("/", "", $url[0][0]);
return $result;
}
else
return false;
}
Thanks!
If you want a server name that can't be set by the client, use $_SERVER['SERVER_NAME']. It is set by the server itself but can also be forged under certain circumstances using a bug, as Gumbo points out and links to in the comments.
I think the one you are referring to is
$_SERVER['HTTP_HOST'];
which, given the HTTP prefix means it comes from the HTTP Headers.
You might want to use:
$_SERVER['SERVER_NAME']
which is defined by the server and can't be changed via a request?
this will get the hostname server-side, but if you're running on a commercial host (not hosting yourself), I don't imagine this will be all that useful.
$host = php_uname( 'n' );
If you're using Apache, what you should do is make your server / site only answer to certain names (else there should be a default that doesn't do much). You can do with with the ServerName and ServerAlias directives.
Edit: as pointed by Gumbo, the original poster probably means HTTP_HOST rather than HOST_NAME. Otherwise, my answer is plain wrong.
The HTTP_HOST variable reflects the domain name that the visitor used to access the site. If doesn't have anything to do with file paths! Its value is conveniently stored in $_SERVER['HTTP_HOST']. Is there any other way to get it? Of course, there're normally several ways to do things. For instance, this works when PHP runs as Apache module.
<?php
$request_headers = apache_request_headers();
echo $request_headers['Host'];
?>
The question is: why would anyone want to do such a thing? Why replace a reliable standard method with a quirky workaround that eventually fetches the same piece of data from the same place?
You have the concern that $_SERVER['HTTP_HOST'] is altered by the HTTP request. Of course it is: that's where it comes from. The browser has to specify what site it wants to visit (that's the base of name based virtual hosts) and if it sends a rogue value, well, it just won't reach the site.
Of course $_SERVER['HTTP_HOST'] can be modified by the client - because in fact IT IS sent by the client. This is part of the http protocol. If you want to get the primary server name defined in the vhost configuration of apache or whatever you can access $_SERVER['SERVER_NAME'] as proposed by the others.
I suggest it is not wise to extract the domain name from the file path of the server (which is stored in __FILE__) as it may render your application non-relocatable (it will no longer be storage location agnostic).
You may see the contents of the array by dumping it within the script using var_dump($_SERVER) but keep in mind the not all web servers and all web server settings expose the same environment. This is documented in the web server documentation and I think it is partly documented in the php online docs.
Update / Important notice: As others pointed out, the content of $_SERVER['SERVER_NAME'] could be spoofed if apache is configured for UseCanonicalName off (which may be a default setting if you are using eg Plesk-based hosting). So actually going with the __FILE__ can solve this (if your doc root contains the host name). The bigger problem of the first approach is that it can be used to inject any sort of stuff into your application (SQL, JavaScript) because php programmers usually take it granted that SERVER_NAME is no user input and thus apply no sanitizing to it.
You don't. That's the purpose of the $_SERVER variables. If you want to get the HOST_NAME from the path, you must first get the PATH from $_SERVER['HTTP_HOST']

Categories