From 6ef7bd91a684e23c5d8c601b2a5060f0176a6d7b Mon Sep 17 00:00:00 2001 From: Chris Burgess Date: Mon, 13 Jul 2015 12:45:51 +1200 Subject: [PATCH] CRM-16832. Do not redirect offsite when fed invalid keys. URLs to test: * http://civicrm.dev/civicrm/contribute/transact?qfKey=xxx&entryURL=http://evil.example.com/ should go to CIVICRM_UF_BASEURL * http://civicrm.dev/civicrm/contribute/transact?qfKey=xxx&entryURL=/civicrm/contribute/transact%3Fid%3D1 should go to /civicrm/contribute/transact?id=1 * http://civicrm.dev/civicrm/contribute/transact?qfKey=xxx&entryURL=http://civicrm.dev/civicrm/contribute/transact%3Fid%3D1 should go to /civicrm/contribute/transact?id=1 --- CRM/Core/Controller.php | 21 ++- tests/phpunit/WebTest/Utils/RedirectTest.php | 135 +++++++++++++++++++ 2 files changed, 149 insertions(+), 7 deletions(-) create mode 100644 tests/phpunit/WebTest/Utils/RedirectTest.php diff --git a/CRM/Core/Controller.php b/CRM/Core/Controller.php index 7744c60d1f..b07b398af8 100644 --- a/CRM/Core/Controller.php +++ b/CRM/Core/Controller.php @@ -858,18 +858,25 @@ class CRM_Core_Controller extends HTML_QuickForm_Controller { } /** - * Instead of outputting a fatal error message, we'll just redirect to the entryURL if present + * Instead of outputting a fatal error message, we'll just redirect + * to the entryURL if present * * @return void */ public function invalidKeyRedirect() { - if ($this->_entryURL) { - CRM_Core_Session::setStatus(ts('Your browser session has expired and we are unable to complete your form submission. We have returned you to the initial step so you can complete and resubmit the form. If you experience continued difficulties, please contact us for assistance.')); - return CRM_Utils_System::redirect($this->_entryURL); - } - else { - self::invalidKeyCommon(); + if ($this->_entryURL && $url_parts = parse_url($this->_entryURL)) { + // CRM-16832: Ensure local redirects only. + if (!empty($url_parts['path'])) { + // Prepend a slash, but don't duplicate it. + $redirect_url = '/' . ltrim($url_parts['path'], '/'); + if (!empty($url_parts['query'])) { + $redirect_url .= '?' . $url_parts['query']; + } + CRM_Core_Session::setStatus(ts('Your browser session has expired and we are unable to complete your form submission. We have returned you to the initial step so you can complete and resubmit the form. If you experience continued difficulties, please contact us for assistance.')); + return CRM_Utils_System::redirect($redirect_url); + } } + self::invalidKeyCommon(); } } diff --git a/tests/phpunit/WebTest/Utils/RedirectTest.php b/tests/phpunit/WebTest/Utils/RedirectTest.php new file mode 100644 index 0000000000..39d3829302 --- /dev/null +++ b/tests/phpunit/WebTest/Utils/RedirectTest.php @@ -0,0 +1,135 @@ +settings = new CiviSeleniumSettings(); + if (property_exists($this->settings, 'serverStartupTimeOut') && $this->settings->serverStartupTimeOut) { + global $CiviSeleniumTestCase_polled; + if (!$CiviSeleniumTestCase_polled) { + $CiviSeleniumTestCase_polled = TRUE; + CRM_Utils_Network::waitForServiceStartup( + $this->drivers[0]->getHost(), + $this->drivers[0]->getPort(), + $this->settings->serverStartupTimeOut + ); + } + } + } + + protected function setUp() { + parent::setUp(); + //URL should eventually be adapted for multisite + $this->url = $this->settings->sandboxURL; + + $this->ch = curl_init(); + curl_setopt($this->ch, CURLOPT_HEADER, FALSE); + curl_setopt($this->ch, CURLOPT_FOLLOWLOCATION, FALSE); + // curl_setopt($this->ch, CURLOPT_ENCODING, 'gzip'); + // curl_setopt($this->ch, CURLOPT_VERBOSE, 0); + } + + /** + * + */ + private function tryRedirect($input_url, $expected_url) { + // file_put_contents('php://stderr', $input_url . "\n", FILE_APPEND); + $url = $this->url . '/' . $input_url; + $expected_url = $this->url . '/' . $expected_url; + curl_setopt($this->ch, CURLOPT_URL, $url); + $req = curl_exec($this->ch); + $this->assertEquals(0, curl_errno($this->ch), 'cURL error: ' . curl_error($this->ch)); + if (!curl_errno($this->ch)) { + $info = curl_getinfo($this->ch); + // file_put_contents('php://stderr', print_r($info,1), FILE_APPEND); + $this->assertEquals($expected_url, $info['redirect_url']); + $this->assertEquals('302', $info['http_code']); + } + } + + /** + * Handle onsite redirects with absolute URL. + */ + public function testAbsoluteOnsiteRedirect() { + $this->tryRedirect("civicrm/contribute/transact?qfKey=xxx&entryURL={$this->url}/civicrm/contribute/transact%3Fid%3D1", 'civicrm/contribute/transact?id=1'); + } + + /** + * Handle onsite redirects with slash prefix and query params. + */ + public function testOnsiteRedirectWithSlashPrefixAndQueryParams() { + $this->tryRedirect('civicrm/contribute/transact?qfKey=xxx&entryURL=/civicrm/contribute/transact%3Fid%3D1', 'civicrm/contribute/transact?id=1'); + } + + /** + * Handle onsite redirects with non-CiviCRM paths. + */ + public function testOtherpathRedirect() { + $this->tryRedirect('civicrm/contribute/transact?qfKey=xxx&entryURL=asdf', 'asdf'); + } + + /** + * Handle offsite redirects without path as onsite redirects. + */ + public function testOffsiteRedirectNoPath() { + $this->tryRedirect('civicrm/contribute/transact?qfKey=xxx&entryURL=http://evil.example.com/', ''); + } + + /** + * Handle offsite redirects with paths as onsite redirects. + */ + public function testOffsiteRedirectWithPath() { + $this->tryRedirect('civicrm/contribute/transact?qfKey=xxx&entryURL=http://evil.example.com/civicrm', 'civicrm'); + } + +} -- 2.25.1