From 638f4f92177bfd3c495dfffb256e4a603b2df92d Mon Sep 17 00:00:00 2001 From: patrykb0802 Date: Wed, 20 Aug 2025 16:45:49 +0200 Subject: [PATCH 01/53] #3167 Fixed escaping fields --- WebContent/WEB-INF/jsp/compoundEvents.jsp | 6 +++--- WebContent/WEB-INF/jsp/eventHandlers.jsp | 2 +- WebContent/WEB-INF/jsp/include/staticEditor.jsp | 2 +- WebContent/WEB-INF/jsp/mailingLists.jsp | 4 ++-- WebContent/WEB-INF/jsp/maintenanceEvents.jsp | 2 +- WebContent/WEB-INF/jsp/pointEdit/pointProperties.jsp | 4 ++-- WebContent/WEB-INF/jsp/pointHierarchySLTS.jsp | 7 ++++--- WebContent/WEB-INF/jsp/publisherList.jsp | 2 +- WebContent/WEB-INF/jsp/scheduledEvents.jsp | 2 +- WebContent/WEB-INF/jsp/usersProfiles.jsp | 2 +- WebContent/WEB-INF/jsp/watchList.jsp | 6 +++--- WebContent/resources/common.js | 1 - WebContent/resources/emailRecipients.js | 4 ++-- 13 files changed, 22 insertions(+), 22 deletions(-) diff --git a/WebContent/WEB-INF/jsp/compoundEvents.jsp b/WebContent/WEB-INF/jsp/compoundEvents.jsp index 3e86e391d5..f518d11dc9 100644 --- a/WebContent/WEB-INF/jsp/compoundEvents.jsp +++ b/WebContent/WEB-INF/jsp/compoundEvents.jsp @@ -38,7 +38,7 @@ var pointRoot = dojo.widget.manager.getWidgetById('rootPoint'); for (i=0; i "+ dp.name}); + pointNode = dojo.widget.createWidget("TreeNode", {title: " "+ escapeHtml(dp.name)}); pointRoot.addChild(pointNode); for (j=0; j "+ eventType.description +" ("+ eventType.eventDetectorKey +")", + title: " " + escapeHtml(eventType.description) + " (" + escapeHtml(eventType.eventDetectorKey) + ")", widgetId: widgetId, object: eventType.eventDetectorKey }); @@ -157,7 +157,7 @@ } function updateCompoundEvent(ced) { - $("ced"+ ced.id +"Name").innerHTML = ced.name; + $("ced"+ ced.id +"Name").textContent = ced.name; setCompoundEventImg(ced.disabled, $("ced"+ ced.id +"Img")); } diff --git a/WebContent/WEB-INF/jsp/eventHandlers.jsp b/WebContent/WEB-INF/jsp/eventHandlers.jsp index 5866e498ab..57551be424 100644 --- a/WebContent/WEB-INF/jsp/eventHandlers.jsp +++ b/WebContent/WEB-INF/jsp/eventHandlers.jsp @@ -228,7 +228,7 @@ img = "images/cog_process.png"; var node = dojo.widget.createWidget("TreeNode", { - title: " "+ handler.message +"", + title: " "+ escapeHtml(handler.message) +"", widgetId: "h"+ handler.id, object: handler }); diff --git a/WebContent/WEB-INF/jsp/include/staticEditor.jsp b/WebContent/WEB-INF/jsp/include/staticEditor.jsp index 65e2797840..328b6d8f76 100644 --- a/WebContent/WEB-INF/jsp/include/staticEditor.jsp +++ b/WebContent/WEB-INF/jsp/include/staticEditor.jsp @@ -228,7 +228,7 @@ break; case 'link': ViewDwr.saveLinkComponent(staticEditor.componentId, - $get("linkText"), $get("linkLink"), + escapeHtml($get("linkText")), $get("linkLink"), posX, posY, viewId, function(response) { if (response.hasMessages) diff --git a/WebContent/WEB-INF/jsp/mailingLists.jsp b/WebContent/WEB-INF/jsp/mailingLists.jsp index 43a52be8c6..1d5e7fe32f 100644 --- a/WebContent/WEB-INF/jsp/mailingLists.jsp +++ b/WebContent/WEB-INF/jsp/mailingLists.jsp @@ -164,7 +164,7 @@ } function updateMailingList(ml) { - $set("ml"+ ml.id +"Name", ml.name); + $set("ml"+ ml.id +"Name", escapeHtml(ml.name)); } function createUserEntry() { @@ -232,7 +232,7 @@ function appendAddressEntry(addressEntry) { var content = createFromTemplate("mleAddress_TEMPLATE_", addressEntry.referenceId, "mailingListEntriesTable"); - $("mle"+ addressEntry.referenceId +"Address").innerHTML = addressEntry.referenceAddress; + $("mle"+ addressEntry.referenceId +"Address").textContent = addressEntry.referenceAddress; } function deleteAddressEntry(entryId) { diff --git a/WebContent/WEB-INF/jsp/maintenanceEvents.jsp b/WebContent/WEB-INF/jsp/maintenanceEvents.jsp index 2a99708b8c..587a122f96 100644 --- a/WebContent/WEB-INF/jsp/maintenanceEvents.jsp +++ b/WebContent/WEB-INF/jsp/maintenanceEvents.jsp @@ -156,7 +156,7 @@ } function updateMaintenanceEvent(me) { - $("me"+ me.id +"Name").innerHTML = me.description; + $("me"+ me.id +"Name").textContent = me.description; if (me.disabled) updateImg("me"+ me.id +"Img", "images/hammer_disabled.png", "", true); else diff --git a/WebContent/WEB-INF/jsp/pointEdit/pointProperties.jsp b/WebContent/WEB-INF/jsp/pointEdit/pointProperties.jsp index 1b0d4cc20d..605ec0caa5 100644 --- a/WebContent/WEB-INF/jsp/pointEdit/pointProperties.jsp +++ b/WebContent/WEB-INF/jsp/pointEdit/pointProperties.jsp @@ -53,7 +53,7 @@
- " title=""/> +
@@ -62,7 +62,7 @@ - "/> + diff --git a/WebContent/WEB-INF/jsp/pointHierarchySLTS.jsp b/WebContent/WEB-INF/jsp/pointHierarchySLTS.jsp index c0a5ac24bf..3e28722429 100644 --- a/WebContent/WEB-INF/jsp/pointHierarchySLTS.jsp +++ b/WebContent/WEB-INF/jsp/pointHierarchySLTS.jsp @@ -817,12 +817,13 @@ var messages = { $button.disable(); $button.spin(); dialog.setClosable(false); - $.ajax({ + const rawInput = dialog.getModalBody().find('input').val(); + $.ajax({ type: "POST", dataType: "json", - url:myLocation+"pointHierarchy/new/0/"+dialog.getModalBody().find('input').val(), + url:myLocation+"pointHierarchy/new/0/"+encodeURIComponent(rawInput), success: function(msg){ - var titleNewNode = dialog.getModalBody().find('input').val(); + var titleNewNode = escapeHtml(rawInput); dialog.getModalBody().html('

'+messages.folder+':

  • '+messages.key+':'+msg+'
  • '+messages.title+':'+titleNewNode+'
'); $button.hide(); $button.stopSpin(); diff --git a/WebContent/WEB-INF/jsp/publisherList.jsp b/WebContent/WEB-INF/jsp/publisherList.jsp index 43246b24db..abe5e1f425 100644 --- a/WebContent/WEB-INF/jsp/publisherList.jsp +++ b/WebContent/WEB-INF/jsp/publisherList.jsp @@ -34,7 +34,7 @@ dwr.util.removeAllRows("publisherList"); dwr.util.addRows("publisherList", publishers, [ - function(p) { return ""+ p.name +""; }, + function(p) { return ""+ escapeHtml(p.name) +""; }, function(p) { return p.typeMessage; }, function(p) { return p.configDescription; }, function(p) { diff --git a/WebContent/WEB-INF/jsp/scheduledEvents.jsp b/WebContent/WEB-INF/jsp/scheduledEvents.jsp index 11f3da220a..2a2f0f121c 100644 --- a/WebContent/WEB-INF/jsp/scheduledEvents.jsp +++ b/WebContent/WEB-INF/jsp/scheduledEvents.jsp @@ -150,7 +150,7 @@ } function updateScheduledEvent(se) { - $("se"+ se.id +"Name").innerHTML = se.description; + $("se"+ se.id +"Name").textContent = se.description; setScheduledEventImg(se.disabled, $("se"+ se.id +"Img")); } diff --git a/WebContent/WEB-INF/jsp/usersProfiles.jsp b/WebContent/WEB-INF/jsp/usersProfiles.jsp index 6515924741..e4da905b3f 100644 --- a/WebContent/WEB-INF/jsp/usersProfiles.jsp +++ b/WebContent/WEB-INF/jsp/usersProfiles.jsp @@ -288,7 +288,7 @@ } function updateUserProfile(userProfile) { - $("u"+ userProfile.id +"UserProfileName").innerHTML = userProfile.name; + $("u"+ userProfile.id +"UserProfileName").textContent = userProfile.name; setUserImg(true, userProfile.disabled, $("u"+ userProfile.id +"Img")); console.log("u"+ editingUserProfileId +"Img") } diff --git a/WebContent/WEB-INF/jsp/watchList.jsp b/WebContent/WEB-INF/jsp/watchList.jsp index 2ad9d93b81..c2b7128138 100644 --- a/WebContent/WEB-INF/jsp/watchList.jsp +++ b/WebContent/WEB-INF/jsp/watchList.jsp @@ -127,7 +127,7 @@ function addFolder(folder, parent) { var folderNode = dojo.widget.createWidget("TreeNode", { - title: " "+ folder.name, + title: " "+ escapeHtml(folder.name), isFolder: "true", lazyLoadData: folder }); @@ -658,10 +658,10 @@ - - ${sst:escapeLessThan(wl.value)} + ${sst:escapeLessThan(wl.value)} diff --git a/WebContent/resources/common.js b/WebContent/resources/common.js index 49dba3ca4b..f5d60b5cc3 100644 --- a/WebContent/resources/common.js +++ b/WebContent/resources/common.js @@ -1320,7 +1320,6 @@ function unescapeHtml(value) { function escapeHtml(value) { let div = document.createElement("div"); div.textContent = value; - div.innerText = value; return div.innerHTML; } diff --git a/WebContent/resources/emailRecipients.js b/WebContent/resources/emailRecipients.js index 370d530cac..a0355b243d 100644 --- a/WebContent/resources/emailRecipients.js +++ b/WebContent/resources/emailRecipients.js @@ -180,7 +180,7 @@ mango.erecip.EmailRecipients = function(prefix, testEmailMessage, mailingLists, else if (id.startsWith("A")) list[list.length] = { recipientType: 3, // EmailRecipient.TYPE_ADDRESS - referenceAddress: $(this.prefix + id +"Description").innerHTML}; + referenceAddress: $(this.prefix + id +"Description").textContent}; else dojo.debug("Unknown recipient mango id: "+ id); } @@ -215,7 +215,7 @@ mango.erecip.EmailRecipients = function(prefix, testEmailMessage, mailingLists, this.addListEntry = function(id, imgName, description) { createFromTemplate(this.prefix +"_TEMPLATE_", id, this.prefix +"List"); $(this.prefix + id +"Img").src = imgName; - $(this.prefix + id +"Description").innerHTML = description; + $(this.prefix + id +"Description").textContent = description; } this.getMailingList = function(id) { From 553d46b64319dd68aec97d276c5359fa6d3a30b1 Mon Sep 17 00:00:00 2001 From: patrykb0802 Date: Tue, 9 Sep 2025 14:51:48 +0200 Subject: [PATCH 02/53] #3167 Fixed escaping fields --- WebContent/WEB-INF/jsp/include/staticEditor.jsp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WebContent/WEB-INF/jsp/include/staticEditor.jsp b/WebContent/WEB-INF/jsp/include/staticEditor.jsp index 328b6d8f76..65e2797840 100644 --- a/WebContent/WEB-INF/jsp/include/staticEditor.jsp +++ b/WebContent/WEB-INF/jsp/include/staticEditor.jsp @@ -228,7 +228,7 @@ break; case 'link': ViewDwr.saveLinkComponent(staticEditor.componentId, - escapeHtml($get("linkText")), $get("linkLink"), + $get("linkText"), $get("linkLink"), posX, posY, viewId, function(response) { if (response.hasMessages) From b91323c2492a03f90568423df74621730d7038d9 Mon Sep 17 00:00:00 2001 From: patrykb0802 Date: Fri, 19 Sep 2025 12:56:29 +0200 Subject: [PATCH 03/53] PR #3188: revert client-side escapes; rely on DWR converters (compoundEvents.jsp, eventHandlers.jsp, mailingLists.jsp, emailRecipients.js, common.js) --- WebContent/WEB-INF/jsp/compoundEvents.jsp | 6 +++--- WebContent/WEB-INF/jsp/eventHandlers.jsp | 2 +- WebContent/WEB-INF/jsp/mailingLists.jsp | 4 ++-- WebContent/resources/common.js | 1 + WebContent/resources/emailRecipients.js | 4 ++-- 5 files changed, 9 insertions(+), 8 deletions(-) diff --git a/WebContent/WEB-INF/jsp/compoundEvents.jsp b/WebContent/WEB-INF/jsp/compoundEvents.jsp index f518d11dc9..3e86e391d5 100644 --- a/WebContent/WEB-INF/jsp/compoundEvents.jsp +++ b/WebContent/WEB-INF/jsp/compoundEvents.jsp @@ -38,7 +38,7 @@ var pointRoot = dojo.widget.manager.getWidgetById('rootPoint'); for (i=0; i "+ escapeHtml(dp.name)}); + pointNode = dojo.widget.createWidget("TreeNode", {title: " "+ dp.name}); pointRoot.addChild(pointNode); for (j=0; j " + escapeHtml(eventType.description) + " (" + escapeHtml(eventType.eventDetectorKey) + ")", + title: " "+ eventType.description +" ("+ eventType.eventDetectorKey +")", widgetId: widgetId, object: eventType.eventDetectorKey }); @@ -157,7 +157,7 @@ } function updateCompoundEvent(ced) { - $("ced"+ ced.id +"Name").textContent = ced.name; + $("ced"+ ced.id +"Name").innerHTML = ced.name; setCompoundEventImg(ced.disabled, $("ced"+ ced.id +"Img")); } diff --git a/WebContent/WEB-INF/jsp/eventHandlers.jsp b/WebContent/WEB-INF/jsp/eventHandlers.jsp index 57551be424..5866e498ab 100644 --- a/WebContent/WEB-INF/jsp/eventHandlers.jsp +++ b/WebContent/WEB-INF/jsp/eventHandlers.jsp @@ -228,7 +228,7 @@ img = "images/cog_process.png"; var node = dojo.widget.createWidget("TreeNode", { - title: " "+ escapeHtml(handler.message) +"", + title: " "+ handler.message +"", widgetId: "h"+ handler.id, object: handler }); diff --git a/WebContent/WEB-INF/jsp/mailingLists.jsp b/WebContent/WEB-INF/jsp/mailingLists.jsp index 1d5e7fe32f..43a52be8c6 100644 --- a/WebContent/WEB-INF/jsp/mailingLists.jsp +++ b/WebContent/WEB-INF/jsp/mailingLists.jsp @@ -164,7 +164,7 @@ } function updateMailingList(ml) { - $set("ml"+ ml.id +"Name", escapeHtml(ml.name)); + $set("ml"+ ml.id +"Name", ml.name); } function createUserEntry() { @@ -232,7 +232,7 @@ function appendAddressEntry(addressEntry) { var content = createFromTemplate("mleAddress_TEMPLATE_", addressEntry.referenceId, "mailingListEntriesTable"); - $("mle"+ addressEntry.referenceId +"Address").textContent = addressEntry.referenceAddress; + $("mle"+ addressEntry.referenceId +"Address").innerHTML = addressEntry.referenceAddress; } function deleteAddressEntry(entryId) { diff --git a/WebContent/resources/common.js b/WebContent/resources/common.js index f5d60b5cc3..49dba3ca4b 100644 --- a/WebContent/resources/common.js +++ b/WebContent/resources/common.js @@ -1320,6 +1320,7 @@ function unescapeHtml(value) { function escapeHtml(value) { let div = document.createElement("div"); div.textContent = value; + div.innerText = value; return div.innerHTML; } diff --git a/WebContent/resources/emailRecipients.js b/WebContent/resources/emailRecipients.js index a0355b243d..370d530cac 100644 --- a/WebContent/resources/emailRecipients.js +++ b/WebContent/resources/emailRecipients.js @@ -180,7 +180,7 @@ mango.erecip.EmailRecipients = function(prefix, testEmailMessage, mailingLists, else if (id.startsWith("A")) list[list.length] = { recipientType: 3, // EmailRecipient.TYPE_ADDRESS - referenceAddress: $(this.prefix + id +"Description").textContent}; + referenceAddress: $(this.prefix + id +"Description").innerHTML}; else dojo.debug("Unknown recipient mango id: "+ id); } @@ -215,7 +215,7 @@ mango.erecip.EmailRecipients = function(prefix, testEmailMessage, mailingLists, this.addListEntry = function(id, imgName, description) { createFromTemplate(this.prefix +"_TEMPLATE_", id, this.prefix +"List"); $(this.prefix + id +"Img").src = imgName; - $(this.prefix + id +"Description").textContent = description; + $(this.prefix + id +"Description").innerHTML = description; } this.getMailingList = function(id) { From ff46b13c0cf184bd160b147a4b34f0068f4edf6f Mon Sep 17 00:00:00 2001 From: patrykb0802 Date: Fri, 19 Sep 2025 13:20:38 +0200 Subject: [PATCH 04/53] #3167 Fixed escaping fields --- WebContent/WEB-INF/jsp/maintenanceEvents.jsp | 2 +- WebContent/WEB-INF/jsp/publisherList.jsp | 2 +- WebContent/WEB-INF/jsp/scheduledEvents.jsp | 2 +- WebContent/WEB-INF/jsp/usersProfiles.jsp | 2 +- WebContent/WEB-INF/jsp/watchList.jsp | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/WebContent/WEB-INF/jsp/maintenanceEvents.jsp b/WebContent/WEB-INF/jsp/maintenanceEvents.jsp index 587a122f96..2a99708b8c 100644 --- a/WebContent/WEB-INF/jsp/maintenanceEvents.jsp +++ b/WebContent/WEB-INF/jsp/maintenanceEvents.jsp @@ -156,7 +156,7 @@ } function updateMaintenanceEvent(me) { - $("me"+ me.id +"Name").textContent = me.description; + $("me"+ me.id +"Name").innerHTML = me.description; if (me.disabled) updateImg("me"+ me.id +"Img", "images/hammer_disabled.png", "", true); else diff --git a/WebContent/WEB-INF/jsp/publisherList.jsp b/WebContent/WEB-INF/jsp/publisherList.jsp index abe5e1f425..43246b24db 100644 --- a/WebContent/WEB-INF/jsp/publisherList.jsp +++ b/WebContent/WEB-INF/jsp/publisherList.jsp @@ -34,7 +34,7 @@ dwr.util.removeAllRows("publisherList"); dwr.util.addRows("publisherList", publishers, [ - function(p) { return ""+ escapeHtml(p.name) +""; }, + function(p) { return ""+ p.name +""; }, function(p) { return p.typeMessage; }, function(p) { return p.configDescription; }, function(p) { diff --git a/WebContent/WEB-INF/jsp/scheduledEvents.jsp b/WebContent/WEB-INF/jsp/scheduledEvents.jsp index 2a2f0f121c..11f3da220a 100644 --- a/WebContent/WEB-INF/jsp/scheduledEvents.jsp +++ b/WebContent/WEB-INF/jsp/scheduledEvents.jsp @@ -150,7 +150,7 @@ } function updateScheduledEvent(se) { - $("se"+ se.id +"Name").textContent = se.description; + $("se"+ se.id +"Name").innerHTML = se.description; setScheduledEventImg(se.disabled, $("se"+ se.id +"Img")); } diff --git a/WebContent/WEB-INF/jsp/usersProfiles.jsp b/WebContent/WEB-INF/jsp/usersProfiles.jsp index e4da905b3f..6515924741 100644 --- a/WebContent/WEB-INF/jsp/usersProfiles.jsp +++ b/WebContent/WEB-INF/jsp/usersProfiles.jsp @@ -288,7 +288,7 @@ } function updateUserProfile(userProfile) { - $("u"+ userProfile.id +"UserProfileName").textContent = userProfile.name; + $("u"+ userProfile.id +"UserProfileName").innerHTML = userProfile.name; setUserImg(true, userProfile.disabled, $("u"+ userProfile.id +"Img")); console.log("u"+ editingUserProfileId +"Img") } diff --git a/WebContent/WEB-INF/jsp/watchList.jsp b/WebContent/WEB-INF/jsp/watchList.jsp index c2b7128138..d88338f524 100644 --- a/WebContent/WEB-INF/jsp/watchList.jsp +++ b/WebContent/WEB-INF/jsp/watchList.jsp @@ -127,7 +127,7 @@ function addFolder(folder, parent) { var folderNode = dojo.widget.createWidget("TreeNode", { - title: " "+ escapeHtml(folder.name), + title: " "+ folder.name, isFolder: "true", lazyLoadData: folder }); From 50c733b7a337994fdb1711b5140ac50834a20de5 Mon Sep 17 00:00:00 2001 From: patrykb0802 Date: Mon, 29 Sep 2025 15:27:36 +0200 Subject: [PATCH 05/53] #3167 Fixed escaping fields --- WebContent/WEB-INF/dwr.xml | 21 +- WebContent/WEB-INF/jsp/compoundEvents.jsp | 2 +- .../WEB-INF/jsp/include/settingsEditor.jsp | 18 +- WebContent/WEB-INF/jsp/mailingLists.jsp | 2 +- WebContent/WEB-INF/jsp/maintenanceEvents.jsp | 2 +- .../WEB-INF/jsp/pointEdit/pointProperties.jsp | 8 +- WebContent/WEB-INF/jsp/pointHierarchySLTS.jsp | 41 ++-- .../jsp/publisherEdit/editHttpSender.jsp | 8 +- .../WEB-INF/jsp/publisherEdit/editPachube.jsp | 8 +- .../jsp/publisherEdit/editPersistent.jsp | 8 +- WebContent/WEB-INF/jsp/publisherList.jsp | 6 +- WebContent/WEB-INF/jsp/scheduledEvents.jsp | 2 +- WebContent/WEB-INF/jsp/usersProfiles.jsp | 2 +- .../serotonin/mango/vo/report/ReportVO.java | 3 +- .../mango/web/dwr/PointLinksDwr.java | 6 +- .../mango/web/dwr/security/AllowHtml.java | 12 ++ .../dwr/security/SanitizingStringEditor.java | 31 +++ .../security/SelectiveXssBeanConverter.java | 204 ++++++++++++++++++ .../mango/web/dwr/security/XssSanitizer.java | 44 ++++ 19 files changed, 384 insertions(+), 44 deletions(-) create mode 100644 src/com/serotonin/mango/web/dwr/security/AllowHtml.java create mode 100644 src/com/serotonin/mango/web/dwr/security/SanitizingStringEditor.java create mode 100644 src/com/serotonin/mango/web/dwr/security/SelectiveXssBeanConverter.java create mode 100644 src/com/serotonin/mango/web/dwr/security/XssSanitizer.java diff --git a/WebContent/WEB-INF/dwr.xml b/WebContent/WEB-INF/dwr.xml index 2bc5418cee..461561fdf8 100644 --- a/WebContent/WEB-INF/dwr.xml +++ b/WebContent/WEB-INF/dwr.xml @@ -22,9 +22,16 @@ + + + + + + @@ -157,12 +164,14 @@ + @@ -240,8 +249,18 @@ + + + + + + + + + + + - diff --git a/WebContent/WEB-INF/jsp/compoundEvents.jsp b/WebContent/WEB-INF/jsp/compoundEvents.jsp index 3e86e391d5..d3571d3fa7 100644 --- a/WebContent/WEB-INF/jsp/compoundEvents.jsp +++ b/WebContent/WEB-INF/jsp/compoundEvents.jsp @@ -96,7 +96,7 @@ editingCompoundEvent = ced; $set("xid", ced.xid); - $set("name", ced.name); + $set("name", unescapeHtml(ced.name)); $set("alarmLevel", ced.alarmLevel); $set("rtn", ced.returnToNormal); $set("condition", ced.condition); diff --git a/WebContent/WEB-INF/jsp/include/settingsEditor.jsp b/WebContent/WEB-INF/jsp/include/settingsEditor.jsp index 84ad9df6d4..5d56ae000b 100644 --- a/WebContent/WEB-INF/jsp/include/settingsEditor.jsp +++ b/WebContent/WEB-INF/jsp/include/settingsEditor.jsp @@ -153,15 +153,15 @@ this.updatePointList = function(dataTypes) { dwr.util.removeAllOptions("settingsPointList"); - - for (i=0; i", true); else diff --git a/WebContent/WEB-INF/jsp/pointEdit/pointProperties.jsp b/WebContent/WEB-INF/jsp/pointEdit/pointProperties.jsp index 605ec0caa5..e2e7c50f5b 100644 --- a/WebContent/WEB-INF/jsp/pointEdit/pointProperties.jsp +++ b/WebContent/WEB-INF/jsp/pointEdit/pointProperties.jsp @@ -49,20 +49,20 @@ - +
- + " title=""/>
- + - + "/> diff --git a/WebContent/WEB-INF/jsp/pointHierarchySLTS.jsp b/WebContent/WEB-INF/jsp/pointHierarchySLTS.jsp index 3e28722429..0437618e69 100644 --- a/WebContent/WEB-INF/jsp/pointHierarchySLTS.jsp +++ b/WebContent/WEB-INF/jsp/pointHierarchySLTS.jsp @@ -815,22 +815,33 @@ var messages = { dialog.getButton('btn-Close').disable(); var $button = this; $button.disable(); - $button.spin(); - dialog.setClosable(false); - const rawInput = dialog.getModalBody().find('input').val(); + $button.spin(); + dialog.setClosable(false); + let inputs = dialog.getModalBody().find('input'); + let titleNewNode; + if(inputs.length == 1) { + let inputNode = inputs[0]; + titleNewNode = inputNode.value ? inputNode.value.replaceAll('\\','').replaceAll('\/','') : ''; + } + + if(!titleNewNode) { + dialog.getModalBody().html('

'+messages.folderNotAdd+'

'+ messages.errorThrown +':'+errorThrown+'

'); + dialog.setClosable(true); + dialog.getButton('btn-Close').enable(); + return; + } + $.ajax({ - type: "POST", - dataType: "json", - url:myLocation+"pointHierarchy/new/0/"+encodeURIComponent(rawInput), - success: function(msg){ - var titleNewNode = escapeHtml(rawInput); - dialog.getModalBody().html('

'+messages.folder+':

  • '+messages.key+':'+msg+'
  • '+messages.title+':'+titleNewNode+'
'); - $button.hide(); - $button.stopSpin(); - dialog.setClosable(true); - dialog.getButton('btn-Close').enable(); - dialog.close(); - reload(); + type: "POST", + dataType: "json", + url:myLocation+"pointHierarchy/new/0/"+titleNewNode, + success: function(msg){ + $button.hide(); + $button.stopSpin(); + dialog.setClosable(true); + dialog.getButton('btn-Close').enable(); + dialog.close(); + reload(); }, error: function(XMLHttpRequest, textStatus, errorThrown) { dialog.getModalBody().html('

'+messages.folderNotAdd+'

'+ messages.errorThrown +':'+errorThrown+'

'); diff --git a/WebContent/WEB-INF/jsp/publisherEdit/editHttpSender.jsp b/WebContent/WEB-INF/jsp/publisherEdit/editHttpSender.jsp index 54a80b122b..1e2483ce3e 100644 --- a/WebContent/WEB-INF/jsp/publisherEdit/editHttpSender.jsp +++ b/WebContent/WEB-INF/jsp/publisherEdit/editHttpSender.jsp @@ -35,7 +35,7 @@ var list = response.data.allPoints; for (var i=0; i"; }, function(data) { return data.pointType; }, function(data) { diff --git a/WebContent/WEB-INF/jsp/publisherEdit/editPachube.jsp b/WebContent/WEB-INF/jsp/publisherEdit/editPachube.jsp index bd9c9742a2..a583bd0c85 100644 --- a/WebContent/WEB-INF/jsp/publisherEdit/editPachube.jsp +++ b/WebContent/WEB-INF/jsp/publisherEdit/editPachube.jsp @@ -27,7 +27,7 @@ var list = response.data.allPoints; for (var i=0; i"; }, function(data) { return data.pointType; }, function(data) { diff --git a/WebContent/WEB-INF/jsp/publisherEdit/editPersistent.jsp b/WebContent/WEB-INF/jsp/publisherEdit/editPersistent.jsp index 99c3bfecf1..b5fa09a62f 100644 --- a/WebContent/WEB-INF/jsp/publisherEdit/editPersistent.jsp +++ b/WebContent/WEB-INF/jsp/publisherEdit/editPersistent.jsp @@ -28,7 +28,7 @@ var list = response.data.allPoints; for (var i=0; i"; }, function(data) { return data.pointType; }, function(data) { diff --git a/WebContent/WEB-INF/jsp/publisherList.jsp b/WebContent/WEB-INF/jsp/publisherList.jsp index 43246b24db..e900111c51 100644 --- a/WebContent/WEB-INF/jsp/publisherList.jsp +++ b/WebContent/WEB-INF/jsp/publisherList.jsp @@ -34,7 +34,11 @@ dwr.util.removeAllRows("publisherList"); dwr.util.addRows("publisherList", publishers, [ - function(p) { return ""+ p.name +""; }, + function(p) { + let b = document.createElement("b"); + b.appendChild(document.createTextNode(p.name)); + return b; + }, function(p) { return p.typeMessage; }, function(p) { return p.configDescription; }, function(p) { diff --git a/WebContent/WEB-INF/jsp/scheduledEvents.jsp b/WebContent/WEB-INF/jsp/scheduledEvents.jsp index 11f3da220a..2a2f0f121c 100644 --- a/WebContent/WEB-INF/jsp/scheduledEvents.jsp +++ b/WebContent/WEB-INF/jsp/scheduledEvents.jsp @@ -150,7 +150,7 @@ } function updateScheduledEvent(se) { - $("se"+ se.id +"Name").innerHTML = se.description; + $("se"+ se.id +"Name").textContent = se.description; setScheduledEventImg(se.disabled, $("se"+ se.id +"Img")); } diff --git a/WebContent/WEB-INF/jsp/usersProfiles.jsp b/WebContent/WEB-INF/jsp/usersProfiles.jsp index 6515924741..81d406ff82 100644 --- a/WebContent/WEB-INF/jsp/usersProfiles.jsp +++ b/WebContent/WEB-INF/jsp/usersProfiles.jsp @@ -148,7 +148,7 @@ function showUserProfileCB(userProfile) { //show($("deleteButton")); show($("userProfileDetails")); - $set("userProfileName", userProfile.name); + $set("userProfileName", unescapeHtml(userProfile.name)); if (dataSources != null){ var i, j, dscb, dp; diff --git a/src/com/serotonin/mango/vo/report/ReportVO.java b/src/com/serotonin/mango/vo/report/ReportVO.java index 22419b61bb..de1b66b0a7 100644 --- a/src/com/serotonin/mango/vo/report/ReportVO.java +++ b/src/com/serotonin/mango/vo/report/ReportVO.java @@ -32,6 +32,7 @@ import com.serotonin.mango.vo.DataPointVO; import com.serotonin.mango.vo.GetExtendedName; import com.serotonin.mango.vo.User; +import com.serotonin.mango.web.dwr.security.AllowHtml; import com.serotonin.timer.CronTimerTrigger; import com.serotonin.util.StringUtils; import com.serotonin.web.dwr.DwrResponseI18n; @@ -75,7 +76,7 @@ public class ReportVO implements Serializable, JsonSerializable, GetExtendedName private int userId; private String username; - @JsonRemoteProperty + @JsonRemoteProperty @AllowHtml private String name; private List points = new ArrayList(); diff --git a/src/com/serotonin/mango/web/dwr/PointLinksDwr.java b/src/com/serotonin/mango/web/dwr/PointLinksDwr.java index 215d8734f6..5ea37fcf4a 100644 --- a/src/com/serotonin/mango/web/dwr/PointLinksDwr.java +++ b/src/com/serotonin/mango/web/dwr/PointLinksDwr.java @@ -40,6 +40,7 @@ import com.serotonin.mango.vo.User; import com.serotonin.mango.vo.link.PointLinkVO; import com.serotonin.mango.vo.permission.Permissions; +import com.serotonin.mango.web.dwr.security.XssSanitizer; import com.serotonin.util.StringUtils; import com.serotonin.web.dwr.DwrResponseI18n; import com.serotonin.web.i18n.LocalizableMessage; @@ -64,10 +65,11 @@ public Map init() { List sourcePoints = new ArrayList(); List targetPoints = new ArrayList(); for (DataPointVO point : allPoints) { + String safeName = XssSanitizer.escape(point.getExtendedName()); if (Permissions.hasDataPointReadPermission(user, point)) - sourcePoints.add(new IntValuePair(point.getId(), point.getExtendedName())); + sourcePoints.add(new IntValuePair(point.getId(), safeName)); if (point.getPointLocator().isSettable() && Permissions.hasDataPointSetPermission(user, point)) - targetPoints.add(new IntValuePair(point.getId(), point.getExtendedName())); + targetPoints.add(new IntValuePair(point.getId(), safeName)); } data.put("sourcePoints", sourcePoints); diff --git a/src/com/serotonin/mango/web/dwr/security/AllowHtml.java b/src/com/serotonin/mango/web/dwr/security/AllowHtml.java new file mode 100644 index 0000000000..32df94e3d1 --- /dev/null +++ b/src/com/serotonin/mango/web/dwr/security/AllowHtml.java @@ -0,0 +1,12 @@ +package com.serotonin.mango.web.dwr.security; + +import java.lang.annotation.*; + +/** + * Marks a field or method parameter as safe to carry raw HTML. + * Use sparingly: only for server-generated HTML fragments you really intend to render via innerHTML. + */ +@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface AllowHtml {} diff --git a/src/com/serotonin/mango/web/dwr/security/SanitizingStringEditor.java b/src/com/serotonin/mango/web/dwr/security/SanitizingStringEditor.java new file mode 100644 index 0000000000..7ddcc99c37 --- /dev/null +++ b/src/com/serotonin/mango/web/dwr/security/SanitizingStringEditor.java @@ -0,0 +1,31 @@ +package com.serotonin.mango.web.dwr.security; + +import java.beans.PropertyEditorSupport; + +/** + * PropertyEditor that escapes HTML to mitigate XSS on inbound form/request binding. + * Applied globally via @ControllerAdvice binder. + */ +public class SanitizingStringEditor extends PropertyEditorSupport { + + private final boolean escape; + + public SanitizingStringEditor(boolean escape) { + this.escape = escape; + } + + @Override + public void setAsText(String text) { + if (!escape) { + setValue(text); + } else { + setValue(XssSanitizer.escape(text)); + } + } + + @Override + public String getAsText() { + Object value = getValue(); + return value == null ? null : value.toString(); + } +} diff --git a/src/com/serotonin/mango/web/dwr/security/SelectiveXssBeanConverter.java b/src/com/serotonin/mango/web/dwr/security/SelectiveXssBeanConverter.java new file mode 100644 index 0000000000..a733b94ea9 --- /dev/null +++ b/src/com/serotonin/mango/web/dwr/security/SelectiveXssBeanConverter.java @@ -0,0 +1,204 @@ +package com.serotonin.mango.web.dwr.security; + +import org.directwebremoting.extend.Converter; +import org.directwebremoting.extend.ConverterManager; +import org.directwebremoting.extend.InboundContext; +import org.directwebremoting.extend.InboundVariable; +import org.directwebremoting.extend.MarshallException; +import org.directwebremoting.extend.OutboundContext; +import org.directwebremoting.extend.OutboundVariable; +import com.serotonin.web.i18n.I18NUtils; +import com.serotonin.web.i18n.LocalizableMessage; +import org.directwebremoting.WebContextFactory; + +import java.util.IdentityHashMap; +import java.beans.BeanInfo; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Method; +import java.util.*; + +/** + * DWR Converter that escapes String properties in beans for XSS safety, + * unless the property getter or field is annotated with @AllowHtml. + * + * Notes: + * - Works as a drop-in next to the stock converters. + * - We serialize the bean into a Map, sanitizing Strings on the way, + * then delegate to ConverterManager to produce the actual OutboundVariable. + * - Inbound is intentionally not supported (fail-fast) to avoid silent trust of HTML. + */ +public class SelectiveXssBeanConverter implements Converter { + private ConverterManager converterManager; + private final Map, Map> allowCache = new WeakHashMap<>(); + private static final Object CYCLE_MARKER = new Object(); + + @Override + public void setConverterManager(ConverterManager converterManager) { + this.converterManager = converterManager; + } + + @Override + public OutboundVariable convertOutbound(Object bean, OutboundContext outctx) throws MarshallException { + if (bean == null) + return converterManager.convertOutbound(null, outctx); + try { + Object sanitizedTop = sanitizeValue(bean, false, null, new IdentityHashMap<>()); + return converterManager.convertOutbound(sanitizedTop, outctx); + } catch (Exception e) { + throw new MarshallException(bean.getClass(), e); + } + } + + @Override + public Object convertInbound(Class paramType, InboundVariable data, InboundContext inctx) throws MarshallException { + throw new MarshallException(paramType, "Inbound not supported by SelectiveXssBeanConverter"); + } + + // ====== helpers ====== + + private Object sanitizeValue(Object value, + boolean parentAllowsHtml, + Map propAllowCache, + IdentityHashMap seen) throws Exception { + if (value == null) return null; + + // --- cycle guard for non-scalar objects --- + if (!isSimpleScalar(value)) { + if (seen.put(value, CYCLE_MARKER) != null) { + return null; // break cycles + } + } + + // --- LocalizableMessage -> localized String, then maybe escape --- + if (value instanceof LocalizableMessage) { + String text = localize((LocalizableMessage) value); + return parentAllowsHtml ? text : escapeHtml(text); + } + + // --- plain String --- + if (value instanceof CharSequence) { + String s = value.toString(); + return parentAllowsHtml ? s : escapeHtml(s); + } + + // --- simple scalars pass through --- + if (isSimpleScalar(value)) return value; + + // --- arrays --- + if (value.getClass().isArray()) { + int len = java.lang.reflect.Array.getLength(value); + Object copy = java.lang.reflect.Array.newInstance(value.getClass().getComponentType(), len); + for (int i = 0; i < len; i++) { + Object elem = java.lang.reflect.Array.get(value, i); + java.lang.reflect.Array.set(copy, i, sanitizeValue(elem, false, null, seen)); + } + return copy; + } + + // --- collections --- + if (value instanceof Collection) { + Collection src = (Collection) value; + java.util.List out = new java.util.ArrayList<>(src.size()); + for (Object o : src) out.add(sanitizeValue(o, false, null, seen)); + return out; + } + + // --- maps --- + if (value instanceof Map) { + Map src = (Map) value; + Map out = new LinkedHashMap<>(src.size()); + for (Map.Entry e : src.entrySet()) { + out.put(e.getKey(), sanitizeValue(e.getValue(), false, null, seen)); + } + return out; + } + + // --- bean -> Map with @AllowHtml support --- + BeanInfo info = Introspector.getBeanInfo(value.getClass(), Object.class); + Map classAllow = (propAllowCache != null) + ? propAllowCache + : allowCache.computeIfAbsent(value.getClass(), this::scanAllowedHtmlProps); + + Map out = new LinkedHashMap<>(); + for (PropertyDescriptor pd : info.getPropertyDescriptors()) { + Method getter = pd.getReadMethod(); + if (getter == null) continue; + Object v; + try { + v = getter.invoke(value); + } catch (Exception ex) { + continue; + } + boolean allowHtml = Boolean.TRUE.equals(classAllow.get(pd.getName())); + out.put(pd.getName(), sanitizeValue(v, allowHtml, null, seen)); + } + return out; + } + + private Map scanAllowedHtmlProps(Class type) { + Map m = new HashMap<>(); + try { + BeanInfo info = Introspector.getBeanInfo(type, Object.class); + for (PropertyDescriptor pd : info.getPropertyDescriptors()) { + String pn = pd.getName(); + boolean allow = false; + + if (!allow) { + Method getter = pd.getReadMethod(); + if (getter != null && hasAllowHtml(getter)) { + allow = true; + } else { + try { + var field = type.getDeclaredField(pn); + if (hasAllowHtml(field)) allow = true; + } catch (NoSuchFieldException ignore) { + } + } + } + m.put(pn, allow); + } + } catch (Exception ignore) { + } + return m; + } + + private boolean hasAllowHtml(AnnotatedElement e) { + return e != null && e.isAnnotationPresent(AllowHtml.class); + } + + private static String localize(LocalizableMessage lm) { + return lm.getLocalizedMessage( + I18NUtils.getBundle(WebContextFactory.get().getHttpServletRequest()) + ); + } + + private static boolean isSimpleScalar(Object v) { + return v instanceof Number + || v instanceof Boolean + || v instanceof Character + || v instanceof Enum + || v instanceof java.util.Date + || v instanceof java.sql.Date + || v instanceof java.sql.Time + || v instanceof java.sql.Timestamp + || v instanceof java.util.UUID; + } + + private static String escapeHtml(String s) { + StringBuilder sb = new StringBuilder(s.length() + 16); + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + switch (c) { + case '&': sb.append("&"); break; + case '<': sb.append("<"); break; + case '>': sb.append(">"); break; + case '"': sb.append(""");break; + case '\'':sb.append("'"); break; + default: sb.append(c); + } + } + return sb.toString(); + } +} \ No newline at end of file diff --git a/src/com/serotonin/mango/web/dwr/security/XssSanitizer.java b/src/com/serotonin/mango/web/dwr/security/XssSanitizer.java new file mode 100644 index 0000000000..3c877ff6d5 --- /dev/null +++ b/src/com/serotonin/mango/web/dwr/security/XssSanitizer.java @@ -0,0 +1,44 @@ +package com.serotonin.mango.web.dwr.security; + +/** + * Tiny, fast HTML escaper for XSS prevention. + * - Escapes only the 5 dangerous chars: & < > " ' + * - Does NOT escape '/' to avoid breaking URLs. + * - Linear, allocation-conscious, early-out when no specials. + */ +public final class XssSanitizer { + + private XssSanitizer() {} + + /** Returns escaped text or null if input is null. */ + public static String escape(String in) { + if (in == null) return null; + + // Fast path: scan and bail if nothing to escape + int len = in.length(); + boolean needs = false; + for (int i = 0; i < len; i++) { + char c = in.charAt(i); + if (c == '&' || c == '<' || c == '>' || c == '"' || c == '\'') { + needs = true; + break; + } + } + if (!needs) return in; // zero-copy, avoids allocations + + // Slow path: escape + StringBuilder sb = new StringBuilder(len + 16); + for (int i = 0; i < len; i++) { + char c = in.charAt(i); + switch (c) { + case '&': sb.append("&"); break; + case '<': sb.append("<"); break; + case '>': sb.append(">"); break; + case '"': sb.append("""); break; + case '\'': sb.append("'"); break; + default: sb.append(c); + } + } + return sb.toString(); + } +} From 807c18d6312c90b295463e50a0be75ac3431cb29 Mon Sep 17 00:00:00 2001 From: patrykb0802 Date: Mon, 29 Sep 2025 15:28:03 +0200 Subject: [PATCH 06/53] #3167 Fixed escaping fields --- WebContent/WEB-INF/jsp/reports.jsp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WebContent/WEB-INF/jsp/reports.jsp b/WebContent/WEB-INF/jsp/reports.jsp index fd92eb1666..8458b1de15 100644 --- a/WebContent/WEB-INF/jsp/reports.jsp +++ b/WebContent/WEB-INF/jsp/reports.jsp @@ -142,7 +142,7 @@ hide("noReportInstances"); dwr.util.addRows("reportInstancesList", instanceArray, [ - function(ri) { return "" + escapeHtml(ri.name) + ""; }, + function(ri) { return ri.name; }, function(ri) { return ri.prettyRunStartTime; }, function(ri) { return ri.prettyRunDuration; }, function(ri) { return ri.prettyReportStartTime; }, From 85bb87e79a56a8273ca99672cff79976415b27a7 Mon Sep 17 00:00:00 2001 From: Kamil Jarmusik Date: Mon, 20 Oct 2025 12:25:36 +0200 Subject: [PATCH 07/53] #3167 Fixed escaping fields: - Created universal converter XssBeanConverter; - Change annotation name from AllowHtml to NoEscape; - Corrected escaped pointlinks; --- WebContent/WEB-INF/dwr.xml | 37 ++-- WebContent/WEB-INF/jsp/reports.jsp | 15 +- src/br/org/scadabr/vo/scripting/ScriptVO.java | 3 +- .../serotonin/mango/vo/report/ReportVO.java | 6 +- .../mango/web/dwr/PointLinksDwr.java | 7 +- .../web/dwr/XssDataPointBeanConverter.java | 20 -- .../web/dwr/XssDataPointVoConverter.java | 23 -- .../{AllowHtml.java => NoEscape.java} | 4 +- .../dwr/security/SanitizingStringEditor.java | 31 --- .../security/SelectiveXssBeanConverter.java | 204 ------------------ .../web/dwr/security/XssBeanConverter.java | 151 +++++++++++++ .../mango/web/dwr/security/XssSanitizer.java | 44 ---- .../web/security/XssProtectUtils.java | 50 +++-- 13 files changed, 218 insertions(+), 377 deletions(-) delete mode 100644 src/com/serotonin/mango/web/dwr/XssDataPointBeanConverter.java delete mode 100644 src/com/serotonin/mango/web/dwr/XssDataPointVoConverter.java rename src/com/serotonin/mango/web/dwr/security/{AllowHtml.java => NoEscape.java} (74%) delete mode 100644 src/com/serotonin/mango/web/dwr/security/SanitizingStringEditor.java delete mode 100644 src/com/serotonin/mango/web/dwr/security/SelectiveXssBeanConverter.java create mode 100644 src/com/serotonin/mango/web/dwr/security/XssBeanConverter.java delete mode 100644 src/com/serotonin/mango/web/dwr/security/XssSanitizer.java diff --git a/WebContent/WEB-INF/dwr.xml b/WebContent/WEB-INF/dwr.xml index 461561fdf8..5dd2087439 100644 --- a/WebContent/WEB-INF/dwr.xml +++ b/WebContent/WEB-INF/dwr.xml @@ -20,18 +20,10 @@ - - - - - - - - - + + + + @@ -118,7 +110,7 @@ - + @@ -164,14 +156,12 @@ - @@ -199,8 +189,8 @@ - - + + @@ -225,8 +215,8 @@ - - + + @@ -252,13 +242,14 @@ - + + + - diff --git a/WebContent/WEB-INF/jsp/reports.jsp b/WebContent/WEB-INF/jsp/reports.jsp index 8458b1de15..318964b300 100644 --- a/WebContent/WEB-INF/jsp/reports.jsp +++ b/WebContent/WEB-INF/jsp/reports.jsp @@ -158,15 +158,15 @@ return ""; var result = ""; + "onclick='exportData(\""+ ri.name +"\", "+ ri.id +")'/>"; if (ri.includeEvents != ) result += ""; + "onclick='exportEventData(\""+ ri.name +"\", "+ ri.id +")'/>"; if (ri.includeUserComments) result += ""; + "onclick='exportUserComments(\""+ ri.name +"\", "+ ri.id +")'/>"; result += ""+ @@ -204,15 +204,15 @@ } function exportData(name, instanceId) { - window.location = "export/"+ name +".csv?instanceId="+ instanceId; + window.location = "export/"+ encodeURIComponent(name) +".csv?instanceId="+ instanceId; } function exportEventData(name, instanceId) { - window.location = "eventExport/"+ name +"Events.csv?instanceId="+ instanceId; + window.location = "eventExport/"+ encodeURIComponent(name) +"Events.csv?instanceId="+ instanceId; } function exportUserComments(name, instanceId) { - window.location = "userCommentExport/"+ name +"Comments.csv?instanceId="+ instanceId; + window.location = "userCommentExport/"+ encodeURIComponent(name) +"Comments.csv?instanceId="+ instanceId; } function viewChart(instanceId) { @@ -336,8 +336,7 @@ } function updateReport(id, name) { - let escapedName = escapeHtml(name); - $("r"+ id +"Name").innerHTML = escapedName; + $("r"+ id +"Name").innerHTML = name; } function clearMessages() { diff --git a/src/br/org/scadabr/vo/scripting/ScriptVO.java b/src/br/org/scadabr/vo/scripting/ScriptVO.java index c4231e96f2..cb5b349dda 100644 --- a/src/br/org/scadabr/vo/scripting/ScriptVO.java +++ b/src/br/org/scadabr/vo/scripting/ScriptVO.java @@ -18,6 +18,7 @@ import com.serotonin.mango.Common; import com.serotonin.mango.vo.GetExtendedName; import com.serotonin.mango.vo.User; +import com.serotonin.mango.web.dwr.security.NoEscape; import com.serotonin.util.StringUtils; import com.serotonin.web.dwr.DwrResponseI18n; import org.scada_lts.mango.service.ScriptService; @@ -97,7 +98,7 @@ public static List getTypeList() { private String xid; @JsonRemoteProperty private String name; - @JsonRemoteProperty + @JsonRemoteProperty @NoEscape private String script; private int userId; diff --git a/src/com/serotonin/mango/vo/report/ReportVO.java b/src/com/serotonin/mango/vo/report/ReportVO.java index de1b66b0a7..42b5d1816b 100644 --- a/src/com/serotonin/mango/vo/report/ReportVO.java +++ b/src/com/serotonin/mango/vo/report/ReportVO.java @@ -32,7 +32,6 @@ import com.serotonin.mango.vo.DataPointVO; import com.serotonin.mango.vo.GetExtendedName; import com.serotonin.mango.vo.User; -import com.serotonin.mango.web.dwr.security.AllowHtml; import com.serotonin.timer.CronTimerTrigger; import com.serotonin.util.StringUtils; import com.serotonin.web.dwr.DwrResponseI18n; @@ -49,6 +48,7 @@ import org.scada_lts.mango.service.UserService; import org.scada_lts.permissions.service.GetDataPointsWithAccess; import org.scada_lts.utils.ColorUtils; +import org.scada_lts.utils.PathSecureUtils; import org.scada_lts.web.mvc.api.dto.ReportDTO; /** @@ -76,7 +76,7 @@ public class ReportVO implements Serializable, JsonSerializable, GetExtendedName private int userId; private String username; - @JsonRemoteProperty @AllowHtml + @JsonRemoteProperty private String name; private List points = new ArrayList(); @@ -874,6 +874,8 @@ public void validateRun(DwrResponseI18n response, User user) { response.addContextualMessage("name", "reports.validate.required"); if (StringUtils.isLengthGreaterThan(name, 100)) response.addContextualMessage("name", "reports.validate.longerThan100"); + if(!PathSecureUtils.ValidationPaths.validateFilename(name + ".csv") || name.contains(";")) + response.addContextualMessage("name", "validate.invalidValue"); if (points.isEmpty()) response.addContextualMessage("points", "reports.validate.needPoint"); if (dateRangeType != ReportVO.DATE_RANGE_TYPE_RELATIVE && dateRangeType != ReportVO.DATE_RANGE_TYPE_SPECIFIC) diff --git a/src/com/serotonin/mango/web/dwr/PointLinksDwr.java b/src/com/serotonin/mango/web/dwr/PointLinksDwr.java index 5ea37fcf4a..ca6e943552 100644 --- a/src/com/serotonin/mango/web/dwr/PointLinksDwr.java +++ b/src/com/serotonin/mango/web/dwr/PointLinksDwr.java @@ -40,8 +40,6 @@ import com.serotonin.mango.vo.User; import com.serotonin.mango.vo.link.PointLinkVO; import com.serotonin.mango.vo.permission.Permissions; -import com.serotonin.mango.web.dwr.security.XssSanitizer; -import com.serotonin.util.StringUtils; import com.serotonin.web.dwr.DwrResponseI18n; import com.serotonin.web.i18n.LocalizableMessage; import com.serotonin.web.taglib.DateFunctions; @@ -65,11 +63,10 @@ public Map init() { List sourcePoints = new ArrayList(); List targetPoints = new ArrayList(); for (DataPointVO point : allPoints) { - String safeName = XssSanitizer.escape(point.getExtendedName()); if (Permissions.hasDataPointReadPermission(user, point)) - sourcePoints.add(new IntValuePair(point.getId(), safeName)); + sourcePoints.add(new IntValuePair(point.getId(), point.getExtendedName())); if (point.getPointLocator().isSettable() && Permissions.hasDataPointSetPermission(user, point)) - targetPoints.add(new IntValuePair(point.getId(), safeName)); + targetPoints.add(new IntValuePair(point.getId(), point.getExtendedName())); } data.put("sourcePoints", sourcePoints); diff --git a/src/com/serotonin/mango/web/dwr/XssDataPointBeanConverter.java b/src/com/serotonin/mango/web/dwr/XssDataPointBeanConverter.java deleted file mode 100644 index 19a6c90697..0000000000 --- a/src/com/serotonin/mango/web/dwr/XssDataPointBeanConverter.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.serotonin.mango.web.dwr; - -import com.serotonin.mango.web.dwr.beans.DataPointBean; -import org.directwebremoting.convert.BeanConverter; -import org.directwebremoting.extend.MarshallException; -import org.directwebremoting.extend.OutboundContext; -import org.directwebremoting.extend.OutboundVariable; - -import static org.scada_lts.web.security.XssProtectUtils.escapeHtml; - -public class XssDataPointBeanConverter extends BeanConverter { - - @Override - public OutboundVariable convertOutbound(Object data, OutboundContext outctx) throws MarshallException { - DataPointBean dataPointBean = (DataPointBean)data; - dataPointBean.setName(escapeHtml(dataPointBean.getName())); - dataPointBean.setXid(escapeHtml(dataPointBean.getXid())); - return super.convertOutbound(dataPointBean, outctx); - } -} diff --git a/src/com/serotonin/mango/web/dwr/XssDataPointVoConverter.java b/src/com/serotonin/mango/web/dwr/XssDataPointVoConverter.java deleted file mode 100644 index 629b9e4a92..0000000000 --- a/src/com/serotonin/mango/web/dwr/XssDataPointVoConverter.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.serotonin.mango.web.dwr; - -import com.serotonin.mango.vo.DataPointVO; -import org.directwebremoting.convert.BeanConverter; -import org.directwebremoting.extend.*; - -import static org.scada_lts.web.security.XssProtectUtils.escapeHtml; - -public class XssDataPointVoConverter extends BeanConverter { - - @Override - public OutboundVariable convertOutbound(Object data, OutboundContext outctx) throws MarshallException { - DataPointVO dataPointVo = (DataPointVO)data; - dataPointVo.setName(escapeHtml(dataPointVo.getName())); - dataPointVo.setXid(escapeHtml(dataPointVo.getXid())); - dataPointVo.setDataSourceXid(escapeHtml(dataPointVo.getDataSourceXid())); - dataPointVo.setChartColour(escapeHtml(dataPointVo.getChartColour())); - dataPointVo.setDataSourceName(escapeHtml(dataPointVo.getDataSourceName())); - dataPointVo.setDescription(escapeHtml(dataPointVo.getDescription())); - dataPointVo.setDeviceName(escapeHtml(dataPointVo.getDeviceName())); - return super.convertOutbound(dataPointVo, outctx); - } -} diff --git a/src/com/serotonin/mango/web/dwr/security/AllowHtml.java b/src/com/serotonin/mango/web/dwr/security/NoEscape.java similarity index 74% rename from src/com/serotonin/mango/web/dwr/security/AllowHtml.java rename to src/com/serotonin/mango/web/dwr/security/NoEscape.java index 32df94e3d1..54ee5ca1db 100644 --- a/src/com/serotonin/mango/web/dwr/security/AllowHtml.java +++ b/src/com/serotonin/mango/web/dwr/security/NoEscape.java @@ -6,7 +6,7 @@ * Marks a field or method parameter as safe to carry raw HTML. * Use sparingly: only for server-generated HTML fragments you really intend to render via innerHTML. */ -@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD }) +@Target({ ElementType.FIELD }) @Retention(RetentionPolicy.RUNTIME) @Documented -public @interface AllowHtml {} +public @interface NoEscape {} diff --git a/src/com/serotonin/mango/web/dwr/security/SanitizingStringEditor.java b/src/com/serotonin/mango/web/dwr/security/SanitizingStringEditor.java deleted file mode 100644 index 7ddcc99c37..0000000000 --- a/src/com/serotonin/mango/web/dwr/security/SanitizingStringEditor.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.serotonin.mango.web.dwr.security; - -import java.beans.PropertyEditorSupport; - -/** - * PropertyEditor that escapes HTML to mitigate XSS on inbound form/request binding. - * Applied globally via @ControllerAdvice binder. - */ -public class SanitizingStringEditor extends PropertyEditorSupport { - - private final boolean escape; - - public SanitizingStringEditor(boolean escape) { - this.escape = escape; - } - - @Override - public void setAsText(String text) { - if (!escape) { - setValue(text); - } else { - setValue(XssSanitizer.escape(text)); - } - } - - @Override - public String getAsText() { - Object value = getValue(); - return value == null ? null : value.toString(); - } -} diff --git a/src/com/serotonin/mango/web/dwr/security/SelectiveXssBeanConverter.java b/src/com/serotonin/mango/web/dwr/security/SelectiveXssBeanConverter.java deleted file mode 100644 index a733b94ea9..0000000000 --- a/src/com/serotonin/mango/web/dwr/security/SelectiveXssBeanConverter.java +++ /dev/null @@ -1,204 +0,0 @@ -package com.serotonin.mango.web.dwr.security; - -import org.directwebremoting.extend.Converter; -import org.directwebremoting.extend.ConverterManager; -import org.directwebremoting.extend.InboundContext; -import org.directwebremoting.extend.InboundVariable; -import org.directwebremoting.extend.MarshallException; -import org.directwebremoting.extend.OutboundContext; -import org.directwebremoting.extend.OutboundVariable; -import com.serotonin.web.i18n.I18NUtils; -import com.serotonin.web.i18n.LocalizableMessage; -import org.directwebremoting.WebContextFactory; - -import java.util.IdentityHashMap; -import java.beans.BeanInfo; -import java.beans.Introspector; -import java.beans.PropertyDescriptor; -import java.lang.reflect.AnnotatedElement; -import java.lang.reflect.Method; -import java.util.*; - -/** - * DWR Converter that escapes String properties in beans for XSS safety, - * unless the property getter or field is annotated with @AllowHtml. - * - * Notes: - * - Works as a drop-in next to the stock converters. - * - We serialize the bean into a Map, sanitizing Strings on the way, - * then delegate to ConverterManager to produce the actual OutboundVariable. - * - Inbound is intentionally not supported (fail-fast) to avoid silent trust of HTML. - */ -public class SelectiveXssBeanConverter implements Converter { - private ConverterManager converterManager; - private final Map, Map> allowCache = new WeakHashMap<>(); - private static final Object CYCLE_MARKER = new Object(); - - @Override - public void setConverterManager(ConverterManager converterManager) { - this.converterManager = converterManager; - } - - @Override - public OutboundVariable convertOutbound(Object bean, OutboundContext outctx) throws MarshallException { - if (bean == null) - return converterManager.convertOutbound(null, outctx); - try { - Object sanitizedTop = sanitizeValue(bean, false, null, new IdentityHashMap<>()); - return converterManager.convertOutbound(sanitizedTop, outctx); - } catch (Exception e) { - throw new MarshallException(bean.getClass(), e); - } - } - - @Override - public Object convertInbound(Class paramType, InboundVariable data, InboundContext inctx) throws MarshallException { - throw new MarshallException(paramType, "Inbound not supported by SelectiveXssBeanConverter"); - } - - // ====== helpers ====== - - private Object sanitizeValue(Object value, - boolean parentAllowsHtml, - Map propAllowCache, - IdentityHashMap seen) throws Exception { - if (value == null) return null; - - // --- cycle guard for non-scalar objects --- - if (!isSimpleScalar(value)) { - if (seen.put(value, CYCLE_MARKER) != null) { - return null; // break cycles - } - } - - // --- LocalizableMessage -> localized String, then maybe escape --- - if (value instanceof LocalizableMessage) { - String text = localize((LocalizableMessage) value); - return parentAllowsHtml ? text : escapeHtml(text); - } - - // --- plain String --- - if (value instanceof CharSequence) { - String s = value.toString(); - return parentAllowsHtml ? s : escapeHtml(s); - } - - // --- simple scalars pass through --- - if (isSimpleScalar(value)) return value; - - // --- arrays --- - if (value.getClass().isArray()) { - int len = java.lang.reflect.Array.getLength(value); - Object copy = java.lang.reflect.Array.newInstance(value.getClass().getComponentType(), len); - for (int i = 0; i < len; i++) { - Object elem = java.lang.reflect.Array.get(value, i); - java.lang.reflect.Array.set(copy, i, sanitizeValue(elem, false, null, seen)); - } - return copy; - } - - // --- collections --- - if (value instanceof Collection) { - Collection src = (Collection) value; - java.util.List out = new java.util.ArrayList<>(src.size()); - for (Object o : src) out.add(sanitizeValue(o, false, null, seen)); - return out; - } - - // --- maps --- - if (value instanceof Map) { - Map src = (Map) value; - Map out = new LinkedHashMap<>(src.size()); - for (Map.Entry e : src.entrySet()) { - out.put(e.getKey(), sanitizeValue(e.getValue(), false, null, seen)); - } - return out; - } - - // --- bean -> Map with @AllowHtml support --- - BeanInfo info = Introspector.getBeanInfo(value.getClass(), Object.class); - Map classAllow = (propAllowCache != null) - ? propAllowCache - : allowCache.computeIfAbsent(value.getClass(), this::scanAllowedHtmlProps); - - Map out = new LinkedHashMap<>(); - for (PropertyDescriptor pd : info.getPropertyDescriptors()) { - Method getter = pd.getReadMethod(); - if (getter == null) continue; - Object v; - try { - v = getter.invoke(value); - } catch (Exception ex) { - continue; - } - boolean allowHtml = Boolean.TRUE.equals(classAllow.get(pd.getName())); - out.put(pd.getName(), sanitizeValue(v, allowHtml, null, seen)); - } - return out; - } - - private Map scanAllowedHtmlProps(Class type) { - Map m = new HashMap<>(); - try { - BeanInfo info = Introspector.getBeanInfo(type, Object.class); - for (PropertyDescriptor pd : info.getPropertyDescriptors()) { - String pn = pd.getName(); - boolean allow = false; - - if (!allow) { - Method getter = pd.getReadMethod(); - if (getter != null && hasAllowHtml(getter)) { - allow = true; - } else { - try { - var field = type.getDeclaredField(pn); - if (hasAllowHtml(field)) allow = true; - } catch (NoSuchFieldException ignore) { - } - } - } - m.put(pn, allow); - } - } catch (Exception ignore) { - } - return m; - } - - private boolean hasAllowHtml(AnnotatedElement e) { - return e != null && e.isAnnotationPresent(AllowHtml.class); - } - - private static String localize(LocalizableMessage lm) { - return lm.getLocalizedMessage( - I18NUtils.getBundle(WebContextFactory.get().getHttpServletRequest()) - ); - } - - private static boolean isSimpleScalar(Object v) { - return v instanceof Number - || v instanceof Boolean - || v instanceof Character - || v instanceof Enum - || v instanceof java.util.Date - || v instanceof java.sql.Date - || v instanceof java.sql.Time - || v instanceof java.sql.Timestamp - || v instanceof java.util.UUID; - } - - private static String escapeHtml(String s) { - StringBuilder sb = new StringBuilder(s.length() + 16); - for (int i = 0; i < s.length(); i++) { - char c = s.charAt(i); - switch (c) { - case '&': sb.append("&"); break; - case '<': sb.append("<"); break; - case '>': sb.append(">"); break; - case '"': sb.append(""");break; - case '\'':sb.append("'"); break; - default: sb.append(c); - } - } - return sb.toString(); - } -} \ No newline at end of file diff --git a/src/com/serotonin/mango/web/dwr/security/XssBeanConverter.java b/src/com/serotonin/mango/web/dwr/security/XssBeanConverter.java new file mode 100644 index 0000000000..78f40005a3 --- /dev/null +++ b/src/com/serotonin/mango/web/dwr/security/XssBeanConverter.java @@ -0,0 +1,151 @@ +package com.serotonin.mango.web.dwr.security; + +import org.directwebremoting.convert.BeanConverter; +import org.directwebremoting.extend.*; +import org.scada_lts.web.security.XssProtectUtils; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.util.*; +import java.util.function.BiFunction; +import java.util.function.Function; + +public class XssBeanConverter extends BeanConverter { + + @Override + public OutboundVariable convertOutbound(Object value, OutboundContext outctx) throws MarshallException { + convert(value, (object, noEscape) -> convertObject(object, noEscape, this::escapeIfString)); + return super.convertOutbound(value, outctx); + } + + @Override + public Object convertInbound(Class paramType, InboundVariable iv, InboundContext inctx) throws MarshallException { + convert(iv.getValue(), (object, noEscape) -> convertObject(object, noEscape, this::unescapeIfString)); + return super.convertInbound(paramType, iv, inctx); + } + + private void convert(Object object, BiFunction convert) { + Field[] declaredFields = object.getClass().getDeclaredFields(); + for(Field field: declaredFields) { + if(!java.lang.reflect.Modifier.isStatic(field.getModifiers()) && !java.lang.reflect.Modifier.isFinal(field.getModifiers())) { + try { + field.setAccessible(true); + Object value = field.get(object); + if(value != null) + field.set(object, convert.apply(value, field.isAnnotationPresent(NoEscape.class))); + } catch (Exception e) { + throw new RuntimeException(e); + } finally { + field.setAccessible(false); + } + } + } + } + + private Object convertObject(Object object, boolean noConvert, Function doConvert) { + + if(noConvert) + return object; + + if (object instanceof String) { + return convertStringObject(object, doConvert); + } + + if (object.getClass().isArray()) { + return convertArrayObject(object, doConvert); + } + + if (object instanceof List) { + return convertListObject(object, doConvert); + } + + if (object instanceof Set) { + return convertSetObject(object, doConvert); + } + + if (object instanceof Map) { + return convertMapObject(object, doConvert); + } + + return object; + } + + private Object convertStringObject(Object stringObject, Function convert) { + return convert.apply(stringObject); + } + + private Object convertMapObject(Object mapObject, Function convert) { + Map in = (Map) mapObject; + Map out = new HashMap<>(); + + for(Object key: in.keySet().toArray()) { + Object val = in.get(key); + out.put(convert.apply(key), convert.apply(val)); + } + + try { + Constructor ctor = mapObject.getClass().getConstructor(Map.class); + return ctor.newInstance(out); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private Object convertSetObject(Object setObject, Function convert) { + try { + Collection in = (Collection) setObject; + Collection out = new HashSet<>(); + + for(Object obj: in.toArray()) { + out.add(convert.apply(obj)); + } + + Constructor ctor = setObject.getClass().getConstructor(Collection.class); + return ctor.newInstance(out); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private Object convertListObject(Object listObject, Function convert) { + try { + Collection in = (Collection) listObject; + Collection out = new ArrayList<>(); + + for(Object obj: in.toArray()) { + out.add(convert.apply(obj)); + } + + Constructor ctor = listObject.getClass().getConstructor(Collection.class); + return ctor.newInstance(out); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private Object convertArrayObject(Object arrayObject, Function convert) { + int len = java.lang.reflect.Array.getLength(arrayObject); + Object out = java.lang.reflect.Array.newInstance(arrayObject.getClass().getComponentType(), len); + for (int i = 0; i < len; i++) { + Object elem = java.lang.reflect.Array.get(arrayObject, i); + java.lang.reflect.Array.set(out, i, convert.apply(elem)); + } + return out; + } + + private Object escapeIfString(Object object) { + if(object instanceof String) { + return XssProtectUtils.escapeHtml((String) object); + } else { + return object; + } + } + + private Object unescapeIfString(Object o) { + if(o instanceof String) { + return XssProtectUtils.unescapeHtml((String) o); + } else { + return o; + } + } +} diff --git a/src/com/serotonin/mango/web/dwr/security/XssSanitizer.java b/src/com/serotonin/mango/web/dwr/security/XssSanitizer.java deleted file mode 100644 index 3c877ff6d5..0000000000 --- a/src/com/serotonin/mango/web/dwr/security/XssSanitizer.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.serotonin.mango.web.dwr.security; - -/** - * Tiny, fast HTML escaper for XSS prevention. - * - Escapes only the 5 dangerous chars: & < > " ' - * - Does NOT escape '/' to avoid breaking URLs. - * - Linear, allocation-conscious, early-out when no specials. - */ -public final class XssSanitizer { - - private XssSanitizer() {} - - /** Returns escaped text or null if input is null. */ - public static String escape(String in) { - if (in == null) return null; - - // Fast path: scan and bail if nothing to escape - int len = in.length(); - boolean needs = false; - for (int i = 0; i < len; i++) { - char c = in.charAt(i); - if (c == '&' || c == '<' || c == '>' || c == '"' || c == '\'') { - needs = true; - break; - } - } - if (!needs) return in; // zero-copy, avoids allocations - - // Slow path: escape - StringBuilder sb = new StringBuilder(len + 16); - for (int i = 0; i < len; i++) { - char c = in.charAt(i); - switch (c) { - case '&': sb.append("&"); break; - case '<': sb.append("<"); break; - case '>': sb.append(">"); break; - case '"': sb.append("""); break; - case '\'': sb.append("'"); break; - default: sb.append(c); - } - } - return sb.toString(); - } -} diff --git a/src/org/scada_lts/web/security/XssProtectUtils.java b/src/org/scada_lts/web/security/XssProtectUtils.java index de9e5e9544..8f1818d5a3 100644 --- a/src/org/scada_lts/web/security/XssProtectUtils.java +++ b/src/org/scada_lts/web/security/XssProtectUtils.java @@ -2,31 +2,53 @@ import org.springframework.web.util.HtmlUtils; +import java.util.HashMap; +import java.util.Map; + public final class XssProtectUtils { + private static final Map newLineAndWhitespaceCodes = new HashMap<>(); + + static { + newLineAndWhitespaceCodes.put(" ", "\n"); + newLineAndWhitespaceCodes.put(" ", "\r"); + + newLineAndWhitespaceCodes.put(" ", "\t"); + newLineAndWhitespaceCodes.put(" ", "\u000B"); + newLineAndWhitespaceCodes.put(" ", "\f"); + newLineAndWhitespaceCodes.put("", "\u001C"); + newLineAndWhitespaceCodes.put("", "\u001D"); + newLineAndWhitespaceCodes.put("", "\u001E"); + newLineAndWhitespaceCodes.put("", "\u001F"); + } + public XssProtectUtils() {} public static String escapeHtml(String value) { if(value == null) return ""; - String content = HtmlUtils.htmlEscape(value); - return whiteSpaceHtmlCode(newLineHtmlCode(content)); + return escapeNewLineAndWhitespace(HtmlUtils.htmlEscape(value)); } - private static String newLineHtmlCode(String content) { - if(content.contains("\n")) { - return content.replace("\n", " ").replace("\r", ""); + public static String unescapeHtml(String value) { + if(value == null) + return ""; + return HtmlUtils.htmlUnescape(unescapeNewLineAndWhitespace(value)); + } + + private static String escapeNewLineAndWhitespace(String content) { + String result = content; + for(Map.Entry entry: newLineAndWhitespaceCodes.entrySet()) { + result = result.replace(entry.getValue(), entry.getKey()); } - return content.replace("\r", " "); + return content; } - private static String whiteSpaceHtmlCode(String content) { - return content.replace("\t", " ") - .replace("\u000B"," ") - .replace("\f", " ") - .replace("\u001C", "") - .replace("\u001D", "") - .replace("\u001E", "") - .replace("\u001F", ""); + private static String unescapeNewLineAndWhitespace(String content) { + String result = content; + for(Map.Entry entry: newLineAndWhitespaceCodes.entrySet()) { + result = result.replace(entry.getKey(), entry.getValue()); + } + return content; } } From 41ed7c39f84e319f2f64038c979acc8e5aa35153 Mon Sep 17 00:00:00 2001 From: Kamil Jarmusik Date: Mon, 20 Oct 2025 13:48:07 +0200 Subject: [PATCH 08/53] #3167 Fixed escaping fields: - Created class XssBeanConverterUtils; - Added test: XssBeanConverterUtilsTest and class: TestGetExtendedName for test; - Moved NoEscape and XssBeanConverter to org.scada_lts.web.dwr.security; - Added converted RecipientListEntryBean and IntValuePair in dwr.xml; --- WebContent/WEB-INF/dwr.xml | 4 +- build.gradle | 1 + src/br/org/scadabr/vo/scripting/ScriptVO.java | 2 +- .../scada_lts}/web/dwr/security/NoEscape.java | 2 +- .../web/dwr/security/XssBeanConverter.java | 23 +++++++ .../utils/XssBeanConverterUtils.java} | 63 +++++++++++-------- .../utils/XssBeanConverterUtilsTest.java | 32 ++++++++++ test/utils/mock/TestGetExtendedName.java | 37 +++++++++++ 8 files changed, 134 insertions(+), 30 deletions(-) rename src/{com/serotonin/mango => org/scada_lts}/web/dwr/security/NoEscape.java (87%) create mode 100644 src/org/scada_lts/web/dwr/security/XssBeanConverter.java rename src/{com/serotonin/mango/web/dwr/security/XssBeanConverter.java => org/scada_lts/web/dwr/security/utils/XssBeanConverterUtils.java} (61%) create mode 100644 test/org/scada_lts/web/dwr/security/utils/XssBeanConverterUtilsTest.java create mode 100644 test/utils/mock/TestGetExtendedName.java diff --git a/WebContent/WEB-INF/dwr.xml b/WebContent/WEB-INF/dwr.xml index 5dd2087439..45000fd4b8 100644 --- a/WebContent/WEB-INF/dwr.xml +++ b/WebContent/WEB-INF/dwr.xml @@ -23,7 +23,7 @@ - + @@ -251,6 +251,8 @@ + + diff --git a/build.gradle b/build.gradle index 6e243f6466..18228ad749 100644 --- a/build.gradle +++ b/build.gradle @@ -249,6 +249,7 @@ test { includeTestsMatching "com.serotonin.mango.vo.EngineeringUnitsTypesTest" includeTestsMatching "org.scada_lts.cache.PointHierarchyCacheTestsSuite" includeTestsMatching "com.serotonin.mango.rt.dataImage.types.BinaryValueTestsSuite" + includeTestsMatching "org.scada_lts.web.dwr.security.utils.XssBeanConverterUtilsTest" } failFast = true diff --git a/src/br/org/scadabr/vo/scripting/ScriptVO.java b/src/br/org/scadabr/vo/scripting/ScriptVO.java index cb5b349dda..507e24f0a3 100644 --- a/src/br/org/scadabr/vo/scripting/ScriptVO.java +++ b/src/br/org/scadabr/vo/scripting/ScriptVO.java @@ -18,7 +18,7 @@ import com.serotonin.mango.Common; import com.serotonin.mango.vo.GetExtendedName; import com.serotonin.mango.vo.User; -import com.serotonin.mango.web.dwr.security.NoEscape; +import org.scada_lts.web.dwr.security.NoEscape; import com.serotonin.util.StringUtils; import com.serotonin.web.dwr.DwrResponseI18n; import org.scada_lts.mango.service.ScriptService; diff --git a/src/com/serotonin/mango/web/dwr/security/NoEscape.java b/src/org/scada_lts/web/dwr/security/NoEscape.java similarity index 87% rename from src/com/serotonin/mango/web/dwr/security/NoEscape.java rename to src/org/scada_lts/web/dwr/security/NoEscape.java index 54ee5ca1db..03485af170 100644 --- a/src/com/serotonin/mango/web/dwr/security/NoEscape.java +++ b/src/org/scada_lts/web/dwr/security/NoEscape.java @@ -1,4 +1,4 @@ -package com.serotonin.mango.web.dwr.security; +package org.scada_lts.web.dwr.security; import java.lang.annotation.*; diff --git a/src/org/scada_lts/web/dwr/security/XssBeanConverter.java b/src/org/scada_lts/web/dwr/security/XssBeanConverter.java new file mode 100644 index 0000000000..a137f04c4c --- /dev/null +++ b/src/org/scada_lts/web/dwr/security/XssBeanConverter.java @@ -0,0 +1,23 @@ +package org.scada_lts.web.dwr.security; + +import org.directwebremoting.convert.BeanConverter; +import org.directwebremoting.extend.*; + +import static org.scada_lts.web.dwr.security.utils.XssBeanConverterUtils.convertObjectEscaped; +import static org.scada_lts.web.dwr.security.utils.XssBeanConverterUtils.convertObjectUnescaped; + +public class XssBeanConverter extends BeanConverter { + + @Override + public OutboundVariable convertOutbound(Object value, OutboundContext outctx) throws MarshallException { + convertObjectEscaped(value); + return super.convertOutbound(value, outctx); + } + + @Override + public Object convertInbound(Class paramType, InboundVariable iv, InboundContext inctx) throws MarshallException { + convertObjectUnescaped(iv.getValue()); + return super.convertInbound(paramType, iv, inctx); + } + +} diff --git a/src/com/serotonin/mango/web/dwr/security/XssBeanConverter.java b/src/org/scada_lts/web/dwr/security/utils/XssBeanConverterUtils.java similarity index 61% rename from src/com/serotonin/mango/web/dwr/security/XssBeanConverter.java rename to src/org/scada_lts/web/dwr/security/utils/XssBeanConverterUtils.java index 78f40005a3..6f846cd80a 100644 --- a/src/com/serotonin/mango/web/dwr/security/XssBeanConverter.java +++ b/src/org/scada_lts/web/dwr/security/utils/XssBeanConverterUtils.java @@ -1,7 +1,9 @@ -package com.serotonin.mango.web.dwr.security; +package org.scada_lts.web.dwr.security.utils; -import org.directwebremoting.convert.BeanConverter; -import org.directwebremoting.extend.*; +import com.serotonin.mango.util.LoggingUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.scada_lts.web.dwr.security.NoEscape; import org.scada_lts.web.security.XssProtectUtils; import java.lang.reflect.Constructor; @@ -10,31 +12,36 @@ import java.util.function.BiFunction; import java.util.function.Function; -public class XssBeanConverter extends BeanConverter { +import static java.lang.reflect.Modifier.isStatic; - @Override - public OutboundVariable convertOutbound(Object value, OutboundContext outctx) throws MarshallException { - convert(value, (object, noEscape) -> convertObject(object, noEscape, this::escapeIfString)); - return super.convertOutbound(value, outctx); +public final class XssBeanConverterUtils { + + private XssBeanConverterUtils() {} + + private static final Logger LOG = LogManager.getLogger(XssBeanConverterUtils.class); + + public static void convertObjectEscaped(Object value) { + convert(value, (object, noEscape) -> convertObject(object, noEscape, XssBeanConverterUtils::escapeIfString)); } - @Override - public Object convertInbound(Class paramType, InboundVariable iv, InboundContext inctx) throws MarshallException { - convert(iv.getValue(), (object, noEscape) -> convertObject(object, noEscape, this::unescapeIfString)); - return super.convertInbound(paramType, iv, inctx); + public static void convertObjectUnescaped(Object value) { + convert(value, (object, noEscape) -> convertObject(object, noEscape, XssBeanConverterUtils::unescapeIfString)); } - private void convert(Object object, BiFunction convert) { + private static void convert(Object object, BiFunction convert) { Field[] declaredFields = object.getClass().getDeclaredFields(); for(Field field: declaredFields) { - if(!java.lang.reflect.Modifier.isStatic(field.getModifiers()) && !java.lang.reflect.Modifier.isFinal(field.getModifiers())) { + if(!isStatic(field.getModifiers())) { try { field.setAccessible(true); Object value = field.get(object); - if(value != null) - field.set(object, convert.apply(value, field.isAnnotationPresent(NoEscape.class))); + if(value != null) { + Object converted = convert.apply(value, field.isAnnotationPresent(NoEscape.class)); + if(converted != null) + field.set(object, converted); + } } catch (Exception e) { - throw new RuntimeException(e); + LOG.error(LoggingUtils.exceptionInfo(e)); } finally { field.setAccessible(false); } @@ -42,7 +49,7 @@ private void convert(Object object, BiFunction convert) } } - private Object convertObject(Object object, boolean noConvert, Function doConvert) { + private static Object convertObject(Object object, boolean noConvert, Function doConvert) { if(noConvert) return object; @@ -70,11 +77,11 @@ private Object convertObject(Object object, boolean noConvert, Function convert) { + private static Object convertStringObject(Object stringObject, Function convert) { return convert.apply(stringObject); } - private Object convertMapObject(Object mapObject, Function convert) { + private static Object convertMapObject(Object mapObject, Function convert) { Map in = (Map) mapObject; Map out = new HashMap<>(); @@ -87,11 +94,12 @@ private Object convertMapObject(Object mapObject, Function conve Constructor ctor = mapObject.getClass().getConstructor(Map.class); return ctor.newInstance(out); } catch (Exception e) { - throw new RuntimeException(e); + LOG.error(LoggingUtils.exceptionInfo(e)); + return null; } } - private Object convertSetObject(Object setObject, Function convert) { + private static Object convertSetObject(Object setObject, Function convert) { try { Collection in = (Collection) setObject; Collection out = new HashSet<>(); @@ -103,11 +111,12 @@ private Object convertSetObject(Object setObject, Function conve Constructor ctor = setObject.getClass().getConstructor(Collection.class); return ctor.newInstance(out); } catch (Exception e) { - throw new RuntimeException(e); + LOG.error(LoggingUtils.exceptionInfo(e)); + return null; } } - private Object convertListObject(Object listObject, Function convert) { + private static Object convertListObject(Object listObject, Function convert) { try { Collection in = (Collection) listObject; Collection out = new ArrayList<>(); @@ -123,7 +132,7 @@ private Object convertListObject(Object listObject, Function con } } - private Object convertArrayObject(Object arrayObject, Function convert) { + private static Object convertArrayObject(Object arrayObject, Function convert) { int len = java.lang.reflect.Array.getLength(arrayObject); Object out = java.lang.reflect.Array.newInstance(arrayObject.getClass().getComponentType(), len); for (int i = 0; i < len; i++) { @@ -133,7 +142,7 @@ private Object convertArrayObject(Object arrayObject, Function c return out; } - private Object escapeIfString(Object object) { + private static Object escapeIfString(Object object) { if(object instanceof String) { return XssProtectUtils.escapeHtml((String) object); } else { @@ -141,7 +150,7 @@ private Object escapeIfString(Object object) { } } - private Object unescapeIfString(Object o) { + private static Object unescapeIfString(Object o) { if(o instanceof String) { return XssProtectUtils.unescapeHtml((String) o); } else { diff --git a/test/org/scada_lts/web/dwr/security/utils/XssBeanConverterUtilsTest.java b/test/org/scada_lts/web/dwr/security/utils/XssBeanConverterUtilsTest.java new file mode 100644 index 0000000000..b537a5859d --- /dev/null +++ b/test/org/scada_lts/web/dwr/security/utils/XssBeanConverterUtilsTest.java @@ -0,0 +1,32 @@ +package org.scada_lts.web.dwr.security.utils; + +import com.serotonin.mango.vo.GetExtendedName; +import org.junit.Assert; +import org.junit.Test; +import utils.mock.TestGetExtendedName; + +public class XssBeanConverterUtilsTest { + + private final GetExtendedName object = new TestGetExtendedName(""); + + private final GetExtendedName objectEscaped = new TestGetExtendedName("<script>alert(1)</script>"); + + @Test + public void convertObjectEscaped() { + + //when: + XssBeanConverterUtils.convertObjectEscaped(object); + + //then: + Assert.assertEquals(objectEscaped, object); + } + + @Test + public void convertObjectUnescaped() { + //when: + XssBeanConverterUtils.convertObjectUnescaped(objectEscaped); + + //then: + Assert.assertEquals(object, objectEscaped); + } +} \ No newline at end of file diff --git a/test/utils/mock/TestGetExtendedName.java b/test/utils/mock/TestGetExtendedName.java new file mode 100644 index 0000000000..997e1f5108 --- /dev/null +++ b/test/utils/mock/TestGetExtendedName.java @@ -0,0 +1,37 @@ +package utils.mock; + +import com.serotonin.mango.vo.GetExtendedName; + +public class TestGetExtendedName implements GetExtendedName { + + private final String name; + + public TestGetExtendedName(String name) { + this.name = name; + } + + @Override + public String getName() { + return name; + } + + @Override + public String toString() { + return "$classname{" + + "name='" + getName() + '\'' + + '}'; + } + + @Override + public int hashCode() { + return getName().hashCode(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof GetExtendedName)) return false; + GetExtendedName that = (GetExtendedName) o; + return this.getName().equals(that.getName()); + } +} From 46df75bb12fd6a8649fb6925984fa96876d2719a Mon Sep 17 00:00:00 2001 From: patrykb0802 Date: Wed, 22 Oct 2025 13:46:02 +0200 Subject: [PATCH 09/53] #3206 Fixed escaping fields in Event handlers, Users profiles, watch lists --- WebContent/WEB-INF/dwr.xml | 1 + WebContent/WEB-INF/jsp/eventHandlers.jsp | 3 ++- WebContent/WEB-INF/jsp/usersProfiles.jsp | 8 ++++---- WebContent/WEB-INF/jsp/watchList.jsp | 8 +++++--- src/br/org/scadabr/vo/usersProfiles/UsersProfileVO.java | 3 ++- 5 files changed, 14 insertions(+), 9 deletions(-) diff --git a/WebContent/WEB-INF/dwr.xml b/WebContent/WEB-INF/dwr.xml index 45000fd4b8..71055791da 100644 --- a/WebContent/WEB-INF/dwr.xml +++ b/WebContent/WEB-INF/dwr.xml @@ -253,6 +253,7 @@ + diff --git a/WebContent/WEB-INF/jsp/eventHandlers.jsp b/WebContent/WEB-INF/jsp/eventHandlers.jsp index ce3d3b6fe1..eb7c280ade 100644 --- a/WebContent/WEB-INF/jsp/eventHandlers.jsp +++ b/WebContent/WEB-INF/jsp/eventHandlers.jsp @@ -206,11 +206,12 @@ function createEventTypeNode(widgetId, eventType, parent) { var node = dojo.widget.createWidget("TreeNode", { - title: " "+ eventType.description, + title: " ", widgetId: widgetId, object: eventType }); parent.addChild(node); + $(widgetId + "Txt").textContent = eventType.description; setAlarmLevelImg(eventType.alarmLevel, $(widgetId +"Img")); addHandlerNodes(eventType.handlers, node); } diff --git a/WebContent/WEB-INF/jsp/usersProfiles.jsp b/WebContent/WEB-INF/jsp/usersProfiles.jsp index e5e7faff76..71794a97d8 100644 --- a/WebContent/WEB-INF/jsp/usersProfiles.jsp +++ b/WebContent/WEB-INF/jsp/usersProfiles.jsp @@ -86,7 +86,7 @@ if(isUnnamedWatchList(keyTranslations, watchlists[i].name)) continue; id = watchlists[i].id; - wlhtml += '
'; + wlhtml += '
'; wlhtml += '
'; wlhtml += ''; wlhtml += ''; @@ -110,7 +110,7 @@ if (views != null){ for (i=0; i '+ views[i].name +'
'; + vwhtml += '
'; vwhtml += '
'; vwhtml += '
'; vwhtml += ''; @@ -148,7 +148,7 @@ function showUserProfileCB(userProfile) { //show($("deleteButton")); show($("userProfileDetails")); - $set("userProfileName", unescapeHtml(userProfile.name)); + $set("userProfileName", userProfile.name); if (dataSources != null){ var i, j, dscb, dp; @@ -288,7 +288,7 @@ } function updateUserProfile(userProfile) { - $("u"+ userProfile.id +"UserProfileName").innerHTML = userProfile.name; + $("u"+ userProfile.id +"UserProfileName").textContent = userProfile.name; setUserImg(true, userProfile.disabled, $("u"+ userProfile.id +"Img")); console.log("u"+ editingUserProfileId +"Img") } diff --git a/WebContent/WEB-INF/jsp/watchList.jsp b/WebContent/WEB-INF/jsp/watchList.jsp index d88338f524..e6b0e9d25a 100644 --- a/WebContent/WEB-INF/jsp/watchList.jsp +++ b/WebContent/WEB-INF/jsp/watchList.jsp @@ -120,14 +120,16 @@ function addPointNames(folder) { var i; for (i=0; i "+ folder.name, + title: " "+ safe.innerHTML, isFolder: "true", lazyLoadData: folder }); @@ -154,7 +156,7 @@ function addPoint(point, parent) { var spanNode = document.createElement("span"); spanNode.id = 'ph'+ point.key +'Name'; - spanNode.textContent = point.value; + spanNode.textContent = unescapeHtml(point.value); var pointNode = dojo.widget.createWidget("TreeNode", { title: " " + spanNode.innerHTML + "", diff --git a/src/br/org/scadabr/vo/usersProfiles/UsersProfileVO.java b/src/br/org/scadabr/vo/usersProfiles/UsersProfileVO.java index 7b1856fa49..41948c34bf 100644 --- a/src/br/org/scadabr/vo/usersProfiles/UsersProfileVO.java +++ b/src/br/org/scadabr/vo/usersProfiles/UsersProfileVO.java @@ -14,13 +14,14 @@ import com.serotonin.mango.vo.dataSource.DataSourceVO; import com.serotonin.mango.vo.permission.DataPointAccess; import org.scada_lts.mango.service.DataSourceService; +import org.scada_lts.web.dwr.security.NoEscape; @JsonRemoteEntity public class UsersProfileVO implements Cloneable, JsonSerializable { public static final String XID_PREFIX = "UP_"; - @JsonRemoteProperty + @JsonRemoteProperty @NoEscape private String name; @JsonRemoteProperty From c434525f9b411f9fcadb253120ee06013d7a6dd5 Mon Sep 17 00:00:00 2001 From: patrykb0802 Date: Fri, 24 Oct 2025 12:36:58 +0200 Subject: [PATCH 10/53] #3209 Fixed escaping fields in mailing lists, maintenance events, point links, publishers --- WebContent/WEB-INF/jsp/mailingLists.jsp | 2 +- WebContent/WEB-INF/jsp/maintenanceEvents.jsp | 5 ++++- WebContent/WEB-INF/jsp/pointLinks.jsp | 6 ++++-- WebContent/WEB-INF/jsp/publisherEdit.jsp | 2 +- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/WebContent/WEB-INF/jsp/mailingLists.jsp b/WebContent/WEB-INF/jsp/mailingLists.jsp index 282308c3c7..7105d607c6 100644 --- a/WebContent/WEB-INF/jsp/mailingLists.jsp +++ b/WebContent/WEB-INF/jsp/mailingLists.jsp @@ -195,7 +195,7 @@ function appendUserEntry(userEntry) { var content = createFromTemplate("mleUser_TEMPLATE_", userEntry.referenceId, "mailingListEntriesTable"); setUserImg(userEntry.user.admin, userEntry.user.disabled, $("mle"+ userEntry.referenceId +"Img")); - $("mle"+ userEntry.referenceId +"Username").innerHTML = userEntry.user.username; + $("mle"+ userEntry.referenceId +"Username").textContent = userEntry.user.username; } function deleteUserEntry(entryId) { diff --git a/WebContent/WEB-INF/jsp/maintenanceEvents.jsp b/WebContent/WEB-INF/jsp/maintenanceEvents.jsp index 931e70fee5..778258f424 100644 --- a/WebContent/WEB-INF/jsp/maintenanceEvents.jsp +++ b/WebContent/WEB-INF/jsp/maintenanceEvents.jsp @@ -53,7 +53,10 @@ oncedays[oncedays.length] = new OptionData(i, i); MaintenanceEventsDwr.getMaintenanceEvents(function(response) { - dwr.util.addOptions("dataSourceId", response.data.dataSources, "key", "value"); + var ds = response.data.dataSources.map(function(o) { + return { key: o.key, value: unescapeHtml(o.value) }; + }); + dwr.util.addOptions("dataSourceId", ds, "key", "value"); var events = response.data.events; for (var i=0; i ({ key: p.key, value: unescapeHtml(p.value) })); + const tgtOpts = response.targetPoints.map(p => ({ key: p.key, value: unescapeHtml(p.value) })); sourcePoints = response.sourcePoints; // Add points to source and target selects - dwr.util.addOptions("sourcePointId", response.sourcePoints, "key", "value"); + dwr.util.addOptions("sourcePointId", srcOpts, "key", "value"); jQuery("#sourcePointId").chosen({ allow_single_deselect: true, placeholder_text_single: "", search_contains: true, width: "100%" }); - dwr.util.addOptions("targetPointId", response.targetPoints, "key", "value"); + dwr.util.addOptions("targetPointId", tgtOpts, "key", "value"); jQuery("#targetPointId").chosen({ allow_single_deselect: true, placeholder_text_single: "", diff --git a/WebContent/WEB-INF/jsp/publisherEdit.jsp b/WebContent/WEB-INF/jsp/publisherEdit.jsp index e6d5528553..cda8c32e75 100644 --- a/WebContent/WEB-INF/jsp/publisherEdit.jsp +++ b/WebContent/WEB-INF/jsp/publisherEdit.jsp @@ -95,7 +95,7 @@ From 032165e3e30e2b341905f1c3a8b8e3ba5519db7a Mon Sep 17 00:00:00 2001 From: patrykb0802 Date: Fri, 24 Oct 2025 14:00:41 +0200 Subject: [PATCH 11/53] #3197 CVE-2021-26829 Mitigation Guidance [System settings] --- WebContent/WEB-INF/tags/page.tag | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/WebContent/WEB-INF/tags/page.tag b/WebContent/WEB-INF/tags/page.tag index 6c28a5b385..1e74c63756 100644 --- a/WebContent/WEB-INF/tags/page.tag +++ b/WebContent/WEB-INF/tags/page.tag @@ -30,7 +30,9 @@ <c:choose> - <c:when test="${!empty instanceDescriptionHeader}">${instanceDescriptionHeader}</c:when> + <c:when test="${!empty instanceDescriptionHeader}"> + <c:out value="${instanceDescriptionHeader}"/> + </c:when> <c:otherwise><spring:message code="header.title"/></c:otherwise> </c:choose> @@ -218,7 +220,9 @@ From d5d0155829ac0ff4fbada52cd199aa72ac20b0f5 Mon Sep 17 00:00:00 2001 From: Kamil Jarmusik Date: Mon, 27 Oct 2025 10:14:38 +0100 Subject: [PATCH 12/53] #3167 fixed escaping fields: - Fixed XssProtectUtils.unescapeNewLineAndWhitespace, XssProtectUtils.escapeNewLineAndWhitespace --- src/org/scada_lts/web/security/XssProtectUtils.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/org/scada_lts/web/security/XssProtectUtils.java b/src/org/scada_lts/web/security/XssProtectUtils.java index 8f1818d5a3..5b0f7563b2 100644 --- a/src/org/scada_lts/web/security/XssProtectUtils.java +++ b/src/org/scada_lts/web/security/XssProtectUtils.java @@ -41,7 +41,7 @@ private static String escapeNewLineAndWhitespace(String content) { for(Map.Entry entry: newLineAndWhitespaceCodes.entrySet()) { result = result.replace(entry.getValue(), entry.getKey()); } - return content; + return result; } private static String unescapeNewLineAndWhitespace(String content) { @@ -49,6 +49,6 @@ private static String unescapeNewLineAndWhitespace(String content) { for(Map.Entry entry: newLineAndWhitespaceCodes.entrySet()) { result = result.replace(entry.getKey(), entry.getValue()); } - return content; + return result; } } From 7857b12b90dc3544f3d4e49ac7666a7b07a10883 Mon Sep 17 00:00:00 2001 From: patrykb0802 Date: Mon, 27 Oct 2025 11:32:55 +0100 Subject: [PATCH 13/53] #3206 Fixed escaping fields in Event handlers, Users profiles, watch lists --- WebContent/WEB-INF/dwr.xml | 13 ++++++------- WebContent/WEB-INF/jsp/eventHandlers.jsp | 4 ++-- WebContent/WEB-INF/jsp/usersProfiles.jsp | 4 ++-- WebContent/WEB-INF/jsp/watchList.jsp | 4 +--- .../scadabr/vo/usersProfiles/UsersProfileVO.java | 3 +-- 5 files changed, 12 insertions(+), 16 deletions(-) diff --git a/WebContent/WEB-INF/dwr.xml b/WebContent/WEB-INF/dwr.xml index 71055791da..f0065d85d1 100644 --- a/WebContent/WEB-INF/dwr.xml +++ b/WebContent/WEB-INF/dwr.xml @@ -171,15 +171,9 @@ - - - - - - @@ -253,7 +247,12 @@ - + + + + + + diff --git a/WebContent/WEB-INF/jsp/eventHandlers.jsp b/WebContent/WEB-INF/jsp/eventHandlers.jsp index eb7c280ade..6daa0b55da 100644 --- a/WebContent/WEB-INF/jsp/eventHandlers.jsp +++ b/WebContent/WEB-INF/jsp/eventHandlers.jsp @@ -205,13 +205,13 @@ } function createEventTypeNode(widgetId, eventType, parent) { + console.log("eventType.description: " + eventType.description); var node = dojo.widget.createWidget("TreeNode", { - title: " ", + title: " " + eventType.description + "", widgetId: widgetId, object: eventType }); parent.addChild(node); - $(widgetId + "Txt").textContent = eventType.description; setAlarmLevelImg(eventType.alarmLevel, $(widgetId +"Img")); addHandlerNodes(eventType.handlers, node); } diff --git a/WebContent/WEB-INF/jsp/usersProfiles.jsp b/WebContent/WEB-INF/jsp/usersProfiles.jsp index 71794a97d8..7166604389 100644 --- a/WebContent/WEB-INF/jsp/usersProfiles.jsp +++ b/WebContent/WEB-INF/jsp/usersProfiles.jsp @@ -86,7 +86,7 @@ if(isUnnamedWatchList(keyTranslations, watchlists[i].name)) continue; id = watchlists[i].id; - wlhtml += '
'; + wlhtml += '
'; wlhtml += '
'; wlhtml += '
- +
'; wlhtml += ''; @@ -110,7 +110,7 @@ if (views != null){ for (i=0; i '+ escapeHtml(views[i].name) +'
'; + vwhtml += '
'; vwhtml += '
'; vwhtml += '
'; vwhtml += ''; diff --git a/WebContent/WEB-INF/jsp/watchList.jsp b/WebContent/WEB-INF/jsp/watchList.jsp index e6b0e9d25a..24a052a880 100644 --- a/WebContent/WEB-INF/jsp/watchList.jsp +++ b/WebContent/WEB-INF/jsp/watchList.jsp @@ -126,10 +126,8 @@ } function addFolder(folder, parent) { - const safe = document.createElement('span'); - safe.textContent = unescapeHtml(folder.name); var folderNode = dojo.widget.createWidget("TreeNode", { - title: " "+ safe.innerHTML, + title: "" + folder.name + "", isFolder: "true", lazyLoadData: folder }); diff --git a/src/br/org/scadabr/vo/usersProfiles/UsersProfileVO.java b/src/br/org/scadabr/vo/usersProfiles/UsersProfileVO.java index 41948c34bf..7b1856fa49 100644 --- a/src/br/org/scadabr/vo/usersProfiles/UsersProfileVO.java +++ b/src/br/org/scadabr/vo/usersProfiles/UsersProfileVO.java @@ -14,14 +14,13 @@ import com.serotonin.mango.vo.dataSource.DataSourceVO; import com.serotonin.mango.vo.permission.DataPointAccess; import org.scada_lts.mango.service.DataSourceService; -import org.scada_lts.web.dwr.security.NoEscape; @JsonRemoteEntity public class UsersProfileVO implements Cloneable, JsonSerializable { public static final String XID_PREFIX = "UP_"; - @JsonRemoteProperty @NoEscape + @JsonRemoteProperty private String name; @JsonRemoteProperty From 36df160a919d2839e1667e25bf945e640aaa2999 Mon Sep 17 00:00:00 2001 From: patrykb0802 Date: Mon, 27 Oct 2025 11:40:21 +0100 Subject: [PATCH 14/53] #3209 Fixed escaping fields in mailing lists, maintenance events, point links, publishers --- WebContent/WEB-INF/jsp/pointLinks.jsp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/WebContent/WEB-INF/jsp/pointLinks.jsp b/WebContent/WEB-INF/jsp/pointLinks.jsp index a7ecf20d29..694a6de922 100644 --- a/WebContent/WEB-INF/jsp/pointLinks.jsp +++ b/WebContent/WEB-INF/jsp/pointLinks.jsp @@ -28,8 +28,8 @@ function init() { PointLinksDwr.init(function(response) { - const srcOpts = response.sourcePoints.map(p => ({ key: p.key, value: unescapeHtml(p.value) })); - const tgtOpts = response.targetPoints.map(p => ({ key: p.key, value: unescapeHtml(p.value) })); + let srcOpts = response.sourcePoints.map(p => ({ key: p.key, value: unescapeHtml(p.value) })); + let tgtOpts = response.targetPoints.map(p => ({ key: p.key, value: unescapeHtml(p.value) })); sourcePoints = response.sourcePoints; // Add points to source and target selects From a6331984db20c77620e75f8942403f388ae2e04d Mon Sep 17 00:00:00 2001 From: patrykb0802 Date: Mon, 27 Oct 2025 11:52:37 +0100 Subject: [PATCH 15/53] #3206 Fixed escaping fields in Event handlers, Users profiles, watch lists --- WebContent/WEB-INF/dwr.xml | 8 ++++++-- WebContent/WEB-INF/jsp/eventHandlers.jsp | 1 - 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/WebContent/WEB-INF/dwr.xml b/WebContent/WEB-INF/dwr.xml index caa7c75fee..341412c29c 100644 --- a/WebContent/WEB-INF/dwr.xml +++ b/WebContent/WEB-INF/dwr.xml @@ -171,9 +171,15 @@ + + + + + + @@ -250,8 +256,6 @@ - - diff --git a/WebContent/WEB-INF/jsp/eventHandlers.jsp b/WebContent/WEB-INF/jsp/eventHandlers.jsp index 6daa0b55da..706794b5b9 100644 --- a/WebContent/WEB-INF/jsp/eventHandlers.jsp +++ b/WebContent/WEB-INF/jsp/eventHandlers.jsp @@ -205,7 +205,6 @@ } function createEventTypeNode(widgetId, eventType, parent) { - console.log("eventType.description: " + eventType.description); var node = dojo.widget.createWidget("TreeNode", { title: " " + eventType.description + "", widgetId: widgetId, From 114fce88e34b0587d44f946a471429a08843317b Mon Sep 17 00:00:00 2001 From: patrykb0802 Date: Mon, 27 Oct 2025 11:54:29 +0100 Subject: [PATCH 16/53] #3209 Fixed escaping fields in mailing lists, maintenance events, point links, publishers --- WebContent/WEB-INF/dwr.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WebContent/WEB-INF/dwr.xml b/WebContent/WEB-INF/dwr.xml index 341412c29c..4891bce58e 100644 --- a/WebContent/WEB-INF/dwr.xml +++ b/WebContent/WEB-INF/dwr.xml @@ -172,7 +172,7 @@ - + From 53abe053dfeba35568462fd64172348f77a20753 Mon Sep 17 00:00:00 2001 From: patrykb0802 Date: Mon, 27 Oct 2025 13:51:23 +0100 Subject: [PATCH 17/53] #3206 Fixed escaping fields in Event handlers, Users profiles, watch lists --- WebContent/WEB-INF/dwr.xml | 25 +++++++++++------------- WebContent/WEB-INF/jsp/eventHandlers.jsp | 9 ++++++--- WebContent/WEB-INF/jsp/users.jsp | 2 +- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/WebContent/WEB-INF/dwr.xml b/WebContent/WEB-INF/dwr.xml index 4891bce58e..f194894945 100644 --- a/WebContent/WEB-INF/dwr.xml +++ b/WebContent/WEB-INF/dwr.xml @@ -129,7 +129,7 @@ - + @@ -137,14 +137,14 @@ - + - + - - + + - + @@ -155,7 +155,7 @@ - + @@ -180,7 +180,7 @@ - + @@ -241,30 +241,27 @@ - - - - - - + + + ", data.mailingLists, data.users); emailRecipients.write("emailRecipients", "emailRecipients", null, @@ -206,11 +208,12 @@ function createEventTypeNode(widgetId, eventType, parent) { var node = dojo.widget.createWidget("TreeNode", { - title: " " + eventType.description + "", + title: " ", widgetId: widgetId, object: eventType }); parent.addChild(node); + $(widgetId + "Txt").textContent = eventType.description; setAlarmLevelImg(eventType.alarmLevel, $(widgetId +"Img")); addHandlerNodes(eventType.handlers, node); } diff --git a/WebContent/WEB-INF/jsp/users.jsp b/WebContent/WEB-INF/jsp/users.jsp index 50833c25a5..7116b6b5e3 100644 --- a/WebContent/WEB-INF/jsp/users.jsp +++ b/WebContent/WEB-INF/jsp/users.jsp @@ -323,7 +323,7 @@ function updateUser(response) { var user = response.data ? response.data.user : response.user; - $("u"+ user.id +"Username").textContent = user.username; + $("u"+ user.id +"Username").innerHTML = user.username; setUserImg(user.admin, user.disabled, $("u"+ user.id +"Img")); } From dbbdeacf84c194cb12030cfff0a8c8f9d647fe2b Mon Sep 17 00:00:00 2001 From: patrykb0802 Date: Mon, 27 Oct 2025 13:52:15 +0100 Subject: [PATCH 18/53] #3206 Fixed escaping fields in Event handlers, Users profiles, watch lists --- WebContent/WEB-INF/jsp/eventHandlers.jsp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/WebContent/WEB-INF/jsp/eventHandlers.jsp b/WebContent/WEB-INF/jsp/eventHandlers.jsp index 68f400133f..63dd2737e2 100644 --- a/WebContent/WEB-INF/jsp/eventHandlers.jsp +++ b/WebContent/WEB-INF/jsp/eventHandlers.jsp @@ -79,10 +79,8 @@ var pointNode, dataSourceNode, publisherNode, etNode, wid; allPoints = data.allPoints; - console.log("data.mailingLists:" + JSON.stringify(data.mailingLists)); - console.log("data.users:" + JSON.stringify(data.users)); - emailRecipients = new mango.erecip.EmailRecipients("emailRecipients", + emailRecipients = new mango.erecip.EmailRecipients("emailRecipients", "", data.mailingLists, data.users); emailRecipients.write("emailRecipients", "emailRecipients", null, From 44a57fcefaa58f20300e3ea45a6be94e7963db85 Mon Sep 17 00:00:00 2001 From: patrykb0802 Date: Mon, 27 Oct 2025 16:20:01 +0100 Subject: [PATCH 19/53] #3206 Fixed escaping fields in Event handlers, Users profiles, watch lists --- WebContent/WEB-INF/dwr.xml | 21 ++++++-------- WebContent/WEB-INF/jsp/eventHandlers.jsp | 3 +- .../XssLocalizableMessageConverter.java | 28 +++++++++++++++++++ 3 files changed, 37 insertions(+), 15 deletions(-) create mode 100644 src/org/scada_lts/web/dwr/security/XssLocalizableMessageConverter.java diff --git a/WebContent/WEB-INF/dwr.xml b/WebContent/WEB-INF/dwr.xml index f194894945..3ef2367436 100644 --- a/WebContent/WEB-INF/dwr.xml +++ b/WebContent/WEB-INF/dwr.xml @@ -20,7 +20,7 @@ - + @@ -138,18 +138,18 @@ - + - + - + - + - + @@ -182,14 +182,14 @@ - + - + @@ -240,20 +240,15 @@ - - - - - diff --git a/WebContent/WEB-INF/jsp/eventHandlers.jsp b/WebContent/WEB-INF/jsp/eventHandlers.jsp index 63dd2737e2..f5b089c1da 100644 --- a/WebContent/WEB-INF/jsp/eventHandlers.jsp +++ b/WebContent/WEB-INF/jsp/eventHandlers.jsp @@ -206,12 +206,11 @@ function createEventTypeNode(widgetId, eventType, parent) { var node = dojo.widget.createWidget("TreeNode", { - title: " ", + title: " "+ eventType.description, widgetId: widgetId, object: eventType }); parent.addChild(node); - $(widgetId + "Txt").textContent = eventType.description; setAlarmLevelImg(eventType.alarmLevel, $(widgetId +"Img")); addHandlerNodes(eventType.handlers, node); } diff --git a/src/org/scada_lts/web/dwr/security/XssLocalizableMessageConverter.java b/src/org/scada_lts/web/dwr/security/XssLocalizableMessageConverter.java new file mode 100644 index 0000000000..2839bb0ff3 --- /dev/null +++ b/src/org/scada_lts/web/dwr/security/XssLocalizableMessageConverter.java @@ -0,0 +1,28 @@ +package org.scada_lts.web.dwr.security; + +import com.serotonin.web.i18n.I18NUtils; +import com.serotonin.web.i18n.LocalizableMessage; +import org.directwebremoting.WebContextFactory; +import org.directwebremoting.convert.StringConverter; +import org.directwebremoting.extend.MarshallException; +import org.directwebremoting.extend.OutboundContext; +import org.directwebremoting.extend.OutboundVariable; +import org.scada_lts.web.security.XssProtectUtils; + +public class XssLocalizableMessageConverter extends StringConverter { + + @Override + public OutboundVariable convertOutbound(Object data, OutboundContext outctx) throws MarshallException { + if (data == null) { + return super.convertOutbound("", outctx); + } + LocalizableMessage lm = (LocalizableMessage) data; + + String localized = lm.getLocalizedMessage( + I18NUtils.getBundle(WebContextFactory.get().getHttpServletRequest()) + ); + + String escaped = XssProtectUtils.escapeHtml(localized); + return super.convertOutbound(escaped, outctx); + } +} From 2b2db635f8cfae8f568b960476219fa0e17b81b0 Mon Sep 17 00:00:00 2001 From: patrykb0802 Date: Mon, 27 Oct 2025 16:36:35 +0100 Subject: [PATCH 20/53] #3206 Fixed escaping fields in Event handlers, Users profiles, watch lists --- .../XssLocalizableMessageConverter.java | 34 ++++++++++--------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/src/org/scada_lts/web/dwr/security/XssLocalizableMessageConverter.java b/src/org/scada_lts/web/dwr/security/XssLocalizableMessageConverter.java index 2839bb0ff3..1f5788f2b9 100644 --- a/src/org/scada_lts/web/dwr/security/XssLocalizableMessageConverter.java +++ b/src/org/scada_lts/web/dwr/security/XssLocalizableMessageConverter.java @@ -2,27 +2,29 @@ import com.serotonin.web.i18n.I18NUtils; import com.serotonin.web.i18n.LocalizableMessage; +import org.directwebremoting.WebContext; import org.directwebremoting.WebContextFactory; import org.directwebremoting.convert.StringConverter; -import org.directwebremoting.extend.MarshallException; -import org.directwebremoting.extend.OutboundContext; -import org.directwebremoting.extend.OutboundVariable; -import org.scada_lts.web.security.XssProtectUtils; +import org.directwebremoting.extend.*; -public class XssLocalizableMessageConverter extends StringConverter { +import static org.scada_lts.web.dwr.security.utils.XssBeanConverterUtils.convertObjectEscaped; +import static org.scada_lts.web.dwr.security.utils.XssBeanConverterUtils.convertObjectUnescaped; - @Override - public OutboundVariable convertOutbound(Object data, OutboundContext outctx) throws MarshallException { - if (data == null) { - return super.convertOutbound("", outctx); - } - LocalizableMessage lm = (LocalizableMessage) data; +public class XssLocalizableMessageConverter extends StringConverter { + public XssLocalizableMessageConverter() { + } - String localized = lm.getLocalizedMessage( - I18NUtils.getBundle(WebContextFactory.get().getHttpServletRequest()) - ); + public OutboundVariable convertOutbound(Object data, OutboundContext outctx) throws MarshallException { + WebContext webctx = WebContextFactory.get(); + LocalizableMessage lm = (LocalizableMessage)data; + convertObjectEscaped(data); + String s = lm.getLocalizedMessage(I18NUtils.getBundle(webctx.getHttpServletRequest())); + return super.convertOutbound(s, outctx); + } - String escaped = XssProtectUtils.escapeHtml(localized); - return super.convertOutbound(escaped, outctx); + @Override + public Object convertInbound(Class paramType, InboundVariable iv, InboundContext inctx) throws MarshallException { + convertObjectUnescaped(iv.getValue()); + return super.convertInbound(paramType, iv, inctx); } } From a2bd771a7cdc9455134c93a21e61c647e15fb9cc Mon Sep 17 00:00:00 2001 From: Kamil Jarmusik Date: Tue, 28 Oct 2025 01:58:15 +0100 Subject: [PATCH 21/53] #3167 fixed escaping fields - Moved annotation NoEscape, classes XssBeanConverter, XssBeanConverterUtils to org\scada_lts\web\security\dwr package; - Converter support only complex type; - Added tests: XssBeanConverterUtilsEscapedTest, XssBeanConverterUtilsExceptionTest, XssBeanConverterUtilsTest; - Removed annotation @NoEscape for ScriptVO.script; --- WebContent/WEB-INF/dwr.xml | 2 +- build.gradle | 4 +- src/br/org/scadabr/vo/scripting/ScriptVO.java | 3 +- .../security => security/dwr}/NoEscape.java | 2 +- .../security/dwr/ScadaMarshallException.java | 18 ++++++++ .../dwr}/XssBeanConverter.java | 6 +-- .../dwr}/XssBeanConverterUtils.java | 44 ++++++++++++------ .../XssBeanConverterUtilsEscapedTest.java} | 10 ++-- .../XssBeanConverterUtilsExceptionTest.java | 44 ++++++++++++++++++ .../dwr/XssBeanConverterUtilsTest.java | 46 +++++++++++++++++++ .../dwr/XssBeanConverterUtilsTestsSuite.java | 13 ++++++ 11 files changed, 165 insertions(+), 27 deletions(-) rename src/org/scada_lts/web/{dwr/security => security/dwr}/NoEscape.java (88%) create mode 100644 src/org/scada_lts/web/security/dwr/ScadaMarshallException.java rename src/org/scada_lts/web/{dwr/security => security/dwr}/XssBeanConverter.java (72%) rename src/org/scada_lts/web/{dwr/security/utils => security/dwr}/XssBeanConverterUtils.java (72%) rename test/org/scada_lts/web/{dwr/security/utils/XssBeanConverterUtilsTest.java => security/dwr/XssBeanConverterUtilsEscapedTest.java} (70%) create mode 100644 test/org/scada_lts/web/security/dwr/XssBeanConverterUtilsExceptionTest.java create mode 100644 test/org/scada_lts/web/security/dwr/XssBeanConverterUtilsTest.java create mode 100644 test/org/scada_lts/web/security/dwr/XssBeanConverterUtilsTestsSuite.java diff --git a/WebContent/WEB-INF/dwr.xml b/WebContent/WEB-INF/dwr.xml index 726931a91a..215d8053f4 100644 --- a/WebContent/WEB-INF/dwr.xml +++ b/WebContent/WEB-INF/dwr.xml @@ -23,7 +23,7 @@ - + diff --git a/build.gradle b/build.gradle index 5cf3e56cbb..14ade0840c 100644 --- a/build.gradle +++ b/build.gradle @@ -241,14 +241,14 @@ test { includeTestsMatching "com.serotonin.mango.util.StartStopDataPointsUtilsTestsSuite" includeTestsMatching "org.scada_lts.utils.BlockingQueuesUtilsTest" includeTestsMatching "org.scada_lts.web.security.XssValidatorUtilsTestsSuite" - includeTestsMatching "org.scada_lts.web.mvc.api.validation.css.CssValidatorTestsSuite" + includeTestsMatching "org.scada_lts.web.beans.validation.css.CssValidatorTestsSuite1" includeTestsMatching "org.scada_lts.web.beans.validation.xss.XssValidatorTestsSuite" includeTestsMatching "org.scada_lts.utils.CyclicDependencyValidationUtilsTest" includeTestsMatching "org.scada_lts.ds.polling.protocol.opcua.vo.OpcUaDataTypeTestsSuite" includeTestsMatching "com.serotonin.mango.vo.EngineeringUnitsTypesTest" includeTestsMatching "org.scada_lts.cache.PointHierarchyCacheTestsSuite" includeTestsMatching "com.serotonin.mango.rt.dataImage.types.BinaryValueTestsSuite" - includeTestsMatching "org.scada_lts.web.dwr.security.utils.XssBeanConverterUtilsTest" + includeTestsMatching "org.scada_lts.web.security.dwr.XssBeanConverterUtilsTestsSuite" } failFast = true diff --git a/src/br/org/scadabr/vo/scripting/ScriptVO.java b/src/br/org/scadabr/vo/scripting/ScriptVO.java index 507e24f0a3..c4231e96f2 100644 --- a/src/br/org/scadabr/vo/scripting/ScriptVO.java +++ b/src/br/org/scadabr/vo/scripting/ScriptVO.java @@ -18,7 +18,6 @@ import com.serotonin.mango.Common; import com.serotonin.mango.vo.GetExtendedName; import com.serotonin.mango.vo.User; -import org.scada_lts.web.dwr.security.NoEscape; import com.serotonin.util.StringUtils; import com.serotonin.web.dwr.DwrResponseI18n; import org.scada_lts.mango.service.ScriptService; @@ -98,7 +97,7 @@ public static List getTypeList() { private String xid; @JsonRemoteProperty private String name; - @JsonRemoteProperty @NoEscape + @JsonRemoteProperty private String script; private int userId; diff --git a/src/org/scada_lts/web/dwr/security/NoEscape.java b/src/org/scada_lts/web/security/dwr/NoEscape.java similarity index 88% rename from src/org/scada_lts/web/dwr/security/NoEscape.java rename to src/org/scada_lts/web/security/dwr/NoEscape.java index 03485af170..6c4a7dbb65 100644 --- a/src/org/scada_lts/web/dwr/security/NoEscape.java +++ b/src/org/scada_lts/web/security/dwr/NoEscape.java @@ -1,4 +1,4 @@ -package org.scada_lts.web.dwr.security; +package org.scada_lts.web.security.dwr; import java.lang.annotation.*; diff --git a/src/org/scada_lts/web/security/dwr/ScadaMarshallException.java b/src/org/scada_lts/web/security/dwr/ScadaMarshallException.java new file mode 100644 index 0000000000..65ef79eef1 --- /dev/null +++ b/src/org/scada_lts/web/security/dwr/ScadaMarshallException.java @@ -0,0 +1,18 @@ +package org.scada_lts.web.security.dwr; + +import org.directwebremoting.extend.MarshallException; + +public class ScadaMarshallException extends MarshallException { + + public ScadaMarshallException(Class paramType) { + super(paramType); + } + + public ScadaMarshallException(Class paramType, Throwable ex) { + super(paramType, ex); + } + + public ScadaMarshallException(Class paramType, String message) { + super(paramType, message); + } +} diff --git a/src/org/scada_lts/web/dwr/security/XssBeanConverter.java b/src/org/scada_lts/web/security/dwr/XssBeanConverter.java similarity index 72% rename from src/org/scada_lts/web/dwr/security/XssBeanConverter.java rename to src/org/scada_lts/web/security/dwr/XssBeanConverter.java index a137f04c4c..dbd0d04523 100644 --- a/src/org/scada_lts/web/dwr/security/XssBeanConverter.java +++ b/src/org/scada_lts/web/security/dwr/XssBeanConverter.java @@ -1,10 +1,10 @@ -package org.scada_lts.web.dwr.security; +package org.scada_lts.web.security.dwr; import org.directwebremoting.convert.BeanConverter; import org.directwebremoting.extend.*; -import static org.scada_lts.web.dwr.security.utils.XssBeanConverterUtils.convertObjectEscaped; -import static org.scada_lts.web.dwr.security.utils.XssBeanConverterUtils.convertObjectUnescaped; +import static org.scada_lts.web.security.dwr.XssBeanConverterUtils.convertObjectEscaped; +import static org.scada_lts.web.security.dwr.XssBeanConverterUtils.convertObjectUnescaped; public class XssBeanConverter extends BeanConverter { diff --git a/src/org/scada_lts/web/dwr/security/utils/XssBeanConverterUtils.java b/src/org/scada_lts/web/security/dwr/XssBeanConverterUtils.java similarity index 72% rename from src/org/scada_lts/web/dwr/security/utils/XssBeanConverterUtils.java rename to src/org/scada_lts/web/security/dwr/XssBeanConverterUtils.java index 6f846cd80a..656506e124 100644 --- a/src/org/scada_lts/web/dwr/security/utils/XssBeanConverterUtils.java +++ b/src/org/scada_lts/web/security/dwr/XssBeanConverterUtils.java @@ -1,16 +1,16 @@ -package org.scada_lts.web.dwr.security.utils; +package org.scada_lts.web.security.dwr; import com.serotonin.mango.util.LoggingUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.scada_lts.web.dwr.security.NoEscape; import org.scada_lts.web.security.XssProtectUtils; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.util.*; -import java.util.function.BiFunction; +import java.util.function.BiConsumer; import java.util.function.Function; +import java.util.function.Predicate; import static java.lang.reflect.Modifier.isStatic; @@ -20,23 +20,42 @@ private XssBeanConverterUtils() {} private static final Logger LOG = LogManager.getLogger(XssBeanConverterUtils.class); - public static void convertObjectEscaped(Object value) { - convert(value, (object, noEscape) -> convertObject(object, noEscape, XssBeanConverterUtils::escapeIfString)); + public static void convertObjectEscaped(Object value) throws ScadaMarshallException { + doConvertIf(value, isSimpleJavaType().negate(), + XssBeanConverterUtils::doConvert, + object -> convertObject(object, XssBeanConverterUtils::escapeIfString)); } - public static void convertObjectUnescaped(Object value) { - convert(value, (object, noEscape) -> convertObject(object, noEscape, XssBeanConverterUtils::unescapeIfString)); + public static void convertObjectUnescaped(Object value) throws ScadaMarshallException { + doConvertIf(value, isSimpleJavaType().negate(), + XssBeanConverterUtils::doConvert, + object -> convertObject(object, XssBeanConverterUtils::unescapeIfString)); } - private static void convert(Object object, BiFunction convert) { + private static void doConvertIf(Object object, Predicate doIf, + BiConsumer> doConvert, + Function converter) throws ScadaMarshallException { + if(doIf.test(object)) { + doConvert.accept(object, converter); + } else { + throw new ScadaMarshallException(object.getClass()); + } + } + + public static Predicate isSimpleJavaType() { + return object -> object instanceof Boolean || object instanceof String || object instanceof Character + || object instanceof Number || object.getClass().isPrimitive(); + } + + private static void doConvert(Object object, Function convert) { Field[] declaredFields = object.getClass().getDeclaredFields(); for(Field field: declaredFields) { - if(!isStatic(field.getModifiers())) { + if(!isStatic(field.getModifiers()) && !field.isAnnotationPresent(NoEscape.class)) { try { field.setAccessible(true); Object value = field.get(object); if(value != null) { - Object converted = convert.apply(value, field.isAnnotationPresent(NoEscape.class)); + Object converted = convert.apply(value); if(converted != null) field.set(object, converted); } @@ -49,10 +68,7 @@ private static void convert(Object object, BiFunction c } } - private static Object convertObject(Object object, boolean noConvert, Function doConvert) { - - if(noConvert) - return object; + private static Object convertObject(Object object, Function doConvert) { if (object instanceof String) { return convertStringObject(object, doConvert); diff --git a/test/org/scada_lts/web/dwr/security/utils/XssBeanConverterUtilsTest.java b/test/org/scada_lts/web/security/dwr/XssBeanConverterUtilsEscapedTest.java similarity index 70% rename from test/org/scada_lts/web/dwr/security/utils/XssBeanConverterUtilsTest.java rename to test/org/scada_lts/web/security/dwr/XssBeanConverterUtilsEscapedTest.java index b537a5859d..674276504c 100644 --- a/test/org/scada_lts/web/dwr/security/utils/XssBeanConverterUtilsTest.java +++ b/test/org/scada_lts/web/security/dwr/XssBeanConverterUtilsEscapedTest.java @@ -1,18 +1,18 @@ -package org.scada_lts.web.dwr.security.utils; +package org.scada_lts.web.security.dwr; import com.serotonin.mango.vo.GetExtendedName; import org.junit.Assert; import org.junit.Test; import utils.mock.TestGetExtendedName; -public class XssBeanConverterUtilsTest { +public class XssBeanConverterUtilsEscapedTest { private final GetExtendedName object = new TestGetExtendedName(""); private final GetExtendedName objectEscaped = new TestGetExtendedName("<script>alert(1)</script>"); @Test - public void convertObjectEscaped() { + public void when_convertObjectEscaped_then_String_escaped() throws ScadaMarshallException { //when: XssBeanConverterUtils.convertObjectEscaped(object); @@ -22,11 +22,13 @@ public void convertObjectEscaped() { } @Test - public void convertObjectUnescaped() { + public void when_convertObjectUnescaped_then_String_unescaped() throws ScadaMarshallException { + //when: XssBeanConverterUtils.convertObjectUnescaped(objectEscaped); //then: Assert.assertEquals(object, objectEscaped); } + } \ No newline at end of file diff --git a/test/org/scada_lts/web/security/dwr/XssBeanConverterUtilsExceptionTest.java b/test/org/scada_lts/web/security/dwr/XssBeanConverterUtilsExceptionTest.java new file mode 100644 index 0000000000..d99e45f6fa --- /dev/null +++ b/test/org/scada_lts/web/security/dwr/XssBeanConverterUtilsExceptionTest.java @@ -0,0 +1,44 @@ +package org.scada_lts.web.security.dwr; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class XssBeanConverterUtilsExceptionTest { + + @Parameterized.Parameters(name = "{index}: value: {0}, type: {1}") + public static Object[][] data() { + return new Object[][] { + {"", String.class}, + {'o', Character.class}, + {1.0, Double.class}, + {1, Integer.class}, + {Short.valueOf("1"), Short.class}, + {1L, Long.class}, + {true, Boolean.class}, + {false, Boolean.class}, + }; + } + + private final Object value; + + public XssBeanConverterUtilsExceptionTest(Object value, Class type) { + this.value = value; + } + + @Test(expected = ScadaMarshallException.class) + public void when_convertObjectEscaped_for_object_with_type_no_source_then_IllegalArgumentException() throws ScadaMarshallException { + + //when: + XssBeanConverterUtils.convertObjectEscaped(value); + } + + @Test(expected = ScadaMarshallException.class) + public void when_convertObjectUnescaped_for_object_with_type_no_source_then_IllegalArgumentException() throws ScadaMarshallException { + + //when: + XssBeanConverterUtils.convertObjectUnescaped(value); + } + +} \ No newline at end of file diff --git a/test/org/scada_lts/web/security/dwr/XssBeanConverterUtilsTest.java b/test/org/scada_lts/web/security/dwr/XssBeanConverterUtilsTest.java new file mode 100644 index 0000000000..e02792831c --- /dev/null +++ b/test/org/scada_lts/web/security/dwr/XssBeanConverterUtilsTest.java @@ -0,0 +1,46 @@ +package org.scada_lts.web.security.dwr; + +import br.org.scadabr.vo.permission.WatchListAccess; +import com.serotonin.mango.vo.dataSource.DataSourceVO; +import com.serotonin.mango.vo.dataSource.virtual.VirtualDataSourceVO; +import net.sf.mbus4j.SerialPortConnection; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.scada_lts.dao.model.UserIdentifier; + +@RunWith(Parameterized.class) +public class XssBeanConverterUtilsTest { + + @Parameterized.Parameters(name = "{index}: value: {0}, type: {1}") + public static Object[][] data() { + return new Object[][] { + {new VirtualDataSourceVO(), DataSourceVO.class}, + {new WatchListAccess(), WatchListAccess.class}, + {new SerialPortConnection(), SerialPortConnection.class}, + {new UserIdentifier(), UserIdentifier.class}, + {new Exception(), Exception.class}, + }; + } + + private final Object value; + + public XssBeanConverterUtilsTest(Object value, Class type) { + this.value = value; + } + + @Test + public void when_convertObjectEscaped_for_object_with_type_no_source_then_IllegalArgumentException() throws ScadaMarshallException { + + //when: + XssBeanConverterUtils.convertObjectEscaped(value); + } + + @Test + public void when_convertObjectUnescaped_for_object_with_type_no_source_then_IllegalArgumentException() throws ScadaMarshallException { + + //when: + XssBeanConverterUtils.convertObjectUnescaped(value); + } + +} \ No newline at end of file diff --git a/test/org/scada_lts/web/security/dwr/XssBeanConverterUtilsTestsSuite.java b/test/org/scada_lts/web/security/dwr/XssBeanConverterUtilsTestsSuite.java new file mode 100644 index 0000000000..262cde9806 --- /dev/null +++ b/test/org/scada_lts/web/security/dwr/XssBeanConverterUtilsTestsSuite.java @@ -0,0 +1,13 @@ +package org.scada_lts.web.security.dwr; + +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +@RunWith(Suite.class) +@Suite.SuiteClasses({ + XssBeanConverterUtilsTest.class, + XssBeanConverterUtilsExceptionTest.class, + XssBeanConverterUtilsEscapedTest.class +}) +public class XssBeanConverterUtilsTestsSuite { +} From 69a52901e721340f6f3fe0f948a8cde90954381c Mon Sep 17 00:00:00 2001 From: Kamil Jarmusik Date: Tue, 28 Oct 2025 10:47:57 +0100 Subject: [PATCH 22/53] #3197 CVE-2021-26829 Mitigation Guidance [System settings]: - Fixed PathSecureUtils.normalizePath; - Added validation for UploadsPath and GraphicsPath; --- .../mango/service/SystemSettingsService.java | 11 +++++++++-- src/org/scada_lts/utils/PathSecureUtils.java | 16 +++++++++++++--- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/org/scada_lts/mango/service/SystemSettingsService.java b/src/org/scada_lts/mango/service/SystemSettingsService.java index d8d59ec293..ae0eaa411e 100644 --- a/src/org/scada_lts/mango/service/SystemSettingsService.java +++ b/src/org/scada_lts/mango/service/SystemSettingsService.java @@ -23,6 +23,7 @@ import org.scada_lts.serorepl.utils.DirectoryInfo; import org.scada_lts.serorepl.utils.DirectoryUtils; import org.scada_lts.serorepl.utils.StringUtils; +import org.scada_lts.utils.PathSecureUtils; import org.scada_lts.utils.SystemSettingsUtils; import org.scada_lts.web.beans.ApplicationBeans; import org.scada_lts.web.mvc.api.AggregateSettings; @@ -622,7 +623,10 @@ public void saveWorkItemsReportingMisc(boolean workItemsReportingItemsPerSecondE } public void saveResourceGraphicsPathMisc(String webResourceGraphicsPath, DwrResponseI18n response) { - if (webResourceGraphicsPath != null && (StringUtils.isEmpty(webResourceGraphicsPath) + boolean validated = PathSecureUtils.ValidationPaths.validatePath(webResourceGraphicsPath, a -> true); + if(!validated) { + response.addContextualMessage(SystemSettingsDAO.WEB_RESOURCE_GRAPHICS_PATH, "validate.invalidValue"); + } else if (webResourceGraphicsPath != null && (StringUtils.isEmpty(webResourceGraphicsPath) || (webResourceGraphicsPath.endsWith("graphics") || webResourceGraphicsPath.endsWith("graphics" + File.separator)))) { saveResourceGraphicsPathMisc(webResourceGraphicsPath); @@ -632,7 +636,10 @@ public void saveResourceGraphicsPathMisc(String webResourceGraphicsPath, DwrResp } public void saveResourceUploadsPathMisc(String webResourceUploadsPath, DwrResponseI18n response) { - if (webResourceUploadsPath != null && (StringUtils.isEmpty(webResourceUploadsPath) + boolean validated = PathSecureUtils.ValidationPaths.validatePath(webResourceUploadsPath, a -> true); + if(!validated) { + response.addContextualMessage(SystemSettingsDAO.WEB_RESOURCE_UPLOADS_PATH, "validate.invalidValue"); + } else if(webResourceUploadsPath != null && (StringUtils.isEmpty(webResourceUploadsPath) || (webResourceUploadsPath.endsWith("uploads") || webResourceUploadsPath.endsWith("uploads" + File.separator)))) { saveResourceUploadsPathMisc(webResourceUploadsPath); diff --git a/src/org/scada_lts/utils/PathSecureUtils.java b/src/org/scada_lts/utils/PathSecureUtils.java index 1e6bf0196c..6632f4a9c6 100644 --- a/src/org/scada_lts/utils/PathSecureUtils.java +++ b/src/org/scada_lts/utils/PathSecureUtils.java @@ -1,6 +1,7 @@ package org.scada_lts.utils; import com.serotonin.mango.Common; +import com.serotonin.mango.util.LoggingUtils; import org.apache.commons.io.FilenameUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -55,7 +56,12 @@ public static String normalizeSeparator(String path) { } public static Path normalizePath(String path) { - return Paths.get(normalizeSeparator(path)).toFile().getAbsoluteFile().toPath().normalize(); + try { + return Paths.get(normalizeSeparator(path)).toFile().getAbsoluteFile().toPath().normalize(); + } catch (Exception exception) { + LOG.error(LoggingUtils.exceptionInfo(exception)); + return null; + } } private static Optional normalizePath(Path path, BinaryOperator reduce) { @@ -65,7 +71,7 @@ private static Optional normalizePath(Path path, BinaryOperator redu } Path normalizedPath = getAbsoluteResourcePath(path.toString()); - if (normalizedPath.toString().isEmpty()) { + if (normalizedPath == null || normalizedPath.toString().isEmpty()) { return Optional.empty(); } @@ -185,6 +191,9 @@ public static Path getAppContextSystemFilePath() { public static Path getAbsoluteResourcePath(String path) { Path normalizedPath = PathSecureUtils.normalizePath(path); + if(normalizedPath == null) { + return null; + } if (!path.equals(normalizedPath.toString())) { Path basePath = getCatalinaHomePath(); return Path.of(basePath + File.separator + normalizeSeparator(path)); @@ -205,7 +214,8 @@ private static List getImageSystemFilePaths(Supplier getLocalPath, if (!StringUtils.isEmpty(normalizePath) && (normalizePath.endsWith(normalizeFolder) || normalizePath.endsWith(normalizeFolder + File.separator))) { Path path = getAbsoluteResourcePath(normalizePath); - createPath(path, notExistsPath(), paths::add); + if(path != null) + createPath(path, notExistsPath(), paths::add); } Path path = getAppContextSystemFilePath(normalizeFolder); createPath(path, notExistsPath(), paths::add); From 4a578783b7145d4d4cfbc9bf7550ca7cef024fa5 Mon Sep 17 00:00:00 2001 From: Kamil Jarmusik Date: Tue, 28 Oct 2025 11:11:49 +0100 Subject: [PATCH 23/53] Revert "#3206 Fixed escaping fields in Event handlers, Users profiles, watch lists" This reverts commit 2b2db635f8cfae8f568b960476219fa0e17b81b0. --- .../XssLocalizableMessageConverter.java | 34 +++++++++---------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/src/org/scada_lts/web/dwr/security/XssLocalizableMessageConverter.java b/src/org/scada_lts/web/dwr/security/XssLocalizableMessageConverter.java index 1f5788f2b9..2839bb0ff3 100644 --- a/src/org/scada_lts/web/dwr/security/XssLocalizableMessageConverter.java +++ b/src/org/scada_lts/web/dwr/security/XssLocalizableMessageConverter.java @@ -2,29 +2,27 @@ import com.serotonin.web.i18n.I18NUtils; import com.serotonin.web.i18n.LocalizableMessage; -import org.directwebremoting.WebContext; import org.directwebremoting.WebContextFactory; import org.directwebremoting.convert.StringConverter; -import org.directwebremoting.extend.*; +import org.directwebremoting.extend.MarshallException; +import org.directwebremoting.extend.OutboundContext; +import org.directwebremoting.extend.OutboundVariable; +import org.scada_lts.web.security.XssProtectUtils; -import static org.scada_lts.web.dwr.security.utils.XssBeanConverterUtils.convertObjectEscaped; -import static org.scada_lts.web.dwr.security.utils.XssBeanConverterUtils.convertObjectUnescaped; - -public class XssLocalizableMessageConverter extends StringConverter { - public XssLocalizableMessageConverter() { - } +public class XssLocalizableMessageConverter extends StringConverter { + @Override public OutboundVariable convertOutbound(Object data, OutboundContext outctx) throws MarshallException { - WebContext webctx = WebContextFactory.get(); - LocalizableMessage lm = (LocalizableMessage)data; - convertObjectEscaped(data); - String s = lm.getLocalizedMessage(I18NUtils.getBundle(webctx.getHttpServletRequest())); - return super.convertOutbound(s, outctx); - } + if (data == null) { + return super.convertOutbound("", outctx); + } + LocalizableMessage lm = (LocalizableMessage) data; - @Override - public Object convertInbound(Class paramType, InboundVariable iv, InboundContext inctx) throws MarshallException { - convertObjectUnescaped(iv.getValue()); - return super.convertInbound(paramType, iv, inctx); + String localized = lm.getLocalizedMessage( + I18NUtils.getBundle(WebContextFactory.get().getHttpServletRequest()) + ); + + String escaped = XssProtectUtils.escapeHtml(localized); + return super.convertOutbound(escaped, outctx); } } From 96836fdfa492d05558b69d9b4251cf8033c609bd Mon Sep 17 00:00:00 2001 From: Kamil Jarmusik Date: Tue, 28 Oct 2025 11:12:30 +0100 Subject: [PATCH 24/53] Revert "#3206 Fixed escaping fields in Event handlers, Users profiles, watch lists" This reverts commit 44a57fcefaa58f20300e3ea45a6be94e7963db85. --- WebContent/WEB-INF/dwr.xml | 21 ++++++++------ WebContent/WEB-INF/jsp/eventHandlers.jsp | 3 +- .../XssLocalizableMessageConverter.java | 28 ------------------- 3 files changed, 15 insertions(+), 37 deletions(-) delete mode 100644 src/org/scada_lts/web/dwr/security/XssLocalizableMessageConverter.java diff --git a/WebContent/WEB-INF/dwr.xml b/WebContent/WEB-INF/dwr.xml index 3ef2367436..f194894945 100644 --- a/WebContent/WEB-INF/dwr.xml +++ b/WebContent/WEB-INF/dwr.xml @@ -20,7 +20,7 @@ - + @@ -138,18 +138,18 @@ - + - + - + - + - + @@ -182,14 +182,14 @@ - + - + @@ -240,15 +240,20 @@ + + + + + diff --git a/WebContent/WEB-INF/jsp/eventHandlers.jsp b/WebContent/WEB-INF/jsp/eventHandlers.jsp index f5b089c1da..63dd2737e2 100644 --- a/WebContent/WEB-INF/jsp/eventHandlers.jsp +++ b/WebContent/WEB-INF/jsp/eventHandlers.jsp @@ -206,11 +206,12 @@ function createEventTypeNode(widgetId, eventType, parent) { var node = dojo.widget.createWidget("TreeNode", { - title: " "+ eventType.description, + title: " ", widgetId: widgetId, object: eventType }); parent.addChild(node); + $(widgetId + "Txt").textContent = eventType.description; setAlarmLevelImg(eventType.alarmLevel, $(widgetId +"Img")); addHandlerNodes(eventType.handlers, node); } diff --git a/src/org/scada_lts/web/dwr/security/XssLocalizableMessageConverter.java b/src/org/scada_lts/web/dwr/security/XssLocalizableMessageConverter.java deleted file mode 100644 index 2839bb0ff3..0000000000 --- a/src/org/scada_lts/web/dwr/security/XssLocalizableMessageConverter.java +++ /dev/null @@ -1,28 +0,0 @@ -package org.scada_lts.web.dwr.security; - -import com.serotonin.web.i18n.I18NUtils; -import com.serotonin.web.i18n.LocalizableMessage; -import org.directwebremoting.WebContextFactory; -import org.directwebremoting.convert.StringConverter; -import org.directwebremoting.extend.MarshallException; -import org.directwebremoting.extend.OutboundContext; -import org.directwebremoting.extend.OutboundVariable; -import org.scada_lts.web.security.XssProtectUtils; - -public class XssLocalizableMessageConverter extends StringConverter { - - @Override - public OutboundVariable convertOutbound(Object data, OutboundContext outctx) throws MarshallException { - if (data == null) { - return super.convertOutbound("", outctx); - } - LocalizableMessage lm = (LocalizableMessage) data; - - String localized = lm.getLocalizedMessage( - I18NUtils.getBundle(WebContextFactory.get().getHttpServletRequest()) - ); - - String escaped = XssProtectUtils.escapeHtml(localized); - return super.convertOutbound(escaped, outctx); - } -} From d85d2b41b9b31cc02e933c54e7a673b0ed5adc6a Mon Sep 17 00:00:00 2001 From: Kamil Jarmusik Date: Tue, 28 Oct 2025 11:30:36 +0100 Subject: [PATCH 25/53] #3167 fixed escaping fields: - Corrected dwr.xml; --- WebContent/WEB-INF/dwr.xml | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/WebContent/WEB-INF/dwr.xml b/WebContent/WEB-INF/dwr.xml index 215d8053f4..5480c72d0f 100644 --- a/WebContent/WEB-INF/dwr.xml +++ b/WebContent/WEB-INF/dwr.xml @@ -138,15 +138,15 @@ - - - + + + - + @@ -174,7 +174,7 @@ - + @@ -240,19 +240,9 @@ - - - - - - - - - - From ff266cdce172669919ae1995fb64a2274b638869 Mon Sep 17 00:00:00 2001 From: Kamil Jarmusik Date: Tue, 28 Oct 2025 11:45:22 +0100 Subject: [PATCH 26/53] #3206 Fixed escaping fields in Event handlers, Users profiles, watch lists: - Added XssLocalizableMessageConverter; --- WebContent/WEB-INF/dwr.xml | 10 +++--- WebContent/WEB-INF/jsp/eventHandlers.jsp | 3 +- .../dwr/XssLocalizableMessageConverter.java | 31 +++++++++++++++++++ 3 files changed, 37 insertions(+), 7 deletions(-) create mode 100644 src/org/scada_lts/web/security/dwr/XssLocalizableMessageConverter.java diff --git a/WebContent/WEB-INF/dwr.xml b/WebContent/WEB-INF/dwr.xml index 1f48a8a031..36b9bdd61d 100644 --- a/WebContent/WEB-INF/dwr.xml +++ b/WebContent/WEB-INF/dwr.xml @@ -20,7 +20,7 @@ - + @@ -143,13 +143,13 @@ - + - + @@ -182,14 +182,14 @@ - + - + diff --git a/WebContent/WEB-INF/jsp/eventHandlers.jsp b/WebContent/WEB-INF/jsp/eventHandlers.jsp index 63dd2737e2..f5b089c1da 100644 --- a/WebContent/WEB-INF/jsp/eventHandlers.jsp +++ b/WebContent/WEB-INF/jsp/eventHandlers.jsp @@ -206,12 +206,11 @@ function createEventTypeNode(widgetId, eventType, parent) { var node = dojo.widget.createWidget("TreeNode", { - title: " ", + title: " "+ eventType.description, widgetId: widgetId, object: eventType }); parent.addChild(node); - $(widgetId + "Txt").textContent = eventType.description; setAlarmLevelImg(eventType.alarmLevel, $(widgetId +"Img")); addHandlerNodes(eventType.handlers, node); } diff --git a/src/org/scada_lts/web/security/dwr/XssLocalizableMessageConverter.java b/src/org/scada_lts/web/security/dwr/XssLocalizableMessageConverter.java new file mode 100644 index 0000000000..10934e6655 --- /dev/null +++ b/src/org/scada_lts/web/security/dwr/XssLocalizableMessageConverter.java @@ -0,0 +1,31 @@ +package org.scada_lts.web.security.dwr; + +import com.serotonin.mango.Common; +import com.serotonin.web.i18n.LocalizableMessage; +import org.directwebremoting.WebContext; +import org.directwebremoting.WebContextFactory; +import org.directwebremoting.convert.StringConverter; +import org.directwebremoting.extend.*; + +import static org.scada_lts.web.security.dwr.XssBeanConverterUtils.convertObjectEscaped; +import static org.scada_lts.web.security.dwr.XssBeanConverterUtils.convertObjectUnescaped; + + +public class XssLocalizableMessageConverter extends StringConverter { + public XssLocalizableMessageConverter() { + } + + public OutboundVariable convertOutbound(Object data, OutboundContext outctx) throws MarshallException { + WebContext webctx = WebContextFactory.get(); + LocalizableMessage lm = (LocalizableMessage)data; + convertObjectEscaped(data); + String s = lm.getLocalizedMessage(Common.getBundle(webctx.getHttpServletRequest())); + return super.convertOutbound(s, outctx); + } + + @Override + public Object convertInbound(Class paramType, InboundVariable iv, InboundContext inctx) throws MarshallException { + convertObjectUnescaped(iv.getValue()); + return super.convertInbound(paramType, iv, inctx); + } +} From ef76c368939be743c93a8ffc40bf58b9f14dbae2 Mon Sep 17 00:00:00 2001 From: Kamil Jarmusik Date: Tue, 28 Oct 2025 11:59:49 +0100 Subject: [PATCH 27/53] #3206 Fixed escaping fields in Event handlers, Users profiles, watch lists: - Set bean converter for package com.serotonin.mango.web.dwr.beans; --- WebContent/WEB-INF/dwr.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WebContent/WEB-INF/dwr.xml b/WebContent/WEB-INF/dwr.xml index 36b9bdd61d..b3330cfcfd 100644 --- a/WebContent/WEB-INF/dwr.xml +++ b/WebContent/WEB-INF/dwr.xml @@ -182,7 +182,7 @@ - + From e2f051abbba01974de8764a66b50c9c9f93c8519 Mon Sep 17 00:00:00 2001 From: Kamil Jarmusik Date: Tue, 28 Oct 2025 12:24:34 +0100 Subject: [PATCH 28/53] #3167 fixed escaping fields: - Set bean converter for package com.serotonin.mango.vo.mailingList; --- WebContent/WEB-INF/dwr.xml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/WebContent/WEB-INF/dwr.xml b/WebContent/WEB-INF/dwr.xml index 5480c72d0f..24388ae49d 100644 --- a/WebContent/WEB-INF/dwr.xml +++ b/WebContent/WEB-INF/dwr.xml @@ -146,7 +146,7 @@ - + @@ -240,6 +240,9 @@ + + + From 874976b99e493c2b3426d3691a7aa867ffd7eaf7 Mon Sep 17 00:00:00 2001 From: Kamil Jarmusik Date: Tue, 28 Oct 2025 12:35:41 +0100 Subject: [PATCH 29/53] #3206 Fixed escaping fields in Event handlers, Users profiles, watch lists: - Set bean converter for package com.serotonin.mango.vo.event.handlers; --- WebContent/WEB-INF/dwr.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WebContent/WEB-INF/dwr.xml b/WebContent/WEB-INF/dwr.xml index e89bea1edc..a54c6f8d3d 100644 --- a/WebContent/WEB-INF/dwr.xml +++ b/WebContent/WEB-INF/dwr.xml @@ -137,7 +137,7 @@ - + From ad3f73cd742a04e2adec3b5e34d98bc73b4bd0b3 Mon Sep 17 00:00:00 2001 From: Kamil Jarmusik Date: Tue, 28 Oct 2025 12:39:25 +0100 Subject: [PATCH 30/53] #3206 Fixed escaping fields in Event handlers, Users profiles, watch lists: - Set bean converter for com.serotonin.mango.vo.publish.PublisherVO; --- WebContent/WEB-INF/dwr.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WebContent/WEB-INF/dwr.xml b/WebContent/WEB-INF/dwr.xml index a54c6f8d3d..9fca08eb54 100644 --- a/WebContent/WEB-INF/dwr.xml +++ b/WebContent/WEB-INF/dwr.xml @@ -149,7 +149,7 @@ - + From 30afb6aadfd7d9228916cd8c79742b3ff160a8cf Mon Sep 17 00:00:00 2001 From: patrykb0802 Date: Tue, 28 Oct 2025 14:29:55 +0100 Subject: [PATCH 31/53] #3206 Fixed escaping fields in Event handlers, Users profiles, watch lists --- WebContent/WEB-INF/jsp/eventHandlers.jsp | 2 +- WebContent/WEB-INF/jsp/usersProfiles.jsp | 4 ++-- WebContent/WEB-INF/jsp/watchList.jsp | 2 +- src/org/scada_lts/dao/cache/UsersProfileDaoWithCache.java | 5 ++++- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/WebContent/WEB-INF/jsp/eventHandlers.jsp b/WebContent/WEB-INF/jsp/eventHandlers.jsp index f5b089c1da..7821f07721 100644 --- a/WebContent/WEB-INF/jsp/eventHandlers.jsp +++ b/WebContent/WEB-INF/jsp/eventHandlers.jsp @@ -206,7 +206,7 @@ function createEventTypeNode(widgetId, eventType, parent) { var node = dojo.widget.createWidget("TreeNode", { - title: " "+ eventType.description, + title: " "+ eventType.description, widgetId: widgetId, object: eventType }); diff --git a/WebContent/WEB-INF/jsp/usersProfiles.jsp b/WebContent/WEB-INF/jsp/usersProfiles.jsp index 7166604389..e5e7faff76 100644 --- a/WebContent/WEB-INF/jsp/usersProfiles.jsp +++ b/WebContent/WEB-INF/jsp/usersProfiles.jsp @@ -148,7 +148,7 @@ function showUserProfileCB(userProfile) { //show($("deleteButton")); show($("userProfileDetails")); - $set("userProfileName", userProfile.name); + $set("userProfileName", unescapeHtml(userProfile.name)); if (dataSources != null){ var i, j, dscb, dp; @@ -288,7 +288,7 @@ } function updateUserProfile(userProfile) { - $("u"+ userProfile.id +"UserProfileName").textContent = userProfile.name; + $("u"+ userProfile.id +"UserProfileName").innerHTML = userProfile.name; setUserImg(true, userProfile.disabled, $("u"+ userProfile.id +"Img")); console.log("u"+ editingUserProfileId +"Img") } diff --git a/WebContent/WEB-INF/jsp/watchList.jsp b/WebContent/WEB-INF/jsp/watchList.jsp index 24a052a880..effe5613fc 100644 --- a/WebContent/WEB-INF/jsp/watchList.jsp +++ b/WebContent/WEB-INF/jsp/watchList.jsp @@ -127,7 +127,7 @@ function addFolder(folder, parent) { var folderNode = dojo.widget.createWidget("TreeNode", { - title: " " + folder.name + "", + title: " "+ folder.name, isFolder: "true", lazyLoadData: folder }); diff --git a/src/org/scada_lts/dao/cache/UsersProfileDaoWithCache.java b/src/org/scada_lts/dao/cache/UsersProfileDaoWithCache.java index 85764cb319..1f49a15e67 100644 --- a/src/org/scada_lts/dao/cache/UsersProfileDaoWithCache.java +++ b/src/org/scada_lts/dao/cache/UsersProfileDaoWithCache.java @@ -39,7 +39,10 @@ public List selectUserProfileByUserId(int userId) { @Override public List selectProfiles(int offset, int limit) { - return usersProfileCache.selectProfiles(offset, limit); + return usersProfileCache.selectProfiles(offset, limit) + .stream() + .map(UsersProfileVO::new) + .collect(Collectors.toList()); } @Override From 0c5c5d44005b4a1191676f6ffc14e275bf51b594 Mon Sep 17 00:00:00 2001 From: patrykb0802 Date: Tue, 28 Oct 2025 16:05:18 +0100 Subject: [PATCH 32/53] #3209 Fixed escaping fields in mailing lists, maintenance events, point links, publishers --- WebContent/WEB-INF/dwr.xml | 4 ++-- WebContent/WEB-INF/jsp/mailingLists.jsp | 7 +++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/WebContent/WEB-INF/dwr.xml b/WebContent/WEB-INF/dwr.xml index 5480c72d0f..6cf9734d29 100644 --- a/WebContent/WEB-INF/dwr.xml +++ b/WebContent/WEB-INF/dwr.xml @@ -248,8 +248,8 @@ - - + + diff --git a/WebContent/WEB-INF/jsp/mailingLists.jsp b/WebContent/WEB-INF/jsp/mailingLists.jsp index 4aac46e356..d2a9d3ba3b 100644 --- a/WebContent/WEB-INF/jsp/mailingLists.jsp +++ b/WebContent/WEB-INF/jsp/mailingLists.jsp @@ -98,7 +98,10 @@ if (!found) availUsers[availUsers.length] = user; } - dwr.util.addOptions($("userList"), availUsers, "id", "username"); + dwr.util.addOptions($("userList"), availUsers, + function(u) { return u.id; }, + function(u) { return unescapeHtml(u.username); } + ); } function saveMailingList() { @@ -195,7 +198,7 @@ function appendUserEntry(userEntry) { var content = createFromTemplate("mleUser_TEMPLATE_", userEntry.referenceId, "mailingListEntriesTable"); setUserImg(userEntry.user.admin, userEntry.user.disabled, $("mle"+ userEntry.referenceId +"Img")); - $("mle"+ userEntry.referenceId +"Username").textContent = userEntry.user.username; + $("mle"+ userEntry.referenceId +"Username").innerHTML = userEntry.user.username; } function deleteUserEntry(entryId) { From d62e4259926dbb60453be1f69c0772c2d0891467 Mon Sep 17 00:00:00 2001 From: Kamil Jarmusik Date: Wed, 29 Oct 2025 10:51:18 +0100 Subject: [PATCH 33/53] #3167 fixed escaping fields: - Fixed scripting.jsp; - Corrected XssBeanConverter.convertInbound, XssBeanConverter.convertOutbound --- WebContent/WEB-INF/jsp/scripting.jsp | 6 +- src/com/serotonin/mango/vo/DataPointVO.java | 8 +- .../serotonin/mango/vo/event/EventTypeVO.java | 8 +- .../mango/web/dwr/beans/DataPointBean.java | 66 +++++++++- .../scada_lts/web/security/EmptyInstance.java | 5 + .../web/security/dwr/XssBeanConverter.java | 8 +- .../security/dwr/XssBeanConverterUtils.java | 113 ++++++++++++------ .../dwr/XssBeanConverterUtilsEscapedTest.java | 47 ++++++-- .../dwr/XssBeanConverterUtilsTest.java | 90 ++++++++++++++ test/utils/mock/TestGetExtendedName.java | 37 ------ 10 files changed, 294 insertions(+), 94 deletions(-) create mode 100644 src/org/scada_lts/web/security/EmptyInstance.java delete mode 100644 test/utils/mock/TestGetExtendedName.java diff --git a/WebContent/WEB-INF/jsp/scripting.jsp b/WebContent/WEB-INF/jsp/scripting.jsp index cf42c8eaaa..27aec73cda 100644 --- a/WebContent/WEB-INF/jsp/scripting.jsp +++ b/WebContent/WEB-INF/jsp/scripting.jsp @@ -118,9 +118,9 @@ show($("scriptDetails")); editingScript = s; - setValueInNode('xid', s.xid); - setValueInNode('name', s.name); - setValueInNode('script', s.script); + setValueInNode('xid', unescapeHtml(s.xid)); + setValueInNode('name', unescapeHtml(s.name)); + setValueInNode('script', unescapeHtml(s.script)); let handlePointsContext = new ScriptPointsContext(s.pointsOnContext, pointsArray); setPointsContext(handlePointsContext); diff --git a/src/com/serotonin/mango/vo/DataPointVO.java b/src/com/serotonin/mango/vo/DataPointVO.java index ebb29e0656..46f7973f5b 100644 --- a/src/com/serotonin/mango/vo/DataPointVO.java +++ b/src/com/serotonin/mango/vo/DataPointVO.java @@ -50,6 +50,7 @@ import org.scada_lts.ds.messaging.protocol.mqtt.MqttPointLocatorVO; import org.scada_lts.mango.service.DataPointService; import org.scada_lts.utils.ColorUtils; +import org.scada_lts.web.security.EmptyInstance; import java.io.IOException; import java.io.ObjectInputStream; @@ -62,7 +63,7 @@ @JsonRemoteEntity public class DataPointVO implements Serializable, Cloneable, JsonSerializable, ChangeComparable, - ScadaValidation, GetExtendedName { + ScadaValidation, GetExtendedName, EmptyInstance { private static final long serialVersionUID = -1; public static final String XID_PREFIX = "DP_"; @@ -1165,4 +1166,9 @@ public DataPointIdentifier toIdentifier() { .dataSourceName(getDataSourceName()) .build(); } + + @Override + public DataPointVO newInstanceEmpty() { + return new DataPointVO(-1, -1, -1); + } } diff --git a/src/com/serotonin/mango/vo/event/EventTypeVO.java b/src/com/serotonin/mango/vo/event/EventTypeVO.java index 9646d2f7e3..e7242fe72f 100644 --- a/src/com/serotonin/mango/vo/event/EventTypeVO.java +++ b/src/com/serotonin/mango/vo/event/EventTypeVO.java @@ -31,8 +31,9 @@ import com.serotonin.mango.rt.event.type.SystemEventType; import com.serotonin.mango.rt.event.type.DataSourcePointEventType; import com.serotonin.web.i18n.LocalizableMessage; +import org.scada_lts.web.security.EmptyInstance; -public class EventTypeVO { +public class EventTypeVO implements EmptyInstance { /** * The type of event. @see EventType.EventSources */ @@ -173,4 +174,9 @@ public String getEventDetectorKey() { public void setEventDetectorKey(String eventDetectorKey) { this.eventDetectorKey = eventDetectorKey; } + + @Override + public EventTypeVO newInstanceEmpty() { + return new EventTypeVO(-1,-1,-1); + } } diff --git a/src/com/serotonin/mango/web/dwr/beans/DataPointBean.java b/src/com/serotonin/mango/web/dwr/beans/DataPointBean.java index 4a58a4f057..aae7ef0aa6 100644 --- a/src/com/serotonin/mango/web/dwr/beans/DataPointBean.java +++ b/src/com/serotonin/mango/web/dwr/beans/DataPointBean.java @@ -18,10 +18,17 @@ */ package com.serotonin.mango.web.dwr.beans; +import com.serotonin.mango.rt.dataSource.PointLocatorRT; import com.serotonin.mango.vo.DataPointVO; +import com.serotonin.mango.vo.dataSource.DataPointSaveHandler; +import com.serotonin.mango.vo.dataSource.PointLocatorVO; +import com.serotonin.web.dwr.DwrResponseI18n; import com.serotonin.web.i18n.LocalizableMessage; +import org.scada_lts.web.security.EmptyInstance; -public class DataPointBean { +import java.util.List; + +public class DataPointBean implements EmptyInstance { private int id; private String xid; private String name; @@ -87,4 +94,61 @@ public String getXid() { public void setXid(String xid) { this.xid = xid; } + + @Override + public DataPointBean newInstanceEmpty() { + DataPointVO dataPointVO = new DataPointVO(-1, -1, -1); + dataPointVO.setPointLocator(new PointLocatorVO() { + @Override + public int getDataTypeId() { + return 0; + } + + @Override + public LocalizableMessage getDataTypeMessage() { + return null; + } + + @Override + public LocalizableMessage getConfigurationDescription() { + return null; + } + + @Override + public boolean isSettable() { + return false; + } + + @Override + public boolean isRelinquishable() { + return false; + } + + @Override + public PointLocatorRT createRuntime() { + return null; + } + + @Override + public void validate(DwrResponseI18n response) { + + } + + @Override + public DataPointSaveHandler getDataPointSaveHandler() { + return null; + } + + @Override + public void addProperties(List list) { + + } + + @Override + public void addPropertyChanges(List list, Object o) { + + } + }); + return new DataPointBean(dataPointVO); + } } diff --git a/src/org/scada_lts/web/security/EmptyInstance.java b/src/org/scada_lts/web/security/EmptyInstance.java new file mode 100644 index 0000000000..77da767668 --- /dev/null +++ b/src/org/scada_lts/web/security/EmptyInstance.java @@ -0,0 +1,5 @@ +package org.scada_lts.web.security; + +public interface EmptyInstance { + Object newInstanceEmpty(); +} \ No newline at end of file diff --git a/src/org/scada_lts/web/security/dwr/XssBeanConverter.java b/src/org/scada_lts/web/security/dwr/XssBeanConverter.java index dbd0d04523..e7b4e7507e 100644 --- a/src/org/scada_lts/web/security/dwr/XssBeanConverter.java +++ b/src/org/scada_lts/web/security/dwr/XssBeanConverter.java @@ -10,14 +10,14 @@ public class XssBeanConverter extends BeanConverter { @Override public OutboundVariable convertOutbound(Object value, OutboundContext outctx) throws MarshallException { - convertObjectEscaped(value); - return super.convertOutbound(value, outctx); + Object escaped = convertObjectEscaped(value); + return super.convertOutbound(escaped, outctx); } @Override public Object convertInbound(Class paramType, InboundVariable iv, InboundContext inctx) throws MarshallException { - convertObjectUnescaped(iv.getValue()); - return super.convertInbound(paramType, iv, inctx); + Object converted = super.convertInbound(paramType, iv, inctx); + return convertObjectUnescaped(converted); } } diff --git a/src/org/scada_lts/web/security/dwr/XssBeanConverterUtils.java b/src/org/scada_lts/web/security/dwr/XssBeanConverterUtils.java index 656506e124..760413b668 100644 --- a/src/org/scada_lts/web/security/dwr/XssBeanConverterUtils.java +++ b/src/org/scada_lts/web/security/dwr/XssBeanConverterUtils.java @@ -3,12 +3,14 @@ import com.serotonin.mango.util.LoggingUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.scada_lts.web.security.EmptyInstance; import org.scada_lts.web.security.XssProtectUtils; +import org.springframework.util.ReflectionUtils; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.util.*; -import java.util.function.BiConsumer; +import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.Predicate; @@ -20,25 +22,32 @@ private XssBeanConverterUtils() {} private static final Logger LOG = LogManager.getLogger(XssBeanConverterUtils.class); - public static void convertObjectEscaped(Object value) throws ScadaMarshallException { - doConvertIf(value, isSimpleJavaType().negate(), + public static Object convertObjectEscaped(Object value) throws ScadaMarshallException { + return doConvertIf(value, isSimpleJavaType().negate(), XssBeanConverterUtils::doConvert, object -> convertObject(object, XssBeanConverterUtils::escapeIfString)); } - public static void convertObjectUnescaped(Object value) throws ScadaMarshallException { - doConvertIf(value, isSimpleJavaType().negate(), + public static Object convertObjectUnescaped(Object value) throws ScadaMarshallException { + return doConvertIf(value, isSimpleJavaType().negate(), XssBeanConverterUtils::doConvert, object -> convertObject(object, XssBeanConverterUtils::unescapeIfString)); } - private static void doConvertIf(Object object, Predicate doIf, - BiConsumer> doConvert, - Function converter) throws ScadaMarshallException { - if(doIf.test(object)) { - doConvert.accept(object, converter); + private static Object doConvertIf(Object object, Predicate doConvertIf, + BiFunction, Object> doConvert, + Function converter) throws ScadaMarshallException { + if(doConvertIf.test(object)) { + try { + return doConvert.apply(object, converter); + } catch (Exception e) { + LOG.error(LoggingUtils.exceptionInfo(e)); + throw e; + } } else { - throw new ScadaMarshallException(object.getClass()); + ScadaMarshallException marshallException = new ScadaMarshallException(object.getClass()); + LOG.error(LoggingUtils.exceptionInfo(marshallException)); + throw marshallException; } } @@ -47,25 +56,50 @@ public static Predicate isSimpleJavaType() { || object instanceof Number || object.getClass().isPrimitive(); } - private static void doConvert(Object object, Function convert) { - Field[] declaredFields = object.getClass().getDeclaredFields(); - for(Field field: declaredFields) { - if(!isStatic(field.getModifiers()) && !field.isAnnotationPresent(NoEscape.class)) { + private static Object doConvert(Object originObject, Function convert) { + Object object; + try { + object = newInstanceEmpty(originObject); + } catch (Exception e) { + throw new RuntimeException(new ScadaMarshallException(originObject.getClass(), e)); + } + List fields = getAllFields(originObject); + for(Field field: fields) { + if(!isStatic(field.getModifiers())) { try { field.setAccessible(true); - Object value = field.get(object); + Object value = field.get(originObject); if(value != null) { Object converted = convert.apply(value); - if(converted != null) + if(converted != null && !field.isAnnotationPresent(NoEscape.class)) field.set(object, converted); + else + field.set(object, value); } } catch (Exception e) { - LOG.error(LoggingUtils.exceptionInfo(e)); + throw new RuntimeException(new ScadaMarshallException(originObject.getClass(), e)); } finally { field.setAccessible(false); } } } + return object; + } + + private static List getAllFields(Object originObject) { + List fields = new ArrayList<>(); + ReflectionUtils.doWithFields(originObject.getClass(), fields::add); + return fields; + } + + private static Object newInstanceEmpty(Object originObject) throws Exception { + Object object; + if(originObject instanceof EmptyInstance) { + object = ((EmptyInstance) originObject).newInstanceEmpty(); + } else { + object = originObject.getClass().getConstructor().newInstance(); + } + return object; } private static Object convertObject(Object object, Function doConvert) { @@ -98,15 +132,14 @@ private static Object convertStringObject(Object stringObject, Function convert) { - Map in = (Map) mapObject; - Map out = new HashMap<>(); - - for(Object key: in.keySet().toArray()) { - Object val = in.get(key); - out.put(convert.apply(key), convert.apply(val)); - } - try { + Map in = (Map) mapObject; + Map out = new HashMap<>(); + + for(Object key: in.keySet().toArray()) { + Object val = in.get(key); + out.put(convert.apply(key), convert.apply(val)); + } Constructor ctor = mapObject.getClass().getConstructor(Map.class); return ctor.newInstance(out); } catch (Exception e) { @@ -144,18 +177,24 @@ private static Object convertListObject(Object listObject, Function ctor = listObject.getClass().getConstructor(Collection.class); return ctor.newInstance(out); } catch (Exception e) { - throw new RuntimeException(e); + LOG.error(LoggingUtils.exceptionInfo(e)); + return null; } } private static Object convertArrayObject(Object arrayObject, Function convert) { - int len = java.lang.reflect.Array.getLength(arrayObject); - Object out = java.lang.reflect.Array.newInstance(arrayObject.getClass().getComponentType(), len); - for (int i = 0; i < len; i++) { - Object elem = java.lang.reflect.Array.get(arrayObject, i); - java.lang.reflect.Array.set(out, i, convert.apply(elem)); + try { + int len = java.lang.reflect.Array.getLength(arrayObject); + Object out = java.lang.reflect.Array.newInstance(arrayObject.getClass().getComponentType(), len); + for (int i = 0; i < len; i++) { + Object elem = java.lang.reflect.Array.get(arrayObject, i); + java.lang.reflect.Array.set(out, i, convert.apply(elem)); + } + return out; + } catch (Exception e) { + LOG.error(LoggingUtils.exceptionInfo(e)); + return null; } - return out; } private static Object escapeIfString(Object object) { @@ -166,11 +205,11 @@ private static Object escapeIfString(Object object) { } } - private static Object unescapeIfString(Object o) { - if(o instanceof String) { - return XssProtectUtils.unescapeHtml((String) o); + private static Object unescapeIfString(Object object) { + if(object instanceof String) { + return XssProtectUtils.unescapeHtml((String) object); } else { - return o; + return object; } } } diff --git a/test/org/scada_lts/web/security/dwr/XssBeanConverterUtilsEscapedTest.java b/test/org/scada_lts/web/security/dwr/XssBeanConverterUtilsEscapedTest.java index 674276504c..a732d1c17f 100644 --- a/test/org/scada_lts/web/security/dwr/XssBeanConverterUtilsEscapedTest.java +++ b/test/org/scada_lts/web/security/dwr/XssBeanConverterUtilsEscapedTest.java @@ -1,34 +1,61 @@ package org.scada_lts.web.security.dwr; -import com.serotonin.mango.vo.GetExtendedName; +import br.org.scadabr.vo.scripting.ContextualizedScriptVO; import org.junit.Assert; +import org.junit.Before; import org.junit.Test; -import utils.mock.TestGetExtendedName; public class XssBeanConverterUtilsEscapedTest { - private final GetExtendedName object = new TestGetExtendedName(""); + private ContextualizedScriptVO objectUnescaped; + private ContextualizedScriptVO objectEscaped; - private final GetExtendedName objectEscaped = new TestGetExtendedName("<script>alert(1)</script>"); + @Before + public void config() { + objectUnescaped = new ContextualizedScriptVO(); + objectUnescaped.setScript(""); + objectEscaped = new ContextualizedScriptVO(); + objectEscaped.setScript("<script>alert(1)</script>"); + } + + @Test + public void when_convertObjectEscaped_then_Object_escaped() throws ScadaMarshallException { + + //when: + Object result = XssBeanConverterUtils.convertObjectEscaped(objectUnescaped); + + //then: + Assert.assertEquals(objectEscaped, result); + } + + @Test + public void when_convertObjectUnescaped_then_Object_unescaped() throws ScadaMarshallException { + + //when: + Object result = XssBeanConverterUtils.convertObjectUnescaped(objectEscaped); + + //then: + Assert.assertEquals(objectUnescaped, result); + } @Test - public void when_convertObjectEscaped_then_String_escaped() throws ScadaMarshallException { + public void when_convertObjectUnescaped_then_not_same_arg() throws ScadaMarshallException { //when: - XssBeanConverterUtils.convertObjectEscaped(object); + Object result = XssBeanConverterUtils.convertObjectUnescaped(objectEscaped); //then: - Assert.assertEquals(objectEscaped, object); + Assert.assertNotSame(objectEscaped, result); } @Test - public void when_convertObjectUnescaped_then_String_unescaped() throws ScadaMarshallException { + public void when_convertObjectEscaped_then_not_same_arg() throws ScadaMarshallException { //when: - XssBeanConverterUtils.convertObjectUnescaped(objectEscaped); + Object result = XssBeanConverterUtils.convertObjectEscaped(objectUnescaped); //then: - Assert.assertEquals(object, objectEscaped); + Assert.assertNotSame(objectUnescaped, result); } } \ No newline at end of file diff --git a/test/org/scada_lts/web/security/dwr/XssBeanConverterUtilsTest.java b/test/org/scada_lts/web/security/dwr/XssBeanConverterUtilsTest.java index e02792831c..937d5f9cc4 100644 --- a/test/org/scada_lts/web/security/dwr/XssBeanConverterUtilsTest.java +++ b/test/org/scada_lts/web/security/dwr/XssBeanConverterUtilsTest.java @@ -1,25 +1,115 @@ package org.scada_lts.web.security.dwr; import br.org.scadabr.vo.permission.WatchListAccess; +import br.org.scadabr.vo.usersProfiles.UsersProfileVO; +import com.serotonin.db.IntValuePair; +import com.serotonin.mango.rt.dataSource.PointLocatorRT; +import com.serotonin.mango.view.View; +import com.serotonin.mango.vo.DataPointVO; +import com.serotonin.mango.vo.dataSource.DataPointSaveHandler; import com.serotonin.mango.vo.dataSource.DataSourceVO; +import com.serotonin.mango.vo.dataSource.PointLocatorVO; import com.serotonin.mango.vo.dataSource.virtual.VirtualDataSourceVO; +import com.serotonin.mango.vo.event.CompoundEventDetectorVO; +import com.serotonin.mango.vo.event.EventHandlerVO; +import com.serotonin.mango.vo.event.EventTypeVO; +import com.serotonin.mango.vo.mailingList.AddressEntry; +import com.serotonin.mango.vo.mailingList.MailingList; +import com.serotonin.mango.vo.mailingList.UserEntry; +import com.serotonin.mango.vo.report.ReportInstance; +import com.serotonin.mango.vo.report.ReportVO; +import com.serotonin.mango.web.dwr.beans.DataPointBean; +import com.serotonin.mango.web.dwr.beans.EventSourceBean; +import com.serotonin.mango.web.dwr.beans.RecipientListEntryBean; +import com.serotonin.web.dwr.DwrResponseI18n; +import com.serotonin.web.i18n.LocalizableMessage; import net.sf.mbus4j.SerialPortConnection; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.scada_lts.dao.model.UserIdentifier; +import java.util.List; + @RunWith(Parameterized.class) public class XssBeanConverterUtilsTest { @Parameterized.Parameters(name = "{index}: value: {0}, type: {1}") public static Object[][] data() { + DataPointVO dataPoint = new DataPointVO(-1, -1, -1); + dataPoint.setPointLocator(new PointLocatorVO() { + @Override + public int getDataTypeId() { + return 0; + } + + @Override + public LocalizableMessage getDataTypeMessage() { + return null; + } + + @Override + public LocalizableMessage getConfigurationDescription() { + return null; + } + + @Override + public boolean isSettable() { + return false; + } + + @Override + public boolean isRelinquishable() { + return false; + } + + @Override + public PointLocatorRT createRuntime() { + return null; + } + + @Override + public void validate(DwrResponseI18n response) { + + } + + @Override + public DataPointSaveHandler getDataPointSaveHandler() { + return null; + } + + @Override + public void addProperties(List list) { + + } + + @Override + public void addPropertyChanges(List list, Object o) { + + } + }); return new Object[][] { {new VirtualDataSourceVO(), DataSourceVO.class}, {new WatchListAccess(), WatchListAccess.class}, {new SerialPortConnection(), SerialPortConnection.class}, {new UserIdentifier(), UserIdentifier.class}, {new Exception(), Exception.class}, + {new UsersProfileVO(), UsersProfileVO.class}, + {new View(), View.class}, + {dataPoint, DataPointVO.class}, + {new DataPointBean(dataPoint), DataPointBean.class}, + {new CompoundEventDetectorVO(), CompoundEventDetectorVO.class}, + {new EventHandlerVO(), EventHandlerVO.class}, + {new EventTypeVO(-1, -1, -1), EventTypeVO.class}, + {new IntValuePair(), IntValuePair.class}, + {new EventSourceBean(), EventSourceBean.class}, + {new AddressEntry(), AddressEntry.class}, + {new MailingList(), MailingList.class}, + {new UserEntry(), UserEntry.class}, + {new ReportInstance(), ReportInstance.class}, + {new ReportVO(), ReportVO.class}, + {new RecipientListEntryBean(), RecipientListEntryBean.class}, + }; } diff --git a/test/utils/mock/TestGetExtendedName.java b/test/utils/mock/TestGetExtendedName.java deleted file mode 100644 index 997e1f5108..0000000000 --- a/test/utils/mock/TestGetExtendedName.java +++ /dev/null @@ -1,37 +0,0 @@ -package utils.mock; - -import com.serotonin.mango.vo.GetExtendedName; - -public class TestGetExtendedName implements GetExtendedName { - - private final String name; - - public TestGetExtendedName(String name) { - this.name = name; - } - - @Override - public String getName() { - return name; - } - - @Override - public String toString() { - return "$classname{" + - "name='" + getName() + '\'' + - '}'; - } - - @Override - public int hashCode() { - return getName().hashCode(); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof GetExtendedName)) return false; - GetExtendedName that = (GetExtendedName) o; - return this.getName().equals(that.getName()); - } -} From 9d00007b5a96d6b4526b4c0f7e1f7a26bc296a1b Mon Sep 17 00:00:00 2001 From: Kamil Jarmusik Date: Wed, 29 Oct 2025 11:58:33 +0100 Subject: [PATCH 34/53] #3167 fixed escaping fields: - Corrected XssBeanConverterUtils for LocalizableMessage; --- .../scada_lts/web/security/dwr/XssBeanConverterUtils.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/org/scada_lts/web/security/dwr/XssBeanConverterUtils.java b/src/org/scada_lts/web/security/dwr/XssBeanConverterUtils.java index 760413b668..edea4116d8 100644 --- a/src/org/scada_lts/web/security/dwr/XssBeanConverterUtils.java +++ b/src/org/scada_lts/web/security/dwr/XssBeanConverterUtils.java @@ -1,6 +1,7 @@ package org.scada_lts.web.security.dwr; import com.serotonin.mango.util.LoggingUtils; +import com.serotonin.web.i18n.LocalizableMessage; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.scada_lts.web.security.EmptyInstance; @@ -94,7 +95,9 @@ private static List getAllFields(Object originObject) { private static Object newInstanceEmpty(Object originObject) throws Exception { Object object; - if(originObject instanceof EmptyInstance) { + if(originObject instanceof LocalizableMessage) { + object = new LocalizableMessage(""); + } else if(originObject instanceof EmptyInstance) { object = ((EmptyInstance) originObject).newInstanceEmpty(); } else { object = originObject.getClass().getConstructor().newInstance(); From 9895c2d93cc9cba09706c63c7257f3a7d641696d Mon Sep 17 00:00:00 2001 From: Kamil Jarmusik Date: Wed, 29 Oct 2025 12:16:09 +0100 Subject: [PATCH 35/53] #3206 Fixed escaping fields in Event handlers, Users profiles, watch lists: - Corrected XssLocalizableMessageConverter; --- .../security/dwr/XssLocalizableMessageConverter.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/org/scada_lts/web/security/dwr/XssLocalizableMessageConverter.java b/src/org/scada_lts/web/security/dwr/XssLocalizableMessageConverter.java index 10934e6655..d1e2f0026c 100644 --- a/src/org/scada_lts/web/security/dwr/XssLocalizableMessageConverter.java +++ b/src/org/scada_lts/web/security/dwr/XssLocalizableMessageConverter.java @@ -17,15 +17,14 @@ public XssLocalizableMessageConverter() { public OutboundVariable convertOutbound(Object data, OutboundContext outctx) throws MarshallException { WebContext webctx = WebContextFactory.get(); - LocalizableMessage lm = (LocalizableMessage)data; - convertObjectEscaped(data); - String s = lm.getLocalizedMessage(Common.getBundle(webctx.getHttpServletRequest())); - return super.convertOutbound(s, outctx); + LocalizableMessage lm = (LocalizableMessage)convertObjectEscaped(data); + String escaped = lm.getLocalizedMessage(Common.getBundle(webctx.getHttpServletRequest())); + return super.convertOutbound(escaped, outctx); } @Override public Object convertInbound(Class paramType, InboundVariable iv, InboundContext inctx) throws MarshallException { - convertObjectUnescaped(iv.getValue()); - return super.convertInbound(paramType, iv, inctx); + Object converted = super.convertInbound(paramType, iv, inctx); + return convertObjectUnescaped(converted); } } From 6ed1f8a0fc99a727143206d562f887a422b5b625 Mon Sep 17 00:00:00 2001 From: Kamil Jarmusik Date: Wed, 29 Oct 2025 15:22:59 +0100 Subject: [PATCH 36/53] #3206 Fixed escaping fields in Event handlers, Users profiles, watch lists: - Set bean converter for package com.serotonin.mango.vo.report in dwr.xml; --- WebContent/WEB-INF/dwr.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WebContent/WEB-INF/dwr.xml b/WebContent/WEB-INF/dwr.xml index 9fca08eb54..cebbf8c378 100644 --- a/WebContent/WEB-INF/dwr.xml +++ b/WebContent/WEB-INF/dwr.xml @@ -155,7 +155,7 @@ - + From 238b096f45a660d47c730fee43276ebdd679e349 Mon Sep 17 00:00:00 2001 From: Kamil Jarmusik Date: Wed, 29 Oct 2025 16:09:36 +0100 Subject: [PATCH 37/53] #3167 fixed escaping fields: - Corrected: maintenanceEvents.jsp, scheduledEvents.jsp, change textContent to innerHTML for description; - Corrected includeTestsMatching for CssValidatorTestsSuite in build.gradle; --- WebContent/WEB-INF/jsp/maintenanceEvents.jsp | 2 +- WebContent/WEB-INF/jsp/scheduledEvents.jsp | 2 +- build.gradle | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/WebContent/WEB-INF/jsp/maintenanceEvents.jsp b/WebContent/WEB-INF/jsp/maintenanceEvents.jsp index 931e70fee5..81d3243771 100644 --- a/WebContent/WEB-INF/jsp/maintenanceEvents.jsp +++ b/WebContent/WEB-INF/jsp/maintenanceEvents.jsp @@ -156,7 +156,7 @@ } function updateMaintenanceEvent(me) { - $("me"+ me.id +"Name").textContent = me.description; + $("me"+ me.id +"Name").innerHTML = me.description; if (me.disabled) updateImg("me"+ me.id +"Img", "images/hammer_disabled.png", "", true); else diff --git a/WebContent/WEB-INF/jsp/scheduledEvents.jsp b/WebContent/WEB-INF/jsp/scheduledEvents.jsp index 2a2f0f121c..11f3da220a 100644 --- a/WebContent/WEB-INF/jsp/scheduledEvents.jsp +++ b/WebContent/WEB-INF/jsp/scheduledEvents.jsp @@ -150,7 +150,7 @@ } function updateScheduledEvent(se) { - $("se"+ se.id +"Name").textContent = se.description; + $("se"+ se.id +"Name").innerHTML = se.description; setScheduledEventImg(se.disabled, $("se"+ se.id +"Img")); } diff --git a/build.gradle b/build.gradle index 14ade0840c..18ed3a2aeb 100644 --- a/build.gradle +++ b/build.gradle @@ -241,7 +241,7 @@ test { includeTestsMatching "com.serotonin.mango.util.StartStopDataPointsUtilsTestsSuite" includeTestsMatching "org.scada_lts.utils.BlockingQueuesUtilsTest" includeTestsMatching "org.scada_lts.web.security.XssValidatorUtilsTestsSuite" - includeTestsMatching "org.scada_lts.web.beans.validation.css.CssValidatorTestsSuite1" + includeTestsMatching "org.scada_lts.web.beans.validation.css.CssValidatorTestsSuite" includeTestsMatching "org.scada_lts.web.beans.validation.xss.XssValidatorTestsSuite" includeTestsMatching "org.scada_lts.utils.CyclicDependencyValidationUtilsTest" includeTestsMatching "org.scada_lts.ds.polling.protocol.opcua.vo.OpcUaDataTypeTestsSuite" From e7106d0ed0488397883c5aeea5beb7113474b48b Mon Sep 17 00:00:00 2001 From: patrykb0802 Date: Thu, 30 Oct 2025 11:04:44 +0100 Subject: [PATCH 38/53] #3206 Fixed escaping fields in Event handlers, Users profiles, watch lists --- WebContent/WEB-INF/dwr.xml | 4 +++- WebContent/WEB-INF/jsp/eventHandlers.jsp | 4 ++-- WebContent/WEB-INF/jsp/watchList.jsp | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/WebContent/WEB-INF/dwr.xml b/WebContent/WEB-INF/dwr.xml index cebbf8c378..4a0470a508 100644 --- a/WebContent/WEB-INF/dwr.xml +++ b/WebContent/WEB-INF/dwr.xml @@ -129,7 +129,7 @@ - + @@ -246,6 +246,8 @@ + + diff --git a/WebContent/WEB-INF/jsp/eventHandlers.jsp b/WebContent/WEB-INF/jsp/eventHandlers.jsp index 7821f07721..f064dc3be9 100644 --- a/WebContent/WEB-INF/jsp/eventHandlers.jsp +++ b/WebContent/WEB-INF/jsp/eventHandlers.jsp @@ -228,7 +228,7 @@ img = "images/cog_process.png"; var node = dojo.widget.createWidget("TreeNode", { - title: " "+ handler.message +"", + title: " "+ unescapeHtml(handler.message) +"", widgetId: "h"+ handler.id, object: handler }); @@ -286,7 +286,7 @@ $set("handlerTypeSelect", handler.handlerType); $("handlerTypeSelect").disabled = true; $set("xid", handler.xid); - $set("alias", handler.alias); + $set("alias", unescapeHtml(handler.alias)); $set("disabled", handler.disabled); if (handler.handlerType == ) { $set("targetPointSelect", handler.targetPointId); diff --git a/WebContent/WEB-INF/jsp/watchList.jsp b/WebContent/WEB-INF/jsp/watchList.jsp index effe5613fc..192b68c453 100644 --- a/WebContent/WEB-INF/jsp/watchList.jsp +++ b/WebContent/WEB-INF/jsp/watchList.jsp @@ -154,7 +154,7 @@ function addPoint(point, parent) { var spanNode = document.createElement("span"); spanNode.id = 'ph'+ point.key +'Name'; - spanNode.textContent = unescapeHtml(point.value); + spanNode.innerHTML = point.value; var pointNode = dojo.widget.createWidget("TreeNode", { title: " " + spanNode.innerHTML + "", From 371ad447775f6289a09d769b260ed6cd5e32cf19 Mon Sep 17 00:00:00 2001 From: patrykb0802 Date: Thu, 30 Oct 2025 14:33:56 +0100 Subject: [PATCH 39/53] #3206 Fixed escaping fields in Event handlers, Users profiles, watch lists --- WebContent/WEB-INF/dwr.xml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/WebContent/WEB-INF/dwr.xml b/WebContent/WEB-INF/dwr.xml index 4a0470a508..3cb1188688 100644 --- a/WebContent/WEB-INF/dwr.xml +++ b/WebContent/WEB-INF/dwr.xml @@ -142,8 +142,7 @@ - - + From 5a71e4f3c77caf2a70614d375ac220001567dee2 Mon Sep 17 00:00:00 2001 From: patrykb0802 Date: Thu, 30 Oct 2025 14:46:45 +0100 Subject: [PATCH 40/53] Revert "#3206 Fixed escaping fields in Event handlers, Users profiles, watch lists" This reverts commit 371ad447775f6289a09d769b260ed6cd5e32cf19. --- WebContent/WEB-INF/dwr.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/WebContent/WEB-INF/dwr.xml b/WebContent/WEB-INF/dwr.xml index 3cb1188688..4a0470a508 100644 --- a/WebContent/WEB-INF/dwr.xml +++ b/WebContent/WEB-INF/dwr.xml @@ -142,7 +142,8 @@ - + + From 68f8c9f61ebd07f1ed00a22d0d87c267cca70a3c Mon Sep 17 00:00:00 2001 From: Kamil Jarmusik Date: Thu, 30 Oct 2025 18:30:47 +0100 Subject: [PATCH 41/53] #3206 Fixed escaping fields in Event handlers, Users profiles, watch lists: - Fixed double escaped alias in: MaintenanceEventVO, ScheduledEventVO (use NoEscape annotation); - Fixed escaped after save event handler; --- WebContent/WEB-INF/jsp/eventHandlers.jsp | 2 +- src/com/serotonin/mango/vo/event/MaintenanceEventVO.java | 2 ++ src/com/serotonin/mango/vo/event/ScheduledEventVO.java | 2 ++ 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/WebContent/WEB-INF/jsp/eventHandlers.jsp b/WebContent/WEB-INF/jsp/eventHandlers.jsp index f064dc3be9..ef526ca623 100644 --- a/WebContent/WEB-INF/jsp/eventHandlers.jsp +++ b/WebContent/WEB-INF/jsp/eventHandlers.jsp @@ -527,7 +527,7 @@ selectedHandlerNode.onTitleClick(); } else - $set(handler.id +"Msg", handler.message); + $set(handler.id +"Msg", unescapeHtml(handler.message)); setUserMessage(""); selectedHandlerNode.object = handler; diff --git a/src/com/serotonin/mango/vo/event/MaintenanceEventVO.java b/src/com/serotonin/mango/vo/event/MaintenanceEventVO.java index 7917ad83db..caec71587e 100644 --- a/src/com/serotonin/mango/vo/event/MaintenanceEventVO.java +++ b/src/com/serotonin/mango/vo/event/MaintenanceEventVO.java @@ -30,6 +30,7 @@ import com.serotonin.web.taglib.DateFunctions; import org.scada_lts.mango.service.MaintenanceEventService; import org.scada_lts.utils.XidUtils; +import org.scada_lts.web.security.dwr.NoEscape; @JsonRemoteEntity public class MaintenanceEventVO implements ChangeComparable, JsonSerializable { @@ -60,6 +61,7 @@ public class MaintenanceEventVO implements ChangeComparable, private String xid; private int dataSourceId; @JsonRemoteProperty + @NoEscape private String alias; private int alarmLevel = AlarmLevels.NONE; private int scheduleType = TYPE_MANUAL; diff --git a/src/com/serotonin/mango/vo/event/ScheduledEventVO.java b/src/com/serotonin/mango/vo/event/ScheduledEventVO.java index 29d75b272e..3f1716bb1c 100644 --- a/src/com/serotonin/mango/vo/event/ScheduledEventVO.java +++ b/src/com/serotonin/mango/vo/event/ScheduledEventVO.java @@ -45,6 +45,7 @@ import com.serotonin.web.taglib.DateFunctions; import org.scada_lts.mango.service.ScheduledEventService; import org.scada_lts.utils.XidUtils; +import org.scada_lts.web.security.dwr.NoEscape; /** * @author Matthew Lohbihler @@ -85,6 +86,7 @@ public boolean isNew() { private int id = Common.NEW_ID; private String xid; @JsonRemoteProperty + @NoEscape private String alias; private int alarmLevel = AlarmLevels.NONE; private int scheduleType = TYPE_DAILY; From 90ea04a5067600bdeaa246505118e55b9556e3a5 Mon Sep 17 00:00:00 2001 From: Kamil Jarmusik Date: Thu, 30 Oct 2025 18:58:22 +0100 Subject: [PATCH 42/53] #3206 Fixed escaping fields in Event handlers, Users profiles, watch lists: - Removed use NoEscape annotation in: MaintenanceEventVO.alias, ScheduledEventVO.alias - Unescaped in getDescription string fields from: MaintenanceEventVO, ScheduledEventVO because returned LocalizableMessage object escaped by converter; - Corrected maintenanceEvents.jsp, scheduledEvents.jsp use unescapeHtml; --- WebContent/WEB-INF/jsp/maintenanceEvents.jsp | 8 +++--- WebContent/WEB-INF/jsp/scheduledEvents.jsp | 8 +++--- .../mango/vo/event/MaintenanceEventVO.java | 26 +++++++++---------- .../mango/vo/event/ScheduledEventVO.java | 12 ++++----- 4 files changed, 27 insertions(+), 27 deletions(-) diff --git a/WebContent/WEB-INF/jsp/maintenanceEvents.jsp b/WebContent/WEB-INF/jsp/maintenanceEvents.jsp index 81d3243771..adf72bee98 100644 --- a/WebContent/WEB-INF/jsp/maintenanceEvents.jsp +++ b/WebContent/WEB-INF/jsp/maintenanceEvents.jsp @@ -77,9 +77,9 @@ updateToggle(response.data.activated); - $set("xid", me.xid); + $set("xid", unescapeHtml(me.xid)); $set("dataSourceId", me.dataSourceId); - $set("alias", me.alias); + $set("alias", unescapeHtml(me.alias)); $set("alarmLevel", me.alarmLevel); updateAlarmLevelImage(); $set("scheduleType", me.scheduleType); @@ -92,14 +92,14 @@ $set("activeHour", me.activeHour); $set("activeMinute", me.activeMinute); $set("activeSecond", me.activeSecond); - $set("activeCron", me.activeCron); + $set("activeCron", unescapeHtml(me.activeCron)); $set("inactiveYear", me.inactiveYear); $set("inactiveMonth", me.inactiveMonth); $set("inactiveDay", me.inactiveDay); $set("inactiveHour", me.inactiveHour); $set("inactiveMinute", me.inactiveMinute); $set("inactiveSecond", me.inactiveSecond); - $set("inactiveCron", me.inactiveCron); + $set("inactiveCron", unescapeHtml(me.inactiveCron)); setUserMessage(); }); diff --git a/WebContent/WEB-INF/jsp/scheduledEvents.jsp b/WebContent/WEB-INF/jsp/scheduledEvents.jsp index 11f3da220a..2588e03020 100644 --- a/WebContent/WEB-INF/jsp/scheduledEvents.jsp +++ b/WebContent/WEB-INF/jsp/scheduledEvents.jsp @@ -76,8 +76,8 @@ show($("scheduledEventDetails")); editingScheduledEvent = se; - $set("xid", se.xid); - $set("alias", se.alias); + $set("xid", unescapeHtml(se.xid)); + $set("alias", unescapeHtml(se.alias)); $set("alarmLevel", se.alarmLevel); updateAlarmLevelImage(); $set("scheduleType", se.scheduleType); @@ -91,14 +91,14 @@ $set("activeHour", se.activeHour); $set("activeMinute", se.activeMinute); $set("activeSecond", se.activeSecond); - $set("activeCron", se.activeCron); + $set("activeCron", unescapeHtml(se.activeCron)); $set("inactiveYear", se.inactiveYear); $set("inactiveMonth", se.inactiveMonth); $set("inactiveDay", se.inactiveDay); $set("inactiveHour", se.inactiveHour); $set("inactiveMinute", se.inactiveMinute); $set("inactiveSecond", se.inactiveSecond); - $set("inactiveCron", se.inactiveCron); + $set("inactiveCron", unescapeHtml(se.inactiveCron)); setUserMessage(); }); diff --git a/src/com/serotonin/mango/vo/event/MaintenanceEventVO.java b/src/com/serotonin/mango/vo/event/MaintenanceEventVO.java index caec71587e..8f4ddc34e9 100644 --- a/src/com/serotonin/mango/vo/event/MaintenanceEventVO.java +++ b/src/com/serotonin/mango/vo/event/MaintenanceEventVO.java @@ -30,7 +30,8 @@ import com.serotonin.web.taglib.DateFunctions; import org.scada_lts.mango.service.MaintenanceEventService; import org.scada_lts.utils.XidUtils; -import org.scada_lts.web.security.dwr.NoEscape; + +import static org.scada_lts.web.security.XssProtectUtils.unescapeHtml; @JsonRemoteEntity public class MaintenanceEventVO implements ChangeComparable, JsonSerializable { @@ -61,7 +62,6 @@ public class MaintenanceEventVO implements ChangeComparable, private String xid; private int dataSourceId; @JsonRemoteProperty - @NoEscape private String alias; private int alarmLevel = AlarmLevels.NONE; private int scheduleType = TYPE_MANUAL; @@ -307,12 +307,12 @@ public EventTypeVO getEventType() { public LocalizableMessage getDescription() { LocalizableMessage message; - if (!StringUtils.isEmpty(alias)) - message = new LocalizableMessage("common.default", alias); + if (!StringUtils.isEmpty(unescapeHtml(alias))) + message = new LocalizableMessage("common.default", unescapeHtml(alias)); else if (scheduleType == TYPE_MANUAL) - message = new LocalizableMessage("maintenanceEvents.schedule.manual", dataSourceName); + message = new LocalizableMessage("maintenanceEvents.schedule.manual", unescapeHtml(dataSourceName)); else if (scheduleType == TYPE_ONCE) { - message = new LocalizableMessage("maintenanceEvents.schedule.onceUntil", dataSourceName, + message = new LocalizableMessage("maintenanceEvents.schedule.onceUntil", unescapeHtml(dataSourceName), DateFunctions.getTime(new DateTime(activeYear, activeMonth, activeDay, activeHour, activeMinute, activeSecond, 0).getMillis()), DateFunctions.getTime(new DateTime(inactiveYear, inactiveMonth, inactiveDay, inactiveHour, inactiveMinute, inactiveSecond, 0).getMillis())); @@ -320,25 +320,25 @@ else if (scheduleType == TYPE_ONCE) { else if (scheduleType == TYPE_HOURLY) { String activeTime = StringUtils.pad(Integer.toString(activeMinute), '0', 2) + ":" + StringUtils.pad(Integer.toString(activeSecond), '0', 2); - message = new LocalizableMessage("maintenanceEvents.schedule.hoursUntil", dataSourceName, activeTime, + message = new LocalizableMessage("maintenanceEvents.schedule.hoursUntil", unescapeHtml(dataSourceName), activeTime, StringUtils.pad(Integer.toString(inactiveMinute), '0', 2) + ":" + StringUtils.pad(Integer.toString(inactiveSecond), '0', 2)); } else if (scheduleType == TYPE_DAILY) - message = new LocalizableMessage("maintenanceEvents.schedule.dailyUntil", dataSourceName, activeTime(), + message = new LocalizableMessage("maintenanceEvents.schedule.dailyUntil", unescapeHtml(dataSourceName), activeTime(), inactiveTime()); else if (scheduleType == TYPE_WEEKLY) - message = new LocalizableMessage("maintenanceEvents.schedule.weeklyUntil", dataSourceName, weekday(true), + message = new LocalizableMessage("maintenanceEvents.schedule.weeklyUntil", unescapeHtml(dataSourceName), weekday(true), activeTime(), weekday(false), inactiveTime()); else if (scheduleType == TYPE_MONTHLY) - message = new LocalizableMessage("maintenanceEvents.schedule.monthlyUntil", dataSourceName, monthday(true), + message = new LocalizableMessage("maintenanceEvents.schedule.monthlyUntil", unescapeHtml(dataSourceName), monthday(true), activeTime(), monthday(false), inactiveTime()); else if (scheduleType == TYPE_YEARLY) - message = new LocalizableMessage("maintenanceEvents.schedule.yearlyUntil", dataSourceName, monthday(true), + message = new LocalizableMessage("maintenanceEvents.schedule.yearlyUntil", unescapeHtml(dataSourceName), monthday(true), month(true), activeTime(), monthday(false), month(false), inactiveTime()); else if (scheduleType == TYPE_CRON) - message = new LocalizableMessage("maintenanceEvents.schedule.cronUntil", dataSourceName, activeCron, - inactiveCron); + message = new LocalizableMessage("maintenanceEvents.schedule.cronUntil", unescapeHtml(dataSourceName), unescapeHtml(activeCron), + unescapeHtml(inactiveCron)); else throw new ShouldNeverHappenException("Unknown schedule type: " + scheduleType); diff --git a/src/com/serotonin/mango/vo/event/ScheduledEventVO.java b/src/com/serotonin/mango/vo/event/ScheduledEventVO.java index 3f1716bb1c..2c805eefe0 100644 --- a/src/com/serotonin/mango/vo/event/ScheduledEventVO.java +++ b/src/com/serotonin/mango/vo/event/ScheduledEventVO.java @@ -45,7 +45,8 @@ import com.serotonin.web.taglib.DateFunctions; import org.scada_lts.mango.service.ScheduledEventService; import org.scada_lts.utils.XidUtils; -import org.scada_lts.web.security.dwr.NoEscape; + +import static org.scada_lts.web.security.XssProtectUtils.unescapeHtml; /** * @author Matthew Lohbihler @@ -86,7 +87,6 @@ public boolean isNew() { private int id = Common.NEW_ID; private String xid; @JsonRemoteProperty - @NoEscape private String alias; private int alarmLevel = AlarmLevels.NONE; private int scheduleType = TYPE_DAILY; @@ -140,8 +140,8 @@ public String getEventDetectorKey() { public LocalizableMessage getDescription() { LocalizableMessage message; - if (!StringUtils.isEmpty(alias)) - message = new LocalizableMessage("common.default", alias); + if (!StringUtils.isEmpty(unescapeHtml(alias))) + message = new LocalizableMessage("common.default", unescapeHtml(alias)); else if (scheduleType == TYPE_ONCE) { if (returnToNormal) message = new LocalizableMessage("event.schedule.onceUntil", DateFunctions.getTime(new DateTime( @@ -191,9 +191,9 @@ else if (scheduleType == TYPE_YEARLY) { } else if (scheduleType == TYPE_CRON) { if (returnToNormal) - message = new LocalizableMessage("event.schedule.cronUntil", activeCron, inactiveCron); + message = new LocalizableMessage("event.schedule.cronUntil", unescapeHtml(activeCron), unescapeHtml(inactiveCron)); else - message = new LocalizableMessage("event.schedule.cronAt", activeCron); + message = new LocalizableMessage("event.schedule.cronAt", unescapeHtml(activeCron)); } else throw new ShouldNeverHappenException("Unknown schedule type: " + scheduleType); From 2760c3a87ccabb10cca982d621c14363395f6e6e Mon Sep 17 00:00:00 2001 From: Kamil Jarmusik Date: Thu, 30 Oct 2025 19:09:35 +0100 Subject: [PATCH 43/53] #3206 Fixed escaping fields in Event handlers, Users profiles, watch lists: - Corrected: MaintenanceEventVO.getDescription, ScheduledEventVO.getDescription; --- .../mango/vo/event/MaintenanceEventVO.java | 27 +++++++++++-------- .../mango/vo/event/ScheduledEventVO.java | 12 ++++++--- 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/src/com/serotonin/mango/vo/event/MaintenanceEventVO.java b/src/com/serotonin/mango/vo/event/MaintenanceEventVO.java index 8f4ddc34e9..d26f06a52b 100644 --- a/src/com/serotonin/mango/vo/event/MaintenanceEventVO.java +++ b/src/com/serotonin/mango/vo/event/MaintenanceEventVO.java @@ -307,12 +307,17 @@ public EventTypeVO getEventType() { public LocalizableMessage getDescription() { LocalizableMessage message; - if (!StringUtils.isEmpty(unescapeHtml(alias))) - message = new LocalizableMessage("common.default", unescapeHtml(alias)); + String unescapedAlias = unescapeHtml(alias); + String unescapedDataSourceName = unescapeHtml(dataSourceName); + String unescapedActiveCron = unescapeHtml(activeCron); + String unescapedInactiveCron = unescapeHtml(inactiveCron); + + if (!StringUtils.isEmpty(unescapedAlias)) + message = new LocalizableMessage("common.default", unescapedAlias); else if (scheduleType == TYPE_MANUAL) - message = new LocalizableMessage("maintenanceEvents.schedule.manual", unescapeHtml(dataSourceName)); + message = new LocalizableMessage("maintenanceEvents.schedule.manual", unescapedDataSourceName); else if (scheduleType == TYPE_ONCE) { - message = new LocalizableMessage("maintenanceEvents.schedule.onceUntil", unescapeHtml(dataSourceName), + message = new LocalizableMessage("maintenanceEvents.schedule.onceUntil", unescapedDataSourceName, DateFunctions.getTime(new DateTime(activeYear, activeMonth, activeDay, activeHour, activeMinute, activeSecond, 0).getMillis()), DateFunctions.getTime(new DateTime(inactiveYear, inactiveMonth, inactiveDay, inactiveHour, inactiveMinute, inactiveSecond, 0).getMillis())); @@ -320,25 +325,25 @@ else if (scheduleType == TYPE_ONCE) { else if (scheduleType == TYPE_HOURLY) { String activeTime = StringUtils.pad(Integer.toString(activeMinute), '0', 2) + ":" + StringUtils.pad(Integer.toString(activeSecond), '0', 2); - message = new LocalizableMessage("maintenanceEvents.schedule.hoursUntil", unescapeHtml(dataSourceName), activeTime, + message = new LocalizableMessage("maintenanceEvents.schedule.hoursUntil", unescapedDataSourceName, activeTime, StringUtils.pad(Integer.toString(inactiveMinute), '0', 2) + ":" + StringUtils.pad(Integer.toString(inactiveSecond), '0', 2)); } else if (scheduleType == TYPE_DAILY) - message = new LocalizableMessage("maintenanceEvents.schedule.dailyUntil", unescapeHtml(dataSourceName), activeTime(), + message = new LocalizableMessage("maintenanceEvents.schedule.dailyUntil", unescapedDataSourceName, activeTime(), inactiveTime()); else if (scheduleType == TYPE_WEEKLY) - message = new LocalizableMessage("maintenanceEvents.schedule.weeklyUntil", unescapeHtml(dataSourceName), weekday(true), + message = new LocalizableMessage("maintenanceEvents.schedule.weeklyUntil", unescapedDataSourceName, weekday(true), activeTime(), weekday(false), inactiveTime()); else if (scheduleType == TYPE_MONTHLY) - message = new LocalizableMessage("maintenanceEvents.schedule.monthlyUntil", unescapeHtml(dataSourceName), monthday(true), + message = new LocalizableMessage("maintenanceEvents.schedule.monthlyUntil", unescapedDataSourceName, monthday(true), activeTime(), monthday(false), inactiveTime()); else if (scheduleType == TYPE_YEARLY) - message = new LocalizableMessage("maintenanceEvents.schedule.yearlyUntil", unescapeHtml(dataSourceName), monthday(true), + message = new LocalizableMessage("maintenanceEvents.schedule.yearlyUntil", unescapedDataSourceName, monthday(true), month(true), activeTime(), monthday(false), month(false), inactiveTime()); else if (scheduleType == TYPE_CRON) - message = new LocalizableMessage("maintenanceEvents.schedule.cronUntil", unescapeHtml(dataSourceName), unescapeHtml(activeCron), - unescapeHtml(inactiveCron)); + message = new LocalizableMessage("maintenanceEvents.schedule.cronUntil", unescapedDataSourceName, unescapedActiveCron, + unescapedInactiveCron); else throw new ShouldNeverHappenException("Unknown schedule type: " + scheduleType); diff --git a/src/com/serotonin/mango/vo/event/ScheduledEventVO.java b/src/com/serotonin/mango/vo/event/ScheduledEventVO.java index 2c805eefe0..9566bbb41a 100644 --- a/src/com/serotonin/mango/vo/event/ScheduledEventVO.java +++ b/src/com/serotonin/mango/vo/event/ScheduledEventVO.java @@ -140,8 +140,12 @@ public String getEventDetectorKey() { public LocalizableMessage getDescription() { LocalizableMessage message; - if (!StringUtils.isEmpty(unescapeHtml(alias))) - message = new LocalizableMessage("common.default", unescapeHtml(alias)); + String unescapedAlias = unescapeHtml(alias); + String unescapedActiveCron = unescapeHtml(activeCron); + String unescapedInactiveCron = unescapeHtml(inactiveCron); + + if (!StringUtils.isEmpty(unescapedAlias)) + message = new LocalizableMessage("common.default", unescapedAlias); else if (scheduleType == TYPE_ONCE) { if (returnToNormal) message = new LocalizableMessage("event.schedule.onceUntil", DateFunctions.getTime(new DateTime( @@ -191,9 +195,9 @@ else if (scheduleType == TYPE_YEARLY) { } else if (scheduleType == TYPE_CRON) { if (returnToNormal) - message = new LocalizableMessage("event.schedule.cronUntil", unescapeHtml(activeCron), unescapeHtml(inactiveCron)); + message = new LocalizableMessage("event.schedule.cronUntil", unescapedActiveCron, unescapedInactiveCron); else - message = new LocalizableMessage("event.schedule.cronAt", unescapeHtml(activeCron)); + message = new LocalizableMessage("event.schedule.cronAt", unescapedActiveCron); } else throw new ShouldNeverHappenException("Unknown schedule type: " + scheduleType); From 98909c20aa67a8dc6a3de2110668854449ba8b1f Mon Sep 17 00:00:00 2001 From: Kamil Jarmusik Date: Thu, 30 Oct 2025 19:39:49 +0100 Subject: [PATCH 44/53] #3209 Fixed escaping fields in mailing lists, maintenance events, point links, publishers: - Added convert definitions for UserEntryJson, AddressEntryJson; - Fixed escape add address; --- WebContent/WEB-INF/dwr.xml | 4 ++-- WebContent/WEB-INF/jsp/mailingLists.jsp | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/WebContent/WEB-INF/dwr.xml b/WebContent/WEB-INF/dwr.xml index 2b0f4b5e47..c99802b02f 100644 --- a/WebContent/WEB-INF/dwr.xml +++ b/WebContent/WEB-INF/dwr.xml @@ -255,8 +255,8 @@ - - + + Date: Thu, 30 Oct 2025 20:27:32 +0100 Subject: [PATCH 45/53] #3197 CVE-2021-26829 Mitigation Guidance [System settings]: - Corrected default path; --- .../mango/service/SystemSettingsService.java | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/org/scada_lts/mango/service/SystemSettingsService.java b/src/org/scada_lts/mango/service/SystemSettingsService.java index ae0eaa411e..7c34f7bc6e 100644 --- a/src/org/scada_lts/mango/service/SystemSettingsService.java +++ b/src/org/scada_lts/mango/service/SystemSettingsService.java @@ -468,21 +468,29 @@ public int getWorkItemsReportingItemsPerSecondLimit() { } } - public String getWebResourceGraphicsPath(){ + public String getWebResourceGraphicsPath() { String defaultValue = SystemSettingsUtils.getWebResourceGraphicsPath(); try { - return SystemSettingsDAO.getValue(SystemSettingsDAO.WEB_RESOURCE_GRAPHICS_PATH, defaultValue); - } catch (Exception e){ + String path = SystemSettingsDAO.getValue(SystemSettingsDAO.WEB_RESOURCE_GRAPHICS_PATH, defaultValue); + if(PathSecureUtils.ValidationPaths.validatePath(path, a -> true)) { + return path; + } + return defaultValue; + } catch (Exception e) { LOG.error(e.getMessage()); return defaultValue; } } - public String getWebResourceUploadsPath(){ + public String getWebResourceUploadsPath() { String defaultValue = SystemSettingsUtils.getWebResourceUploadsPath(); try { - return SystemSettingsDAO.getValue(SystemSettingsDAO.WEB_RESOURCE_UPLOADS_PATH, defaultValue); - } catch (Exception e){ + String path = SystemSettingsDAO.getValue(SystemSettingsDAO.WEB_RESOURCE_UPLOADS_PATH, defaultValue); + if(PathSecureUtils.ValidationPaths.validatePath(path, a -> true)) { + return path; + } + return defaultValue; + } catch (Exception e) { LOG.error(e.getMessage()); return defaultValue; } From 933da50eb3c1b26952a1fec65eaa61e3d92dbd55 Mon Sep 17 00:00:00 2001 From: patrykb0802 Date: Mon, 3 Nov 2025 09:44:04 +0100 Subject: [PATCH 46/53] #3209 Fixed escaping fields in mailing lists, maintenance events, point links, publishers --- WebContent/WEB-INF/dwr.xml | 3 ++- WebContent/WEB-INF/jsp/publisherList.jsp | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/WebContent/WEB-INF/dwr.xml b/WebContent/WEB-INF/dwr.xml index c99802b02f..78977cc947 100644 --- a/WebContent/WEB-INF/dwr.xml +++ b/WebContent/WEB-INF/dwr.xml @@ -111,7 +111,7 @@ - + @@ -248,6 +248,7 @@ + diff --git a/WebContent/WEB-INF/jsp/publisherList.jsp b/WebContent/WEB-INF/jsp/publisherList.jsp index e900111c51..4da415094f 100644 --- a/WebContent/WEB-INF/jsp/publisherList.jsp +++ b/WebContent/WEB-INF/jsp/publisherList.jsp @@ -36,7 +36,7 @@ [ function(p) { let b = document.createElement("b"); - b.appendChild(document.createTextNode(p.name)); + b.appendChild(document.createTextNode(unescapeHtml(p.name))); return b; }, function(p) { return p.typeMessage; }, From 4ce808621032cdebd7c97945b6b9e9e53b51a7e0 Mon Sep 17 00:00:00 2001 From: Kamil Jarmusik Date: Mon, 3 Nov 2025 15:37:20 +0100 Subject: [PATCH 47/53] #3209 Fixed escaping fields in mailing lists, maintenance events, point links, publishers: - Corrected all publisher types; --- WebContent/WEB-INF/dwr.xml | 12 ++++++++-- WebContent/WEB-INF/jsp/publisherEdit.jsp | 14 +++++------ .../jsp/publisherEdit/editHttpSender.jsp | 24 ++++++++----------- WebContent/WEB-INF/jsp/publisherList.jsp | 10 +++----- 4 files changed, 30 insertions(+), 30 deletions(-) diff --git a/WebContent/WEB-INF/dwr.xml b/WebContent/WEB-INF/dwr.xml index 78977cc947..291675a1ac 100644 --- a/WebContent/WEB-INF/dwr.xml +++ b/WebContent/WEB-INF/dwr.xml @@ -148,8 +148,8 @@ - - + + @@ -249,6 +249,14 @@ + + + + + + + + diff --git a/WebContent/WEB-INF/jsp/publisherEdit.jsp b/WebContent/WEB-INF/jsp/publisherEdit.jsp index cda8c32e75..211c9a4a8f 100644 --- a/WebContent/WEB-INF/jsp/publisherEdit.jsp +++ b/WebContent/WEB-INF/jsp/publisherEdit.jsp @@ -103,20 +103,20 @@ - + @@ -124,7 +124,7 @@ + selectedValue=""/>
- +
- +
- + @@ -134,14 +134,14 @@
- - + + diff --git a/WebContent/WEB-INF/jsp/publisherEdit/editHttpSender.jsp b/WebContent/WEB-INF/jsp/publisherEdit/editHttpSender.jsp index 1e2483ce3e..cb97d8c666 100644 --- a/WebContent/WEB-INF/jsp/publisherEdit/editHttpSender.jsp +++ b/WebContent/WEB-INF/jsp/publisherEdit/editHttpSender.jsp @@ -35,22 +35,22 @@ var list = response.data.allPoints; for (var i=0; i"; @@ -164,7 +164,7 @@ else { hide("noStaticParametersMsg"); dwr.util.addRows("staticParameterList", staticParameterList, [ - function(data) { return data.key +"="+ data.value; }, + function(data) { return escapeHtml(data.key) +"="+ escapeHtml(data.value); }, function(data, options) { return ""; @@ -211,15 +211,11 @@ hide("selectedPointsEmpty"); dwr.util.addRows("selectedPoints", selectedPoints, [ - function(data) { - const span = document.createElement('span'); - span.textContent = data.pointName; - return span; - }, + function(data) { return "" + escapeHtml(data.pointName) + ""; }, function(data) { return ""; }, - function(data) { return data.pointType; }, + function(data) { return "" + data.pointType + ""; }, function(data) { - return ""; }, function(data) { diff --git a/WebContent/WEB-INF/jsp/publisherList.jsp b/WebContent/WEB-INF/jsp/publisherList.jsp index 4da415094f..89425474ec 100644 --- a/WebContent/WEB-INF/jsp/publisherList.jsp +++ b/WebContent/WEB-INF/jsp/publisherList.jsp @@ -34,13 +34,9 @@ dwr.util.removeAllRows("publisherList"); dwr.util.addRows("publisherList", publishers, [ - function(p) { - let b = document.createElement("b"); - b.appendChild(document.createTextNode(unescapeHtml(p.name))); - return b; - }, - function(p) { return p.typeMessage; }, - function(p) { return p.configDescription; }, + function(p) { return "" + p.name + ""; }, + function(p) { return "" + unescapeHtml(p.typeMessage) + ""; }, + function(p) { return "" + unescapeHtml(p.configDescription) + ""; }, function(p) { if (p.enabled) return '" '+ From 91b4d671d470a9f13c257068d5fb64c4d1e91fd8 Mon Sep 17 00:00:00 2001 From: Kamil Jarmusik Date: Mon, 3 Nov 2025 16:53:45 +0100 Subject: [PATCH 48/53] #3209 Fixed escaping fields in mailing lists, maintenance events, point links, publishers: - Corrected editPachube.jsp, editPersistent.jsp; --- WebContent/WEB-INF/jsp/publisherEdit/editPachube.jsp | 6 +----- WebContent/WEB-INF/jsp/publisherEdit/editPersistent.jsp | 6 +----- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/WebContent/WEB-INF/jsp/publisherEdit/editPachube.jsp b/WebContent/WEB-INF/jsp/publisherEdit/editPachube.jsp index a583bd0c85..411f1ea4f2 100644 --- a/WebContent/WEB-INF/jsp/publisherEdit/editPachube.jsp +++ b/WebContent/WEB-INF/jsp/publisherEdit/editPachube.jsp @@ -77,11 +77,7 @@ hide("selectedPointsEmpty"); dwr.util.addRows("selectedPoints", selectedPoints, [ - function(data) { - const span = document.createElement('span'); - span.textContent = data.pointName; - return span; - }, + function(data) { return "" + escapeHtml(data.pointName) + ""; }, function(data) { return ""; }, function(data) { return data.pointType; }, function(data) { diff --git a/WebContent/WEB-INF/jsp/publisherEdit/editPersistent.jsp b/WebContent/WEB-INF/jsp/publisherEdit/editPersistent.jsp index b5fa09a62f..2d22baf2e5 100644 --- a/WebContent/WEB-INF/jsp/publisherEdit/editPersistent.jsp +++ b/WebContent/WEB-INF/jsp/publisherEdit/editPersistent.jsp @@ -76,11 +76,7 @@ hide("selectedPointsEmpty"); dwr.util.addRows("selectedPoints", selectedPoints, [ - function(data) { - const span = document.createElement('span'); - span.textContent = data.pointName; - return span; - }, + function(data) { return "" + escapeHtml(data.pointName) + ""; }, function(data) { return ""; }, function(data) { return data.pointType; }, function(data) { From 92f807e14082dd9474ffef06181e7e979079a678 Mon Sep 17 00:00:00 2001 From: patrykb0802 Date: Mon, 10 Nov 2025 13:51:33 +0100 Subject: [PATCH 49/53] #3167 fixed escaping fields --- WebContent/WEB-INF/dwr.xml | 2 ++ WebContent/WEB-INF/jsp/dataSourceEdit/editHttpReceiver.jsp | 4 ++-- WebContent/WEB-INF/jsp/dataSourceEdit/editSql.jsp | 6 +++--- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/WebContent/WEB-INF/dwr.xml b/WebContent/WEB-INF/dwr.xml index 291675a1ac..87e211ebed 100644 --- a/WebContent/WEB-INF/dwr.xml +++ b/WebContent/WEB-INF/dwr.xml @@ -257,6 +257,8 @@ + + diff --git a/WebContent/WEB-INF/jsp/dataSourceEdit/editHttpReceiver.jsp b/WebContent/WEB-INF/jsp/dataSourceEdit/editHttpReceiver.jsp index 1211019320..5f511a0cd4 100644 --- a/WebContent/WEB-INF/jsp/dataSourceEdit/editHttpReceiver.jsp +++ b/WebContent/WEB-INF/jsp/dataSourceEdit/editHttpReceiver.jsp @@ -182,9 +182,9 @@ } function editPointCBImpl(locator) { - $set("parameterName", locator.parameterName); + $set("parameterName", unescapeHtml(locator.parameterName)); $set("dataTypeId", locator.dataTypeId); - $set("binary0Value", locator.binary0Value); + $set("binary0Value", unescapeHtml(locator.binary0Value)); changeDataTypeId(); } diff --git a/WebContent/WEB-INF/jsp/dataSourceEdit/editSql.jsp b/WebContent/WEB-INF/jsp/dataSourceEdit/editSql.jsp index ea2dc6f8da..d35600c6fc 100644 --- a/WebContent/WEB-INF/jsp/dataSourceEdit/editSql.jsp +++ b/WebContent/WEB-INF/jsp/dataSourceEdit/editSql.jsp @@ -126,9 +126,9 @@ } function editPointCBImpl(locator) { - $set("fieldName", locator.fieldName); - $set("timeOverrideName", locator.timeOverrideName); - $set("updateStatement", locator.updateStatement); + $set("fieldName", unescapeHtml(locator.fieldName)); + $set("timeOverrideName", unescapeHtml(locator.timeOverrideName)); + $set("updateStatement", unescapeHtml(locator.updateStatement)); $set("dataTypeId", locator.dataTypeId); } From bb846a3e5f5526ac30a918bca6b03808e45505ee Mon Sep 17 00:00:00 2001 From: patrykb0802 Date: Fri, 14 Nov 2025 15:35:54 +0100 Subject: [PATCH 50/53] #3209 Fixed escaping fields - new UI --- scadalts-ui/src/apps/App.vue | 11 ++--- .../src/components/common/EscapedTextarea.vue | 47 +++++++++++++++++++ .../datasources/MetaDataSource/point.vue | 6 ++- scadalts-ui/src/utils/common.js | 19 ++++++++ .../src/views/DataObjects/Scripting/index.vue | 15 ++++-- .../src/views/System/SystemSettings/index.vue | 14 ++++-- 6 files changed, 95 insertions(+), 17 deletions(-) create mode 100644 scadalts-ui/src/components/common/EscapedTextarea.vue diff --git a/scadalts-ui/src/apps/App.vue b/scadalts-ui/src/apps/App.vue index 9211f0703f..c2d26da627 100644 --- a/scadalts-ui/src/apps/App.vue +++ b/scadalts-ui/src/apps/App.vue @@ -18,12 +18,8 @@
- - {{ topDescriptionPrefix }} - - - {{ topDescription }} - + +
@@ -89,7 +85,7 @@ import NavigationBar from '../layout/NavigationBar.vue'; import internetMixin from '@/utils/connection-status-utils'; import NotificationAlert from '../layout/snackbars/NotificationAlert.vue'; -import {unescapeHtml} from "@/utils/common"; +import {unescapeHtml, unescapeVueHtml} from "@/utils/common"; export default { name: 'app', @@ -179,6 +175,7 @@ export default { }, methods: { + unescapeVueHtml, fetchCustomCss() { let customCss = this.$store.state.systemSettings.customCss; diff --git a/scadalts-ui/src/components/common/EscapedTextarea.vue b/scadalts-ui/src/components/common/EscapedTextarea.vue new file mode 100644 index 0000000000..54845448c5 --- /dev/null +++ b/scadalts-ui/src/components/common/EscapedTextarea.vue @@ -0,0 +1,47 @@ + + + diff --git a/scadalts-ui/src/components/datasources/MetaDataSource/point.vue b/scadalts-ui/src/components/datasources/MetaDataSource/point.vue index 0c8e2d4f00..af01bcd747 100644 --- a/scadalts-ui/src/components/datasources/MetaDataSource/point.vue +++ b/scadalts-ui/src/components/datasources/MetaDataSource/point.vue @@ -316,7 +316,7 @@
- + > {{ $t('script.runScript') }} @@ -384,8 +384,10 @@