From c1322466b14bece19158760bceed077074c9cc73 Mon Sep 17 00:00:00 2001 From: Corban-Lee Jones Date: Thu, 12 Sep 2024 00:12:29 +0100 Subject: [PATCH] working on xss mitigation --- apps/static/js/base.js | 18 +++++++ apps/static/js/home/content.js | 25 +++++---- apps/static/js/home/filters.js | 31 ++++++----- apps/static/js/home/index.js | 22 +++++--- apps/static/js/home/servers.js | 25 ++++----- apps/static/js/home/subscriptions.js | 52 +++++++++++-------- apps/templates/home/includes/deletemodal.html | 10 ++-- 7 files changed, 116 insertions(+), 67 deletions(-) diff --git a/apps/static/js/base.js b/apps/static/js/base.js index d44428c..dcfe40c 100644 --- a/apps/static/js/base.js +++ b/apps/static/js/base.js @@ -19,6 +19,24 @@ function getCurrentDateTime() { return `${year}-${month}-${day}T${hours}:${minutes}`; } +// Sanitise a given string to remove HTML, making it DOM safe. +function sanitise(string) { + if (typeof string !== "string") { + return string; + } + + const map = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', + "/": '/', + }; + const reg = /[&<>"'/]/ig; + return string.replace(reg, (match) => map[match]); + } + $(document).ready(function() { // Activate all tooltips $('[data-bs-toggle="tooltip"]').tooltip(); diff --git a/apps/static/js/home/content.js b/apps/static/js/home/content.js index 8ae9538..31c57d5 100644 --- a/apps/static/js/home/content.js +++ b/apps/static/js/home/content.js @@ -46,7 +46,9 @@ async function initContentTable() { data: "title", className: "text-truncate", render: function(data, type, row) { - return `${data}` + const title = sanitise(data); + const url = sanitise(row.url); + return `${title}` } }, { @@ -54,7 +56,8 @@ async function initContentTable() { data: "subscription.name", className: "text-nowrap", render: function(data, type, row) { - return `` + const subName = sanitise(data); + return `` } }, { @@ -70,7 +73,9 @@ async function initContentTable() { data: "channel_id", className: "text-start", render: function(data, type, row) { - return `
+ const channelId = sanitise(data); + const messageId = sanitise(row.message_id); + return `
Loading...
@@ -98,7 +103,8 @@ async function initContentTable() { orderable: false, className: "p-0", render: function(data, type, row) { - return `
 
` + const embedColour = sanitise(row.subscription.embed_colour); + return `
 
` } } ] @@ -166,17 +172,18 @@ async function deleteSelectedContent() { const rows = contentTable.rows(".selected").data().toArray(); const names = rows.map(row => { return row.title }); const namesString = arrayToHtmlList(names, true).prop("outerHTML"); - const multiple = names.length > 1; + const isMany = names.length > 1; - await confirmDeleteModal( - `Confirm ${multiple ? "Multiple Deletions" : "Deletion"}`, - `Do you wish to permanently delete ${multiple ? "these" : "this"} ${names.length} content${multiple ? "s" : ""}?

${namesString}`, + await confirmationModal( + `Delete ${isMany ? "Many Tracked Contents" : "a Tracked Content"}`, + `Do you wish to permanently delete ${isMany ? "these" : "this"} ${names.length} Tracked Content${isMany ? "s" : ""}?

${namesString}`, + "danger", async () => { rows.forEach(async row => { await deleteTrackedContent(row.id) }); showToast( "danger", - `Deleted ${names.length} Content${multiple ? "s" : ""}`, + `Deleted ${names.length} Content${isMany ? "s" : ""}`, `${arrayToHtmlList(names, false).prop("outerHTML")}`, 12000 ); diff --git a/apps/static/js/home/filters.js b/apps/static/js/home/filters.js index b28191d..c5ca0f1 100644 --- a/apps/static/js/home/filters.js +++ b/apps/static/js/home/filters.js @@ -40,7 +40,8 @@ async function initFiltersTable() { title: "Name", data: "name", render: function(data, type, row) { - return `` + const name = sanitise(data); + return `` } }, { @@ -55,7 +56,7 @@ async function initFiltersTable() { case 5: return "Fuzzy Match"; default: console.error(`unknown matching algorithm '${data}'`); - return data; + return sanitise(data); } } }, @@ -252,12 +253,14 @@ $(document).on("selectedServerChange", async function() { $("#deleteEditFilter").on("click", async function() { const filterId = parseInt($("#filterId").val()); const filter = filtersTable.row(function(idx, row) { return row.id === filterId }).data(); + const filterName = sanitise(filter.name); $("#filterFormModal").modal("hide"); - await confirmDeleteModal( - "Confirm Deletion", - `Do you wish to permanently delete ${filter.name}?`, // FIX: potential xss attack + await confirmationModal( + "Delete a Filter", + `Do you wish to permanently delete ${filterName}?`, + "danger", async () => { await deleteFilter(filterId); await loadFilters(getCurrentlyActiveServer().guild_id); @@ -265,7 +268,7 @@ $("#deleteEditFilter").on("click", async function() { showToast( "danger", "Deleted a Filter", - filter.name, // FIX: potential xss attack + filterName, 12000 ); }, @@ -279,27 +282,29 @@ async function deleteSelectedFilters() { const rows = filtersTable.rows(".selected").data().toArray(); const names = rows.map(row => row.name); const namesString = arrayToHtmlList(names, true).prop("outerHTML"); - const multiple = names.length > 1; + const isMany = names.length > 1; - await confirmDeleteModal( - `Confirm ${multiple ? "Multiple Deletions" : "Deletion"}`, - `Do you wish to permanently delete ${multiple ? "these" : "this"} ${names.length} filter${multiple ? "s" : ""}?

${namesString}`, + await confirmationModal( + `Delete ${isMany ? "Many Filters" : "a Filter"}`, + `Do you wish to permanently delete ${isMany ? "these" : "this"} ${names.length} filter${isMany ? "s" : ""}?

${namesString}`, + "danger", async () => { rows.forEach(async row => { await deleteFilter(row.id) }); - + showToast( "danger", - `Delete ${names.length} Subscription${multiple ? "s" : ""}`, + `Delete ${names.length} Subscription${isMany ? "s" : ""}`, `${arrayToHtmlList(names, false).prop("outerHTML")}`, 12000 ); - + // Multi-deletion can take time, this timeout ensures the refresh is accurate setTimeout(async () => { await loadFilters(getCurrentlyActiveServer().guild_id); }, 600); }, null + ); } diff --git a/apps/static/js/home/index.js b/apps/static/js/home/index.js index 8e00c97..4a6f07f 100644 --- a/apps/static/js/home/index.js +++ b/apps/static/js/home/index.js @@ -155,23 +155,33 @@ $(document).ready(function() { }); }); -async function confirmDeleteModal(title, description, acceptFunc, declineFunc) { - let $modal = $("#confirmDeleteModal"); +async function confirmationModal(title, bodyText, style, acceptFunc, declineFunc) { + let $modal = $("#confirmationModal"); + + // Ensure valid style and apply it to the confirm button + if (!["danger", "success", "warning", "info", "primary", "secondary"].includes(style)) { + throw new Error(`${style} is not a valid style`); + } + $modal.find(".modal-confirm-btn").addClass(`btn-${style}`); + $modal.find(".modal-title").text(title); - $modal.find(".modal-body > p").html(description); - $modal.find(".confirm-delete-btn").off("click").on("click", async function(e) { + $modal.find(".modal-body > p").html(bodyText); + + $modal.find(".modal-confirm-btn").off("click").on("click", async function(e) { await acceptFunc() $modal.modal("hide"); }); - $modal.find(".dismiss-delete-btn").off("click").on("click", async function(e) { + + $modal.find(".modal-dismiss-btn").off("click").on("click", async function(e) { if (declineFunc) await declineFunc(); $modal.modal("hide"); }); + $modal.modal("show"); } function arrayToHtmlList(array, bold=false) { - $ul = $("
    "); + $ul = $("
      ").addClass("mb-0"); array.forEach(item => { let $li = $("
    • "); diff --git a/apps/static/js/home/servers.js b/apps/static/js/home/servers.js index 3c18363..2312e4d 100644 --- a/apps/static/js/home/servers.js +++ b/apps/static/js/home/servers.js @@ -35,7 +35,7 @@ function addToLoadedServers(server, selectNew=true) { loadedServers[id] = server; // Display the loaded server - addServerTemplate(id, server.guild_id, server.name, server.icon, server.permissions, server.owner); + addServerTemplate(id, sanitise(server.guild_id), sanitise(server.name), sanitise(server.icon), sanitise(server.permissions), sanitise(server.owner)); // Select the newly added server if (selectNew) { @@ -90,10 +90,10 @@ async function loadServerOptions() { servers.forEach(server => { $("#serverOptions").append($("