[SECURITY] Escape the link text in EmailViewHelper 78/49078/2
authorWouter Wolters <typo3@wouterwolters.nl>
Tue, 19 Jul 2016 10:17:32 +0000 (12:17 +0200)
committerOliver Hader <oliver.hader@typo3.org>
Tue, 19 Jul 2016 10:17:36 +0000 (12:17 +0200)
The content of the email link is not escaped correctly.
This leads to XSS in the EmailViewHelper.

Resolves: #76344
Releases: master,7.6,6.2
Security-Commit: 02176ebafd54220201f751b46b54761c9a39d92e
Security-Bulletins: TYPO3-CORE-SA-2016-014, 015, 016, 017, 018
Change-Id: I9ec59b202de39525370a1eeb7f03f1e71a823224
Reviewed-on: https://review.typo3.org/49078
Reviewed-by: Oliver Hader <oliver.hader@typo3.org>
Tested-by: Oliver Hader <oliver.hader@typo3.org>
typo3/sysext/fluid/Classes/ViewHelpers/Link/EmailViewHelper.php
typo3/sysext/fluid/Tests/Unit/ViewHelpers/Link/EmailViewHelperTest.php
typo3/sysext/frontend/Classes/ContentObject/ContentObjectRenderer.php

index 84ce93f..b38b6ac 100644 (file)
@@ -63,20 +63,29 @@ class EmailViewHelper extends \TYPO3\CMS\Fluid\Core\ViewHelper\AbstractTagBasedV
      */
     public function render($email)
     {
-        if (TYPO3_MODE === 'FE') {
-            list($linkHref, $linkText) = $GLOBALS['TSFE']->cObj->getMailTo($email, $email);
+        if ($this->isFrontendAvailable()) {
+            list($linkHref, $linkText) = $GLOBALS['TSFE']->cObj->getMailTo($email, '');
+            $escapeSpecialCharacters = !isset($GLOBALS['TSFE']->spamProtectEmailAddresses) || $GLOBALS['TSFE']->spamProtectEmailAddresses !== 'ascii';
         } else {
             $linkHref = 'mailto:' . $email;
-            $linkText = $email;
+            $linkText = htmlspecialchars($email);
+            $escapeSpecialCharacters = true;
         }
         $tagContent = $this->renderChildren();
         if ($tagContent !== null) {
             $linkText = $tagContent;
         }
         $this->tag->setContent($linkText);
-        $escapeSpecialCharacters = !isset($GLOBALS['TSFE']->spamProtectEmailAddresses) || $GLOBALS['TSFE']->spamProtectEmailAddresses !== 'ascii';
         $this->tag->addAttribute('href', $linkHref, $escapeSpecialCharacters);
         $this->tag->forceClosingTag(true);
         return $this->tag->render();
     }
+
+    /**
+     * @return bool
+     */
+    protected function isFrontendAvailable()
+    {
+        return TYPO3_MODE === 'FE';
+    }
 }
index 4ee2743..a260533 100644 (file)
@@ -13,6 +13,10 @@ namespace TYPO3\CMS\Fluid\Tests\Unit\ViewHelpers\Link;
  *
  * The TYPO3 project - inspiring people to share!
  */
+
+use TYPO3\CMS\Fluid\ViewHelpers\Link\EmailViewHelper;
+use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
+use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
 use TYPO3Fluid\Fluid\Core\ViewHelper\TagBuilder;
 
 /**
@@ -73,4 +77,78 @@ class EmailViewHelperTest extends \TYPO3\CMS\Fluid\Tests\Unit\ViewHelpers\ViewHe
         $this->viewHelper->initialize();
         $this->viewHelper->render('some@email.tld');
     }
+
+    /**
+     * @return array
+     */
+    public function renderEncodesEmailInFrontendDataProvider()
+    {
+        return [
+            'Plain email' => [
+                'some@email.tld',
+                0,
+                '<a href="mailto:some@email.tld">some@email.tld</a>',
+            ],
+            'Plain email with spam protection' => [
+                'some@email.tld',
+                1,
+                '<a href="javascript:linkTo_UnCryptMailto(\'nbjmup+tpnfAfnbjm\/ume\');">some(at)email.tld</a>',
+            ],
+            'Plain email with ascii spam protection' => [
+                'some@email.tld',
+                'ascii',
+                '<a href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#115;&#111;&#109;&#101;&#64;&#101;&#109;&#97;&#105;&#108;&#46;&#116;&#108;&#100;">some(at)email.tld</a>',
+            ],
+            'Susceptible email' => [
+                '"><script>alert(\'email\')</script>',
+                0,
+                '<a href="mailto:&quot;&gt;&lt;script&gt;alert(\'email\')&lt;/script&gt;">&quot;&gt;&lt;script&gt;alert(\'email\')&lt;/script&gt;</a>',
+            ],
+            'Susceptible email with spam protection' => [
+                '"><script>alert(\'email\')</script>',
+                1,
+                '<a href="javascript:linkTo_UnCryptMailto(\'nbjmup+\u0022\u003E\u003Ctdsjqu\u003Ebmfsu(\u0027fnbjm\u0027)\u003C0tdsjqu\u003E\');">&quot;&gt;&lt;script&gt;alert(\'email\')&lt;/script&gt;</a>',
+            ],
+            'Susceptible email with ascii spam protection' => [
+                '"><script>alert(\'email\')</script>',
+                'ascii',
+                '<a href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#34;&#62;&#60;&#115;&#99;&#114;&#105;&#112;&#116;&#62;&#97;&#108;&#101;&#114;&#116;&#40;&#39;&#101;&#109;&#97;&#105;&#108;&#39;&#41;&#60;&#47;&#115;&#99;&#114;&#105;&#112;&#116;&#62;">&quot;&gt;&lt;script&gt;alert(\'email\')&lt;/script&gt;</a>',
+            ],
+        ];
+    }
+
+    /**
+     * @test
+     * @dataProvider renderEncodesEmailInFrontendDataProvider
+     * @param string $email
+     * @param string $spamProtectEmailAddresses
+     * @param string $expected
+     */
+    public function renderEncodesEmailInFrontend($email, $spamProtectEmailAddresses, $expected)
+    {
+        /** @var TypoScriptFrontendController $tsfe */
+        $tsfe = $this->getMockBuilder(TypoScriptFrontendController::class)
+            ->setMethods(['dummy'])
+            ->disableOriginalConstructor()
+            ->getMock();
+        $tsfe->cObj = new ContentObjectRenderer();
+        $tsfe->spamProtectEmailAddresses = $spamProtectEmailAddresses;
+        $tsfe->config = [
+            'config' => [
+                'spamProtectEmailAddresses_atSubst' => '',
+                'spamProtectEmailAddresses_lastDotSubst' => '',
+            ],
+        ];
+        $GLOBALS['TSFE'] = $tsfe;
+        $viewHelper = $this->getMockBuilder(EmailViewHelper::class)
+            ->setMethods(array('isFrontendAvailable', 'renderChildren'))
+            ->getMock();
+        $viewHelper->expects($this->once())->method('isFrontendAvailable')->willReturn(true);
+        $viewHelper->expects($this->once())->method('renderChildren')->willReturn(null);
+        $viewHelper->initialize();
+        $this->assertSame(
+            $expected,
+            $viewHelper->render($email)
+        );
+    }
 }
index d49b917..b1e4881 100644 (file)
@@ -6539,7 +6539,7 @@ class ContentObjectRenderer
     {
         $mailAddress = (string)$mailAddress;
         if ((string)$linktxt === '') {
-            $linktxt = $mailAddress;
+            $linktxt = htmlspecialchars($mailAddress);
         }
 
         $originalMailToUrl = 'mailto:' . $mailAddress;
@@ -6558,7 +6558,7 @@ class ContentObjectRenderer
                 if ($tsfe->config['config']['spamProtectEmailAddresses_atSubst']) {
                     $atLabel = trim($tsfe->config['config']['spamProtectEmailAddresses_atSubst']);
                 }
-                $spamProtectedMailAddress = str_replace('@', $atLabel ? $atLabel : '(at)', $mailAddress);
+                $spamProtectedMailAddress = str_replace('@', $atLabel ? $atLabel : '(at)', htmlspecialchars($mailAddress));
                 if ($tsfe->config['config']['spamProtectEmailAddresses_lastDotSubst']) {
                     $lastDotLabel = trim($tsfe->config['config']['spamProtectEmailAddresses_lastDotSubst']);
                     $lastDotLabel = $lastDotLabel ? $lastDotLabel : '(dot)';