CVE-2016-15008: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: A vulnerability was found in oxguy3 coebot-www and classified as problematic. This issue affects the function displayChannelCommands/displayChannelQuotes/displayChannelAutoreplies/showChannelHighlights/showChannelBoir of the file js/channel.js. The manipulation leads to cross site scripting. The attack may be initiated remotely. The patch is named c1a6c44092585da4236237e0e7da94ee2996a0ca. It is recommended to apply a patch to fix this issue. The associated identifier of this vulnerability is VDB-217355.
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: 2023-01-04T10:15Z
Repository: https://github.com/oxguy3/coebot-www
Language: JavaScript
Files changed: js/channel.js, js/common.js
Commit date: 2016-07-13 23:08:13 -0400
Commit message: fixed XSS injection vulns on channel.php

Diff: diff --git a/js/channel.js b/js/channel.js
index d92b7f6..9ac6133 100644
--- a/js/channel.js
+++ b/js/channel.js
@@ -162,11 +162,11 @@ function displayChannelCommands() {
 		var cmd = channelData.commands[i];
 		var row = '<tr class="row-command row-command-access-' + cmd.restriction +'">';
         row += '<td class="js-commands-editcolumn"><span class="table-edit-btn" data-toggle="modal" data-target="#commandAddModal" data-command="' + cmd.key + '" data-accesslevel="' + cmd.restriction + '" data-response="' + cleanHtmlAttr(cmd.value) + '" data-modaltitle="Edit command"><i class="icon-pencil"></i><span class="sr-only">Edit</span></span></td>';
 		row += '<td><kbd class="command">' + cmd.key + '</kbd></td>';
         row += '<td class="row-command-col-access" data-order="' + cmd.restriction + '">' + prettifyAccessLevel(cmd.restriction) + '</td>';
-        row += '<td class="should-be-linkified should-be-emotified">' + prettifyStringVariables(cmd.value) + '</td>';
+        row += '<td class="should-be-linkified should-be-emotified">' + prettifyStringVariables(cleanHtmlText(cmd.value)) + '</td>';
 		row += '<td>' + Humanize.intComma(cmd.count) + '</td>';
 		row += '</tr>';
 		rows += row;
 	}
     if (rows == "") {
@@ -259,11 +259,11 @@ function displayChannelQuotes() {
 		var quote = channelData.quotes[i];
 		var row = '<tr>';
         row += '<td class="js-quotes-editcolumn"><span class="table-edit-btn" data-toggle="modal" data-target="#quoteAddModal" data-quote="' + cleanHtmlAttr(quote.quote) + '" data-quoteid="' + (i+1) + '" data-modaltitle="Edit quote"><i class="icon-pencil"></i></span></td>';
 
 		row += '<td>' + (i+1) + '</td>';
-        row += '<td>' + quote.quote + '</td>';
+        row += '<td>' + cleanHtmlText(quote.quote) + '</td>';
 
         var tsMoment = (quote.timestamp !== null) ? moment(quote.timestamp) : null;
         var tsStr = (quote.timestamp !== null) ? tsMoment.calendar() : "Unknown";
         var cleanTs = (quote.timestamp !== null) ? cleanHtmlAttr(tsMoment.format('LLLL')) : "This quote was added before CoeBot kept track of when quotes were added.";
         var sortNum = (quote.timestamp !== null) ? quote.timestamp : 0;
@@ -318,11 +318,11 @@ function displayChannelAutoreplies() {
         var reply = channelData.autoReplies[i];
         var row = '<tr>';
         row += '<td class="js-autoreplies-editcolumn"><span class="table-edit-btn" data-toggle="modal" data-target="#autoreplyAddModal" data-trigger="' + cleanHtmlAttr(reply.trigger) + '" data-response="' + cleanHtmlAttr(reply.response) + '" data-arid="' + (i+1) + '" data-modaltitle="Edit auto-reply"><i class="icon-pencil"></i></span></td>';
         row += '<td>' + (i+1) + '</td>';
         row += '<td title="RegEx: ' + cleanHtmlAttr(reply.trigger) + '">' + prettifyRegex(reply.trigger) + '</td>';
-        row += '<td>' + prettifyStringVariables(reply.response) + '</td>';
+        row += '<td>' + prettifyStringVariables(cleanHtmlText(reply.response)) + '</td>';
         row += '</tr>';
         rows += row;
     }
     if (rows == "") {
         rows = '<tr><td colspan="4" class="text-center">' + EMPTY_TABLE_PLACEHOLDER + '</td></tr>';
@@ -547,11 +547,11 @@ function showChannelHighlights() {
     var shouldSortTable = true;
     for (var i = 0; i < highlightsStats.streams.length; i++) {
         var strm = highlightsStats.streams[i];
         var row = '<tr>';
 
-        row += '<td><span class="fake-link js-highlight-btn" data-hlid="' + strm.id + '">' + strm.title + '</span></td>';
+        row += '<td><span class="fake-link js-highlight-btn" data-hlid="' + strm.id + '">' + cleanHtmlText(strm.title) + '</span></td>';
 
         var startMoment = moment.unix(strm.start);
         var cleanStart = cleanHtmlAttr(startMoment.format('LLLL'));
         row += '<td title="' + cleanStart + '" data-order="' + strm.start + '">' + startMoment.calendar() + '</td>';
 
@@ -732,13 +732,13 @@ function loadChannelBoir() {
 function showChannelBoir() {
 
     var boirContainer = $('.js-boir-container');
     var html = "";
 
-    html += '<div class="boir-character"><strong>Character:</strong> ' + channelBoirData.character + "</div>";
-    html += '<div class="boir-floor"><strong>Floor:</strong> ' + channelBoirData.floor + "</div>";
-    html += '<div class="boir-seed"><strong>Seed:</strong> ' + channelBoirData.seed + "</div>";
+    html += '<div class="boir-character"><strong>Character:</strong> ' + cleanHtmlText(channelBoirData.character) + "</div>";
+    html += '<div class="boir-floor"><strong>Floor:</strong> ' + cleanHtmlText(channelBoirData.floor) + "</div>";
+    html += '<div class="boir-seed"><strong>Seed:</strong> ' + cleanHtmlText(channelBoirData.seed) + "</div>";
     html += '<h3>Items</h3>';
 
     html += '<div class="well boir-items items-container">';//<div class="row">';
     for (var i = 0; i < channelBoirData.items.length; i++) {
         var item = channelBoirData.items[i];
@@ -752,32 +752,32 @@ function showChannelBoir() {
     html += '<p class="small">Item data from <a href="http://platinumgod.co.uk/rebirth" target="_blank">platinumgod.co.uk</a></em></p>'
 
     if (typeof channelBoirData.flyItems !== 'undefined' && typeof channelBoirData.flyProgress !== 'undefined') {
         html += '<div class="col-md-6 text-center">';
         html += '<h3>Lord of the Flies</h3>';
-        html += '<input type="text" class="dial js-boir-dial js-boir-dial-fly" value="' + channelBoirData.flyProgress + '">';
+        html += '<input type="text" class="dial js-boir-dial js-boir-dial-fly" value="' + cleanHtmlAttr(channelBoirData.flyProgress) + '">';
 
         // html += '<h4>Items</h4>';
         html += '<div class="row"><div class="col-sm-8 col-sm-offset-2"><ul class="list-group">'
         for (var i = 0; i < channelBoirData.flyItems.length; i++) {
             var item = channelBoirData.flyItems[i];
-            html += '<li class="list-group-item">' + item + '</li>';
+            html += '<li class="list-group-item">' + cleanHtmlText(item) + '</li>';
         }
         html += '</ul></div></div>';
         html += '</div>';
     }
 
     if (typeof channelBoirData.guppyItems !== 'undefined' && typeof channelBoirData.guppyProgress !== 'undefined') {
         html += '<div class="col-md-6 text-center">';
         html += '<h3>Guppy</h3>';
-        html += '<input type="text" class="dial js-boir-dial js-boir-dial-guppy" value="' + channelBoirData.guppyProgress + '">';
+        html += '<input type="text" class="dial js-boir-dial js-boir-dial-guppy" value="' + cleanHtmlAttr(channelBoirData.guppyProgress) + '">';
 
         // html += '<h4>Items</h4>';
         html += '<div class="row"><div class="col-sm-8 col-sm-offset-2"><ul class="list-group">'
         for (var i = 0; i < channelBoirData.guppyItems.length; i++) {
             var item = channelBoirData.guppyItems[i];
-            html += '<li class="list-group-item">' + item + '</li>';
+            html += '<li class="list-group-item">' + cleanHtmlText(item) + '</li>';
         }
         html += '</ul></div></div>';
     }
         html += '</div>';
 
@@ -963,10 +963,11 @@ function displayChannelReqsongs() {
 
 
 }
 
 function updateReqsongs() {
+	return true; //temporary disable
     $.ajax({
         data: {
             a: "listReqsong",
             channel: channel
         },
@@ -1211,11 +1212,11 @@ $(document).ready(function() {
     });
 
     checkIfLiveChannel();
     setInterval(checkIfLiveChannel, 30000);
 
-    $(".command").prepend('<span class="command-prefix">' + channelData.commandPrefix + '</span>');
+    $(".command").prepend('<span class="command-prefix">' + cleanHtmlText(channelData.commandPrefix) + '</span>');
 
 
     var commandPrefixForUrl = channelData.commandPrefix;
 
     if (commandPrefixForUrl == "+") {
diff --git a/js/common.js b/js/common.js
index a007520..a2b3de8 100644
--- a/js/common.js
+++ b/js/common.js
@@ -22,10 +22,24 @@ function prettifyRegex(pattern) {
     pattern = pattern.replace(/(\\Q|\\E|\\b)/g, "");
     pattern = pattern.replace(/\\s/g, " ");
     return pattern;
 }
 
+var cleanHtmlTextMap = {
+   "&": "&amp;",
+   "<": "&lt;",
+   ">": "&gt;",
+   '"': '&quot;',
+   "'": '&#39;',
+   "/": '&#x2F;'
+ };
+
+ function cleanHtmlText(string) {
+    return String(string).replace(/[&<>"'\/]/g, function (s) {
+        return cleanHtmlTextMap[s];
+    });
+ }
 function cleanHtmlAttr(val) {
     return val.replace(/"/g, "&quot;");
 }
 
 // channels.json json file
@@ -162,13 +176,13 @@ function updateIsLive(streams, channels) {
 
         // get the hover text. if we're live, it's fancier
         var popover = isLiveTitles[liveStatus];
         if (liveStatus == isLiveOn) {
             popover = '<div class="islive-popover">';
-            
+
             popover += '<img src="'+stream.preview.medium+'" class="img-responsive" height="180" width="320">';
-            
+
             popover += '<i class="icon-gamepad"></i> ' + ((stream.channel.game) ? stream.channel.game : "Unknown") + '<br>';
             popover += '<i class="icon-user"></i> ' + Humanize.intComma(stream.viewers) + '';
             popover += '</div>';
 
             current.attr("data-title", (stream.channel.status) ? stream.channel.status : "Untitled Broadcast");
@@ -207,6 +221,6 @@ function sortUnorderedList(selector) {
        var compA = $(a).text().toUpperCase();
        var compB = $(b).text().toUpperCase();
        return (compA < compB) ? -1 : (compA > compB) ? 1 : 0;
     })
     $.each(listitems, function(idx, itm) { mylist.append(itm); })
-}
\ No newline at end of file
+}

Vulnerable code:         row += '<td class="should-be-linkified should-be-emotified">' + prettifyStringVariables(cmd.value) + '</td>';
        row += '<td>' + quote.quote + '</td>';
        row += '<td>' + prettifyStringVariables(reply.response) + '</td>';
        row += '<td><span class="fake-link js-highlight-btn" data-hlid="' + strm.id + '">' + strm.title + '</span></td>';
    html += '<div class="boir-character"><strong>Character:</strong> ' + channelBoirData.character + "</div>";
    html += '<div class="boir-floor"><strong>Floor:</strong> ' + channelBoirData.floor + "</div>";
    html += '<div class="boir-seed"><strong>Seed:</strong> ' + channelBoirData.seed + "</div>";
        html += '<input type="text" class="dial js-boir-dial js-boir-dial-fly" value="' + channelBoirData.flyProgress + '">';
            html += '<li class="list-group-item">' + item + '</li>';
        html += '<input type="text" class="dial js-boir-dial js-boir-dial-guppy" value="' + channelBoirData.guppyProgress + '">';
            html += '<li class="list-group-item">' + item + '</li>';
    $(".command").prepend('<span class="command-prefix">' + channelData.commandPrefix + '</span>');
            
            
}
Fixed code:         row += '<td class="should-be-linkified should-be-emotified">' + prettifyStringVariables(cleanHtmlText(cmd.value)) + '</td>';
        row += '<td>' + cleanHtmlText(quote.quote) + '</td>';
        row += '<td>' + prettifyStringVariables(cleanHtmlText(reply.response)) + '</td>';
        row += '<td><span class="fake-link js-highlight-btn" data-hlid="' + strm.id + '">' + cleanHtmlText(strm.title) + '</span></td>';
    html += '<div class="boir-character"><strong>Character:</strong> ' + cleanHtmlText(channelBoirData.character) + "</div>";
    html += '<div class="boir-floor"><strong>Floor:</strong> ' + cleanHtmlText(channelBoirData.floor) + "</div>";
    html += '<div class="boir-seed"><strong>Seed:</strong> ' + cleanHtmlText(channelBoirData.seed) + "</div>";
        html += '<input type="text" class="dial js-boir-dial js-boir-dial-fly" value="' + cleanHtmlAttr(channelBoirData.flyProgress) + '">';
            html += '<li class="list-group-item">' + cleanHtmlText(item) + '</li>';
        html += '<input type="text" class="dial js-boir-dial js-boir-dial-guppy" value="' + cleanHtmlAttr(channelBoirData.guppyProgress) + '">';
            html += '<li class="list-group-item">' + cleanHtmlText(item) + '</li>';
	return true; //temporary disable
    $(".command").prepend('<span class="command-prefix">' + cleanHtmlText(channelData.commandPrefix) + '</span>');
var cleanHtmlTextMap = {
   "&": "&amp;",
   "<": "&lt;",
   ">": "&gt;",
   '"': '&quot;',
   "'": '&#39;',
   "/": '&#x2F;'
 };

 function cleanHtmlText(string) {
    return String(string).replace(/[&<>"'\/]/g, function (s) {
        return cleanHtmlTextMap[s];
    });
 }


}