Skip to content

Commit 3e8832d

Browse files
committed
Fix cross-site scripting (XSS) via HTML messages with malicious svg/namespace
Credits to SSD Secure Disclosure (https://ssd-disclosure.com/)
1 parent 2531eb7 commit 3e8832d

File tree

3 files changed

+44
-2
lines changed

3 files changed

+44
-2
lines changed

Diff for: CHANGELOG

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ CHANGELOG Roundcube Webmail
77
- Elastic: Fix context menu (paste) on the recipient input (#7431)
88
- Fix problem with forwarding inline images attached to messages with no HTML part (#7414)
99
- Fix problem with handling attached images with same name when using database_attachments/redundant_attachments (#7455)
10+
- Security: Fix cross-site scripting (XSS) via HTML messages with malicious svg/namespace
1011

1112
RELEASE 1.4.6
1213
-------------

Diff for: program/lib/Roundcube/rcube_washtml.php

+5-2
Original file line numberDiff line numberDiff line change
@@ -521,7 +521,10 @@ private function dumpHtml($node, $level = 20)
521521
$xpath = new DOMXPath($node->ownerDocument);
522522
foreach ($xpath->query('namespace::*') as $ns) {
523523
if ($ns->nodeName != 'xmlns:xml') {
524-
$dump .= ' ' . $ns->nodeName . '="' . $ns->nodeValue . '"';
524+
$dump .= sprintf(' %s="%s"',
525+
$ns->nodeName,
526+
htmlspecialchars($ns->nodeValue, ENT_QUOTES, $this->config['charset'])
527+
);
525528
}
526529
}
527530
}
@@ -588,7 +591,7 @@ public function wash($html)
588591
$this->max_nesting_level = (int) @ini_get('xdebug.max_nesting_level');
589592

590593
// SVG need to be parsed as XML
591-
$this->is_xml = stripos($html, '<html') === false && stripos($html, '<svg') !== false;
594+
$this->is_xml = !preg_match('/<(html|head|body)/i', $html) && stripos($html, '<svg') !== false;
592595
$method = $this->is_xml ? 'loadXML' : 'loadHTML';
593596

594597
// DOMDocument does not support HTML5, try Masterminds parser if available

Diff for: tests/Framework/Washtml.php

+38
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,44 @@ function test_wash_svg()
315315
$this->assertSame($washed, $exp, "SVG content");
316316
}
317317

318+
/**
319+
* Test SVG cleanup
320+
*/
321+
function test_wash_svg2()
322+
{
323+
$svg = '<head xmlns="&quot;&gt;&lt;script&gt;alert(document.domain)&lt;/script&gt;"><svg></svg></head>';
324+
$exp = '<!-- html ignored --><!-- head ignored --><svg xmlns="http://www.w3.org/1999/xhtml"></svg>';
325+
326+
$washer = new rcube_washtml;
327+
$washed = $washer->wash($svg);
328+
329+
$this->assertSame($washed, $exp, "SVG content");
330+
331+
$svg = '<head xmlns="&quot; onload=&quot;alert(document.domain)">Hello victim!<svg></svg></head>';
332+
$exp = '<!-- html ignored --><!-- head ignored -->Hello victim!<svg xmlns="http://www.w3.org/1999/xhtml"></svg>';
333+
334+
$washer = new rcube_washtml;
335+
$washed = $washer->wash($svg);
336+
337+
$this->assertSame($washed, $exp, "SVG content");
338+
339+
$svg = '<p>Hello victim!<svg xmlns="&quot; onload=&quot;alert(document.domain)"></svg></p>';
340+
$exp = '<p>Hello victim!<svg /></p>';
341+
342+
$washer = new rcube_washtml;
343+
$washed = $washer->wash($svg);
344+
345+
$this->assertSame($washed, $exp, "SVG content");
346+
347+
$svg = '<svg xmlns="&quot; onload=&quot;alert(document.domain)" />';
348+
$exp = '<svg xmlns="&quot; onload=&quot;alert(document.domain)" />';
349+
350+
$washer = new rcube_washtml;
351+
$washed = $washer->wash($svg);
352+
353+
$this->assertSame($washed, $exp, "SVG content");
354+
}
355+
318356
/**
319357
* Test position:fixed cleanup - (#5264)
320358
*/

0 commit comments

Comments
 (0)