CVE-2020-26249: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: Red Discord Bot Dashboard is an easy-to-use interactive web dashboard to control your Redbot. In Red Discord Bot before version 0.1.7a an RCE exploit has been discovered. This exploit allows Discord users with specially crafted Server names and Usernames/Nicknames to inject code into the webserver front-end code. By abusing this exploit, it's possible to perform destructive actions and/or access sensitive information. This high severity exploit has been fixed on version 0.1.7a. There are no workarounds, bot owners must upgrade their relevant packages (Dashboard module and Dashboard webserver) in order to patch this issue.
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: 8.7
Published: 2020-12-09T00:15Z
Repository: https://github.com/Cog-Creators/Red-Dashboard
Language: HTML
Files changed: reddash/app/home/templates/guild.html
Commit date: 2020-12-01 14:15:49 +0000
Commit message: [UI] Fix SelectPicker not rendering properly

Diff: diff --git a/reddash/app/home/templates/guild.html b/reddash/app/home/templates/guild.html
index ac6d206..ef1aed5 100755
--- a/reddash/app/home/templates/guild.html
+++ b/reddash/app/home/templates/guild.html
@@ -928,24 +928,24 @@
         var img = $(elm)
         if (img.attr('data-is-animated') === "True") {
             img.attr("src", `${img.attr("data-src-url")}png`)
         }
     }
+
+    function safe(str) {
+        return String(str).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
+    }
 </script>
 
 {% if data['status'] == 1 and data['data']['status'] == 1 %}
 
-{% if 'aliascc' in data['data']['permslist'] %}
+{% if 'aliascc' in data['data']['permslist'] and false%}
 <script>
     /* ---------------------------------------------------------------------------------------------------------------------
                                                         Aliases group
        --------------------------------------------------------------------------------------------------------------------- */
 
-    function safe(str) {
-        return String(str).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
-    }
-
     // Alias modal
     $(document).on('click', '.editaliasbutton', function () {
         var command = $(this).parent().parent().data("command")
         var textarea = $("#aliasModalCommand")
         textarea.text(command)
@@ -1184,32 +1184,39 @@
             if (json.status === 0) {
                 $("#targetstatus").html(`{{ _('Failed to fetch targets') }}: ${json.message}`)
             } else if (json.status === 1 && json.data.status === 0) {
                 $("#targetstatus").html(`{{ _('Failed to fetch targets') }}: ${json.data.message}`)
             } else {
+                let big_ol_dict = {}
                 select.html("")
 
                 var chopt = [`<optgroup label="{{ _('Channels') }}">`]
                 for (let [id, name] of json.data.CHANNELS) {
-                    chopt.push(`<option value=${id}>${name}</option>`)
+                    chopt.push(`<option value=${id} class="selectpicker-element-${id}">Loading...</option>`)
+                    big_ol_dict[id] = name
                 }
                 chopt.push("</optgroup>")
                 select.append(chopt.join(""))
 
                 var ropt = [`<optgroup label="{{ _('Roles') }}">`]
                 for (let [id, name] of json.data.ROLES) {
-                    ropt.push(`<option value=${id}>${name}</option>`)
+                    ropt.push(`<option value=${id} class="selectpicker-element-${id}">Loading...</option>`)
+                    big_ol_dict[id] = name
                 }
                 ropt.push("</optgroup>")
                 select.append(ropt.join(""))
 
                 var uopt = [`<optgroup label="{{ _('Users') }}">`]
                 for (let [id, name] of json.data.USERS) {
-                    uopt.push(`<option value=${id}>${name}</option>`)
+                    uopt.push(`<option value=${id} class="selectpicker-element-${id}">Loading...</option>`)
+                    big_ol_dict[id] = name
                 }
                 uopt.push("</optgroup>")
                 select.append(uopt.join(""))
+                for (let [id, name] of Object.entries(big_ol_dict)) {
+                    $(`.selectpicker-element-${id}`).text(name)
+                }
             }
             select.selectpicker({ title: "{{ _('Choose target') }}" })
             select.removeAttr("disabled")
             select.selectpicker("refresh")
         }
@@ -1297,22 +1304,28 @@
                 $("#fetchrulesstatus").html(`{{ _('Failed to fetch rules') }}: ${json.data.message}`)
             } else {
                 $("#rulesdiv").html("")
                 var overall = ['<h3 style="margin-bottom: 10px">{{ _("Cog rules") }}</h3>']
                 var allcoglines = ["<ul>"]
+
+                let big_ol_dict_two = {}
+                let cog_counter = 0
+
                 for (let [cog, rules] of Object.entries(json.data.COG)) {
                     var coglines = []
                     for (let rule of rules) {
                         if (rule.type === "Default") {
                             coglines.unshift(`<li>{{ _('By default, users are') }} ${rule.permission} {{ _('permission to use the') }} <code>${cog}</code> {{ _('cog') }}.</li>`)
                         } else if (rule.type === "Role") {
-                            coglines.push(`<li>{{ _('Users with the') }} <code>${rule.name}</code> {{ _('role') }} (${rule.id}) {{ _('are') }} ${rule.permission} {{ _('permission to use the') }} <code>${cog}</code> {{ _('cog') }}.</li>`)
+                            coglines.push(`<li>{{ _('Users with the') }} <code id="cog-rules-${cog_counter}">Loading...</code> {{ _('role') }} (${rule.id}) {{ _('are') }} ${rule.permission} {{ _('permission to use the') }} <code>${cog}</code> {{ _('cog') }}.</li>`)
                         } else if (rule.type === "Channel") {
-                            coglines.push(`<li>{{ _('Users in the') }} <code>${rule.name}</code> {{ _('channel') }} (${rule.id}) {{ _('are') }} ${rule.permission} {{ _('permission to use the') }} <code>${cog}</code> {{ _('cog') }}.</li>`)
+                            coglines.push(`<li>{{ _('Users in the') }} <code id="cog-rules-${cog_counter}">Loading...</code> {{ _('channel') }} (${rule.id}) {{ _('are') }} ${rule.permission} {{ _('permission to use the') }} <code>${cog}</code> {{ _('cog') }}.</li>`)
                         } else {
-                            coglines.push(`<li>{{ _('User') }} <code>${rule.name}</code> (${rule.id}) {{ _('is') }} ${rule.permission} {{ _('permission to use the') }} <code>${cog}</code> {{ _('cog') }}.</li>`)
+                            coglines.push(`<li>{{ _('User') }} <code id="cog-rules-${cog_counter}">Loading...</code> (${rule.id}) {{ _('is') }} ${rule.permission} {{ _('permission to use the') }} <code>${cog}</code> {{ _('cog') }}.</li>`)
                         }
+                        big_ol_dict_two[`cog-rules-${cog_counter}`] = rule.name
+                        cog_counter += 1
                     }
                     if (coglines) {
                         allcoglines = allcoglines.concat(coglines)
                     }
                 }
@@ -1322,22 +1335,27 @@
                 }
                 overall = overall.concat(allcoglines)
 
                 overall.push('<h3 style="margin-bottom: 10px">{{ _("Command rules") }}</h3>')
                 var allcmdlines = ["<ul>"]
+
+                let cmd_counter = 0
+
                 for (let [cmd, rules] of Object.entries(json.data.COMMAND)) {
                     var cmdlines = []
                     for (let rule of rules) {
                         if (rule.type === "Default") {
                             cmdlines.unshift(`<li>{{ _('By default, users are') }} ${rule.permission} {{ _('permission to use the') }} <code>${cmd}</code> {{ _('command') }}.</li>`)
                         } else if (rule.type === "Role") {
-                            cmdlines.push(`<li>{{ _('Users with the') }} <code>${rule.name}</code> {{ _('role') }} (${rule.id}) {{ _('are') }} ${rule.permission} {{ _('permission to use the') }} <code>${cmd}</code> {{ _('command') }}.</li>`)
+                            cmdlines.push(`<li>{{ _('Users with the') }} <code id="cmd-rules-${cmd_counter}">Loading...</code> {{ _('role') }} (${rule.id}) {{ _('are') }} ${rule.permission} {{ _('permission to use the') }} <code>${cmd}</code> {{ _('command') }}.</li>`)
                         } else if (rule.type === "Channel") {
-                            cmdlines.push(`<li>{{ _('Users in the') }} <code>${rule.name}</code> {{ _('channel') }} (${rule.id}) {{ _('are') }} ${rule.permission} {{ _('permission to use the') }} <code>${cmd}</code> {{ _('command') }}.</li>`)
+                            cmdlines.push(`<li>{{ _('Users in the') }} <code id="cmd-rules-${cmd_counter}">Loading...</code> {{ _('channel') }} (${rule.id}) {{ _('are') }} ${rule.permission} {{ _('permission to use the') }} <code>${cmd}</code> {{ _('command') }}.</li>`)
                         } else {
-                            cmdlines.push(`<li>{{ _('User') }} <code>${rule.name}</code> (${rule.id}) {{ _('is') }} ${rule.permission} {{ _('permission to use the') }} <code>${cmd}</code> {{ _('command') }}.</li>`)
+                            cmdlines.push(`<li>{{ _('User') }} <code id="cmd-rules-${cmd_counter}">Loading...</code> (${rule.id}) {{ _('is') }} ${rule.permission} {{ _('permission to use the') }} <code>${cmd}</code> {{ _('command') }}.</li>`)
                         }
+                        big_ol_dict_two[`cmd-rules-${cmd_counter}`] = rule.name
+                        cmd_counter += 1
                     }
                     if (cmdlines) {
                         allcmdlines = allcmdlines.concat(cmdlines)
                     }
                 }
@@ -1345,10 +1363,13 @@
                 if (allcmdlines.length === 2) {
                     allcmdlines = ["<p>{{ _('No command rules have been created') }}.</p>"]
                 }
                 overall = overall.concat(allcmdlines)
                 $("#rulesdiv").html(overall.join(""))
+                for (let [id, name] of Object.entries(big_ol_dict_two)) {
+                    $(`#${id}`).text(name)
+                }
                 $("#fetchrulesstatus").html("{{ _('Refreshed rules') }}.")
             }
         }
         try {
             xhr.send()
@@ -1376,22 +1397,24 @@
         })
     })
 
     $(document).on('click', '.adminroleoption', function () {
         var elm = $(this)
+        let random_number = Math.floor(Math.random() * Math.floor(100000))
         $("#adminrolelist").append(`
                 <li>
                     <div class="row">
                         <div class="col-md-10 col-8">
-                            <input class="form-control adminroleinput" value="${elm.text()}" disabled=True data-id="${elm.attr("data-id")}">
+                            <input class="form-control adminroleinput" value="Loading..." disabled=True data-id="${elm.attr("data-id")}" id="admin-role-${random_number}">
                         </div>
                         <div class="col-md-1 col-1">
                             <span class="admin-role-x clickable"><i class="tim-icons icon-simple-remove" style="float: right; margin-top: 10px;"></i></span>
                         </div>
                     </div>
                 </li>
             `)
+        $(`#admin-role-${random_number}`).val(elm.text())
         elm.remove()
     })
 
     $("#adminroleupdate").click(function () {
         $("#adminrolestatus").html("{{ _('Loading...') }}")
@@ -1440,22 +1463,24 @@
         })
     })
 
     $(document).on('click', '.modroleoption', function () {
         var elm = $(this)
+        let random_number = Math.floor(Math.random() * Math.floor(100000))
         $("#modrolelist").append(`
                 <li>
                     <div class="row">
                         <div class="col-md-10 col-8">
-                            <input class="form-control modroleinput" value="${elm.text()}" disabled=True data-id="${elm.attr("data-id")}">
+                            <input class="form-control modroleinput" value="Loading..." disabled=True data-id="${elm.attr("data-id")}" id="mod-role-${random_number}">
                         </div>
                         <div class="col-md-1 col-1">
                             <span class="mod-role-x clickable"><i class="tim-icons icon-simple-remove" style="float: right; margin-top: 10px;"></i></span>
                         </div>
                     </div>
                 </li>
             `)
+        $(`#mod-role-${random_number}`).val(elm.text())
         elm.remove()
     })
 
     $("#modroleupdate").click(function () {
         $("#modrolestatus").html("{{ _('Loading...') }}")

Vulnerable code: {% if 'aliascc' in data['data']['permslist'] %}
    function safe(str) {
        return String(str).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
    }

                    chopt.push(`<option value=${id}>${name}</option>`)
                    ropt.push(`<option value=${id}>${name}</option>`)
                    uopt.push(`<option value=${id}>${name}</option>`)
                            coglines.push(`<li>{{ _('Users with the') }} <code>${rule.name}</code> {{ _('role') }} (${rule.id}) {{ _('are') }} ${rule.permission} {{ _('permission to use the') }} <code>${cog}</code> {{ _('cog') }}.</li>`)
                            coglines.push(`<li>{{ _('Users in the') }} <code>${rule.name}</code> {{ _('channel') }} (${rule.id}) {{ _('are') }} ${rule.permission} {{ _('permission to use the') }} <code>${cog}</code> {{ _('cog') }}.</li>`)
                            coglines.push(`<li>{{ _('User') }} <code>${rule.name}</code> (${rule.id}) {{ _('is') }} ${rule.permission} {{ _('permission to use the') }} <code>${cog}</code> {{ _('cog') }}.</li>`)
                            cmdlines.push(`<li>{{ _('Users with the') }} <code>${rule.name}</code> {{ _('role') }} (${rule.id}) {{ _('are') }} ${rule.permission} {{ _('permission to use the') }} <code>${cmd}</code> {{ _('command') }}.</li>`)
                            cmdlines.push(`<li>{{ _('Users in the') }} <code>${rule.name}</code> {{ _('channel') }} (${rule.id}) {{ _('are') }} ${rule.permission} {{ _('permission to use the') }} <code>${cmd}</code> {{ _('command') }}.</li>`)
                            cmdlines.push(`<li>{{ _('User') }} <code>${rule.name}</code> (${rule.id}) {{ _('is') }} ${rule.permission} {{ _('permission to use the') }} <code>${cmd}</code> {{ _('command') }}.</li>`)
                            <input class="form-control adminroleinput" value="${elm.text()}" disabled=True data-id="${elm.attr("data-id")}">
                            <input class="form-control modroleinput" value="${elm.text()}" disabled=True data-id="${elm.attr("data-id")}">
Fixed code: 
    function safe(str) {
        return String(str).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
    }
{% if 'aliascc' in data['data']['permslist'] and false%}
                let big_ol_dict = {}
                    chopt.push(`<option value=${id} class="selectpicker-element-${id}">Loading...</option>`)
                    big_ol_dict[id] = name
                    ropt.push(`<option value=${id} class="selectpicker-element-${id}">Loading...</option>`)
                    big_ol_dict[id] = name
                    uopt.push(`<option value=${id} class="selectpicker-element-${id}">Loading...</option>`)
                    big_ol_dict[id] = name
                for (let [id, name] of Object.entries(big_ol_dict)) {
                    $(`.selectpicker-element-${id}`).text(name)
                }

                let big_ol_dict_two = {}
                let cog_counter = 0

                            coglines.push(`<li>{{ _('Users with the') }} <code id="cog-rules-${cog_counter}">Loading...</code> {{ _('role') }} (${rule.id}) {{ _('are') }} ${rule.permission} {{ _('permission to use the') }} <code>${cog}</code> {{ _('cog') }}.</li>`)
                            coglines.push(`<li>{{ _('Users in the') }} <code id="cog-rules-${cog_counter}">Loading...</code> {{ _('channel') }} (${rule.id}) {{ _('are') }} ${rule.permission} {{ _('permission to use the') }} <code>${cog}</code> {{ _('cog') }}.</li>`)
                            coglines.push(`<li>{{ _('User') }} <code id="cog-rules-${cog_counter}">Loading...</code> (${rule.id}) {{ _('is') }} ${rule.permission} {{ _('permission to use the') }} <code>${cog}</code> {{ _('cog') }}.</li>`)
                        big_ol_dict_two[`cog-rules-${cog_counter}`] = rule.name
                        cog_counter += 1

                let cmd_counter = 0

                            cmdlines.push(`<li>{{ _('Users with the') }} <code id="cmd-rules-${cmd_counter}">Loading...</code> {{ _('role') }} (${rule.id}) {{ _('are') }} ${rule.permission} {{ _('permission to use the') }} <code>${cmd}</code> {{ _('command') }}.</li>`)
                            cmdlines.push(`<li>{{ _('Users in the') }} <code id="cmd-rules-${cmd_counter}">Loading...</code> {{ _('channel') }} (${rule.id}) {{ _('are') }} ${rule.permission} {{ _('permission to use the') }} <code>${cmd}</code> {{ _('command') }}.</li>`)
                            cmdlines.push(`<li>{{ _('User') }} <code id="cmd-rules-${cmd_counter}">Loading...</code> (${rule.id}) {{ _('is') }} ${rule.permission} {{ _('permission to use the') }} <code>${cmd}</code> {{ _('command') }}.</li>`)
                        big_ol_dict_two[`cmd-rules-${cmd_counter}`] = rule.name
                        cmd_counter += 1
                for (let [id, name] of Object.entries(big_ol_dict_two)) {
                    $(`#${id}`).text(name)
                }
        let random_number = Math.floor(Math.random() * Math.floor(100000))
                            <input class="form-control adminroleinput" value="Loading..." disabled=True data-id="${elm.attr("data-id")}" id="admin-role-${random_number}">
        $(`#admin-role-${random_number}`).val(elm.text())
        let random_number = Math.floor(Math.random() * Math.floor(100000))
                            <input class="form-control modroleinput" value="Loading..." disabled=True data-id="${elm.attr("data-id")}" id="mod-role-${random_number}">
        $(`#mod-role-${random_number}`).val(elm.text())