From 47c4971e10f60426330421e709387a4b857e0881 Mon Sep 17 00:00:00 2001 From: Vincent Petry Date: Wed, 5 Mar 2014 15:02:05 +0100 Subject: Show warning page when accessing server from an untrusted domain Added early check for the requested domain host and show a warning page if the domain is not trusted. Backport of 9b6b02a from stable6 --- lib/base.php | 16 +++++++++++++ lib/request.php | 74 +++++++++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 72 insertions(+), 18 deletions(-) diff --git a/lib/base.php b/lib/base.php index e8f67db8881..3c3c13c8c47 100644 --- a/lib/base.php +++ b/lib/base.php @@ -612,6 +612,22 @@ class OC { exit(); } + $host = OC_Request::insecureServerHost(); + // if the host passed in headers isn't trusted + if (!OC::$CLI + // overwritehost is always trusted + && OC_Request::getOverwriteHost() === null + && !OC_Request::isTrustedDomain($host)) { + + header('HTTP/1.1 400 Bad Request'); + header('Status: 400 Bad Request'); + OC_Template::printErrorPage( + 'You are accessing the server from an untrusted domain.', + 'Please contact your administrator' + ); + return; + } + $request = OC_Request::getPathInfo(); if(substr($request, -3) !== '.js') {// we need these files during the upgrade self::checkMaintenanceMode(); diff --git a/lib/request.php b/lib/request.php index d83e432866c..a0070a50168 100755 --- a/lib/request.php +++ b/lib/request.php @@ -18,49 +18,87 @@ class OC_Request { } /** - * @brief Checks whether a domain is considered as trusted. This is used to prevent Host Header Poisoning. - * @param string $host - * @return bool + * @brief Checks whether a domain is considered as trusted from the list + * of trusted domains. If no trusted domains have been configured, returns + * true. + * This is used to prevent Host Header Poisoning. + * @param string $host + * @return bool true if the given domain is trusted or if no trusted domains + * have been configured */ public static function isTrustedDomain($domain) { - $trustedList = \OC_Config::getValue('trusted_domains', array('')); - return in_array($domain, $trustedList); + $trustedList = \OC_Config::getValue('trusted_domains', array()); + if (empty($trustedList)) { + return true; + } + return in_array($domain, $trustedList); } /** - * @brief Returns the server host + * @brief Returns the unverified server host from the headers without checking + * whether it is a trusted domain * @returns string the server host * * Returns the server host, even if the website uses one or more * reverse proxies */ - public static function serverHost() { - if(OC::$CLI) { - return 'localhost'; - } - if(OC_Config::getValue('overwritehost', '') !== '' and self::isOverwriteCondition()) { - return OC_Config::getValue('overwritehost'); - } + public static function insecureServerHost() { + $host = null; if (isset($_SERVER['HTTP_X_FORWARDED_HOST'])) { if (strpos($_SERVER['HTTP_X_FORWARDED_HOST'], ",") !== false) { $host = trim(array_pop(explode(",", $_SERVER['HTTP_X_FORWARDED_HOST']))); - } - else{ + } else { $host = $_SERVER['HTTP_X_FORWARDED_HOST']; } } else { if (isset($_SERVER['HTTP_HOST'])) { $host = $_SERVER['HTTP_HOST']; - } - else if (isset($_SERVER['SERVER_NAME'])) { + } else if (isset($_SERVER['SERVER_NAME'])) { $host = $_SERVER['SERVER_NAME']; } } + return $host; + } + + /** + * Returns the overwritehost setting from the config if set and + * if the overwrite condition is met + * @return overwritehost value or null if not defined or the defined condition + * isn't met + */ + public static function getOverwriteHost() { + if(OC_Config::getValue('overwritehost', '') !== '' and self::isOverwriteCondition()) { + return OC_Config::getValue('overwritehost'); + } + return null; + } + + /** + * @brief Returns the server host from the headers, or the first configured + * trusted domain if the host isn't in the trusted list + * @returns string the server host + * + * Returns the server host, even if the website uses one or more + * reverse proxies + */ + public static function serverHost() { + if(OC::$CLI) { + return 'localhost'; + } + + // overwritehost is always trusted + $host = self::getOverwriteHost(); + if ($host !== null) { + return $host; + } + + // get the host from the headers + $host = self::insecureServerHost(); // Verify that the host is a trusted domain if the trusted domains // are defined // If no trusted domain is provided the first trusted domain is returned - if(self::isTrustedDomain($host) || \OC_Config::getValue('trusted_domains', "") === "") { + if (self::isTrustedDomain($host)) { return $host; } else { $trustedList = \OC_Config::getValue('trusted_domains', array('')); -- cgit v1.2.3 From 03810e68284add4c4b8e98ed4343a274724a3ac9 Mon Sep 17 00:00:00 2001 From: Vincent Petry Date: Wed, 5 Mar 2014 15:41:28 +0100 Subject: Added localhost as trusted domain Backport of f1b948d from stable6 --- lib/request.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/request.php b/lib/request.php index a0070a50168..07ad80877de 100755 --- a/lib/request.php +++ b/lib/request.php @@ -7,6 +7,8 @@ */ class OC_Request { + const REGEX_LOCALHOST = '/^(127\.0\.0\.1|localhost)(:[0-9]+|)$/'; + /** * @brief Check overwrite condition * @returns bool @@ -31,6 +33,9 @@ class OC_Request { if (empty($trustedList)) { return true; } + if (preg_match(self::REGEX_LOCALHOST, $domain) === 1) { + return true; + } return in_array($domain, $trustedList); } -- cgit v1.2.3 From 6a07f9fc559a74ccf37accd50c876493aeb53fe2 Mon Sep 17 00:00:00 2001 From: Vincent Petry Date: Wed, 5 Mar 2014 17:04:15 +0100 Subject: Added unit tests for serverHost and other related functions Backport of 98ff74a from stable6 --- tests/lib/request.php | 136 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) diff --git a/tests/lib/request.php b/tests/lib/request.php index 090cebc9231..2c4cee445be 100644 --- a/tests/lib/request.php +++ b/tests/lib/request.php @@ -70,4 +70,140 @@ class Test_Request extends PHPUnit_Framework_TestCase { array('/oc/core1', '/oc/core/index.php'), ); } + public function testInsecureServerHost() { + unset($_SERVER['HTTP_X_FORWARDED_HOST']); + unset($_SERVER['HTTP_HOST']); + unset($_SERVER['SERVER_NAME']); + $_SERVER['SERVER_NAME'] = 'from.server.name:8080'; + $host = OC_Request::insecureServerHost(); + $this->assertEquals('from.server.name:8080', $host); + + $_SERVER['HTTP_HOST'] = 'from.host.header:8080'; + $host = OC_Request::insecureServerHost(); + $this->assertEquals('from.host.header:8080', $host); + + $_SERVER['HTTP_X_FORWARDED_HOST'] = 'from.forwarded.host:8080'; + $host = OC_Request::insecureServerHost(); + $this->assertEquals('from.forwarded.host:8080', $host); + + $_SERVER['HTTP_X_FORWARDED_HOST'] = 'from.forwarded.host2:8080,another.one:9000'; + $host = OC_Request::insecureServerHost(); + $this->assertEquals('from.forwarded.host2:8080', $host); + + // clean up + unset($_SERVER['HTTP_X_FORWARDED_HOST']); + unset($_SERVER['HTTP_HOST']); + unset($_SERVER['SERVER_NAME']); + } + + public function testGetOverwriteHost() { + unset($_SERVER['REMOTE_ADDR']); + OC_Config::deleteKey('overwritecondaddr'); + OC_Config::deleteKey('overwritehost'); + $host = OC_Request::getOverwriteHost(); + $this->assertNull($host); + + OC_Config::setValue('overwritehost', ''); + $host = OC_Request::getOverwriteHost(); + $this->assertNull($host); + + OC_Config::setValue('overwritehost', 'host.one.test:8080'); + $host = OC_Request::getOverwriteHost(); + $this->assertEquals('host.one.test:8080', $host); + + $_SERVER['REMOTE_ADDR'] = 'somehost.test:8080'; + OC_Config::setValue('overwritecondaddr', '^somehost\..*$'); + $host = OC_Request::getOverwriteHost(); + $this->assertEquals('host.one.test:8080', $host); + + OC_Config::setValue('overwritecondaddr', '^somethingelse.*$'); + $host = OC_Request::getOverwriteHost(); + $this->assertNull($host); + + // clean up + unset($_SERVER['REMOTE_ADDR']); + OC_Config::deleteKey('overwritecondaddr'); + OC_Config::deleteKey('overwritehost'); + } + + /** + * @dataProvider trustedDomainDataProvider + */ + public function testIsTrustedDomain($trustedDomains, $testDomain, $result) { + OC_Config::deleteKey('trusted_domains'); + if ($trustedDomains !== null) { + OC_Config::setValue('trusted_domains', $trustedDomains); + } + + $this->assertEquals($result, OC_Request::isTrustedDomain($testDomain)); + + // clean up + OC_Config::deleteKey('trusted_domains'); + } + + public function trustedDomainDataProvider() { + $trustedHostTestList = array('host.one.test:8080', 'host.two.test:8080'); + return array( + // empty defaults to true + array(null, 'host.one.test:8080', true), + array('', 'host.one.test:8080', true), + array(array(), 'host.one.test:8080', true), + + // trust list when defined + array($trustedHostTestList, 'host.two.test:8080', true), + array($trustedHostTestList, 'host.two.test:9999', false), + array($trustedHostTestList, 'host.three.test:8080', false), + + // trust localhost regardless of trust list + array($trustedHostTestList, 'localhost', true), + array($trustedHostTestList, 'localhost:8080', true), + array($trustedHostTestList, '127.0.0.1', true), + array($trustedHostTestList, '127.0.0.1:8080', true), + + // do not trust invalid localhosts + array($trustedHostTestList, 'localhost:1:2', false), + array($trustedHostTestList, 'localhost: evil.host', false), + ); + } + + public function testServerHost() { + OC_Config::deleteKey('overwritecondaddr'); + OC_Config::setValue('overwritehost', 'overwritten.host:8080'); + OC_Config::setValue( + 'trusted_domains', + array( + 'trusted.host:8080', + 'second.trusted.host:8080' + ) + ); + $_SERVER['HTTP_HOST'] = 'trusted.host:8080'; + + // CLI always gives localhost + $oldCLI = OC::$CLI; + OC::$CLI = true; + $host = OC_Request::serverHost(); + $this->assertEquals('localhost', $host); + OC::$CLI = false; + + // overwritehost overrides trusted domain + $host = OC_Request::serverHost(); + $this->assertEquals('overwritten.host:8080', $host); + + // trusted domain returned when used + OC_Config::deleteKey('overwritehost'); + $host = OC_Request::serverHost(); + $this->assertEquals('trusted.host:8080', $host); + + // trusted domain returned when untrusted one in header + $_SERVER['HTTP_HOST'] = 'untrusted.host:8080'; + OC_Config::deleteKey('overwritehost'); + $host = OC_Request::serverHost(); + $this->assertEquals('trusted.host:8080', $host); + + // clean up + OC_Config::deleteKey('overwritecondaddr'); + OC_Config::deleteKey('overwritehost'); + unset($_SERVER['HTTP_HOST']); + OC::$CLI = $oldCLI; + } } -- cgit v1.2.3 From 7ba91c346f60a0e1f886ed5f04ac7de4cc1e20fa Mon Sep 17 00:00:00 2001 From: Vincent Petry Date: Wed, 5 Mar 2014 17:04:45 +0100 Subject: Fixed X-Forwarded-Host parsing Backport of d7163c9 from stable6 --- lib/request.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/request.php b/lib/request.php index 07ad80877de..4c627db3e6f 100755 --- a/lib/request.php +++ b/lib/request.php @@ -51,7 +51,8 @@ class OC_Request { $host = null; if (isset($_SERVER['HTTP_X_FORWARDED_HOST'])) { if (strpos($_SERVER['HTTP_X_FORWARDED_HOST'], ",") !== false) { - $host = trim(array_pop(explode(",", $_SERVER['HTTP_X_FORWARDED_HOST']))); + $parts = explode(',', $_SERVER['HTTP_X_FORWARDED_HOST']); + $host = trim(current($parts)); } else { $host = $_SERVER['HTTP_X_FORWARDED_HOST']; } -- cgit v1.2.3