CVE-2020-5223: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: In PrivateBin versions 1.2.0 before 1.2.2, and 1.3.0 before 1.3.2, a persistent XSS attack is possible. Under certain conditions, a user provided attachment file name can inject HTML leading to a persistent Cross-site scripting (XSS) vulnerability. The vulnerability has been fixed in PrivateBin v1.3.2 & v1.2.2. Admins are urged to upgrade to these versions to protect the affected users.
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: 4.4
Published: 2020-01-23T02:15Z
Repository: https://github.com/PrivateBin/PrivateBin
Language: PHP
Files changed: CHANGELOG.md, js/privatebin.js, js/test/AttachmentViewer.js, tpl/bootstrap.php, tpl/page.php
Commit date: 2019-12-25 09:14:32 +0100
Commit message: addressing jsverifyRngState 8b8f0d4ec2a67139b5, fixes HTML injection via filename, closes #554

Diff: diff --git a/CHANGELOG.md b/CHANGELOG.md
index 18d6bf3..c5f49c5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,10 +2,11 @@
 
   * **1.4 (not yet released)**
     * ADDED: Translation for Ukrainian (#533)
     * ADDED: Option to send a mail with the link, when creating a paste (#398)
     * ADDED: Add support for CONFIG_PATH environment variable (#552)
+    * FIXED: HTML injection via unescaped attachment filename (#554)
     * FIXED: Password disabling option (#527)
   * **1.3.1 (2019-09-22)**
     * ADDED: Translation for Bulgarian (#455)
     * CHANGED: Improved mobile UI - obscured send button and hard to click shortener button (#477)
     * CHANGED: Enhanced URL shortener integration (#479)
diff --git a/js/privatebin.js b/js/privatebin.js
index eea8767..dc7adef 100644
--- a/js/privatebin.js
+++ b/js/privatebin.js
@@ -2793,12 +2793,16 @@ jQuery.PrivateBin = (function($, RawDeflate) {
         me.moveAttachmentTo = function($element, label)
         {
             // move elemement to new place
             $attachmentLink.appendTo($element);
 
-            // update text
-            I18n._($attachmentLink, label, $attachmentLink.attr('download'));
+            // update text - ensuring no HTML is inserted into the text node
+            I18n._(
+                $attachmentLink,
+                $('<div />').text(label).html(),
+                $('<div />').text($attachmentLink.attr('download')).html()
+            );
         };
 
         /**
          * read file data as data URL using the FileReader API
          *
diff --git a/js/test/AttachmentViewer.js b/js/test/AttachmentViewer.js
index 438b2f8..e891deb 100644
--- a/js/test/AttachmentViewer.js
+++ b/js/test/AttachmentViewer.js
@@ -23,11 +23,11 @@ describe('AttachmentViewer', function () {
                         mimeType.substring(0, 6) === 'audio/' ||
                         mimeType.substring(0, 6) === 'video/' ||
                         mimeType.match(/\/pdf/i)
                     ),
                     results = [];
-                prefix = prefix.replace(/%(s|d)/g, '%%');
+                prefix  = prefix.replace(/%(s|d)/g, '%%');
                 postfix = postfix.replace(/%(s|d)/g, '%%');
                 $('body').html(
                     '<div id="attachment" role="alert" class="hidden alert ' +
                     'alert-info"><span class="glyphicon glyphicon-download-' +
                     'alt" aria-hidden="true"></span> <a class="alert-link">' +
@@ -88,11 +88,11 @@ describe('AttachmentViewer', function () {
                 $.PrivateBin.AttachmentViewer.moveAttachmentTo(element, prefix + '%s' + postfix);
                 if (filename.length) {
                     results.push(
                         element.children()[0].href === data &&
                         element.children()[0].getAttribute('download') === filename &&
-                        element.children()[0].text === prefix + filename + postfix
+                        element.children()[0].text === $('<div />').text(prefix + filename + postfix).html()
                     );
                 } else {
                     results.push(element.children()[0].href === data);
                 }
                 $.PrivateBin.AttachmentViewer.removeAttachment();
diff --git a/tpl/bootstrap.php b/tpl/bootstrap.php
index 0d095e2..50e3e25 100644
--- a/tpl/bootstrap.php
+++ b/tpl/bootstrap.php
@@ -70,11 +70,11 @@ if ($MARKDOWN):
 <?php
 endif;
 ?>
 		<script type="text/javascript" data-cfasync="false" src="js/purify-2.0.7.js" integrity="sha512-XjNEK1xwh7SJ/7FouwV4VZcGW9cMySL3SwNpXgrURLBcXXQYtZdqhGoNdEwx9vwLvFjUGDQVNgpOrTsXlSTiQg==" crossorigin="anonymous"></script>
 		<script type="text/javascript" data-cfasync="false" src="js/legacy.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-LYos+qXHIRqFf5ZPNphvtTB0cgzHUizu2wwcOwcwz/VIpRv9lpcBgPYz4uq6jx0INwCAj6Fbnl5HoKiLufS2jg==" crossorigin="anonymous"></script>
-		<script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-Bppv5Tt3fBwlJMQaL3ktLhrCNKZQwWo9ox4A/CAXiUs3rQLjfPSuEVViyuYy11e9oOaTKrMb7ejrkwDjhK1NmA==" crossorigin="anonymous"></script>
+		<script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-HxU8g6MFviBCv6fEKEL+bn0fICxg9++ZUxxqySWXkX4iGgLodH/OVZDfr3dgKK5vZAv+0niWLuf+CWZ+/gUwWw==" crossorigin="anonymous"></script>
 		<link rel="apple-touch-icon" href="img/apple-touch-icon.png?<?php echo rawurlencode($VERSION); ?>" sizes="180x180" />
 		<link rel="icon" type="image/png" href="img/favicon-32x32.png?<?php echo rawurlencode($VERSION); ?>" sizes="32x32" />
 		<link rel="icon" type="image/png" href="img/favicon-16x16.png?<?php echo rawurlencode($VERSION); ?>" sizes="16x16" />
 		<link rel="manifest" href="manifest.json?<?php echo rawurlencode($VERSION); ?>" />
 		<link rel="mask-icon" href="img/safari-pinned-tab.svg?<?php echo rawurlencode($VERSION); ?>" color="#ffcc00" />
diff --git a/tpl/page.php b/tpl/page.php
index 5f13e77..dcb939b 100644
--- a/tpl/page.php
+++ b/tpl/page.php
@@ -48,11 +48,11 @@ if ($MARKDOWN):
 <?php
 endif;
 ?>
 		<script type="text/javascript" data-cfasync="false" src="js/purify-2.0.7.js" integrity="sha512-XjNEK1xwh7SJ/7FouwV4VZcGW9cMySL3SwNpXgrURLBcXXQYtZdqhGoNdEwx9vwLvFjUGDQVNgpOrTsXlSTiQg==" crossorigin="anonymous"></script>
 		<script type="text/javascript" data-cfasync="false" src="js/legacy.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-LYos+qXHIRqFf5ZPNphvtTB0cgzHUizu2wwcOwcwz/VIpRv9lpcBgPYz4uq6jx0INwCAj6Fbnl5HoKiLufS2jg==" crossorigin="anonymous"></script>
-		<script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-Bppv5Tt3fBwlJMQaL3ktLhrCNKZQwWo9ox4A/CAXiUs3rQLjfPSuEVViyuYy11e9oOaTKrMb7ejrkwDjhK1NmA==" crossorigin="anonymous"></script>
+		<script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-HxU8g6MFviBCv6fEKEL+bn0fICxg9++ZUxxqySWXkX4iGgLodH/OVZDfr3dgKK5vZAv+0niWLuf+CWZ+/gUwWw==" crossorigin="anonymous"></script>
 		<link rel="apple-touch-icon" href="img/apple-touch-icon.png?<?php echo rawurlencode($VERSION); ?>" sizes="180x180" />
 		<link rel="icon" type="image/png" href="img/favicon-32x32.png?<?php echo rawurlencode($VERSION); ?>" sizes="32x32" />
 		<link rel="icon" type="image/png" href="img/favicon-16x16.png?<?php echo rawurlencode($VERSION); ?>" sizes="16x16" />
 		<link rel="manifest" href="manifest.json?<?php echo rawurlencode($VERSION); ?>" />
 		<link rel="mask-icon" href="img/safari-pinned-tab.svg?<?php echo rawurlencode($VERSION); ?>" color="#ffcc00" />

Vulnerable code:             // update text
            I18n._($attachmentLink, label, $attachmentLink.attr('download'));
                prefix = prefix.replace(/%(s|d)/g, '%%');
                        element.children()[0].text === prefix + filename + postfix
		<script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-Bppv5Tt3fBwlJMQaL3ktLhrCNKZQwWo9ox4A/CAXiUs3rQLjfPSuEVViyuYy11e9oOaTKrMb7ejrkwDjhK1NmA==" crossorigin="anonymous"></script>
		<script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-Bppv5Tt3fBwlJMQaL3ktLhrCNKZQwWo9ox4A/CAXiUs3rQLjfPSuEVViyuYy11e9oOaTKrMb7ejrkwDjhK1NmA==" crossorigin="anonymous"></script>
Fixed code:     * FIXED: HTML injection via unescaped attachment filename (#554)
            // update text - ensuring no HTML is inserted into the text node
            I18n._(
                $attachmentLink,
                $('<div />').text(label).html(),
                $('<div />').text($attachmentLink.attr('download')).html()
            );
                prefix  = prefix.replace(/%(s|d)/g, '%%');
                        element.children()[0].text === $('<div />').text(prefix + filename + postfix).html()
		<script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-HxU8g6MFviBCv6fEKEL+bn0fICxg9++ZUxxqySWXkX4iGgLodH/OVZDfr3dgKK5vZAv+0niWLuf+CWZ+/gUwWw==" crossorigin="anonymous"></script>
		<script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-HxU8g6MFviBCv6fEKEL+bn0fICxg9++ZUxxqySWXkX4iGgLodH/OVZDfr3dgKK5vZAv+0niWLuf+CWZ+/gUwWw==" crossorigin="anonymous"></script>