diff --git a/config/global.ini.php b/config/global.ini.php index d2a91d2944a2ccb9ac1929427ba89e5b44dd82f5..3cb99030981e176c21cd4af281936d20e2cc633c 100644 --- a/config/global.ini.php +++ b/config/global.ini.php @@ -184,6 +184,22 @@ serve_widget_and_data = 1 ; If set to 1, Piwik adds a response header to workaround the IE+Flash+HTTPS bug. reverse_proxy = 0 +; List of proxy headers for client IP addresses +; +; CloudFlare +;proxy_client_headers[] = HTTP_CF_CONNECTING_IP +; +; ISP proxy +;proxy_client_headers[] = HTTP_CLIENT_IP +; +; de facto standard +;proxy_client_headers[] = HTTP_X_FORWARDED_FOR + +; List of proxy headers for host IP addresses +; +; de facto standard +;proxy_host_headers[] = HTTP_X_FORWARDED_HOST + ; The release server is an essential part of the Piwik infrastructure/ecosystem ; to provide the latest software version. latest_version_url = http://piwik.org/latest.zip @@ -193,7 +209,6 @@ latest_version_url = http://piwik.org/latest.zip ; subscribeNewsletter. api_service_url = http://api.piwik.org - [Tracker] ; set to 0 if you want to stop tracking the visitors. Useful if you need to stop all the connections on the DB. record_statistics = 1 @@ -255,11 +270,6 @@ page_maximum_length = 1024; ; for IPv4 addresses, valid values are 0..4 ip_address_mask_length = 1 -; IP address list of trusted reverse proxy(-ies) for which -; all IP addresses in the proxy header are accepted; -; if not set, only public IP addresses are accepted -;trusted_proxy_addresses[] = - [mail] transport = ; smtp (using the configuration below) or empty (using built-in mail() function) port = ; optional; defaults to 25 when security is none or tls; 465 for ssl diff --git a/core/Common.php b/core/Common.php index 62a4913cda9e58368f206f646327ae89cbca5f05..95154796bad7ce8621c62b2f9732ee0adb6a5b5e 100644 --- a/core/Common.php +++ b/core/Common.php @@ -784,34 +784,6 @@ class Piwik_Common * IP addresses */ - /** - * Test whether or not an IP address is public. - * For performance, we don't check to see if an IP address is actually in an assigned/allocated netblock. - * - * @param long|string $ip - * @return bool True if IP in private address range; false otherwise - */ - static public function isPublicIp($ip) - { - if(is_int($ip)) - { - $ip = sprintf("%u", $ip); - } - - // reference: http://www.iana.org/abuse/faq.html - if(($ip >= '167772160' && $ip <= '184549375') // 10.0.0.0/8 (private) - || ($ip >= '2896166912' && $ip <= '2887778303') // 172.16.0.0/12 (private) - || ($ip >= '3232235520' && $ip <= '3232301055') // 192.168.0.0/16 (private) - || ($ip >= '2851995648' && $ip <= '2852061183') // 169.254.0.0/16 (auto-configuration) - || ($ip >= '2130706432' && $ip <= '2147483647') // 127.0.0.0/8 (loopback) - || ($ip >= '3758096384' && $ip <= '4026531839')) // 224.0.0.0 - 239.255.255.255 (multicast; should never appear as a source address) - { - return false; - } - - return true; - } - /** * Convert dotted IP to a stringified integer representation * @@ -836,95 +808,76 @@ class Piwik_Common /** * Returns the best possible IP of the current user, in the format A.B.C.D + * For example, this could be the proxy client's IP address. * * @return string ip */ static public function getIpString() { - static $trustedProxies = null; - if(is_null($trustedProxies)) + static $clientHeaders = null; + if(is_null($clientHeaders)) { - $trustedProxies = array(); if(!empty($GLOBALS['PIWIK_TRACKER_MODE'])) { - $trustedProxies = @Piwik_Tracker_Config::getInstance()->Tracker['trusted_proxy_addresses']; + $clientHeaders = @Piwik_Tracker_Config::getInstance()->General['proxy_client_headers']; } else { $config = Zend_Registry::get('config'); - if($config !== false && isset($config->Tracker->trusted_proxy_addresses)) + if($config !== false && isset($config->General->proxy_client_headers)) { - $trustedProxies = $config->Tracker->trusted_proxy_addresses->toArray(); + $clientHeaders = $config->General->proxy_client_headers->toArray(); } } - if(!is_array($trustedProxies)) + if(!is_array($clientHeaders)) { - $trustedProxies = array(); + $clientHeaders = array(); } } - // default - $ip = isset($_SERVER['REMOTE_ADDR']) ? self::getFirstIpFromList($_SERVER['REMOTE_ADDR']) : '0.0.0.0'; - - return self::getProxyIp($ip, $trustedProxies); + return self::getProxyFromHeader($_SERVER['REMOTE_ADDR'], $clientHeaders); } /** - * Returns the proxy client's IP address + * Returns the proxy * - * @param string $ip DottedIP address of client - * @param array List of dotted IP addresses of trusted proxies + * @param string $default Default value to return if no matching proxy header + * @param array $proxyHeaders List of proxy headers + * @return string */ - static public function getProxyIp($ip, $proxies) + static public function getProxyFromHeader($default, $proxyHeaders) { - // note: these may be spoofed - static $clientHeaders = array( - // CloudFlare - 'HTTP_CF_CONNECTING_IP', - - // ISP proxy - 'HTTP_CLIENT_IP', - - // de facto standard - 'HTTP_X_FORWARDED_FOR', - ); - // examine proxy headers - foreach($clientHeaders as $clientHeader) + foreach($proxyHeaders as $proxyHeader) { - if(!empty($_SERVER[$clientHeader])) + if(!empty($_SERVER[$proxyHeader])) { - $clientIp = self::getFirstIpFromList($_SERVER[$clientHeader]); - if(!empty($clientIp) && stripos($clientIp, 'unknown') === false) + $proxyIp = self::getFirstElementFromList($_SERVER[$proxyHeader]); + if(!empty($proxyIp) && stripos($proxyIp, 'unknown') === false) { - // accept public IP addresses, and any IP address through a trusted proxy - if(self::isPublicIp(ip2long($clientIp)) || - in_array($ip, $proxies)) - { - return $clientIp; - } + return $proxyIp; } } } - return $ip; + return $default; } /** - * Returns the first element of a comma separated list of IPs + * Returns the first element of a comma separated list * - * @param string $ip + * @param string $csv * * @return string first element before ',' */ - static public function getFirstIpFromList($ip) + static public function getFirstElementFromList($csv) { - $p = strpos($ip, ','); + $p = strpos($csv, ','); if($p !== false) { - return trim(self::sanitizeInputValues(substr($ip, 0, $p))); + return trim(self::sanitizeInputValues(substr($csv, 0, $p))); } - return trim(self::sanitizeInputValues($ip)); + return trim(self::sanitizeInputValues($csv)); } /* diff --git a/core/Url.php b/core/Url.php index b95e1ecbf11138cadafb66c0ae21f18f76a24b6f..4f75e302d1d50c65a79d6306010c4c60e2f80af2 100644 --- a/core/Url.php +++ b/core/Url.php @@ -156,17 +156,26 @@ class Piwik_Url */ static public function getCurrentHost($default = 'unknown') { - if (!empty($_SERVER['HTTP_X_FORWARDED_HOST'])) + static $hostHeaders = null; + if(is_null($hostHeaders)) { - return Piwik_Common::getFirstIpFromList($_SERVER['HTTP_X_FORWARDED_HOST']); + $config = Zend_Registry::get('config'); + if($config !== false && isset($config->General->proxy_host_headers)) + { + $hostHeaders = $config->General->proxy_host_headers->toArray(); + } + if(!is_array($hostHeaders)) + { + $hostHeaders = array(); + } } if(isset($_SERVER['HTTP_HOST'])) { - return $_SERVER['HTTP_HOST']; + $default = Piwik_Common::sanitizeInputValue($_SERVER['HTTP_HOST']); } - return $default; + return Piwik_Common::getProxyFromHeader($default, $hostHeaders); } /** diff --git a/tests/config/mysqli.template.php b/tests/config/mysqli.template.php index c5453e9e0265edbe6f1037838415721f7224e00b..34b73228bf6d7249882d5cfdbdc85f3e4722c092 100644 --- a/tests/config/mysqli.template.php +++ b/tests/config/mysqli.template.php @@ -29,3 +29,6 @@ dbname= @database.test.name@ adapter= MYSQLI tables_prefix= piwiktests_ schema= Myisam + +[General] +proxy_client_headers[] = HTTP_X_FORWARDED_FOR diff --git a/tests/config/pdo_mysql.template.php b/tests/config/pdo_mysql.template.php index 8f98c00a22f0f47fbb00fbd119a39987dd90d34a..17af4b2eef98c9c286a04e82370bcb9325d9d0b1 100644 --- a/tests/config/pdo_mysql.template.php +++ b/tests/config/pdo_mysql.template.php @@ -29,3 +29,6 @@ dbname= @database.test.name@ adapter= PDO_MYSQL tables_prefix= piwiktests_ schema= Myisam + +[General] +proxy_client_headers[] = HTTP_X_FORWARDED_FOR diff --git a/tests/core/Common.test.php b/tests/core/Common.test.php index 830fa542efc6c7de8b4667276be3f384db1271ce..2b499c08441613090039dade1de62f9bb53ee07d 100644 --- a/tests/core/Common.test.php +++ b/tests/core/Common.test.php @@ -886,65 +886,54 @@ class Test_Piwik_Common extends UnitTestCase } } - function test_isPublicIp() + function test_getFirstElementFromList() { - $ips = array( - 167772159 => true, - '167772159' => true, - 167772160 => false, - '167772160' => false, - 184549375 => false, - '184549375' => false, - 184549376 => true, - '184549376' => true, - 3232235519 => true, - '3232235519' => true, - 3232235520 => false, - '3232235520' => false, - 323224 => true, - '323224' => true, - 3232301055 => false, - '3232301055' => false, - 3232301056 => true, - '3232301056' => true, + $tests = array( + '' => '', + 'a' => 'a', + ' a ' => 'a', + ' a, b' => 'a', + 'a ,b ' => 'a', + ',b' => '', ); - foreach($ips as $ip => $expected) + + foreach($tests as $csv => $expected) { - $this->assertEqual( Piwik_Common::isPublicIp($ip), $expected, $ip ); + $this->assertEqual( Piwik_Common::getFirstElementFromList($csv), $expected); } } - function test_getProxyIp() + function test_getProxyFromHeader() { $ips = array( - '0.0.0.0' => true, - '72.14.204.99' => true, - '127.0.0.1' => false, - '169.254.0.1' => false, - '208.80.152.2' => true, - '224.0.0.1' => false, + '0.0.0.0', + '72.14.204.99', + '127.0.0.1', + '169.254.0.1', + '208.80.152.2', + '224.0.0.1', ); // no proxies - foreach($ips as $ip => $dontcare) + foreach($ips as $ip) { - $this->assertEqual( Piwik_Common::getProxyIp($ip, array()), $ip, $ip ); + $this->assertEqual( Piwik_Common::getProxyFromHeader($ip, array()), $ip, $ip ); } // 1.1.1.1 is not a trusted proxy $_SERVER['REMOTE_ADDR'] = '1.1.1.1'; - foreach($ips as $ip => $expected) + foreach($ips as $ip) { - $_SERVER['HTTP_X_FORWARDED_FOR'] = $ip; - $this->assertEqual( Piwik_Common::getProxyIp('1.1.1.1', array('2.2.2.2')), $expected ? $ip : '1.1.1.1', $ip); + $_SERVER['HTTP_X_FORWARDED_FOR'] = ''; + $this->assertEqual( Piwik_Common::getProxyFromHeader('1.1.1.1', array('HTTP_X_FORWARDED_FOR')), '1.1.1.1', $ip); } // 1.1.1.1 is a trusted proxy $_SERVER['REMOTE_ADDR'] = '1.1.1.1'; - foreach($ips as $ip => $dontcare) + foreach($ips as $ip) { $_SERVER['HTTP_X_FORWARDED_FOR'] = $ip; - $this->assertEqual( Piwik_Common::getProxyIp('1.1.1.1', array('1.1.1.1')), $ip, $ip); + $this->assertEqual( Piwik_Common::getProxyFromHeader('1.1.1.1', array('HTTP_X_FORWARDED_FOR')), $ip, $ip); } } }