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