summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--application/config/config.php16
-rw-r--r--system/core/Input.php141
-rw-r--r--user_guide_src/source/changelog.rst23
3 files changed, 121 insertions, 59 deletions
diff --git a/application/config/config.php b/application/config/config.php
index eaccbf75e..ab1508e7b 100644
--- a/application/config/config.php
+++ b/application/config/config.php
@@ -406,15 +406,19 @@ $config['rewrite_short_tags'] = FALSE;
| Reverse Proxy IPs
|--------------------------------------------------------------------------
|
-| If your server is behind a reverse proxy, you must whitelist the proxy IP
-| addresses from which CodeIgniter should trust the HTTP_X_FORWARDED_FOR
-| header in order to properly identify the visitor's IP address.
-| Comma-delimited, e.g. '10.0.1.200,10.0.1.201'
+| If your server is behind a reverse proxy, you must whitelist the proxy
+| IP addresses from which CodeIgniter should trust headers such as
+| HTTP_X_FORWARDED_FOR and HTTP_CLIENT_IP in order to properly identify
+| the visitor's IP address.
|
+| You can use both an array or a comma-separated list of proxy addresses,
+| as well as specifying whole subnets. Here are a few examples:
+|
+| Comma-separated: '10.0.1.200,192.168.5.0/24'
+| Array: array('10.0.1.200', '192.168.5.0/24')
*/
$config['proxy_ips'] = '';
-
/* End of file config.php */
-/* Location: ./application/config/config.php */
+/* Location: ./application/config/config.php */ \ No newline at end of file
diff --git a/system/core/Input.php b/system/core/Input.php
index 657fce625..4a0caa5b5 100644
--- a/system/core/Input.php
+++ b/system/core/Input.php
@@ -328,60 +328,117 @@ class CI_Input {
return $this->ip_address;
}
- if (config_item('proxy_ips') != '' && $this->server('HTTP_X_FORWARDED_FOR') && $this->server('REMOTE_ADDR'))
+ $proxy_ips = config_item('proxy_ips');
+ if (empty($proxy_ips))
{
- $has_ranges = strpos($proxies, '/') !== FALSE;
- $proxies = preg_split('/[\s,]/', config_item('proxy_ips'), -1, PREG_SPLIT_NO_EMPTY);
- $proxies = is_array($proxies) ? $proxies : array($proxies);
+ $proxy_ips = FALSE;
+ }
+ elseif ( ! is_array($proxy_ips))
+ {
+ $proxy_ips = explode(',', str_replace(' ', '', $proxy_ips));
+ }
- if ($has_ranges)
- {
- $long_ip = ip2long($_SERVER['REMOTE_ADDR']);
- $bit_32 = 1 << 32;
+ $this->ip_address = $this->server('REMOTE_ADDR');
- // Go through each of the IP Addresses to check for and
- // test against range notation
- foreach ($proxies as $ip)
+ if ($proxy_ips)
+ {
+ foreach (array('HTTP_X_FORWARDED_FOR', 'HTTP_CLIENT_IP', 'HTTP_X_CLIENT_IP', 'HTTP_X_CLUSTER_CLIENT_IP') as $header)
+ {
+ if (($spoof = $this->server($header)) !== NULL)
{
- list($address, $mask_length) = explode('/', $ip, 2);
+ // Some proxies typically list the whole chain of IP
+ // addresses through which the client has reached us.
+ // e.g. client_ip, proxy_ip1, proxy_ip2, etc.
+ if (strpos($spoof, ',') !== FALSE)
+ {
+ $spoof = explode(',', $spoof, 2);
+ $spoof = $spoof[0];
+ }
- // Generate the bitmask for a 32 bit IP Address
- $bitmask = $bit_32 - (1 << (32 - (int) $mask_length));
- if (($long_ip & $bitmask) === $address)
+ if ( ! $this->valid_ip($spoof))
+ {
+ $spoof = NULL;
+ }
+ else
{
- $this->ip_address = $_SERVER['HTTP_X_FORWARDED_FOR'];
break;
}
}
-
}
- else
+
+ if ($spoof !== NULL)
{
- $this->ip_address = in_array($_SERVER['REMOTE_ADDR'], $proxies)
- ? $_SERVER['HTTP_X_FORWARDED_FOR']
- : $_SERVER['REMOTE_ADDR'];
- }
- }
- elseif ( ! $this->server('HTTP_CLIENT_IP') && $this->server('REMOTE_ADDR'))
- {
- $this->ip_address = $_SERVER['REMOTE_ADDR'];
- }
- elseif ($this->server('REMOTE_ADDR') && $this->server('HTTP_CLIENT_IP'))
- {
- $this->ip_address = $_SERVER['HTTP_CLIENT_IP'];
- }
- elseif ($this->server('HTTP_CLIENT_IP'))
- {
- $this->ip_address = $_SERVER['HTTP_CLIENT_IP'];
- }
- elseif ($this->server('HTTP_X_FORWARDED_FOR'))
- {
- $this->ip_address = $_SERVER['HTTP_X_FORWARDED_FOR'];
- }
+ for ($i = 0, $c = count($proxy_ips), $separator = (strlen($ip) === 32 ? '.' : ':'); $i < $c; $i++)
+ {
+ // Check if we have an IP address or a subnet
+ if (strpos($proxy_ips[$i], '/') === FALSE)
+ {
+ // An IP address (and not a subnet) is specified.
+ // We can compare right away.
+ if ($proxy_ips[$i] === $this->ip_address)
+ {
+ $this->ip_address = $spoof;
+ break;
+ }
+
+ continue;
+ }
- if ($this->ip_address === FALSE)
- {
- return $this->ip_address = '0.0.0.0';
+ // We have a subnet ... now the heavy lifting begins
+ isset($separator) OR $separator = $this->valid_ip($this->ip_address, 'ipv6') ? ':' : '.';
+
+ // If the proxy entry doesn't match the IP protocol - skip it
+ if (strpos($proxy_ips[$i], $separator) === FALSE)
+ {
+ continue;
+ }
+
+ // Convert the REMOTE_ADDR IP address to binary, if needed
+ if ( ! isset($ip, $convert_func))
+ {
+ if ($separator === ':')
+ {
+ // Make sure we're have the "full" IPv6 format
+ $ip = str_replace('::', str_repeat(':', 9 - substr_count($this->ip_address, ':')), $this->ip_address);
+ $convert_func = is_php('5.3')
+ ? function ($value)
+ {
+ return str_pad(base_convert($value, 16, 2), 16, '0', STR_PAD_LEFT);
+ }
+ : create_function('$value', 'return str_pad(base_convert($value, 16, 2), 16, "0", STR_PAD_LEFT);');
+ }
+ else
+ {
+ $ip = $this->ip_address;
+ $convert_func = is_php('5.3')
+ ? function ($value)
+ {
+ return str_pad(decbin($value), 8, '0', STR_PAD_LEFT);
+ }
+ : create_function('$value', 'return str_pad(decbin($value), 8, "0", STR_PAD_LEFT);');
+ }
+
+ $ip = implode(array_map($convert_func, explode($separator, $ip)));
+ }
+
+ // Split the netmask length off the network address
+ list($netaddr, $masklen) = explode('/', $proxy_ips[$i], 2);
+
+ // Again, an IPv6 address is most likely in a compressed form
+ if ($separator === ':')
+ {
+ $netaddr = str_replace('::', str_repeat(':', 9 - substr_count($netaddr, ':')), $netaddr);
+ }
+
+ // Convert to a binary form and finally compare
+ $netaddr = implode(array_map($convert_func, explode($separator, $netaddr)));
+ if (strncmp($ip, $netaddr, $masklen) === 0)
+ {
+ $this->ip_address = $spoof;
+ break;
+ }
+ }
+ }
}
if (strpos($this->ip_address, ',') !== FALSE)
diff --git a/user_guide_src/source/changelog.rst b/user_guide_src/source/changelog.rst
index 486a67696..1eb8d107d 100644
--- a/user_guide_src/source/changelog.rst
+++ b/user_guide_src/source/changelog.rst
@@ -212,18 +212,20 @@ Release Date: Not Released
- Core
- Changed private methods in the :doc:`URI Library <libraries/uri>` to protected so MY_URI can override them.
- - Removed ``CI_CORE`` boolean constant from CodeIgniter.php (no longer Reactor and Core versions).
+ - Removed ``CI_CORE`` boolean constant from *CodeIgniter.php* (no longer Reactor and Core versions).
- :doc:`Loader Library <libraries/loader>` changes include:
- - Added method get_vars() to the Loader to retrieve all variables loaded with $this->load->vars().
- - CI_Loader::_ci_autoloader() is now a protected method.
- - Added autoloading of drivers with $autoload['drivers'].
- - CI_Loader::library() will now load drivers as well, for backward compatibility of converted libraries (like Session).
+ - Added method ``get_vars()`` to the Loader to retrieve all variables loaded with ``$this->load->vars()``.
+ - ``CI_Loader::_ci_autoloader()`` is now a protected method.
+ - Added autoloading of drivers with ``$autoload['drivers']``.
+ - ``CI_Loader::library()`` will now load drivers as well, for backward compatibility of converted libraries (like Session).
- ``is_loaded()`` function from *system/core/Commons.php* now returns a reference.
- - $config['rewrite_short_tags'] now has no effect when using PHP 5.4 as *<?=* will always be available.
- - Added ``method()`` to the :doc:`Input Library <libraries/input>` to retrieve ``$_SERVER['REQUEST_METHOD']``.
- - Modified valid_ip() to use PHP's filter_var() in the :doc:`Input Library <libraries/input>`.
+ - ``$config['rewrite_short_tags']`` now has no effect when using PHP 5.4 as *<?=* will always be available.
+ - :doc:`Input Library <libraries/input>` changes include:
+ - Added ``method()`` to retrieve ``$_SERVER['REQUEST_METHOD']``.
+ - Modified ``valid_ip()`` to use PHP's ``filter_var()``.
+ - Added support for arrays and network addresses (e.g. 192.168.1.1/24) for use with the *proxy_ips* setting.
- Added support for HTTP-Only cookies with new config option *cookie_httponly* (default FALSE).
- - Renamed method _call_hook() to call_hook() in the :doc:`Hooks Library <general/hooks>`.
+ - Renamed method ``_call_hook()`` to ``call_hook()`` in the :doc:`Hooks Library <general/hooks>`.
- :doc:`Output Library <libraries/output>` changes include:
- Added method ``get_content_type()``.
- Added a second argument to method ``set_content_type()`` that allows setting the document charset as well.
@@ -235,9 +237,8 @@ Release Date: Not Released
- Added method ``strip_image_tags()``.
- Added ``$config['csrf_regeneration']``, which makes token regeneration optional.
- Added ``$config['csrf_exclude_uris']``, which allows you list URIs which will not have the CSRF validation methods run.
- - Changed ``_exception_handler()`` to respect php.ini 'display_errors' setting.
+ - Changed ``_exception_handler()`` to respect php.ini *display_errors* setting.
- Removed redundant conditional to determine HTTP server protocol in ``set_status_header()``.
- - Added support for IPv4 range masks (e.g. 192.168.1.1/24) to specify ranges of IP addresses for use with the *proxy_ips* setting.
Bug fixes for 3.0
------------------