CVE-2021-25935 (JavaScript, published 2021-05-25T19:15Z)
Description: 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.
Severity: LOW (CVSS v3 base score: 5.4)
Weakness: CWE-79 \u2014 Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
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.
Repository: https://github.com/OpenNMS/opennms
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 message: NMS-13126: Escape foreignSource and nodeLabel string in requisition UI



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>