From eb3a4dbda404a592dd04e78283ce7ebb41d907ef Mon Sep 17 00:00:00 2001 From: Benni Mack <benni@typo3.org> Date: Tue, 20 Feb 2018 08:50:59 +0100 Subject: [PATCH] [BUGFIX] Do not check HTTP referrer anymore Under certain circumstances some browsers do not set the HTTP referrer anymore due to privacy reasons. Hence, checking the referrer breaks functionality. Resolves: #83768 Releases: master, 8.7, 7.6 Change-Id: Ia8f882e07a9e2091ceb38aee814badb97403250d Reviewed-on: https://review.typo3.org/55819 Reviewed-by: Markus Klein <markus.klein@typo3.org> Tested-by: Markus Klein <markus.klein@typo3.org> Tested-by: TYPO3com <no-reply@typo3.com> --- .../Controller/EditDocumentController.php | 232 ++++++++---------- .../Controller/File/FileController.php | 11 +- .../SimpleDataHandlerController.php | 31 +-- .../AbstractUserAuthentication.php | 6 - .../Important-83768-RemoveReferrerCheck.rst | 39 +++ .../Controller/ImportExportController.php | 21 +- 6 files changed, 164 insertions(+), 176 deletions(-) create mode 100644 typo3/sysext/core/Documentation/Changelog/7.6.x/Important-83768-RemoveReferrerCheck.rst diff --git a/typo3/sysext/backend/Classes/Controller/EditDocumentController.php b/typo3/sysext/backend/Classes/Controller/EditDocumentController.php index 4ce2274088b2..678b84edb1be 100644 --- a/typo3/sysext/backend/Classes/Controller/EditDocumentController.php +++ b/typo3/sysext/backend/Classes/Controller/EditDocumentController.php @@ -565,145 +565,125 @@ class EditDocumentController extends AbstractModule 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'] - && $this->vC != $beUser->veriCode() - && !$GLOBALS['TYPO3_CONF_VARS']['SYS']['doNotCheckReferer'] + // Perform the saving operation with TCEmain: + $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 and veriCode was not valid either!', - 1, - [$refInfo['host'], $httpHost] - ); - debug('Error: Referer host did not match with server host.'); - } else { - // Perform the saving operation with TCEmain: - $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'; - } - // Traverse all new records and forge the content of ->editconf so we can continue to EDIT - // these records! - if ($tableName == 'pages' - && $this->retUrl != BackendUtility::getModuleUrl('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'; + } + // Traverse all new records and forge the content of ->editconf so we can continue to EDIT + // these records! + if ($tableName == 'pages' + && $this->retUrl != BackendUtility::getModuleUrl('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']; - } - // Determine related page ID for workspace context + // Determine related page ID for workspace context + } else { + 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 { - 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(isset($_POST['_saveandclosedok']) || isset($_POST['_translation_savedok_x']) ? $this->retUrl : $this->R_URL_parts['path'] . '?' . GeneralUtility::implodeArrayForUrl('', $this->R_URL_getvars)); + // Store this information for later use + $this->previewData['table'] = $table; + $this->previewData['id'] = $id; } + $tce->printLogErrorMessages(isset($_POST['_saveandclosedok']) || isset($_POST['_translation_savedok_x']) ? $this->retUrl : $this->R_URL_parts['path'] . '?' . GeneralUtility::implodeArrayForUrl('', $this->R_URL_getvars)); // || 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 diff --git a/typo3/sysext/backend/Classes/Controller/File/FileController.php b/typo3/sysext/backend/Classes/Controller/File/FileController.php index f3092a368dfa..1ef21e685c75 100644 --- a/typo3/sysext/backend/Classes/Controller/File/FileController.php +++ b/typo3/sysext/backend/Classes/Controller/File/FileController.php @@ -144,15 +144,8 @@ class FileController $this->fileProcessor->init([], $GLOBALS['TYPO3_CONF_VARS']['BE']['fileExtensions']); $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'] && $this->vC !== $this->getBackendUser()->veriCode() && !$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(); } /** diff --git a/typo3/sysext/backend/Classes/Controller/SimpleDataHandlerController.php b/typo3/sysext/backend/Classes/Controller/SimpleDataHandlerController.php index 081b43fca557..9844f93ebf4e 100644 --- a/typo3/sysext/backend/Classes/Controller/SimpleDataHandlerController.php +++ b/typo3/sysext/backend/Classes/Controller/SimpleDataHandlerController.php @@ -203,25 +203,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'] && $this->vC != $this->getBackendUser()->veriCode() && !$GLOBALS['TYPO3_CONF_VARS']['SYS']['doNotCheckReferer']) { - $this->tce->log('', 0, 0, 0, 1, 'Referer host "%s" and server host "%s" did not match and veriCode was not valid either!', 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 ($this->uPT && (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 ($this->uPT && (isset($this->data['pages']) || isset($this->cmd['pages']))) { + BackendUtility::setUpdateSignal('updatePageTree'); } } diff --git a/typo3/sysext/core/Classes/Authentication/AbstractUserAuthentication.php b/typo3/sysext/core/Classes/Authentication/AbstractUserAuthentication.php index 77bd9c38f10f..f90d373d45e1 100644 --- a/typo3/sysext/core/Classes/Authentication/AbstractUserAuthentication.php +++ b/typo3/sysext/core/Classes/Authentication/AbstractUserAuthentication.php @@ -602,12 +602,6 @@ abstract class AbstractUserAuthentication } // check referer 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(); } diff --git a/typo3/sysext/core/Documentation/Changelog/7.6.x/Important-83768-RemoveReferrerCheck.rst b/typo3/sysext/core/Documentation/Changelog/7.6.x/Important-83768-RemoveReferrerCheck.rst new file mode 100644 index 000000000000..ad4e59a11f06 --- /dev/null +++ b/typo3/sysext/core/Documentation/Changelog/7.6.x/Important-83768-RemoveReferrerCheck.rst @@ -0,0 +1,39 @@ +.. 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. + +Usages of the configuration option :php:`[SYS][doNotCheckReferer]` within TYPO3 Core have been removed, as this is not +needed anymore. However, the option can still be set for extensions implementing this option. + + +Impact +====== + +Backend users will not notice any differences. + + +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 diff --git a/typo3/sysext/impexp/Classes/Controller/ImportExportController.php b/typo3/sysext/impexp/Classes/Controller/ImportExportController.php index b72ffeb172b8..8869caf7042e 100644 --- a/typo3/sysext/impexp/Classes/Controller/ImportExportController.php +++ b/typo3/sysext/impexp/Classes/Controller/ImportExportController.php @@ -960,22 +960,11 @@ class ImportExportController extends BaseScriptClass $this->fileProcessor->init([], $GLOBALS['TYPO3_CONF_VARS']['BE']['fileExtensions']); $this->fileProcessor->setActionPermissions(); $this->fileProcessor->setExistingFilesConflictMode((int)GeneralUtility::_GP('overwriteExistingFiles') === 1 ? DuplicationBehavior::REPLACE : DuplicationBehavior::CANCEL); - // 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->vC != $this->getBackendUser()->veriCode() - ) { - $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; } } } -- GitLab