CVE-2017-2661: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: ClusterLabs pcs before version 0.9.157 is vulnerable to a cross-site scripting vulnerability due to improper validation of Node name field when creating new cluster or adding existing cluster.
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: 2018-03-12T15:29Z
Repository: https://github.com/ClusterLabs/pcs
Language: JavaScript
Files changed: CHANGELOG.md, pcsd/public/js/nodes-ember.js, pcsd/public/js/pcsd.js
Commit date: 2017-03-21 14:26:32 +0100
Commit message: web UI: fixed XSS vulnerability

Diff: diff --git a/CHANGELOG.md b/CHANGELOG.md
index 58a94fdd..a00c1056 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -23,18 +23,22 @@
   with another node
 - Stopping a cluster does not timeout too early and it generally works better
   even if the cluster is running Virtual IP resources ([rhbz#1334429])
 - `pcs booth remove` now works correctly even if the booth resource group is
   disabled (another fix) ([rhbz#1389941])
+- Fixed Cross-site scripting (XSS) vulnerability in web UI ([CVE-2017-2661],
+  [rhbz#1434111])
 
+[CVE-2017-2661]: https://access.redhat.com/security/cve/CVE-2017-2661
 [rhbz#1303969]: https://bugzilla.redhat.com/show_bug.cgi?id=1303969
 [rhbz#1315627]: https://bugzilla.redhat.com/show_bug.cgi?id=1315627
 [rhbz#1334429]: https://bugzilla.redhat.com/show_bug.cgi?id=1334429
 [rhbz#1362493]: https://bugzilla.redhat.com/show_bug.cgi?id=1362493
 [rhbz#1378742]: https://bugzilla.redhat.com/show_bug.cgi?id=1378742
 [rhbz#1389941]: https://bugzilla.redhat.com/show_bug.cgi?id=1389941
 [rhbz#1421702]: https://bugzilla.redhat.com/show_bug.cgi?id=1421702
+[rhbz#1434111]: https://bugzilla.redhat.com/show_bug.cgi?id=1434111
 
 
 ## [0.9.156] - 2017-02-10
 
 ### Added
diff --git a/pcsd/public/js/nodes-ember.js b/pcsd/public/js/nodes-ember.js
index cb2f4d16..41fede89 100644
--- a/pcsd/public/js/nodes-ember.js
+++ b/pcsd/public/js/nodes-ember.js
@@ -73,11 +73,11 @@ Pcs = Ember.Application.createWithMixins({
     var out =
       '<table class="darkdatatable"><tr><th>OPTION</th><th>VALUE</th></tr>\n';
     var banned_options = ["SBD_OPTS", "SBD_WATCHDOG_DEV", "SBD_PACEMAKER"];
     $.each(this.get("sbd_config"), function(opt, val) {
       if (banned_options.indexOf(opt) == -1) {
-        out += '<tr><td>' + opt + '</td><td>' + val + '</td></tr>\n';
+        out += '<tr><td>' + htmlEncode(opt) + '</td><td>' + htmlEncode(val) + '</td></tr>\n';
       }
     });
     return out + '</table>';
   }.property("sbd_config"),
 
@@ -877,11 +877,11 @@ Pcs.ResourceObj = Ember.Object.extend({
     );
     return "color: " + color + ((color != "green")? "; font-weight: bold;" : "");
   }.property("status_val"),
   show_status: function() {
     return '<span style="' + this.get('status_style') + '">'
-      + this.get('status') + (this.get("is_unmanaged") ? " (unmanaged)" : "")
+      + htmlEncode(this.get('status')) + (this.get("is_unmanaged") ? " (unmanaged)" : "")
       + '</span>';
   }.property("status_style", "disabled"),
   status_class: function() {
     if (
       this.get("status_val") == get_status_value("ok") ||
diff --git a/pcsd/public/js/pcsd.js b/pcsd/public/js/pcsd.js
index c96f9baa..6f57de0f 100644
--- a/pcsd/public/js/pcsd.js
+++ b/pcsd/public/js/pcsd.js
@@ -819,11 +819,11 @@ function auth_nodes_dialog(unauth_nodes, callback_success, callback_success_one)
     dialog_obj.find("#pass_for_all").hide();
   }
 
   dialog_obj.find('#auth_nodes_list').empty();
   unauth_nodes.forEach(function(node) {
-    dialog_obj.find('#auth_nodes_list').append("\t\t\t<tr><td>" + node + '</td><td><input type="password" name="' + node + '-pass"></td></tr>\n');
+    dialog_obj.find('#auth_nodes_list').append("\t\t\t<tr><td>" + htmlEncode(node) + '</td><td><input type="password" name="' + htmlEncode(node) + '-pass"></td></tr>\n');
   });
 
 }
 
 function add_existing_dialog() {

Vulnerable code:         out += '<tr><td>' + opt + '</td><td>' + val + '</td></tr>\n';
      + this.get('status') + (this.get("is_unmanaged") ? " (unmanaged)" : "")
    dialog_obj.find('#auth_nodes_list').append("\t\t\t<tr><td>" + node + '</td><td><input type="password" name="' + node + '-pass"></td></tr>\n');
Fixed code: - Fixed Cross-site scripting (XSS) vulnerability in web UI ([CVE-2017-2661],
  [rhbz#1434111])
[CVE-2017-2661]: https://access.redhat.com/security/cve/CVE-2017-2661
[rhbz#1434111]: https://bugzilla.redhat.com/show_bug.cgi?id=1434111
        out += '<tr><td>' + htmlEncode(opt) + '</td><td>' + htmlEncode(val) + '</td></tr>\n';
      + htmlEncode(this.get('status')) + (this.get("is_unmanaged") ? " (unmanaged)" : "")
    dialog_obj.find('#auth_nodes_list').append("\t\t\t<tr><td>" + htmlEncode(node) + '</td><td><input type="password" name="' + htmlEncode(node) + '-pass"></td></tr>\n');