Remote Code Execution (RCE) Exploit on Cross Site Scripting (XSS) Vulnerability
Description
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.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
red-dashboardPyPI | < 0.1.7a | 0.1.7a |
Affected products
1- Range: < 0.1.7a
Patches
2a6b978533800[UI] Fix SelectPicker not rendering properly
1 file changed · +41 −16
reddash/app/home/templates/guild.html+41 −16 modified@@ -930,20 +930,20 @@ <h5>{{ data['message'] }}</h5> img.attr("src", `${img.attr("data-src-url")}png`) } } + + function safe(str) { + return String(str).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"'); + } </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, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"'); - } - // Alias modal $(document).on('click', '.editaliasbutton', function () { var command = $(this).parent().parent().data("command") @@ -1186,28 +1186,35 @@ <h5>{{ data['message'] }}</h5> } 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") @@ -1299,18 +1306,24 @@ <h5>{{ data['message'] }}</h5> $("#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) @@ -1324,18 +1337,23 @@ <h5>{{ data['message'] }}</h5> 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) @@ -1347,6 +1365,9 @@ <h5>{{ data['message'] }}</h5> } 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') }}.") } } @@ -1378,18 +1399,20 @@ <h5>{{ data['message'] }}</h5> $(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() }) @@ -1442,18 +1465,20 @@ <h5>{{ data['message'] }}</h5> $(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() })
99d88b840674Fix unformatted HTML
1 file changed · +6 −2
reddash/app/home/templates/dashboard.html+6 −2 modified@@ -72,6 +72,7 @@ <h1>{{ _('Loading servers...') }}</h1> } else { var base_guild_url = "{{ url_for('home_blueprint.guild', guild='123456789123456789') }}" $("#serverrow").html("") + let counter = 0 for (let g of json.data) { var current_guild_url = base_guild_url.replace("123456789123456789", g.id) $("#serverrow").append(` @@ -80,13 +81,16 @@ <h1>{{ _('Loading servers...') }}</h1> <div class="card h-100" onmouseover="playGif(this)" onmouseout="stopGif(this)"> <img class="card-img-top" src="${g.icon}png" alt="Card image cap" data-src-url="${g.icon}" data-is-animated=${g.animated}> <div class="card-body"> - <h5 class="card-title">${g.name}</h5> - <p class="card-text">Owner: ${g.owner}</p> + <h5 class="card-title" id="guild-counter-${counter}">Loading...</h5> + <p class="card-text" id="owner-counter-${counter}">Owner: Loading...</p> </div> </div> </a> </div> `) + $(`#guild-counter-${counter}`).text(g.name) + $(`#owner-counter-${counter}`).text(g.owner) + counter += 1 } } }
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
7- github.com/advisories/GHSA-hm45-mgqm-gjm4ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2020-26249ghsaADVISORY
- github.com/Cog-Creators/Red-Dashboard/commit/99d88b840674674166ce005b784ae8e31e955ab1ghsax_refsource_MISCWEB
- github.com/Cog-Creators/Red-Dashboard/commit/a6b9785338003ec87fb75305e7d1cc2d40c7ab91ghsax_refsource_MISCWEB
- github.com/Cog-Creators/Red-Dashboard/security/advisories/GHSA-hm45-mgqm-gjm4ghsax_refsource_CONFIRMWEB
- github.com/pypa/advisory-database/tree/main/vulns/red-dashboard/PYSEC-2020-98.yamlghsaWEB
- pypi.org/project/Red-Dashboardghsax_refsource_MISCWEB
News mentions
0No linked articles in our index yet.