[BUGFIX] Do not check HTTP referrer anymore 56/55556/4
authorMichael Schams <typo3@2018.schams.net>
Mon, 5 Feb 2018 10:04:46 +0000 (21:04 +1100)
committerSusanne Moog <susanne.moog@typo3.org>
Fri, 9 Feb 2018 18:14:46 +0000 (19:14 +0100)
Under certain circumstances some browsers do not set the HTTP referrer
anymore due to privacy reasons. Hence, checking the referrer breaks
functionality.

The configuration option [SYS][doNotCheckReferer] is also removed as
it is not needed anymore.

Resolves: #83768
Releases: master, 8.7, 7.6
Change-Id: Ia8f882e07a9e2091ceb38aee814badb97403250d
Reviewed-on: https://review.typo3.org/55556
Reviewed-by: Benni Mack <benni@typo3.org>
Tested-by: Benni Mack <benni@typo3.org>
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Markus Klein <markus.klein@typo3.org>
Tested-by: Markus Klein <markus.klein@typo3.org>
Reviewed-by: Susanne Moog <susanne.moog@typo3.org>
Tested-by: Susanne Moog <susanne.moog@typo3.org>
typo3/sysext/backend/Classes/Controller/EditDocumentController.php
typo3/sysext/backend/Classes/Controller/File/FileController.php
typo3/sysext/backend/Classes/Controller/SimpleDataHandlerController.php
typo3/sysext/core/Classes/Authentication/AbstractUserAuthentication.php
typo3/sysext/core/Configuration/DefaultConfiguration.php
typo3/sysext/core/Configuration/DefaultConfigurationDescription.yaml
typo3/sysext/core/Documentation/Changelog/master/Important-83768-RemoveReferrerCheck.rst [new file with mode: 0644]
typo3/sysext/impexp/Classes/Controller/ImportExportController.php
typo3/sysext/install/Classes/Service/SilentConfigurationUpgradeService.php

index c5e87b2..a2c9cb1 100644 (file)
@@ -557,146 +557,129 @@ class EditDocumentController
         if (is_array($this->mirror)) {
             $tce->setMirror($this->mirror);
         }
-        // Checking referer / executing
-        $refInfo = parse_url(GeneralUtility::getIndpEnv('HTTP_REFERER'));
-        $httpHost = GeneralUtility::getIndpEnv('TYPO3_HOST_ONLY');
-        if ($httpHost != $refInfo['host']
-            && !$GLOBALS['TYPO3_CONF_VARS']['SYS']['doNotCheckReferer']
+
+        // Perform the saving operation with DataHandler:
+        $tce->process_uploads($_FILES);
+        $tce->process_datamap();
+        $tce->process_cmdmap();
+        // If pages are being edited, we set an instruction about updating the page tree after this operation.
+        if ($tce->pagetreeNeedsRefresh
+            && (isset($this->data['pages']) || $beUser->workspace != 0 && !empty($this->data))
         ) {
-            $tce->log(
-                '',
-                0,
-                0,
-                0,
-                1,
-                'Referer host \'%s\' and server host \'%s\' did not match!',
-                1,
-                [$refInfo['host'], $httpHost]
-            );
-            debug('Error: Referer host did not match with server host.');
-        } else {
-            // Perform the saving operation with DataHandler:
-            $tce->process_uploads($_FILES);
-            $tce->process_datamap();
-            $tce->process_cmdmap();
-            // If pages are being edited, we set an instruction about updating the page tree after this operation.
-            if ($tce->pagetreeNeedsRefresh
-                && (isset($this->data['pages']) || $beUser->workspace != 0 && !empty($this->data))
-            ) {
-                BackendUtility::setUpdateSignal('updatePageTree');
-            }
-            // If there was saved any new items, load them:
-            if (!empty($tce->substNEWwithIDs_table)) {
-                // save the expanded/collapsed states for new inline records, if any
-                FormEngineUtility::updateInlineView($this->uc, $tce);
-                $newEditConf = [];
-                foreach ($this->editconf as $tableName => $tableCmds) {
-                    $keys = array_keys($tce->substNEWwithIDs_table, $tableName);
-                    if (!empty($keys)) {
-                        foreach ($keys as $key) {
-                            $editId = $tce->substNEWwithIDs[$key];
-                            // Check if the $editId isn't a child record of an IRRE action
-                            if (!(is_array($tce->newRelatedIDs[$tableName])
-                                && in_array($editId, $tce->newRelatedIDs[$tableName]))
-                            ) {
-                                // Translate new id to the workspace version:
-                                if ($versionRec = BackendUtility::getWorkspaceVersionOfRecord(
-                                    $beUser->workspace,
-                                    $tableName,
-                                    $editId,
-                                    'uid'
-                                )) {
-                                    $editId = $versionRec['uid'];
-                                }
-                                $newEditConf[$tableName][$editId] = 'edit';
-                            }
-                            /** @var \TYPO3\CMS\Backend\Routing\UriBuilder $uriBuilder */
-                            $uriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class);
-                            // Traverse all new records and forge the content of ->editconf so we can continue to EDIT
-                            // these records!
-                            if ($tableName === 'pages'
-                                && $this->retUrl != (string)$uriBuilder->buildUriFromRoute('dummy')
-                                && $this->returnNewPageId
-                            ) {
-                                $this->retUrl .= '&id=' . $tce->substNEWwithIDs[$key];
+            BackendUtility::setUpdateSignal('updatePageTree');
+        }
+        // If there was saved any new items, load them:
+        if (!empty($tce->substNEWwithIDs_table)) {
+            // save the expanded/collapsed states for new inline records, if any
+            FormEngineUtility::updateInlineView($this->uc, $tce);
+            $newEditConf = [];
+            foreach ($this->editconf as $tableName => $tableCmds) {
+                $keys = array_keys($tce->substNEWwithIDs_table, $tableName);
+                if (!empty($keys)) {
+                    foreach ($keys as $key) {
+                        $editId = $tce->substNEWwithIDs[$key];
+                        // Check if the $editId isn't a child record of an IRRE action
+                        if (!(is_array($tce->newRelatedIDs[$tableName])
+                            && in_array($editId, $tce->newRelatedIDs[$tableName]))
+                        ) {
+                            // Translate new id to the workspace version:
+                            if ($versionRec = BackendUtility::getWorkspaceVersionOfRecord(
+                                $beUser->workspace,
+                                $tableName,
+                                $editId,
+                                'uid'
+                            )) {
+                                $editId = $versionRec['uid'];
                             }
+                            $newEditConf[$tableName][$editId] = 'edit';
+                        }
+                        /** @var \TYPO3\CMS\Backend\Routing\UriBuilder $uriBuilder */
+                        $uriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class);
+                        // Traverse all new records and forge the content of ->editconf so we can continue to EDIT
+                        // these records!
+                        if ($tableName === 'pages'
+                            && $this->retUrl != (string)$uriBuilder->buildUriFromRoute('dummy')
+                            && $this->returnNewPageId
+                        ) {
+                            $this->retUrl .= '&id=' . $tce->substNEWwithIDs[$key];
                         }
-                    } else {
-                        $newEditConf[$tableName] = $tableCmds;
                     }
+                } else {
+                    $newEditConf[$tableName] = $tableCmds;
                 }
-                // Resetting editconf if newEditConf has values:
-                if (!empty($newEditConf)) {
-                    $this->editconf = $newEditConf;
-                }
-                // Finally, set the editconf array in the "getvars" so they will be passed along in URLs as needed.
-                $this->R_URL_getvars['edit'] = $this->editconf;
-                // Unsetting default values since we don't need them anymore.
-                unset($this->R_URL_getvars['defVals']);
-                // Re-compile the store* values since editconf changed...
-                $this->compileStoreDat();
             }
-            // See if any records was auto-created as new versions?
-            if (!empty($tce->autoVersionIdMap)) {
-                $this->fixWSversioningInEditConf($tce->autoVersionIdMap);
+            // Resetting editconf if newEditConf has values:
+            if (!empty($newEditConf)) {
+                $this->editconf = $newEditConf;
+            }
+            // Finally, set the editconf array in the "getvars" so they will be passed along in URLs as needed.
+            $this->R_URL_getvars['edit'] = $this->editconf;
+            // Unsetting default values since we don't need them anymore.
+            unset($this->R_URL_getvars['defVals']);
+            // Re-compile the store* values since editconf changed...
+            $this->compileStoreDat();
+        }
+        // See if any records was auto-created as new versions?
+        if (!empty($tce->autoVersionIdMap)) {
+            $this->fixWSversioningInEditConf($tce->autoVersionIdMap);
+        }
+        // If a document is saved and a new one is created right after.
+        if (isset($_POST['_savedoknew']) && is_array($this->editconf)) {
+            $this->closeDocument(self::DOCUMENT_CLOSE_MODE_NO_REDIRECT);
+            // Finding the current table:
+            reset($this->editconf);
+            $nTable = key($this->editconf);
+            // Finding the first id, getting the records pid+uid
+            reset($this->editconf[$nTable]);
+            $nUid = key($this->editconf[$nTable]);
+            $recordFields = 'pid,uid';
+            if (!empty($GLOBALS['TCA'][$nTable]['ctrl']['versioningWS'])) {
+                $recordFields .= ',t3ver_oid';
             }
-            // If a document is saved and a new one is created right after.
-            if (isset($_POST['_savedoknew']) && is_array($this->editconf)) {
-                $this->closeDocument(self::DOCUMENT_CLOSE_MODE_NO_REDIRECT);
-                // Finding the current table:
-                reset($this->editconf);
-                $nTable = key($this->editconf);
-                // Finding the first id, getting the records pid+uid
-                reset($this->editconf[$nTable]);
-                $nUid = key($this->editconf[$nTable]);
-                $recordFields = 'pid,uid';
-                if (!empty($GLOBALS['TCA'][$nTable]['ctrl']['versioningWS'])) {
-                    $recordFields .= ',t3ver_oid';
+            $nRec = BackendUtility::getRecord($nTable, $nUid, $recordFields);
+            // Determine insertion mode ('top' is self-explaining,
+            // otherwise new elements are inserted after one using a negative uid)
+            $insertRecordOnTop = ($this->getNewIconMode($nTable) === 'top');
+            // Setting a blank editconf array for a new record:
+            $this->editconf = [];
+            // Determine related page ID for regular live context
+            if ($nRec['pid'] != -1) {
+                if ($insertRecordOnTop) {
+                    $relatedPageId = $nRec['pid'];
+                } else {
+                    $relatedPageId = -$nRec['uid'];
                 }
-                $nRec = BackendUtility::getRecord($nTable, $nUid, $recordFields);
-                // Determine insertion mode ('top' is self-explaining,
-                // otherwise new elements are inserted after one using a negative uid)
-                $insertRecordOnTop = ($this->getNewIconMode($nTable) === 'top');
-                // Setting a blank editconf array for a new record:
-                $this->editconf = [];
-                // Determine related page ID for regular live context
-                if ($nRec['pid'] != -1) {
-                    if ($insertRecordOnTop) {
-                        $relatedPageId = $nRec['pid'];
-                    } else {
-                        $relatedPageId = -$nRec['uid'];
-                    }
+            } else {
+                // Determine related page ID for workspace context
+                if ($insertRecordOnTop) {
+                    // Fetch live version of workspace version since the pid value is always -1 in workspaces
+                    $liveRecord = BackendUtility::getRecord($nTable, $nRec['t3ver_oid'], $recordFields);
+                    $relatedPageId = $liveRecord['pid'];
                 } else {
-                    // Determine related page ID for workspace context
-                    if ($insertRecordOnTop) {
-                        // Fetch live version of workspace version since the pid value is always -1 in workspaces
-                        $liveRecord = BackendUtility::getRecord($nTable, $nRec['t3ver_oid'], $recordFields);
-                        $relatedPageId = $liveRecord['pid'];
-                    } else {
-                        // Use uid of live version of workspace version
-                        $relatedPageId = -$nRec['t3ver_oid'];
-                    }
+                    // Use uid of live version of workspace version
+                    $relatedPageId = -$nRec['t3ver_oid'];
                 }
-                $this->editconf[$nTable][$relatedPageId] = 'new';
-                // Finally, set the editconf array in the "getvars" so they will be passed along in URLs as needed.
-                $this->R_URL_getvars['edit'] = $this->editconf;
-                // Re-compile the store* values since editconf changed...
-                $this->compileStoreDat();
             }
-            // If a preview is requested
-            if (isset($_POST['_savedokview'])) {
-                // Get the first table and id of the data array from DataHandler
-                $table = reset(array_keys($this->data));
-                $id = reset(array_keys($this->data[$table]));
-                if (!MathUtility::canBeInterpretedAsInteger($id)) {
-                    $id = $tce->substNEWwithIDs[$id];
-                }
-                // Store this information for later use
-                $this->previewData['table'] = $table;
-                $this->previewData['id'] = $id;
+            $this->editconf[$nTable][$relatedPageId] = 'new';
+            // Finally, set the editconf array in the "getvars" so they will be passed along in URLs as needed.
+            $this->R_URL_getvars['edit'] = $this->editconf;
+            // Re-compile the store* values since editconf changed...
+            $this->compileStoreDat();
+        }
+        // If a preview is requested
+        if (isset($_POST['_savedokview'])) {
+            // Get the first table and id of the data array from DataHandler
+            $table = reset(array_keys($this->data));
+            $id = reset(array_keys($this->data[$table]));
+            if (!MathUtility::canBeInterpretedAsInteger($id)) {
+                $id = $tce->substNEWwithIDs[$id];
             }
-            $tce->printLogErrorMessages();
+            // Store this information for later use
+            $this->previewData['table'] = $table;
+            $this->previewData['id'] = $id;
         }
+        $tce->printLogErrorMessages();
+
         //  || count($tce->substNEWwithIDs)... If any new items has been save, the document is CLOSED
         // because if not, we just get that element re-listed as new. And we don't want that!
         if ((int)$this->closeDoc < self::DOCUMENT_CLOSE_MODE_DEFAULT
index 136439f..b8338f4 100644 (file)
@@ -143,15 +143,8 @@ class FileController
         // Initializing:
         $this->fileProcessor->setActionPermissions();
         $this->fileProcessor->setExistingFilesConflictMode($this->overwriteExistingFiles);
-        // Checking referrer / executing:
-        $refInfo = parse_url(GeneralUtility::getIndpEnv('HTTP_REFERER'));
-        $httpHost = GeneralUtility::getIndpEnv('TYPO3_HOST_ONLY');
-        if ($httpHost !== $refInfo['host'] && !$GLOBALS['TYPO3_CONF_VARS']['SYS']['doNotCheckReferer']) {
-            $this->fileProcessor->writeLog(0, 2, 1, 'Referrer host "%s" and server host "%s" did not match!', [$refInfo['host'], $httpHost]);
-        } else {
-            $this->fileProcessor->start($this->file);
-            $this->fileData = $this->fileProcessor->processData();
-        }
+        $this->fileProcessor->start($this->file);
+        $this->fileData = $this->fileProcessor->processData();
     }
 
     /**
index c2d0e56..d222d9e 100644 (file)
@@ -172,25 +172,18 @@ class SimpleDataHandlerController
         if (is_array($this->mirror)) {
             $this->tce->setMirror($this->mirror);
         }
-        // Checking referer / executing
-        $refInfo = parse_url(GeneralUtility::getIndpEnv('HTTP_REFERER'));
-        $httpHost = GeneralUtility::getIndpEnv('TYPO3_HOST_ONLY');
-        if ($httpHost != $refInfo['host'] && !$GLOBALS['TYPO3_CONF_VARS']['SYS']['doNotCheckReferer']) {
-            $this->tce->log('', 0, 0, 0, 1, 'Referer host "%s" and server host "%s" did not match!', 1, [$refInfo['host'], $httpHost]);
-        } else {
-            // Register uploaded files
-            $this->tce->process_uploads($_FILES);
-            // Execute actions:
-            $this->tce->process_datamap();
-            $this->tce->process_cmdmap();
-            // Clearing cache:
-            if (!empty($this->cacheCmd)) {
-                $this->tce->clear_cacheCmd($this->cacheCmd);
-            }
-            // Update page tree?
-            if (isset($this->data['pages']) || isset($this->cmd['pages'])) {
-                BackendUtility::setUpdateSignal('updatePageTree');
-            }
+        // Register uploaded files
+        $this->tce->process_uploads($_FILES);
+        // Execute actions:
+        $this->tce->process_datamap();
+        $this->tce->process_cmdmap();
+        // Clearing cache:
+        if (!empty($this->cacheCmd)) {
+            $this->tce->clear_cacheCmd($this->cacheCmd);
+        }
+        // Update page tree?
+        if (isset($this->data['pages']) || isset($this->cmd['pages'])) {
+            BackendUtility::setUpdateSignal('updatePageTree');
         }
     }
 
index 3e533bc..a987dc3 100644 (file)
@@ -596,12 +596,6 @@ abstract class AbstractUserAuthentication implements LoggerAwareInterface
             $this->logger->debug('Active login (eg. with login form)');
             // check referrer for submitted login values
             if ($this->formfield_status && $loginData['uident'] && $loginData['uname']) {
-                $httpHost = GeneralUtility::getIndpEnv('TYPO3_HOST_ONLY');
-                if (!$this->getMethodEnabled && ($httpHost != $authInfo['refInfo']['host'] && !$GLOBALS['TYPO3_CONF_VARS']['SYS']['doNotCheckReferer'])) {
-                    throw new \RuntimeException('TYPO3 Fatal Error: Error: This host address ("' . $httpHost . '") and the referer host ("' . $authInfo['refInfo']['host'] . '") mismatches! ' .
-                        'It is possible that the environment variable HTTP_REFERER is not passed to the script because of a proxy. ' .
-                        'The site administrator can disable this check in the "All Configuration" section of the Install Tool (flag: TYPO3_CONF_VARS[SYS][doNotCheckReferer]).', 1270853930);
-                }
                 // Delete old user session if any
                 $this->logoff();
             }
index e74dc3a..415224b 100644 (file)
@@ -75,7 +75,6 @@ return [
         'encryptionKey' => '',
         'cookieDomain' => '',
         'cookieSecure' => 0,
-        'doNotCheckReferer' => false,
         'recursiveDomainSearch' => false,
         'trustedHostsPattern' => 'SERVER_NAME',
         'devIPmask' => '127.0.0.1,::1',
index 8c81d2d..6ab93eb 100644 (file)
@@ -86,9 +86,6 @@ SYS:
               '1': 'Force HTTPS: the cookie will only be set if a secure (HTTPS) connection exists - use this in combination with lockSSL since otherwise the application will fail and throw an exception'
               '2': 'The cookie will be set in each case, but uses the secure flag if a secure (HTTPS) connection exists'
             description: 'Indicates that the cookie should only be transmitted over a secure HTTPS connection from the client.'
-        doNotCheckReferer:
-            type: bool
-            description: 'If set, it''s NOT checked numerous places that the referring host is the same as the current. This is an option you should set if you have problems with proxies not passing the HTTP_REFERER variable.'
         recursiveDomainSearch:
             type: bool
             description: 'If set, the search for domain records will be done recursively by stripping parts of the hostname off until a matching domain record is found.'
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Important-83768-RemoveReferrerCheck.rst b/typo3/sysext/core/Documentation/Changelog/master/Important-83768-RemoveReferrerCheck.rst
new file mode 100644 (file)
index 0000000..b7de05f
--- /dev/null
@@ -0,0 +1,40 @@
+.. include:: ../../Includes.txt
+
+=========================================
+Important: #83768 - Remove referrer check
+=========================================
+
+See :issue:`83768`
+
+Description
+===========
+
+Browser vendors are considering or have already announced **not** to send the referrer URL/path in HTTP requests when
+links are followed or forms are submitted due to privacy reasons. TYPO3 used the referrer as a meagre CSRF protection
+for the backend. However, this has been replaced by proper CSRF protection tokens for every backend action and therefore,
+the referrer check became obsolete and has been removed.
+
+The configuration option :php:`[SYS][doNotCheckReferer]` has been also removed as this is not needed anymore.
+
+
+Impact
+======
+
+Backend users will not notice any differences.
+
+TYPO3 extensions will not longer be able to use :php:`[SYS][doNotCheckReferer]`.
+
+
+Affected Installations
+======================
+
+All installations are affected.
+
+
+Migration
+=========
+
+TYPO3 extensions that use option :php:`[SYS][doNotCheckReferer]` to implement a kind of CSRF protection, should use
+proper CSRF protection tokens provided by the core.
+
+.. index:: Backend, FullyScanned
index 9a2475e..2a7b987 100644 (file)
@@ -980,21 +980,11 @@ class ImportExportController extends BaseScriptClass
         $this->fileProcessor->setActionPermissions();
         $conflictMode = empty(GeneralUtility::_GP('overwriteExistingFiles')) ? DuplicationBehavior::__default : DuplicationBehavior::REPLACE;
         $this->fileProcessor->setExistingFilesConflictMode(DuplicationBehavior::cast($conflictMode));
-        // Checking referer / executing:
-        $refInfo = parse_url(GeneralUtility::getIndpEnv('HTTP_REFERER'));
-        $httpHost = GeneralUtility::getIndpEnv('TYPO3_HOST_ONLY');
-        if (
-            $httpHost != $refInfo['host']
-            && !$GLOBALS['$TYPO3_CONF_VARS']['SYS']['doNotCheckReferer']
-        ) {
-            $this->fileProcessor->writeLog(0, 2, 1, 'Referer host "%s" and server host "%s" did not match!', [$refInfo['host'], $httpHost]);
-        } else {
-            $this->fileProcessor->start($file);
-            $result = $this->fileProcessor->processData();
-            if (!empty($result['upload'])) {
-                foreach ($result['upload'] as $uploadedFiles) {
-                    $this->uploadedFiles += $uploadedFiles;
-                }
+        $this->fileProcessor->start($file);
+        $result = $this->fileProcessor->processData();
+        if (!empty($result['upload'])) {
+            foreach ($result['upload'] as $uploadedFiles) {
+                $this->uploadedFiles += $uploadedFiles;
             }
         }
     }
index 62f53d1..9d9e4ca 100644 (file)
@@ -122,7 +122,9 @@ class SilentConfigurationUpgradeService
         // #82803
         'FE/content_doktypes',
         // #83081
-        'BE/fileExtensions'
+        'BE/fileExtensions',
+        // #83768
+        'SYS/doNotCheckReferer',
     ];
 
     public function __construct(ConfigurationManager $configurationManager = null)