[!!!][SECURITY] Fix link spoofing in prefixLocalAnchors 23/35223/2
authorHelmut Hummel <helmut.hummel@typo3.org>
Wed, 10 Dec 2014 10:07:58 +0000 (11:07 +0100)
committerOliver Hader <oliver.hader@typo3.org>
Wed, 10 Dec 2014 10:07:59 +0000 (11:07 +0100)
Specially crafted request could lead to anchors prefixed
with URLs to domains controlled by the attacker on the
domain root page (home page). No other pages are affected!

Fix this by prefixing the anchors with a canonical URL
to the current request. This could lead to the situation
that the prefix does not match the current REQUEST_URI
which leads to a page reload instead of just "jumping" to the page section.

Additionally this change assures that REQUEST_URI always starts
with a slash, which mitigates similar attack vectors when using
getIndpEnv('REQUEST_URI')

To mitigate the impact of this breaking change, the REQUEST_URI
is used for anchor prefix if a backend user is logged in,
to not disturb the preview functionality of the home page.

In case prefixLocalAnchors is used in the HTML parser configuration
with prefixLocalAnchors = 2, always the canonical URL is used as prefix.

This change does *not* fix, that arbitrary (non functional) GET parameters
will be included in the generated prefix URL. To fix this it is recommended
to use absRefPrefix instead of baseUrl and prefixLocalAnchors.

Resolves: #62723
Releases: 4.5, 6.2, master
Security-Commit: 404fe57aefb1ffbbf8a918d4805f97055a78f160
Security-Bulletin: TYPO3-CORE-SA-2014-003
Change-Id: Icb07584bb1366d895a502b1ef7584593e2b6681a
Reviewed-on: http://review.typo3.org/35223
Reviewed-by: Oliver Hader <oliver.hader@typo3.org>
Tested-by: Oliver Hader <oliver.hader@typo3.org>
typo3/sysext/core/Classes/Html/HtmlParser.php
typo3/sysext/core/Classes/Utility/GeneralUtility.php
typo3/sysext/frontend/Classes/ContentObject/ContentObjectRenderer.php
typo3/sysext/frontend/Classes/Controller/TypoScriptFrontendController.php

index 9c4c9d6..6e5e39f 100644 (file)
@@ -866,11 +866,14 @@ class HtmlParser {
                                                                                        }
                                                                                        if ($params['prefixLocalAnchors']) {
                                                                                                if ($tagAttrib[0][$attr][0] === '#') {
-                                                                                                       $prefix = GeneralUtility::getIndpEnv('TYPO3_REQUEST_URL');
-                                                                                                       $tagAttrib[0][$attr] = $prefix . $tagAttrib[0][$attr];
-                                                                                                       if ($params['prefixLocalAnchors'] == 2 && GeneralUtility::isFirstPartOfStr($prefix, GeneralUtility::getIndpEnv('TYPO3_SITE_URL'))) {
-                                                                                                               $tagAttrib[0][$attr] = substr($tagAttrib[0][$attr], strlen(GeneralUtility::getIndpEnv('TYPO3_SITE_URL')));
+                                                                                                       if ($params['prefixLocalAnchors'] == 2) {
+                                                                                                               /** @var \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer $contentObjectRenderer */
+                                                                                                               $contentObjectRenderer = GeneralUtility::makeInstance('TYPO3\\CMS\\Frontend\\ContentObject\\ContentObjectRenderer');
+                                                                                                               $prefix = $contentObjectRenderer->getUrlToCurrentLocation();
+                                                                                                       } else {
+                                                                                                               $prefix = GeneralUtility::getIndpEnv('TYPO3_REQUEST_URL');
                                                                                                        }
+                                                                                                       $tagAttrib[0][$attr] = $prefix . $tagAttrib[0][$attr];
                                                                                                }
                                                                                        }
                                                                                        if ($params['prefixRelPathWith']) {
index 3a8ef1d..9579824 100755 (executable)
@@ -3398,7 +3398,7 @@ Connection: close
                                        // This is for ISS/CGI which does not have the REQUEST_URI available.
                                        $retVal = '/' . ltrim(self::getIndpEnv('SCRIPT_NAME'), '/') . ($_SERVER['QUERY_STRING'] ? '?' . $_SERVER['QUERY_STRING'] : '');
                                } else {
-                                       $retVal = $_SERVER['REQUEST_URI'];
+                                       $retVal = '/' . ltrim($_SERVER['REQUEST_URI'], '/');
                                }
                                // Add a prefix if TYPO3 is behind a proxy: ext-domain.com => int-server.com/prefix
                                if (self::cmpIP($_SERVER['REMOTE_ADDR'], $GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyIP'])) {
index 0724f05..cf06de1 100644 (file)
@@ -6288,6 +6288,29 @@ class ContentObjectRenderer {
        }
 
        /**
+        * Returns the canonical URL to the current "location", which include the current page ID and type
+        * and optionally the query string
+        *
+        * @param bool $addQueryString Whether additional GET arguments in the query string should be included or not
+        * @return string
+        */
+       public function getUrlToCurrentLocation($addQueryString = TRUE) {
+               $conf = array();
+               $conf['parameter'] = $GLOBALS['TSFE']->id . ',' . $GLOBALS['TSFE']->type;
+               if ($addQueryString) {
+                       $conf['addQueryString'] = '1';
+                       $linkVars = implode(',', array_keys(GeneralUtility::explodeUrl2Array($GLOBALS['TSFE']->linkVars)));
+                       $conf['addQueryString.'] = array(
+                               'method' => 'GET',
+                               'exclude' => 'id,type,cHash' . ($linkVars ? ',' . $linkVars : '')
+                       );
+                       $conf['useCacheHash'] = GeneralUtility::_GET('cHash') ? '1' : '0';
+               }
+
+               return $this->typoLink_URL($conf);
+       }
+
+       /**
         * Returns the URL of a "typolink" create from the input parameter string, url-parameters and target
         *
         * @param string $params Link parameter; eg. "123" for page id, "kasperYYYY@typo3.com" for email address, "http://...." for URL, "fileadmin/blabla.txt" for file.
index 2232547..73ff42a 100644 (file)
@@ -4360,7 +4360,14 @@ if (version == "n3") {
         * @todo Define visibility
         */
        public function prefixLocalAnchorsWithScript() {
-               $scriptPath = $GLOBALS['TSFE']->absRefPrefix . substr(GeneralUtility::getIndpEnv('TYPO3_REQUEST_URL'), strlen(GeneralUtility::getIndpEnv('TYPO3_SITE_URL')));
+               if (!$this->beUserLogin) {
+                       $scriptPath = $this->cObj->getUrlToCurrentLocation();
+               } else {
+                       // To break less existing sites, we allow the REQUEST_URI to be used for the prefix
+                       $scriptPath = GeneralUtility::getIndpEnv('REQUEST_URI');
+                       // Disable the cache so that these URI will not be the ones to be cached
+                       $this->disableCache();
+               }
                $originalContent = $this->content;
                $this->content = preg_replace('/(<(?:a|area).*?href=")(#[^"]*")/i', '${1}' . htmlspecialchars($scriptPath) . '${2}', $originalContent);
                // There was an error in the call to preg_replace, so keep the original content (behavior prior to PHP 5.2)