From 9ac557f2473844f3c2207189f371f827dbaddb71 Mon Sep 17 00:00:00 2001 From: Andrey Andreev Date: Sat, 6 Oct 2012 20:27:57 +0300 Subject: Add IPv6 and array() support for *proxy_ips* configuration --- application/config/config.php | 16 ++-- system/core/Input.php | 141 +++++++++++++++++++++++++----------- user_guide_src/source/changelog.rst | 23 +++--- 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 ` 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 ` 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 *` to retrieve ``$_SERVER['REQUEST_METHOD']``. - - Modified valid_ip() to use PHP's filter_var() in the :doc:`Input Library `. + - ``$config['rewrite_short_tags']`` now has no effect when using PHP 5.4 as *` 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 `. + - Renamed method ``_call_hook()`` to ``call_hook()`` in the :doc:`Hooks Library `. - :doc:`Output Library ` 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 ------------------ -- cgit v1.2.3-24-g4f1b