From db6ff6d6e39bdc7aabf0505d2b688d45f515c6d6 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sun, 1 Feb 2026 23:40:36 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=9B=A1=EF=B8=8F=20Sentinel:=20[HIGH]=20Fi?= =?UTF-8?q?x=20Stored=20XSS=20in=20Devices,=20Users=20and=20RMCs=20dashboa?= =?UTF-8?q?rds?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refactored the dynamic table construction in Devices, Users, and RMCs dashboards to use safe jQuery methods. - Replaced `.innerHTML` usage with jQuery element creation and `.text()` for all user-controllable data. - Replaced inline string-based `onclick` attributes with jQuery event handlers. - Added a global `escapeHTML` utility in `account.js` for manual sanitization where needed. - Safely constructed popover content using jQuery and `.text()`. This fix mitigates a critical Stored XSS vulnerability where malicious data from infected hosts or other users could execute scripts in the context of the RMS dashboard. Co-authored-by: richkmeli <7313162+richkmeli@users.noreply.github.com> --- .jules/sentinel.md | 4 + src/main/resources/static/js/rms/account.js | 10 ++ src/main/resources/static/js/rms/devices.js | 118 ++++++++++++-------- src/main/resources/static/js/rms/rmcs.js | 45 ++++---- src/main/resources/static/js/rms/users.js | 72 +++++++----- 5 files changed, 150 insertions(+), 99 deletions(-) create mode 100644 .jules/sentinel.md diff --git a/.jules/sentinel.md b/.jules/sentinel.md new file mode 100644 index 0000000..f7628a9 --- /dev/null +++ b/.jules/sentinel.md @@ -0,0 +1,4 @@ +## 2026-02-01 - Recurring Stored XSS in Frontend +**Vulnerability:** Widespread Stored XSS across multiple dashboard pages (Devices, Users, RMCs) due to direct use of `.innerHTML` with unsanitized data from the server. +**Learning:** The application follows a pattern of building table rows as large HTML strings and injecting them into the DOM. This is particularly dangerous for a C2 server where data often originates from untrusted "infected" hosts. +**Prevention:** Always escape dynamic content before inserting it into HTML. The most robust approach in this codebase is to use jQuery's safe DOM construction methods (e.g., `.text()`) and event handlers instead of string-based `innerHTML` and `onclick` attributes. diff --git a/src/main/resources/static/js/rms/account.js b/src/main/resources/static/js/rms/account.js index 067ac48..29df492 100644 --- a/src/main/resources/static/js/rms/account.js +++ b/src/main/resources/static/js/rms/account.js @@ -1,3 +1,13 @@ +function escapeHTML(str) { + if (str === null || str === undefined) return ""; + return str.toString() + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """) + .replace(/'/g, "'"); +} + $(document).ready(function () { $("#logoutNavBar").hide(); diff --git a/src/main/resources/static/js/rms/devices.js b/src/main/resources/static/js/rms/devices.js index b07ca80..a04017c 100644 --- a/src/main/resources/static/js/rms/devices.js +++ b/src/main/resources/static/js/rms/devices.js @@ -43,53 +43,75 @@ function loadDevicesJSONtoTable(devicesListJSON) { console.log("loadDevicesJSONtoTable"); createDevicesTableHeader(); - let devicesList = JSON.parse(devicesListJSON);//jQuery.parseJSON(devicesListJSON); - let tbody = document.createElement("tbody"); - - console.log("devicesList: " + devicesList + " type: " + typeof (devicesList) + " length: " + devicesList.length); - - for (var i = 0; i < devicesList.length; ++i) { - - console.log(devicesList[i]) - - var name = devicesList[i].name; - var IP = devicesList[i].ip; - var serverPort = devicesList[i].serverPort; - var lastConnection = devicesList[i].lastConnection; - var encryptionKey = devicesList[i].encryptionKey; - var associatedUser = devicesList[i].associatedUserEmail; - var commands = devicesList[i].commands; - var commandsOutput = devicesList[i].commandsOutput; - var installationId = devicesList[i].installationId; - var location = devicesList[i].locationAsPosition; - var deviceInfo = devicesList[i].deviceInfoDevName; - - var timeSinceNow = timeSince(new Date(Number(lastConnection))) + " ago"; - - var row = document.createElement("tr"); - row.id = "tableRow" + i; - row.innerHTML = ( - "" + - "" + - "

IP: " + IP + "

" + - "

Port: " + serverPort + "

" + - "

Encryption Key: " + encryptionKey + "

" + - "\"> " + name + "
" + - "" + - "" + timeSinceNow + "" + - "" + associatedUser + "" + - "" + commands + "" + - "" + commandsOutput + "" + - "" + - " " + - " " + - "" + - ""); - - tbody.appendChild(row); + let devicesList = JSON.parse(devicesListJSON); + let $tbody = $(""); + + for (let i = 0; i < devicesList.length; ++i) { + let device = devicesList[i]; + let name = device.name; + let timeSinceNow = timeSince(new Date(Number(device.lastConnection))) + " ago"; + + let $row = $("").attr("id", "tableRow" + i); + + // Name cell with popover + let $popoverDiv = $("
"); + $popoverDiv.append($("

").text("IP: " + device.ip)); + $popoverDiv.append($("

").text("Port: " + device.serverPort)); + $popoverDiv.append($("

").text("Encryption Key: " + device.encryptionKey)); + + let $nameA = $("", { + tabindex: "0", + type: "button", + "class": "btn btn-outline-info", + "data-html": "true", + "data-toggle": "popover", + title: name + " Info", + "data-content": $popoverDiv.html(), + text: name + }); + $row.append($("").append($nameA)); + + // Other cells + $row.append($("").text(timeSinceNow)); + $row.append($("").text(device.associatedUserEmail)); + $row.append($("").text(device.commands)); + $row.append($("").text(device.commandsOutput)); + + // Actions + let $actionsCell = $(""); + + $actionsCell.append($(""); - - // ""); - - tbody.appendChild(row); - // index++ + $row.append($actionsCell); + $tbody.append($row); } - rmcsTable.appendChild(tbody); + $("#rmcsTable").append($tbody); } function deleteRmc(associatedUser, indexTableRow, rmcId) { diff --git a/src/main/resources/static/js/rms/users.js b/src/main/resources/static/js/rms/users.js index 954d1de..3ca21dd 100644 --- a/src/main/resources/static/js/rms/users.js +++ b/src/main/resources/static/js/rms/users.js @@ -93,32 +93,54 @@ function createUsersTableHeader() { function loadUsersJSONtoTable(usersListJSON) { createUsersTableHeader(); - var usersList = JSON.parse(usersListJSON)//jQuery.parseJSON(usersListJSON); - - var tbody = document.createElement("tbody"); - //var index = 0; - //while (usersList[index] != null) { - //for(var device in usersList){ - for (var i = 0; i < usersList.length; ++i) { - var email = usersList[i].email - var password = usersList[i].password - var admin = usersList[i].admin - - var row = document.createElement("tr"); - row.id = "tableRow" + i; - row.innerHTML = ( - //"" + (index + 1) + "" + - "" + email + "" + - "Show Password" + - //"" + - //"" + password + "" + - "" + admin + "" + - "" + - ""); - - tbody.appendChild(row); + let usersList = JSON.parse(usersListJSON); + let $tbody = $(""); + + for (let i = 0; i < usersList.length; ++i) { + let user = usersList[i]; + let email = user.email; + let password = user.password; + let admin = user.admin; + + let $row = $("").attr("id", "tableRow" + i); + + $row.append($("").text(email)); + + $row.append($("").append($("", { + type: "button", + "class": "btn btn-outline-secondary", + "data-toggle": "popover", + tabindex: "0", + title: "Password", + "data-content": password, + text: "Show Password" + }))); + + $row.append($("").text(admin)); + + let $actionsCell = $(""); + $actionsCell.append($("