CVE-2016-5303: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: Cross-site scripting (XSS) vulnerability in the Horde Text Filter API in Horde Groupware and Horde Groupware Webmail Edition before 5.2.16 allows remote attackers to inject arbitrary web script or HTML via crafted data:text/html content in a form (1) action or (2) xlink attribute.
CWE description: The product does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.
CVSS v3 base score: 6.1
Published: 2016-12-20T22:59Z
Repository: https://github.com/horde/horde
Language: PHP
Files changed: framework/Text_Filter/lib/Horde/Text/Filter/Xss.php, framework/Text_Filter/test/Horde/Text/Filter/XssTest.php
Commit date: 2016-09-06 16:11:58 +0200
Commit message: Fix XSS with data:html links and form actions.

The academically correct way to filter out xlink hrefs should have been to use hasAttributeNS/getAttributeNS with the XLink namespace. But from my testing browsers don't care about that namespace at all, and only use the xlink: prefix. This means that even if you correctly specify a different prefix for the XLink NS, the links won't be detected by the browser. xlink: prefixes OTOH even work without specifying the XLink namespace in the XML document.

Reported By: Liuzhu <fantasy7082@hotmail.com>

Diff: diff --git a/framework/Text_Filter/lib/Horde/Text/Filter/Xss.php b/framework/Text_Filter/lib/Horde/Text/Filter/Xss.php
index e8556783d85..419283e06fc 100644
--- a/framework/Text_Filter/lib/Horde/Text/Filter/Xss.php
+++ b/framework/Text_Filter/lib/Horde/Text/Filter/Xss.php
@@ -107,15 +107,23 @@ class Horde_Text_Filter_Xss extends Horde_Text_Filter_Base
                 ? array('style')
                 : array();
 
             switch (Horde_String::lower($node->tagName)) {
             case 'a':
-                /* Strip out data URLs living in an A HREF element
+            case 'form':
+                /* Strip out data URLs living in link-like elements
                  * (Bug #8715). */
-                if ($node->hasAttribute('href') &&
-                    preg_match("/\s*data:/i", $node->getAttribute('href'))) {
-                    $remove[] = 'href';
+                if (Horde_String::lower($node->tagName) == 'form') {
+                    $attributes = array('action');
+                } else {
+                    $attributes = array('href', 'xlink:href');
+                }
+                foreach ($attributes as $attribute) {
+                    if ($node->hasAttribute($attribute) &&
+                        preg_match("/\s*data:/i", $node->getAttribute($attribute))) {
+                        $remove[] = $attribute;
+                    }
                 }
                 break;
 
             case 'applet':
             case 'audio':
diff --git a/framework/Text_Filter/test/Horde/Text/Filter/XssTest.php b/framework/Text_Filter/test/Horde/Text/Filter/XssTest.php
index 088ead9ea05..5093952c2d1 100644
--- a/framework/Text_Filter/test/Horde/Text/Filter/XssTest.php
+++ b/framework/Text_Filter/test/Horde/Text/Filter/XssTest.php
@@ -137,11 +137,14 @@ EOT;
             array('<a href="data:text/html;base64,PGh0bWw+PGhlYWQ+PHRpdGxlPnRlc3Q8L3RpdGxlPjwvaGVhZD48Ym9keT48c2NyaXB0PmFsZXJ0KCd4c3M6ICcgKyBkb2N1bWVudC5jb29raWUpPC9zY3JpcHQ+PC9ib2R5PjwvaHRtbD4=" href="data:text/html;base64,PGh0bWw+PGhlYWQ+PHRpdGxlPnRlc3Q8L3RpdGxlPjwvaGVhZD48Ym9keT48c2NyaXB0PmFsZXJ0KCd4c3M6ICcgKyBkb2N1bWVudC5jb29raWUpPC9zY3JpcHQ+PC9ib2R5PjwvaHRtbD4=">Click me</a>', '<a>Click me</a>'),
             array('<a href="data:text/html;base64,PGh0bWw+PGhlYWQ+PHRpdGxlPnRlc3Q8L3RpdGxlPjwvaGVhZD48Ym9keT48c2NyaXB0PmFsZXJ0KCd4c3M6ICcgKyBkb2N1bWVudC5jb29raWUpPC9zY3JpcHQ+PC9ib2R5PjwvaHRtbD4=">Click me</a>', '<a>Click me</a>'),
             array('<body/onload=alert(/xss/)>', ''),
             array('<img src=""> <BODY ONLOAD="a();"><SCRIPT>function a(){alert(\'XSS\');}</SCRIPT><"" />', '<img src=""/>'),
             array('<img src=\'blank.jpg\'style=\'width:expression(alert("xssed"))\'>', '<img src="blank.jpg"/>'),
-            array($framedata, '')
+            array($framedata, ''),
+            array('<svg><a xlink:href="data:text/html,<script>alert(/XSS/)</script>"><rect width="1000" height="1000" fill="white"/></a></svg>', '<svg><a><rect width="1000" height="1000" fill="white"/></a></svg>'),
+            array('<math><a xlink:href="data:text/html,<script>alert(/XSS/)</script>">click</a></math>', '<math><a>click</a></math>'),
+            array('<form action="data:text/html,<script>alert(/XSS/)</script>"><button></form>', '<form><button/></form>'),
         );
     }
 
     public function testStyleXss()
     {

Vulnerable code:                 /* Strip out data URLs living in an A HREF element
                if ($node->hasAttribute('href') &&
                    preg_match("/\s*data:/i", $node->getAttribute('href'))) {
                    $remove[] = 'href';
            array($framedata, '')
Fixed code:             case 'form':
                /* Strip out data URLs living in link-like elements
                if (Horde_String::lower($node->tagName) == 'form') {
                    $attributes = array('action');
                } else {
                    $attributes = array('href', 'xlink:href');
                }
                foreach ($attributes as $attribute) {
                    if ($node->hasAttribute($attribute) &&
                        preg_match("/\s*data:/i", $node->getAttribute($attribute))) {
                        $remove[] = $attribute;
                    }
            array($framedata, ''),
            array('<svg><a xlink:href="data:text/html,<script>alert(/XSS/)</script>"><rect width="1000" height="1000" fill="white"/></a></svg>', '<svg><a><rect width="1000" height="1000" fill="white"/></a></svg>'),
            array('<math><a xlink:href="data:text/html,<script>alert(/XSS/)</script>">click</a></math>', '<math><a>click</a></math>'),
            array('<form action="data:text/html,<script>alert(/XSS/)</script>"><button></form>', '<form><button/></form>'),