CVE-2021-25935: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: In OpenNMS Horizon, versions opennms-17.0.0-1 through opennms-27.1.0-1; OpenNMS Meridian, versions meridian-foundation-2015.1.0-1 through meridian-foundation-2019.1.18-1; meridian-foundation-2020.1.0-1 through meridian-foundation-2020.1.7-1 are vulnerable to Stored Cross-Site Scripting, since the function `add()` performs improper validation checks on the input sent to the `foreign-source` parameter. Due to this flaw an attacker could bypass the existing regex validation and inject an arbitrary script which will be stored in the database.
CWE description: The product does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.
CVSS v3 base score: 5.4
Published: 2021-05-25T19:15Z
Repository: https://github.com/OpenNMS/opennms
Language: JavaScript
Files changed: core/web-assets/src/main/assets/js/apps/onms-requisitions/lib/scripts/controllers/ForeignSource.js, core/web-assets/src/main/assets/js/apps/onms-requisitions/lib/scripts/controllers/Node.js, core/web-assets/src/main/assets/js/apps/onms-requisitions/lib/scripts/controllers/QuickAddNode.js, core/web-assets/src/main/assets/js/apps/onms-requisitions/lib/scripts/controllers/Requisition.js, core/web-assets/src/main/assets/js/apps/onms-requisitions/lib/scripts/controllers/Requisitions.js, core/web-assets/src/main/assets/js/apps/onms-requisitions/lib/scripts/services/Synchronize.js, core/web-assets/src/test/javascript/ng-requisitions/controllers/ForeignSource.test.js, core/web-assets/src/test/javascript/ng-requisitions/controllers/Node.test.js, core/web-assets/src/test/javascript/ng-requisitions/controllers/Requisition.test.js, core/web-assets/src/test/javascript/ng-requisitions/controllers/Requisitions.test.js, opennms-webapp/src/main/webapp/WEB-INF/tags/element/nodelist.tag, opennms-webapp/src/main/webapp/element/node.jsp
Commit date: 2021-03-05 08:25:54 +0100
Commit message: NMS-13126: Escape foreignSource and nodeLabel string in requisition UI


Diff: diff --git a/core/web-assets/src/main/assets/js/apps/onms-requisitions/lib/scripts/controllers/ForeignSource.js b/core/web-assets/src/main/assets/js/apps/onms-requisitions/lib/scripts/controllers/ForeignSource.js
index 2fdde6ab96ad..9a72a07667ff 100644
--- a/core/web-assets/src/main/assets/js/apps/onms-requisitions/lib/scripts/controllers/ForeignSource.js
+++ b/core/web-assets/src/main/assets/js/apps/onms-requisitions/lib/scripts/controllers/ForeignSource.js
@@ -452,11 +452,11 @@ require('../services/Requisitions');
     $scope.save = function() {
       var form = this.fsForm;
       RequisitionsService.startTiming();
       RequisitionsService.saveForeignSourceDefinition($scope.foreignSourceDef).then(
         function() { // success
-          growl.success('The definition for the requisition ' + $scope.foreignSource + ' has been saved.');
+          growl.success('The definition for the requisition ' + _.escape($scope.foreignSource) + ' has been saved.');
           form.$dirty = false;
         },
         $scope.errorHandler
       );
     };
@@ -472,11 +472,11 @@ require('../services/Requisitions');
       bootbox.confirm('Are you sure you want to reset the foreign source definition to the default ?', function(ok) {
         if (ok) {
           RequisitionsService.startTiming();
           RequisitionsService.deleteForeignSourceDefinition($scope.foreignSource).then(
             function() { // success
-              growl.success('The foreign source definition for ' + $scope.foreignSource + 'has been reseted.');
+              growl.success('The foreign source definition for ' + _.escape($scope.foreignSource) + 'has been reseted.');
               $scope.initialize();
             },
             $scope.errorHandler
           );
         }
@@ -515,11 +515,11 @@ require('../services/Requisitions');
     * @name ForeignSourceController:initialize
     * @ngdoc method
     * @methodOf ForeignSourceController
     */
     $scope.initialize = function() {
-      growl.success('Retrieving definition for requisition ' + $scope.foreignSource + '...');
+      growl.success('Retrieving definition for requisition ' + _.escape($scope.foreignSource) + '...');
       RequisitionsService.getForeignSourceDefinition($scope.foreignSource).then(
         function(foreignSourceDef) { // success
           $scope.foreignSourceDef = foreignSourceDef;
           // Updating pagination variables for detectors.
           $scope.filteredDetectors = $scope.foreignSourceDef.detectors;
diff --git a/core/web-assets/src/main/assets/js/apps/onms-requisitions/lib/scripts/controllers/Node.js b/core/web-assets/src/main/assets/js/apps/onms-requisitions/lib/scripts/controllers/Node.js
index 0b9b7e5c7e2f..5f1d9e174472 100644
--- a/core/web-assets/src/main/assets/js/apps/onms-requisitions/lib/scripts/controllers/Node.js
+++ b/core/web-assets/src/main/assets/js/apps/onms-requisitions/lib/scripts/controllers/Node.js
@@ -324,11 +324,11 @@ const RequisitionMetaDataEntry = require('../model/RequisitionMetaDataEntry');
             .then(function() {
               $scope.nodeForm.$setPristine(); // Ignore dirty state
               $scope.goBack();
               // If node was just created, it has no label yet
               if (node.nodeLabel) {
-                growl.success('The node ' + node.nodeLabel + ' has been deleted.');
+                growl.success('The node ' + _.escape(node.nodeLabel) + ' has been deleted.');
               } else {
                 growl.success('The node has been deleted.');
               }
             },
             $scope.errorHandler
@@ -495,11 +495,11 @@ const RequisitionMetaDataEntry = require('../model/RequisitionMetaDataEntry');
     $scope.save = function() {
       var form = this.nodeForm;
       RequisitionsService.startTiming();
       RequisitionsService.saveNode($scope.node).then(
         function() { // success
-          growl.success('The node ' + $scope.node.nodeLabel + ' has been saved.');
+          growl.success('The node ' + _.escape($scope.node.nodeLabel) + ' has been saved.');
           $scope.foreignId = $scope.node.foreignId;
           form.$dirty = false;
         },
         $scope.errorHandler
       );
@@ -511,11 +511,11 @@ const RequisitionMetaDataEntry = require('../model/RequisitionMetaDataEntry');
     * @name NodeController:refresh
     * @ngdoc method
     * @methodOf NodeController
     */
     $scope.refresh = function() {
-      growl.success('Retrieving node ' + $scope.foreignId + ' from requisition ' + $scope.foreignSource + '...');
+      growl.success('Retrieving node ' + _.escape($scope.foreignId) + ' from requisition ' + _.escape($scope.foreignSource) + '...');
       RequisitionsService.getNode($scope.foreignSource, $scope.foreignId).then(
         function(node) { // success
           $scope.node = node;
         },
         $scope.errorHandler
diff --git a/core/web-assets/src/main/assets/js/apps/onms-requisitions/lib/scripts/controllers/QuickAddNode.js b/core/web-assets/src/main/assets/js/apps/onms-requisitions/lib/scripts/controllers/QuickAddNode.js
index 4cada09ae0a6..18f7fa069487 100644
--- a/core/web-assets/src/main/assets/js/apps/onms-requisitions/lib/scripts/controllers/QuickAddNode.js
+++ b/core/web-assets/src/main/assets/js/apps/onms-requisitions/lib/scripts/controllers/QuickAddNode.js
@@ -109,12 +109,12 @@ const QuickNode = require('../model/QuickNode');
     * @ngdoc method
     * @methodOf QuickAddNodeController
     */
     $scope.provision = function() {
       $scope.isSaving = true;
-      growl.info($sanitize('The node ' + $scope.node.nodeLabel + ' is being added to requisition ' + $scope.node.foreignSource + '. Please wait...'));
-      var successMessage = $sanitize('The node ' + $scope.node.nodeLabel + ' has been added to requisition ' + $scope.node.foreignSource);
+      growl.info('The node ' + _.escape($scope.node.nodeLabel) + ' is being added to requisition ' + _.escape($scope.node.foreignSource) + '. Please wait...');
+      var successMessage = 'The node ' + _.escape($scope.node.nodeLabel) + ' has been added to requisition ' + _.escape($scope.node.foreignSource);
       RequisitionsService.quickAddNode($scope.node).then(
         function() { // success
           $scope.reset();
           bootbox.dialog({
             message: successMessage,
@@ -236,11 +236,11 @@ const QuickNode = require('../model/QuickNode');
         if (foreignSource) {
           RequisitionsService.addRequisition(foreignSource).then(
             function() { // success
               RequisitionsService.synchronizeRequisition(foreignSource, false).then(
                 function() {
-                  growl.success('The requisition ' + foreignSource + ' has been created and synchronized.');
+                  growl.success('The requisition ' + _.escape(foreignSource) + ' has been created and synchronized.');
                   $scope.foreignSources.push(foreignSource);
                 },
                 $scope.errorHandler
               );
             },
diff --git a/core/web-assets/src/main/assets/js/apps/onms-requisitions/lib/scripts/controllers/Requisition.js b/core/web-assets/src/main/assets/js/apps/onms-requisitions/lib/scripts/controllers/Requisition.js
index d7c84193a325..cc5aa1a662ac 100644
--- a/core/web-assets/src/main/assets/js/apps/onms-requisitions/lib/scripts/controllers/Requisition.js
+++ b/core/web-assets/src/main/assets/js/apps/onms-requisitions/lib/scripts/controllers/Requisition.js
@@ -198,11 +198,11 @@ require('../services/Synchronize');
     * @ngdoc method
     * @methodOf RequisitionController
     * @param {object} The node's object to delete
     */
     $scope.deleteNode = function(node) {
-      bootbox.confirm('Are you sure you want to remove the node ' + node.nodeLabel + '?', function(ok) {
+      bootbox.confirm('Are you sure you want to remove the node ' + _.escape(node.nodeLabel) + '?', function(ok) {
         if (ok) {
           RequisitionsService.startTiming();
           RequisitionsService.deleteNode(node).then(
             function() { // success
               var index = -1;
@@ -212,11 +212,11 @@ require('../services/Synchronize');
                 }
               }
               if (index > -1) {
                 $scope.filteredNodes.splice(index,1);
               }
-              growl.success('The node ' + node.nodeLabel + ' has been deleted.');
+              growl.success('The node ' + _.escape(node.nodeLabel) + ' has been deleted.');
             },
             $scope.errorHandler
           );
         }
       });
@@ -293,11 +293,11 @@ require('../services/Synchronize');
     $scope.initialize = function(customHandler) {
       var value = $cookies.get('requisitions_page_size');
       if (value) {
         $scope.pageSize = value;
       }
-      growl.success('Retrieving requisition ' + $scope.foreignSource + '...');
+      growl.success('Retrieving requisition ' + _.escape($scope.foreignSource) + '...');
       RequisitionsService.getRequisition($scope.foreignSource).then(
         function(requisition) { // success
           $scope.requisition = requisition;
           $scope.filteredNodes = requisition.nodes;
           $scope.updateFilteredNodes();
diff --git a/core/web-assets/src/main/assets/js/apps/onms-requisitions/lib/scripts/controllers/Requisitions.js b/core/web-assets/src/main/assets/js/apps/onms-requisitions/lib/scripts/controllers/Requisitions.js
index eb5cd1cf6186..47673f96a0c1 100644
--- a/core/web-assets/src/main/assets/js/apps/onms-requisitions/lib/scripts/controllers/Requisitions.js
+++ b/core/web-assets/src/main/assets/js/apps/onms-requisitions/lib/scripts/controllers/Requisitions.js
@@ -170,18 +170,18 @@ require('../services/Synchronize');
           foreignSource: function() { return foreignSource; },
           availableForeignSources: function() { return availableForeignSources; }
         }
       });
       modalInstance.result.then(function(targetForeignSource) {
-        bootbox.confirm('This action will override the existing foreign source definition for the requisition named ' + targetForeignSource + ', using ' + foreignSource + ' as a template. Are you sure you want to continue ? This cannot be undone.', function(ok) {
+        bootbox.confirm('This action will override the existing foreign source definition for the requisition named ' + _.escape(targetForeignSource) + ', using ' + _.escape(foreignSource) + ' as a template. Are you sure you want to continue ? This cannot be undone.', function(ok) {
           if (!ok) {
             return;
           }
           RequisitionsService.startTiming();
           RequisitionsService.cloneForeignSourceDefinition(foreignSource, targetForeignSource).then(
             function() { // success
-              growl.success('The foreign source definition for ' + foreignSource + ' has been cloned to ' + targetForeignSource);
+              growl.success('The foreign source definition for ' + _.escape(foreignSource) + ' has been cloned to ' + _.escape(targetForeignSource));
             },
             $scope.errorHandler
           );
         });
       });
@@ -199,22 +199,22 @@ require('../services/Synchronize');
     $scope.add = function() {
       bootbox.prompt('Please enter the name for the new requisition', function(foreignSource) {
         if (foreignSource) {
           // Validate Requisition
           if (foreignSource.match(/[/\\?:&*'"]/)) {
-            bootbox.alert('Cannot add the requisition ' + foreignSource + ' because the following characters are invalid:<br/>:, /, \\, ?, &, *, \', "');
+            bootbox.alert('Cannot add the requisition ' + _.escape(foreignSource) + ' because the following characters are invalid:<br/>:, /, \\, ?, &, *, \', "');
             return;
           }
           var r = $scope.requisitionsData.getRequisition(foreignSource);
           if (r) {
-            bootbox.alert('Cannot add the requisition ' + foreignSource+ ' because there is already a requisition with that name');
+            bootbox.alert('Cannot add the requisition ' + _.escape(foreignSource) + ' because there is already a requisition with that name');
             return;
           }
           // Create Requisition
           RequisitionsService.addRequisition(foreignSource).then(
             function(r) { // success
-              growl.success('The requisition ' + r.foreignSource + ' has been created.');
+              growl.success('The requisition ' + _.escape(r.foreignSource) + ' has been created.');
             },
             $scope.errorHandler
           );
         }
       });
@@ -269,11 +269,11 @@ require('../services/Synchronize');
     */
     $scope.refresh = function(requisition) {
       RequisitionsService.startTiming();
       RequisitionsService.updateDeployedStatsForRequisition(requisition).then(
         function() { // success
-          growl.success('The deployed statistics for ' + requisition.foreignSource + ' has been updated.');
+          growl.success('The deployed statistics for ' + _.escape(requisition.foreignSource) + ' has been updated.');
         },
         $scope.errorHandler
       );
     };
 
@@ -284,16 +284,16 @@ require('../services/Synchronize');
     * @ngdoc method
     * @methodOf RequisitionsController
     * @param {string} foreignSource The name of the requisition
     */
     $scope.removeAllNodes = function(foreignSource) {
-      bootbox.confirm('Are you sure you want to remove all the nodes from ' + foreignSource + '?', function(ok) {
+      bootbox.confirm('Are you sure you want to remove all the nodes from ' + _.escape(foreignSource) + '?', function(ok) {
         if (ok) {
           RequisitionsService.startTiming();
           RequisitionsService.removeAllNodesFromRequisition(foreignSource).then(
             function() { // success
-              growl.success('All the nodes from ' + foreignSource + ' have been removed, and the requisition has been synchronized.');
+              growl.success('All the nodes from ' + _.escape(foreignSource) + ' have been removed, and the requisition has been synchronized.');
               var req = $scope.requisitionsData.getRequisition(foreignSource);
               req.reset();
             },
             $scope.errorHandler
           );
@@ -308,16 +308,16 @@ require('../services/Synchronize');
     * @ngdoc method
     * @methodOf RequisitionsController
     * @param {string} foreignSource The name of the requisition
     */
     $scope.delete = function(foreignSource) {
-      bootbox.confirm('Are you sure you want to remove the requisition ' + foreignSource + '?', function(ok) {
+      bootbox.confirm('Are you sure you want to remove the requisition ' + _.escape(foreignSource) + '?', function(ok) {
         if (ok) {
           RequisitionsService.startTiming();
           RequisitionsService.deleteRequisition(foreignSource).then(
             function() { // success
-              growl.success('The requisition ' + foreignSource + ' has been deleted.');
+              growl.success('The requisition ' + _.escape(foreignSource) + ' has been deleted.');
             },
             $scope.errorHandler
           );
         }
       });
diff --git a/core/web-assets/src/main/assets/js/apps/onms-requisitions/lib/scripts/services/Synchronize.js b/core/web-assets/src/main/assets/js/apps/onms-requisitions/lib/scripts/services/Synchronize.js
index c2653c9dd152..64050c7eb31d 100644
--- a/core/web-assets/src/main/assets/js/apps/onms-requisitions/lib/scripts/services/Synchronize.js
+++ b/core/web-assets/src/main/assets/js/apps/onms-requisitions/lib/scripts/services/Synchronize.js
@@ -44,18 +44,18 @@ require('./Requisitions');
         */
         var doSynchronize = function(requisition, rescanExisting) {
           RequisitionsService.startTiming();
           RequisitionsService.synchronizeRequisition(requisition.foreignSource, rescanExisting).then(
             function() { // success
-              growl.success('The import operation has been started for ' + requisition.foreignSource + ' (rescanExisting? ' + rescanExisting + ')<br/>Use <b>refresh</b> to update the deployed statistics');
+              growl.success('The import operation has been started for ' + _.escape(requisition.foreignSource) + ' (rescanExisting? ' + rescanExisting + ')<br/>Use <b>refresh</b> to update the deployed statistics');
               requisition.setDeployed(true);
             },
             errorHandler
           );
         };
         bootbox.prompt({
-            title: 'Synchronize Requisition  ' +  requisition.foreignSource,
+            title: 'Synchronize Requisition  ' +  _.escape(requisition.foreignSource),
             message: '<p><b>Choose a scan option: </b></p>',
             inputType: 'radio',
             inputOptions: [
             {
                 text: 'Scan all nodes',
diff --git a/core/web-assets/src/test/javascript/ng-requisitions/controllers/ForeignSource.test.js b/core/web-assets/src/test/javascript/ng-requisitions/controllers/ForeignSource.test.js
index bc699d0da35e..25e6c72982cc 100644
--- a/core/web-assets/src/test/javascript/ng-requisitions/controllers/ForeignSource.test.js
+++ b/core/web-assets/src/test/javascript/ng-requisitions/controllers/ForeignSource.test.js
@@ -4,10 +4,11 @@
 */
 
 'use strict';
 
 const angular = require('angular-js');
+const _ = require('underscore-js');
 require('angular-mocks');
 require('../../../../../src/main/assets/js/apps/onms-requisitions/requisitions');
 
 var controllerFactory, scope, $q, mockModal = {}, mockGrowl = {}, mockRequisitionsService = {}, foreignSource = 'test-requisition';
 
diff --git a/core/web-assets/src/test/javascript/ng-requisitions/controllers/Node.test.js b/core/web-assets/src/test/javascript/ng-requisitions/controllers/Node.test.js
index 0a8f41b491c6..78251bf2a11e 100644
--- a/core/web-assets/src/test/javascript/ng-requisitions/controllers/Node.test.js
+++ b/core/web-assets/src/test/javascript/ng-requisitions/controllers/Node.test.js
@@ -6,10 +6,11 @@
 */
 
 'use strict';
 
 const angular = require('angular-js');
+const _ = require('underscore-js');
 require('angular-mocks');
 require('../../../../../src/main/assets/js/apps/onms-requisitions/requisitions');
 
 const RequisitionNode = require('../../../../../src/main/assets/js/apps/onms-requisitions/lib/scripts/model/RequisitionNode');
 
diff --git a/core/web-assets/src/test/javascript/ng-requisitions/controllers/Requisition.test.js b/core/web-assets/src/test/javascript/ng-requisitions/controllers/Requisition.test.js
index 11a2b85eadda..bad0e61d6e3f 100644
--- a/core/web-assets/src/test/javascript/ng-requisitions/controllers/Requisition.test.js
+++ b/core/web-assets/src/test/javascript/ng-requisitions/controllers/Requisition.test.js
@@ -6,10 +6,11 @@
 */
 
 'use strict';
 
 const angular = require('angular-js');
+const _ = require('underscore-js');
 require('angular-mocks');
 require('../../../../../src/main/assets/js/apps/onms-requisitions/requisitions');
 
 const Requisition = require('../../../../../src/main/assets/js/apps/onms-requisitions/lib/scripts/model/Requisition');
 
diff --git a/core/web-assets/src/test/javascript/ng-requisitions/controllers/Requisitions.test.js b/core/web-assets/src/test/javascript/ng-requisitions/controllers/Requisitions.test.js
index 8f9651c7b221..9ba817ac284b 100644
--- a/core/web-assets/src/test/javascript/ng-requisitions/controllers/Requisitions.test.js
+++ b/core/web-assets/src/test/javascript/ng-requisitions/controllers/Requisitions.test.js
@@ -6,10 +6,11 @@
 */
 
 'use strict';
 
 const angular = require('angular-js');
+const _ = require('underscore-js');
 require('angular-mocks');
 require('../../../../../src/main/assets/js/apps/onms-requisitions/requisitions');
 
 const OnmsDateFormatter = require('../../../../../src/main/assets/js/apps/onms-date-formatter');
 const RequisitionsData = require('../../../../../src/main/assets/js/apps/onms-requisitions/lib/scripts/model/RequisitionsData');
diff --git a/opennms-webapp/src/main/webapp/WEB-INF/tags/element/nodelist.tag b/opennms-webapp/src/main/webapp/WEB-INF/tags/element/nodelist.tag
index 2b9449a65f66..523add480469 100644
--- a/opennms-webapp/src/main/webapp/WEB-INF/tags/element/nodelist.tag
+++ b/opennms-webapp/src/main/webapp/WEB-INF/tags/element/nodelist.tag
@@ -1,20 +1,21 @@
 <%@ attribute name="nodes" type="java.util.List" rtexprvalue="true" required="true" %>
 <%@ attribute name="snmpParm" type="java.lang.String" rtexprvalue="true" required="true" %>
 <%@ attribute name="isMaclikeSearch"  type="java.lang.Boolean" rtexprvalue="true" required="true" %>
 
 <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+<%@taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
 
 <ul class="list-unstyled mb-0">
   <c:forEach var="nodeModel" items="${nodes}">
     <c:url var="nodeLink" value="element/node.jsp">
       <c:param name="node" value="${nodeModel.node.id}"/>
     </c:url>
     <li>
       <c:choose>
         <c:when test="${!empty nodeModel.node.foreignSource}">
-          <div class="NLnode"><a href="${nodeLink}">${nodeModel.node.label}</a>&nbsp;&nbsp;<span class="NLdbid label label-default" title="Database ID: ${nodeModel.node.id}"><i class="fa fa-database"></i>&nbsp;${nodeModel.node.id}</span>&nbsp;<span class="NLfs label label-default" title="Requisition: ${nodeModel.node.foreignSource}"><i class="fa fa-list-alt"></i>&nbsp;${nodeModel.node.foreignSource}</span>&nbsp;<span class="NLfid label label-default" title="Foreign ID: ${nodeModel.node.foreignId}"><i class="fa fa-qrcode"></i>&nbsp;${nodeModel.node.foreignId}</span>&nbsp;<span class="NLloc label label-default" title="Location: ${nodeModel.node.location.locationName}"><i class="fa fa-map-marker"></i>&nbsp;${nodeModel.node.location.locationName}</span><c:if test="${nodeModel.node.hasIngressFlows && nodeModel.node.hasEgressFlows}"><span class="NLflows label label-default" title="Flows: ingress/egress flow data available"><i class="fa fa-exchange"></i>&nbsp;ingress/egress flow data</span></c:if><c:if test="${nodeModel.node.hasIngressFlows && !nodeModel.node.hasEgressFlows}"><span class="NLflows label label-default" title="Flows: ingress flow data available"><i class="fa fa-long-arrow-left"></i>&nbsp;ingress flow data</span></c:if><c:if test="${!nodeModel.node.hasIngressFlows && nodeModel.node.hasEgressFlows}"><span class="NLflows label label-default" title="Flows: egress flow data available"><i class="fa fa-long-arrow-right"></i>&nbsp;egress flow data</span></c:if></div>
+          <div class="NLnode"><a href="${nodeLink}">${nodeModel.node.label}</a>&nbsp;&nbsp;<span class="NLdbid label label-default" title="Database ID: ${nodeModel.node.id}"><i class="fa fa-database"></i>&nbsp;${nodeModel.node.id}</span>&nbsp;<span class="NLfs label label-default" title="Requisition: ${fn:escapeXml(nodeModel.node.foreignSource)}"><i class="fa fa-list-alt"></i>&nbsp;${fn:escapeXml(nodeModel.node.foreignSource)}</span>&nbsp;<span class="NLfid label label-default" title="Foreign ID: ${nodeModel.node.foreignId}"><i class="fa fa-qrcode"></i>&nbsp;${nodeModel.node.foreignId}</span>&nbsp;<span class="NLloc label label-default" title="Location: ${nodeModel.node.location.locationName}"><i class="fa fa-map-marker"></i>&nbsp;${nodeModel.node.location.locationName}</span><c:if test="${nodeModel.node.hasIngressFlows && nodeModel.node.hasEgressFlows}"><span class="NLflows label label-default" title="Flows: ingress/egress flow data available"><i class="fa fa-exchange"></i>&nbsp;ingress/egress flow data</span></c:if><c:if test="${nodeModel.node.hasIngressFlows && !nodeModel.node.hasEgressFlows}"><span class="NLflows label label-default" title="Flows: ingress flow data available"><i class="fa fa-long-arrow-left"></i>&nbsp;ingress flow data</span></c:if><c:if test="${!nodeModel.node.hasIngressFlows && nodeModel.node.hasEgressFlows}"><span class="NLflows label label-default" title="Flows: egress flow data available"><i class="fa fa-long-arrow-right"></i>&nbsp;egress flow data</span></c:if></div>
         </c:when>
         <c:otherwise>
           <div class="NLnode"><a href="${nodeLink}">${nodeModel.node.label}</a>&nbsp;&nbsp;<span class="NLdbid label label-default" title="Database ID: ${nodeModel.node.id}"><i class="fa fa-database"></i>&nbsp;${nodeModel.node.id}</span>&nbsp;<span class="NLloc label label-default" title="Location: ${nodeModel.node.location.locationName}"><i class="fa fa-map-marker"></i>&nbsp;${nodeModel.node.location.locationName}</span> <c:if test="${nodeModel.node.hasFlows}"><span class="NLflows label label-default" title="Flows: flow data available"><i class="fa fa-exchange"></i>&nbsp;flow data</span></c:if></div>
         </c:otherwise>
       </c:choose>
diff --git a/opennms-webapp/src/main/webapp/element/node.jsp b/opennms-webapp/src/main/webapp/element/node.jsp
index a51001e65c84..2446c84d4d48 100644
--- a/opennms-webapp/src/main/webapp/element/node.jsp
+++ b/opennms-webapp/src/main/webapp/element/node.jsp
@@ -309,11 +309,11 @@ function confirmAssetEdit() {
 }
 </script>
 
 <h5>
   <c:if test="${model.foreignSource != null}">
-    <div class="NPnode">Node: <strong>${model.label}</strong>&nbsp;&nbsp;&nbsp;<span class="NPdbid badge badge-secondary " title="Database ID: ${model.id}"><i class="fa fa-database"></i>&nbsp;${model.id}</span>&nbsp;<span class="NPfs badge badge-secondary " title="Requisition: ${model.foreignSource}"><i class="fa fa-list-alt"></i>&nbsp;${model.foreignSource}</span>&nbsp;<span class="NPfid badge badge-secondary " title="Foreign ID: ${model.foreignId}"><i class="fa fa-qrcode"></i>&nbsp;${model.foreignId}</span>&nbsp;<span class="NPloc badge badge-secondary " title="Location: ${model.location}"><i class="fa fa-map-marker"></i>&nbsp;${model.location}</span> <c:if test="${model.node.hasFlows}"><span class="NPflows badge badge-secondary " title="Flows: flow data available"><i class="fa fa-exchange"></i> flow data</span></c:if></div>
+    <div class="NPnode">Node: <strong>${model.label}</strong>&nbsp;&nbsp;&nbsp;<span class="NPdbid badge badge-secondary " title="Database ID: ${model.id}"><i class="fa fa-database"></i>&nbsp;${model.id}</span>&nbsp;<span class="NPfs badge badge-secondary " title="Requisition: ${fn:escapeXml(model.foreignSource)}"><i class="fa fa-list-alt"></i>&nbsp;${fn:escapeXml(model.foreignSource)}</span>&nbsp;<span class="NPfid badge badge-secondary " title="Foreign ID: ${model.foreignId}"><i class="fa fa-qrcode"></i>&nbsp;${model.foreignId}</span>&nbsp;<span class="NPloc badge badge-secondary " title="Location: ${model.location}"><i class="fa fa-map-marker"></i>&nbsp;${model.location}</span> <c:if test="${model.node.hasFlows}"><span class="NPflows badge badge-secondary " title="Flows: flow data available"><i class="fa fa-exchange"></i> flow data</span></c:if></div>
   </c:if>
   <c:if test="${model.foreignSource == null}">
     <div class="NPnode">Node: <strong>${model.label}</strong>&nbsp;&nbsp;&nbsp;<span class="NPdbid badge badge-secondary " title="Database ID: ${model.id}"><i class="fa fa-database"></i>&nbsp;${model.id}</span>&nbsp;<span class="NPloc badge badge-secondary " title="Location: ${model.location}"><i class="fa fa-map-marker"></i>&nbsp;${model.location}</span> <c:if test="${model.node.hasFlows}"><span class="NPflows badge badge-secondary " title="Flows: flow data available"><i class="fa fa-exchange"></i> flow data</span></c:if></div>
   </c:if>
 </h5>

Vulnerable code:           growl.success('The definition for the requisition ' + $scope.foreignSource + ' has been saved.');
              growl.success('The foreign source definition for ' + $scope.foreignSource + 'has been reseted.');
      growl.success('Retrieving definition for requisition ' + $scope.foreignSource + '...');
                growl.success('The node ' + node.nodeLabel + ' has been deleted.');
          growl.success('The node ' + $scope.node.nodeLabel + ' has been saved.');
      growl.success('Retrieving node ' + $scope.foreignId + ' from requisition ' + $scope.foreignSource + '...');
      growl.info($sanitize('The node ' + $scope.node.nodeLabel + ' is being added to requisition ' + $scope.node.foreignSource + '. Please wait...'));
      var successMessage = $sanitize('The node ' + $scope.node.nodeLabel + ' has been added to requisition ' + $scope.node.foreignSource);
                  growl.success('The requisition ' + foreignSource + ' has been created and synchronized.');
      bootbox.confirm('Are you sure you want to remove the node ' + node.nodeLabel + '?', function(ok) {
              growl.success('The node ' + node.nodeLabel + ' has been deleted.');
      growl.success('Retrieving requisition ' + $scope.foreignSource + '...');
        bootbox.confirm('This action will override the existing foreign source definition for the requisition named ' + targetForeignSource + ', using ' + foreignSource + ' as a template. Are you sure you want to continue ? This cannot be undone.', function(ok) {
              growl.success('The foreign source definition for ' + foreignSource + ' has been cloned to ' + targetForeignSource);
            bootbox.alert('Cannot add the requisition ' + foreignSource + ' because the following characters are invalid:<br/>:, /, \\, ?, &, *, \', "');
            bootbox.alert('Cannot add the requisition ' + foreignSource+ ' because there is already a requisition with that name');
              growl.success('The requisition ' + r.foreignSource + ' has been created.');
          growl.success('The deployed statistics for ' + requisition.foreignSource + ' has been updated.');
      bootbox.confirm('Are you sure you want to remove all the nodes from ' + foreignSource + '?', function(ok) {
              growl.success('All the nodes from ' + foreignSource + ' have been removed, and the requisition has been synchronized.');
      bootbox.confirm('Are you sure you want to remove the requisition ' + foreignSource + '?', function(ok) {
              growl.success('The requisition ' + foreignSource + ' has been deleted.');
              growl.success('The import operation has been started for ' + requisition.foreignSource + ' (rescanExisting? ' + rescanExisting + ')<br/>Use <b>refresh</b> to update the deployed statistics');
            title: 'Synchronize Requisition  ' +  requisition.foreignSource,
          <div class="NLnode"><a href="${nodeLink}">${nodeModel.node.label}</a>&nbsp;&nbsp;<span class="NLdbid label label-default" title="Database ID: ${nodeModel.node.id}"><i class="fa fa-database"></i>&nbsp;${nodeModel.node.id}</span>&nbsp;<span class="NLfs label label-default" title="Requisition: ${nodeModel.node.foreignSource}"><i class="fa fa-list-alt"></i>&nbsp;${nodeModel.node.foreignSource}</span>&nbsp;<span class="NLfid label label-default" title="Foreign ID: ${nodeModel.node.foreignId}"><i class="fa fa-qrcode"></i>&nbsp;${nodeModel.node.foreignId}</span>&nbsp;<span class="NLloc label label-default" title="Location: ${nodeModel.node.location.locationName}"><i class="fa fa-map-marker"></i>&nbsp;${nodeModel.node.location.locationName}</span><c:if test="${nodeModel.node.hasIngressFlows && nodeModel.node.hasEgressFlows}"><span class="NLflows label label-default" title="Flows: ingress/egress flow data available"><i class="fa fa-exchange"></i>&nbsp;ingress/egress flow data</span></c:if><c:if test="${nodeModel.node.hasIngressFlows && !nodeModel.node.hasEgressFlows}"><span class="NLflows label label-default" title="Flows: ingress flow data available"><i class="fa fa-long-arrow-left"></i>&nbsp;ingress flow data</span></c:if><c:if test="${!nodeModel.node.hasIngressFlows && nodeModel.node.hasEgressFlows}"><span class="NLflows label label-default" title="Flows: egress flow data available"><i class="fa fa-long-arrow-right"></i>&nbsp;egress flow data</span></c:if></div>
    <div class="NPnode">Node: <strong>${model.label}</strong>&nbsp;&nbsp;&nbsp;<span class="NPdbid badge badge-secondary " title="Database ID: ${model.id}"><i class="fa fa-database"></i>&nbsp;${model.id}</span>&nbsp;<span class="NPfs badge badge-secondary " title="Requisition: ${model.foreignSource}"><i class="fa fa-list-alt"></i>&nbsp;${model.foreignSource}</span>&nbsp;<span class="NPfid badge badge-secondary " title="Foreign ID: ${model.foreignId}"><i class="fa fa-qrcode"></i>&nbsp;${model.foreignId}</span>&nbsp;<span class="NPloc badge badge-secondary " title="Location: ${model.location}"><i class="fa fa-map-marker"></i>&nbsp;${model.location}</span> <c:if test="${model.node.hasFlows}"><span class="NPflows badge badge-secondary " title="Flows: flow data available"><i class="fa fa-exchange"></i> flow data</span></c:if></div>
Fixed code:           growl.success('The definition for the requisition ' + _.escape($scope.foreignSource) + ' has been saved.');
              growl.success('The foreign source definition for ' + _.escape($scope.foreignSource) + 'has been reseted.');
      growl.success('Retrieving definition for requisition ' + _.escape($scope.foreignSource) + '...');
                growl.success('The node ' + _.escape(node.nodeLabel) + ' has been deleted.');
          growl.success('The node ' + _.escape($scope.node.nodeLabel) + ' has been saved.');
      growl.success('Retrieving node ' + _.escape($scope.foreignId) + ' from requisition ' + _.escape($scope.foreignSource) + '...');
      growl.info('The node ' + _.escape($scope.node.nodeLabel) + ' is being added to requisition ' + _.escape($scope.node.foreignSource) + '. Please wait...');
      var successMessage = 'The node ' + _.escape($scope.node.nodeLabel) + ' has been added to requisition ' + _.escape($scope.node.foreignSource);
                  growl.success('The requisition ' + _.escape(foreignSource) + ' has been created and synchronized.');
      bootbox.confirm('Are you sure you want to remove the node ' + _.escape(node.nodeLabel) + '?', function(ok) {
              growl.success('The node ' + _.escape(node.nodeLabel) + ' has been deleted.');
      growl.success('Retrieving requisition ' + _.escape($scope.foreignSource) + '...');
        bootbox.confirm('This action will override the existing foreign source definition for the requisition named ' + _.escape(targetForeignSource) + ', using ' + _.escape(foreignSource) + ' as a template. Are you sure you want to continue ? This cannot be undone.', function(ok) {
              growl.success('The foreign source definition for ' + _.escape(foreignSource) + ' has been cloned to ' + _.escape(targetForeignSource));
            bootbox.alert('Cannot add the requisition ' + _.escape(foreignSource) + ' because the following characters are invalid:<br/>:, /, \\, ?, &, *, \', "');
            bootbox.alert('Cannot add the requisition ' + _.escape(foreignSource) + ' because there is already a requisition with that name');
              growl.success('The requisition ' + _.escape(r.foreignSource) + ' has been created.');
          growl.success('The deployed statistics for ' + _.escape(requisition.foreignSource) + ' has been updated.');
      bootbox.confirm('Are you sure you want to remove all the nodes from ' + _.escape(foreignSource) + '?', function(ok) {
              growl.success('All the nodes from ' + _.escape(foreignSource) + ' have been removed, and the requisition has been synchronized.');
      bootbox.confirm('Are you sure you want to remove the requisition ' + _.escape(foreignSource) + '?', function(ok) {
              growl.success('The requisition ' + _.escape(foreignSource) + ' has been deleted.');
              growl.success('The import operation has been started for ' + _.escape(requisition.foreignSource) + ' (rescanExisting? ' + rescanExisting + ')<br/>Use <b>refresh</b> to update the deployed statistics');
            title: 'Synchronize Requisition  ' +  _.escape(requisition.foreignSource),
const _ = require('underscore-js');
const _ = require('underscore-js');
const _ = require('underscore-js');
const _ = require('underscore-js');
<%@taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
          <div class="NLnode"><a href="${nodeLink}">${nodeModel.node.label}</a>&nbsp;&nbsp;<span class="NLdbid label label-default" title="Database ID: ${nodeModel.node.id}"><i class="fa fa-database"></i>&nbsp;${nodeModel.node.id}</span>&nbsp;<span class="NLfs label label-default" title="Requisition: ${fn:escapeXml(nodeModel.node.foreignSource)}"><i class="fa fa-list-alt"></i>&nbsp;${fn:escapeXml(nodeModel.node.foreignSource)}</span>&nbsp;<span class="NLfid label label-default" title="Foreign ID: ${nodeModel.node.foreignId}"><i class="fa fa-qrcode"></i>&nbsp;${nodeModel.node.foreignId}</span>&nbsp;<span class="NLloc label label-default" title="Location: ${nodeModel.node.location.locationName}"><i class="fa fa-map-marker"></i>&nbsp;${nodeModel.node.location.locationName}</span><c:if test="${nodeModel.node.hasIngressFlows && nodeModel.node.hasEgressFlows}"><span class="NLflows label label-default" title="Flows: ingress/egress flow data available"><i class="fa fa-exchange"></i>&nbsp;ingress/egress flow data</span></c:if><c:if test="${nodeModel.node.hasIngressFlows && !nodeModel.node.hasEgressFlows}"><span class="NLflows label label-default" title="Flows: ingress flow data available"><i class="fa fa-long-arrow-left"></i>&nbsp;ingress flow data</span></c:if><c:if test="${!nodeModel.node.hasIngressFlows && nodeModel.node.hasEgressFlows}"><span class="NLflows label label-default" title="Flows: egress flow data available"><i class="fa fa-long-arrow-right"></i>&nbsp;egress flow data</span></c:if></div>
    <div class="NPnode">Node: <strong>${model.label}</strong>&nbsp;&nbsp;&nbsp;<span class="NPdbid badge badge-secondary " title="Database ID: ${model.id}"><i class="fa fa-database"></i>&nbsp;${model.id}</span>&nbsp;<span class="NPfs badge badge-secondary " title="Requisition: ${fn:escapeXml(model.foreignSource)}"><i class="fa fa-list-alt"></i>&nbsp;${fn:escapeXml(model.foreignSource)}</span>&nbsp;<span class="NPfid badge badge-secondary " title="Foreign ID: ${model.foreignId}"><i class="fa fa-qrcode"></i>&nbsp;${model.foreignId}</span>&nbsp;<span class="NPloc badge badge-secondary " title="Location: ${model.location}"><i class="fa fa-map-marker"></i>&nbsp;${model.location}</span> <c:if test="${model.node.hasFlows}"><span class="NPflows badge badge-secondary " title="Flows: flow data available"><i class="fa fa-exchange"></i> flow data</span></c:if></div>