CVE-2023-5631: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: 
Roundcube before 1.4.15, 1.5.x before 1.5.5, and 1.6.x before 1.6.4 allows stored XSS via an HTML e-mail message with a crafted SVG document because of program/lib/Roundcube/rcube_washtml.php behavior. This could allow a remote attacker

to load arbitrary JavaScript code.








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: 5.4
Published: 2023-10-18T15:15Z
Repository: https://github.com/roundcube/roundcubemail
Language: PHP
Files changed: CHANGELOG.md, program/lib/Roundcube/rcube_washtml.php, tests/Framework/Washtml.php
Commit date: 2023-10-14 18:16:22 +0200
Commit message: Fix cross-site scripting (XSS) vulnerability in handling of SVG in HTML messages (#9168)

Diff: diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9c458403bd..7b737aa726 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -26,10 +26,11 @@
 - Password: Allow LDAP access using LDAP URI and SASL binding (#8402)
 - Fix potential HTTP protocol version mismatch (#8982)
 - Fix PHP8 warnings (#9142, #9160)
 - Fix default 'mime.types' path on Windows (#9113)
 - Managesieve: Fix javascript error when relational or spamtest extension is not enabled (#9139)
+- Fix cross-site scripting (XSS) vulnerability in handling of SVG in HTML messages (#9168)
 
 ## Release 1.6.3
 
 - Fix bug where installto.sh/update.sh scripts were removing some essential options from the config file (#9051)
 - Update jQuery-UI to version 1.13.2 (#9041)
diff --git a/program/lib/Roundcube/rcube_washtml.php b/program/lib/Roundcube/rcube_washtml.php
index 14464fcd4a..74d455ec99 100644
--- a/program/lib/Roundcube/rcube_washtml.php
+++ b/program/lib/Roundcube/rcube_washtml.php
@@ -426,20 +426,21 @@ class rcube_washtml
             if ($is_image && !empty($this->config['blocked_src'])) {
                 return $this->config['blocked_src'];
             }
         }
         else if ($is_image && preg_match('/^data:image\/([^,]+),(.+)$/is', $uri, $matches)) { // RFC2397
+            $type = preg_replace('/\s/', '', $matches[1]);
+
             // svg images can be insecure, we'll sanitize them
-            if (stripos($matches[1], 'svg') !== false) {
+            if (stripos($type, 'svg') !== false) {
                 $svg = $matches[2];
 
-                if (stripos($matches[1], ';base64') !== false) {
-                    $svg  = base64_decode($svg);
-                    $type = $matches[1];
+                if (stripos($type, ';base64') !== false) {
+                    $svg = base64_decode($svg);
                 }
                 else {
-                    $type = $matches[1] . ';base64';
+                    $type .= ';base64';
                 }
 
                 $washer = new self($this->config);
                 $svg    = $washer->wash($svg);
 
diff --git a/tests/Framework/Washtml.php b/tests/Framework/Washtml.php
index b49007fdeb..f9757f3871 100644
--- a/tests/Framework/Washtml.php
+++ b/tests/Framework/Washtml.php
@@ -453,10 +453,28 @@ class Framework_Washtml extends PHPUnit\Framework\TestCase
             ],
             [
                 '<svg><script href="data:text/javascript,alert(1)" /><text x="20" y="20">XSS</text></svg>',
                 '<svg><text x="20" y="20">XSS</text></svg>'
             ],
+            [
+                '<html><svg><use href="data:image/s vg+xml;base64,' // space
+                    . 'PHN2ZyBpZD0ieCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4gPGltYWdlIGhy'
+                    . 'ZWY9IngiIG9uZXJyb3I9ImFsZXJ0KCcxJykiLz48L3N2Zz4=#x"></svg></html>',
+                '<svg><use x-washed="href"></use></svg>'
+            ],
+            [
+                '<html><svg><use href="data:image/s' . "\n" . 'vg+xml;base64,' // new-line
+                    . 'PHN2ZyBpZD0ieCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4gPGltYWdlIGhy'
+                    . 'ZWY9IngiIG9uZXJyb3I9ImFsZXJ0KCcxJykiLz48L3N2Zz4=#x"></svg></html>',
+                '<svg><use x-washed="href"></use></svg>'
+            ],
+            [
+                '<html><svg><use href="data:image/s	vg+xml;base64,' // tab
+                    . 'PHN2ZyBpZD0ieCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4gPGltYWdlIGhy'
+                    . 'ZWY9IngiIG9uZXJyb3I9ImFsZXJ0KCcxJykiLz48L3N2Zz4=#x"></svg></html>',
+                '<svg><use x-washed="href"></use></svg>'
+            ],
         ];
     }
 
     /**
      * Test SVG cleanup

Vulnerable code:             if (stripos($matches[1], 'svg') !== false) {
                if (stripos($matches[1], ';base64') !== false) {
                    $svg  = base64_decode($svg);
                    $type = $matches[1];
                    $type = $matches[1] . ';base64';
Fixed code: - Fix cross-site scripting (XSS) vulnerability in handling of SVG in HTML messages (#9168)
            $type = preg_replace('/\s/', '', $matches[1]);

            if (stripos($type, 'svg') !== false) {
                if (stripos($type, ';base64') !== false) {
                    $svg = base64_decode($svg);
                    $type .= ';base64';
            [
                '<html><svg><use href="data:image/s vg+xml;base64,' // space
                    . 'PHN2ZyBpZD0ieCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4gPGltYWdlIGhy'
                    . 'ZWY9IngiIG9uZXJyb3I9ImFsZXJ0KCcxJykiLz48L3N2Zz4=#x"></svg></html>',
                '<svg><use x-washed="href"></use></svg>'
            ],
            [
                '<html><svg><use href="data:image/s' . "\n" . 'vg+xml;base64,' // new-line
                    . 'PHN2ZyBpZD0ieCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4gPGltYWdlIGhy'
                    . 'ZWY9IngiIG9uZXJyb3I9ImFsZXJ0KCcxJykiLz48L3N2Zz4=#x"></svg></html>',
                '<svg><use x-washed="href"></use></svg>'
            ],
            [
                '<html><svg><use href="data:image/s	vg+xml;base64,' // tab
                    . 'PHN2ZyBpZD0ieCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4gPGltYWdlIGhy'
                    . 'ZWY9IngiIG9uZXJyb3I9ImFsZXJ0KCcxJykiLz48L3N2Zz4=#x"></svg></html>',
                '<svg><use x-washed="href"></use></svg>'
            ],