CVE-2021-25930 (published 2021-05-20T14:15Z)

In OpenNMS Horizon, versions opennms-1-0-stable 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.6-1 are vulnerable to CSRF, due to no CSRF protection, and since there is no validation of an existing user name while renaming a user. As a result, privileges of the renamed user are being overwritten by the old user and the old user is being deleted from the user list.

Severity: MEDIUM
CVSS v2 base score: 4.3 (AV:N/AC:M/Au:N/C:N/I:P/A:N)
CVSS v3 base score: 4.3, MEDIUM (CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:L/A:N)

Weakness information:
cwe_id: CWE-352
cwe_name: Cross-Site Request Forgery (CSRF)
description: The web application does not, or can not, sufficiently verify whether a well-formed, valid, consistent request was intentionally provided by the user who submitted the request.
extended_description: When a web server is designed to receive a request from a client without any mechanism for verifying that it was intentionally sent, then it might be possible for an attacker to trick a client into making an unintentional request to the web server which will be treated as an authentic request. This can be done via a URL, image load, XMLHttpRequest, etc. and can result in exposure of data or unintended code execution.
         

Fix information:
repo_url: https://github.com/OpenNMS/opennms
commit_details: repo_url: https://github.com/OpenNMS/opennms
author: Christian Pape
author_date: 2021-03-12 17:49:14+01:00
msg: NMS-13124: Fixed user deletion by renaming bug and CSRF privilege escalation issue
repository: repo_url: https://github.com/OpenNMS/opennms
repo_name: OpenNMS/opennms
description: Enterprise-Grade Open-Source Network Management Platform
repo_language: Java
owner: OpenNMS
file_changes: filename: UserManager.java
change_type: ModificationType.MODIFY
diff: @@ -995,6 +995,10 @@ public abstract class UserManager implements UserConfig {
                     m_users.remove(oldName);
                     throw new Exception("UserFactory:rename the data contained for old user " + oldName + " is null");
                 } else {
+                    if (m_users.containsKey(newName)) {
+                        throw new Exception("UserFactory: cannot rename user " + oldName + ". An user with the given name " + newName + " already exists");
+                    }
+
                     // Rename the user in the user map.
                     m_users.remove(oldName);
                     data.setUserId(newName);

programming_language: Java
filename: applicationContext-spring-security.xml
change_type: ModificationType.MODIFY
diff: @@ -122,6 +122,18 @@
   <!-- Allow anonymous access to the webstart portion of the app -->
   <http pattern="/webstart/**" security="none"/>
 
+  <http pattern="/admin/userGroupView/**" use-expressions="true" access-denied-page="/accessDenied.jsp" realm="OpenNMS Realm" auto-config="false" entry-point-ref="loginUrlAuthenticationEntryPoint">
+    <!-- see NMS-13124 -->
+    <csrf />
+    <intercept-url pattern="/admin/userGroupView/**" access="hasAnyRole('ROLE_ADMIN')" />
+    <http-basic entry-point-ref="xRequestedWithAwareBasicAuthEntryPoint" />
+    <logout logout-success-url="/" />
+    <custom-filter position="FORM_LOGIN_FILTER" ref="onmsUsernamePasswordAuthenticationFilter" />
+    <custom-filter position="PRE_AUTH_FILTER" ref="attributePreAuthFilter"/>
+    <custom-filter after="PRE_AUTH_FILTER" ref="headerPreAuthFilter"/>
+    <custom-filter position="LAST" ref="authFilterEnabler"/>
+  </http>
+
   <!-- Only one <http> section can match the implicit '/**' pattern -->
   <http pattern="/**" use-expressions="true" access-denied-page="/accessDenied.jsp" realm="OpenNMS Realm" auto-config="false" entry-point-ref="loginUrlAuthenticationEntryPoint">
     <intercept-url pattern="/" access="hasAnyRole('ROLE_ANONYMOUS','ROLE_USER','ROLE_DASHBOARD')" />
@@ -158,7 +170,7 @@
     <intercept-url pattern="/graph/graph.png" access="hasAnyRole('ROLE_USER','ROLE_DASHBOARD')" />
     <intercept-url pattern="/dashboard/**" access="hasAnyRole('ROLE_USER','ROLE_DASHBOARD')" />
     <intercept-url pattern="/coreweb/**" access="hasAnyRole('ROLE_USER','ROLE_DASHBOARD')" />
-    
+
     <!-- DO NOT ALLOW ACCESS TO /osgi -->
     <intercept-url pattern="/osgi/**" access="denyAll" />
 

programming_language: PHP
filename: list.jsp
change_type: ModificationType.MODIFY
diff: @@ -102,6 +102,7 @@
   <input type="hidden" name="operation"/>
   <input type="hidden" name="groupName"/>
   <input type="hidden" name="newName"/>
+  <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
 
   <div class="card">
     <table class="table table-sm table-bordered">

programming_language: HTML
filename: modifyGroup.jsp
change_type: ModificationType.MODIFY
diff: @@ -292,6 +292,7 @@
 <form role="form" class="form" method="post" id="modifyGroup" name="modifyGroup">
   <input type="hidden" name="groupName" value="<%=group.getName()%>"/>
   <input type="hidden" name="operation"/>
+  <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
 
     <div class="card">
         <div class="card-header">

programming_language: HTML
filename: newGroup.jsp
change_type: ModificationType.MODIFY
diff: @@ -76,6 +76,7 @@
   <div class="card-body">
     <form role="form" class="form" id="newGroupForm" method="post" name="newGroupForm" onsubmit="return validateFormInput();">
       <input type="hidden" name="operation" />
+      <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
 
       <div class="form-group">
         <label for="groupName" class="">Group Name</label>

programming_language: HTML
filename: editDetails.jsp
change_type: ModificationType.MODIFY
diff: @@ -70,6 +70,7 @@
   </div>
   <div class="card-body">
     <form role="form" class="form" action="<c:url value='${reqUrl}'/>" method="post" name="editForm">
+      <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
       <input type="hidden" name="operation" value="saveDetails"/>
       <input type="hidden" name="role" value="${fn:escapeXml(role.name)}"/>
 

programming_language: HTML
filename: editSpecific.jsp
change_type: ModificationType.MODIFY
diff: @@ -59,7 +59,8 @@
       <input type="hidden" name="operation" value="saveEntry"/>
       <input type="hidden" name="role" value="${fn:escapeXml(role.name)}"/>
       <input type="hidden" name="schedIndex" value="${schedIndex}"/>
-      <input type="hidden" name="timeIndex" value="${timeIndex}" /> 
+      <input type="hidden" name="timeIndex" value="${timeIndex}" />
+      <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
 
       <div class="form-group form-row">
         <label class="col-sm-2">On-Call Role</label>

programming_language: HTML
filename: list.jsp
change_type: ModificationType.MODIFY
diff: @@ -93,6 +93,7 @@
 <form action="<c:url value='${reqUrl}'/>" method="post" name="roleForm">
 	<input type="hidden" name="operation" />
 	<input type="hidden" name="role" />
+	<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
 </form>
 
 <div class="card">
@@ -148,6 +149,7 @@
 </div> <!-- panel -->
 
 <form action="<c:url value='${reqUrl}'/>" method="post" name="newForm">
+  <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
   <input name="operation" type="hidden" value="new"/>
   <button type="submit" class="btn btn-secondary">Add New On-Call Role</button>
 </form>

programming_language: HTML
filename: view.jsp
change_type: ModificationType.MODIFY
diff: @@ -157,10 +157,12 @@
 <form action="<c:url value='${reqUrl}'/>" method="post" name="editForm">
   <input type="hidden" name="operation" value="editDetails"/>
   <input type="hidden" name="role" value="${fn:escapeXml(role.name)}"/>
+  <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
   <button type="submit" class="btn btn-secondary">Value Details</button>
 </form>
 
 <form action="<c:url value='${reqUrl}'/>" method="post" name="doneForm" class="my-4">
+  <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
   <button type="submit" class="btn btn-secondary">Done</button>
 </form>
 
@@ -220,6 +222,7 @@
 </div>
 
 <form action="<c:url value='${reqUrl}'/>" method="post" name="doneForm" class="mb-4">
+  <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
   <button type="submit" class="btn btn-secondary">Done</button>
 </form>
 

programming_language: HTML
filename: list.jsp
change_type: ModificationType.MODIFY
diff: @@ -95,9 +95,19 @@
     {
         document.allUsers.userID.value=userID;
         var newID = prompt("Enter new name for user.", userID);
-        
-        if (newID != null && newID != "")
-        {
+
+        if (newID != null && newID != "") {
+          if (/.*[&<>"`']+.*/.test(newID)) {
+            alert("The user ID must not contain any HTML markup.");
+            return;
+          }
+
+          var element =  document.getElementById('users(' + _.escape(newID) + ').doModify');
+          if (typeof(element) != 'undefined' && element != null) {
+            alert("A user with this ID already exist.");
+            return;
+          }
+
           document.allUsers.newID.value = newID;
           document.allUsers.action="admin/userGroupView/users/renameUser";
           document.allUsers.submit();
@@ -112,6 +122,7 @@
 <input type="hidden" name="userID"/>
 <input type="hidden" name="newID"/>
 <input type="hidden" name="password"/>
+<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
 
 <p>
   Click on the <i>User ID</i> link to view detailed information about a
@@ -209,4 +220,8 @@
   </div> <!-- panel -->
 </form>
 
+<jsp:include page="/assets/load-assets.jsp" flush="false">
+    <jsp:param name="asset" value="underscore-js" />
+</jsp:include>
+
 <jsp:include page="/includes/bootstrap-footer.jsp" flush="false" />

programming_language: HTML
filename: modifyUser.jsp
change_type: ModificationType.MODIFY
diff: @@ -256,6 +256,7 @@
 <input id="userID" type="hidden" name="userID" value="<%=user.getUserId()%>"/>
 <input id="password" type="hidden" name="password"/>
 <input id="redirect" type="hidden" name="redirect"/>
+<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
 
 <div class="row">
   <div class="col-md-6">

programming_language: JavaScript
filename: newPassword.jsp
change_type: ModificationType.MODIFY
diff: @@ -66,6 +66,8 @@
   </div>
   <div class="card-body">
     <form role="form" class="form" method="post" name="goForm">
+      <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
+
       <div class="form-group">
         <label for="pass1" class="">Password</label>
         <input type="password" class="form-control" id="pass1" name="pass1">

programming_language: HTML
filename: newUser.jsp
change_type: ModificationType.MODIFY
diff: @@ -82,6 +82,7 @@
   </div>
   <div class="card-body">
     <form class="form" role="form" id="newUserForm" method="post" name="newUserForm" onsubmit="return validateFormInput();">
+      <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
       <div class="form-group">
         <label for="userID" class="">User ID</label>
         <input id="userID" type="text" name="userID" class="form-control">

programming_language: PHP
filename: UserIT.java
change_type: ModificationType.MODIFY
diff: @@ -31,6 +31,8 @@ package org.opennms.smoketest;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
+import java.time.Duration;
+import java.time.temporal.ChronoUnit;
 import java.util.List;
 
 import org.junit.Before;
@@ -158,4 +160,168 @@ public class UserIT extends OpenNMSSeleniumIT {
         assertTrue(wait.until(pageContainsText("Password successfully changed")));
     }
 
+    @Test
+    public void testInvalidUserIds() {
+        testInvalidUserId("John<b>Doe</b>",true);
+        testInvalidUserId("Jane'Doe'",true);
+        testInvalidUserId("John&Doe",true);
+        testInvalidUserId("Jane\"\"Doe",true);
+    }
+
+    @Test
+    public void testValidUserIds() {
+        testInvalidUserId("John-Doe",false);
+        testInvalidUserId("Jane/Doe",false);
+        testInvalidUserId("John.Doe",false);
+        testInvalidUserId("Jane#Doe", false);
+        testInvalidUserId("John@Döe.com", false);
+        testInvalidUserId("JohnDoé", false);
+    }
+
+    @Test
+    public void testInvalidGroupIds() {
+        testInvalidGroupId("John<b>Doe</b>",true);
+        testInvalidGroupId("Jane'Doe'",true);
+        testInvalidGroupId("John&Doe",true);
+        testInvalidGroupId("Jane\"\"Doe",true);
+    }
+
+    @Test
+    public void testValidGroupIds() {
+        testInvalidGroupId("John-Doe",false);
+        testInvalidGroupId("Jane/Doe",false);
+        testInvalidGroupId("John.Doe",false);
+        testInvalidGroupId("Jane#Doe", false);
+        testInvalidGroupId("John@Döe.com", false);
+        testInvalidGroupId("JohnDoé", false);
+    }
+
+    public void testInvalidUserId(final String userId, final boolean mustFail) {
+        adminPage();
+        findElementByLink("Configure Users, Groups and On-Call Roles").click();
+        findElementByLink("Configure Users").click();
+        findElementByLink("Add new user").click();
+
+        enterText(By.id("userID"), userId);
+        enterText(By.id("pass1"), "SmokeTestPassword");
+        enterText(By.id("pass2"), "SmokeTestPassword");
+        findElementByXpath("//button[@type='submit' and text()='OK']").click();
+
+        if (mustFail) {
+            try {
+                final Alert alert = wait.withTimeout(Duration.of(5, ChronoUnit.SECONDS)).until(ExpectedConditions.alertIsPresent());
+                alert.dismiss();
+            } catch (final Exception e) {
+                LOG.debug("Got an exception waiting for a 'invalid user ID' alert.", e);
+                throw e;
+            }
+        } else {
+            wait.until(ExpectedConditions.elementToBeClickable(By.name("finish")));
+        }
+    }
+
+    public void testInvalidGroupId(final String groupId, final boolean mustFail) {
+        adminPage();
+        findElementByLink("Configure Users, Groups and On-Call Roles").click();
+        findElementByLink("Configure Groups").click();
+        findElementByLink("Add new group").click();
+
+        enterText(By.id("groupName"), groupId);
+        enterText(By.id("groupComment"), "SmokeTestComment");
+        findElementByXpath("//button[@type='submit' and text()='OK']").click();
+
+        if (mustFail) {
+            try {
+                final Alert alert = wait.withTimeout(Duration.of(5, ChronoUnit.SECONDS)).until(ExpectedConditions.alertIsPresent());
+                alert.dismiss();
+            } catch (final Exception e) {
+                LOG.debug("Got an exception waiting for a 'invalid group ID' alert.", e);
+                throw e;
+            }
+        } else {
+            wait.until(ExpectedConditions.elementToBeClickable(By.name("finish")));
+        }
+    }
+
+    /**
+     * see NMS-13124
+     */
+    @Test
+    public void testCsrfPrivilegeEscalation() {
+        // visit the admin's user page
+        adminPage();
+        findElementByLink("Configure Users, Groups and On-Call Roles").click();
+        findElementByLink("Configure Users").click();
+        findElementByLink("Add new user").click();
+
+        // add a new user 'user'
+        enterText(By.id("userID"), "user");
+        enterText(By.id("pass1"), "pass");
+        enterText(By.id("pass2"), "pass");
+        findElementByXpath("//button[@type='submit' and text()='OK']").click();
+
+        // assign just the ROLE_USER
+        final Select select = new Select(driver.findElement(By.name("availableRoles")));
+        select.selectByValue("ROLE_USER");
+        findElementById("roles.doAdd").click();
+        findElementById("saveUserButton").click();
+
+        // assert that this is correctly set
+        adminPage();
+        findElementByLink("Configure Users, Groups and On-Call Roles").click();
+        findElementByLink("Configure Users").click();
+        findElementById("users(user).doDetails").click();
+
+        assertTrue(wait.until(pageContainsText("ROLE_USER")));
+
+        // now construct an exploit to set ROLE_ADMIN for user 'user'
+        final String html = "<form action='" + stack.opennms().getBaseUrlInternal() + "opennms/admin/userGroupView/users/updateUser' method='POST' enctype='application/x-www-form-urlencoded'>" +
+                "<input type='hidden' name='userID' value='user' />" +
+                "<input type='hidden' name='password' value=' ' />" +
+                "<input type='hidden' name='redirect' value='/admin/userGroupView/users/saveUser' /> <input type='hidden' name='fullName' value=' ' />" +
+                "<input type='hidden' name='userComments' value=' ' />" +
+                "<input type='hidden' name='configuredRoles' value='ROLE_ADMIN' />" +
+                "<input type='hidden' name='email' value=' ' />" +
+                "<input type='hidden' name='pemail' value=' ' />" +
+                "<input type='hidden' name='xmppAddress' value=' ' />" +
+                "<input type='hidden' name='microblog' value=' ' />" +
+                "<input type='hidden' name='numericalService' value=' ' />" +
+                "<input type='hidden' name='numericalPin' value=' ' />" +
+                "<input type='hidden' name='textService' value=' ' />" +
+                "<input type='hidden' name='textPin' value=' ' />" +
+                "<input type='hidden' name='workPhone' value=' ' />" +
+                "<input type='hidden' name='mobilePhone' value=' ' />" +
+                "<input type='hidden' name='homePhone' value=' ' />" +
+                "<input type='hidden' name='tuiPin' value=' ' />" +
+                "<input type='hidden' name='timeZoneId' value=' ' />" +
+                "<input type='hidden' name='dutySchedules' value='0' />" +
+                "<input type='hidden' name='numSchedules' value='1' />" +
+                "<input type='submit' id='submitIt' />" +
+                "</form>";
+        String script = "var foo = document.createElement('div'); " +
+                "foo.innerHTML=\"" + html + "\"; " +
+                "document.body.appendChild(foo)";
+
+        // ...and execute it
+        driver.executeScript(script);
+        findElementById("submitIt").click();
+
+        // this should be denied due to CSRF protection
+        assertTrue(wait.until(pageContainsText("Access denied")));
+
+        // assure that the user's role is still ROLE_USER
+        adminPage();
+        findElementByLink("Configure Users, Groups and On-Call Roles").click();
+        findElementByLink("Configure Users").click();
+        findElementById("users(user).doDetails").click();
+        assertTrue(wait.until(pageContainsText("ROLE_USER")));
+
+        // delete the user
+        adminPage();
+        findElementByLink("Configure Users, Groups and On-Call Roles").click();
+        findElementByLink("Configure Users").click();
+
+        findElementById("users(user).doDelete").click();
+        handleAlert("Are you sure you want to delete the user user?");
+    }
 }

programming_language: Java
repo_url: https://github.com/OpenNMS/opennms
commit_details: repo_url: https://github.com/OpenNMS/opennms
author: Christian Pape
author_date: 2021-04-20 14:25:03+02:00
msg: NMS-13231: Backport Security Issues from Last Month
repository: repo_url: https://github.com/OpenNMS/opennms
repo_name: OpenNMS/opennms
description: Enterprise-Grade Open-Source Network Management Platform
repo_language: Java
owner: OpenNMS
file_changes: filename: ForeignSource.js
change_type: ModificationType.MODIFY
diff: @@ -454,7 +454,7 @@ require('../services/Requisitions');
       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
@@ -474,7 +474,7 @@ require('../services/Requisitions');
           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
@@ -517,7 +517,7 @@ require('../services/Requisitions');
     * @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;

programming_language: JavaScript
filename: Node.js
change_type: ModificationType.MODIFY
diff: @@ -476,7 +476,7 @@ const RequisitionMetaDataEntry = require('../model/RequisitionMetaDataEntry');
       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;
         },
@@ -492,7 +492,7 @@ const RequisitionMetaDataEntry = require('../model/RequisitionMetaDataEntry');
     * @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;

programming_language: JavaScript
filename: QuickAddNode.js
change_type: ModificationType.MODIFY
diff: @@ -111,8 +111,8 @@ const QuickNode = require('../model/QuickNode');
     */
     $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();
@@ -238,7 +238,7 @@ const QuickNode = require('../model/QuickNode');
             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

programming_language: JavaScript
filename: Requisition.js
change_type: ModificationType.MODIFY
diff: @@ -200,7 +200,7 @@ require('../services/Synchronize');
     * @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(
@@ -214,7 +214,7 @@ 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
           );
@@ -295,7 +295,7 @@ require('../services/Synchronize');
       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;

programming_language: JavaScript
filename: Requisitions.js
change_type: ModificationType.MODIFY
diff: @@ -172,14 +172,14 @@ require('../services/Synchronize');
         }
       });
       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
           );
@@ -201,18 +201,18 @@ require('../services/Synchronize');
         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
           );
@@ -271,7 +271,7 @@ require('../services/Synchronize');
       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
       );
@@ -286,12 +286,12 @@ require('../services/Synchronize');
     * @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();
             },
@@ -310,12 +310,12 @@ require('../services/Synchronize');
     * @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
           );

programming_language: JavaScript
filename: Synchronize.js
change_type: ModificationType.MODIFY
diff: @@ -46,7 +46,7 @@ require('./Requisitions');
           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
@@ -58,7 +58,7 @@ require('./Requisitions');
                    'Choose <b>no</b> to synchronize only the new and deleted nodes with the database executing the scan phase only for new nodes.<br/>' +
                    'Choose <b>dbonly</b> to synchronize all the nodes with the database skipping the scan phase.<br/>' +
                    'Choose <b>cancel</b> to abort the request.',
-          title: 'Synchronize Requisition ' + requisition.foreignSource,
+          title: 'Synchronize Requisition ' + _.escape(requisition.foreignSource),
           buttons: {
             fullSync: {
               label: 'Yes',

programming_language: JavaScript
filename: ForeignSource.test.js
change_type: ModificationType.MODIFY
diff: @@ -6,6 +6,7 @@
 'use strict';
 
 const angular = require('angular-js');
+const _ = require('underscore-js');
 require('angular-mocks');
 require('../../../../../src/main/assets/js/apps/onms-requisitions/requisitions');
 

programming_language: JavaScript
filename: Node.test.js
change_type: ModificationType.MODIFY
diff: @@ -8,6 +8,7 @@
 'use strict';
 
 const angular = require('angular-js');
+const _ = require('underscore-js');
 require('angular-mocks');
 require('../../../../../src/main/assets/js/apps/onms-requisitions/requisitions');
 

programming_language: JavaScript
filename: Requisition.test.js
change_type: ModificationType.MODIFY
diff: @@ -8,6 +8,7 @@
 'use strict';
 
 const angular = require('angular-js');
+const _ = require('underscore-js');
 require('angular-mocks');
 require('../../../../../src/main/assets/js/apps/onms-requisitions/requisitions');
 

programming_language: JavaScript
filename: Requisitions.test.js
change_type: ModificationType.MODIFY
diff: @@ -8,6 +8,7 @@
 'use strict';
 
 const angular = require('angular-js');
+const _ = require('underscore-js');
 require('angular-mocks');
 require('../../../../../src/main/assets/js/apps/onms-requisitions/requisitions');
 

programming_language: JavaScript
filename: AddNewUserServlet.java
change_type: ModificationType.MODIFY
diff: @@ -69,6 +69,11 @@ public class AddNewUserServlet extends HttpServlet {
         UserManager userFactory = UserFactory.getInstance();
 
         String userID = request.getParameter("userID");
+
+        if (userID != null && userID.matches(".*[&<>\"`']+.*")) {
+            throw new ServletException("User ID must not contain any HTML markup.");
+        }
+
         String password = request.getParameter("pass1");
 
         boolean hasUser = false;

programming_language: Java
filename: RenameUserServlet.java
change_type: ModificationType.MODIFY
diff: @@ -60,6 +60,10 @@ public class RenameUserServlet extends HttpServlet {
         String userID = request.getParameter("userID");
         String newID = request.getParameter("newID");
 
+        if (newID != null && newID.matches(".*[&<>\"`']+.*")) {
+            throw new ServletException("User ID must not contain any HTML markup.");
+        }
+
         // now save to the xml file
         try {
             UserManager userFactory = UserFactory.getInstance();

programming_language: Java
filename: GroupController.java
change_type: ModificationType.MODIFY
diff: @@ -151,7 +151,11 @@ public class GroupController extends AbstractController implements InitializingB
         
         String oldName = request.getParameter("groupName");
         String newName = request.getParameter("newName");
-        
+
+        if (newName != null && newName.matches(".*[&<>\"`']+.*")) {
+            throw new ServletException("Group ID must not contain any HTML markup.");
+        }
+
         if (StringUtils.hasText(oldName) && StringUtils.hasText(newName)) {
             m_groupRepository.renameGroup(oldName, newName);
         }
@@ -312,6 +316,14 @@ public class GroupController extends AbstractController implements InitializingB
             groupComment = "";
         }
 
+        if (groupName != null && groupName.matches(".*[&<>\"`']+.*")) {
+            throw new ServletException("Group ID must not contain any HTML markup.");
+        }
+
+        if (groupComment != null && groupComment.matches(".*[&<>\"`']+.*")) {
+            throw new ServletException("Group comment must not contain any HTML markup.");
+        }
+
         boolean hasGroup = false;
         try {
             hasGroup = m_groupRepository.groupExists(groupName);

programming_language: Java
filename: eventNotices.jsp
change_type: ModificationType.MODIFY
diff: @@ -36,6 +36,7 @@
 %>
 <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
 <%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn"%>
+<%@ taglib uri="https://www.owasp.org/index.php/OWASP_Java_Encoder_Project" prefix="e"%>
 
 <jsp:include page="/includes/bootstrap.jsp" flush="false" >
   <jsp:param name="title" value="Event Notifications" />
@@ -109,31 +110,31 @@
           <c:forEach items="${notifications}" var="notification">
           <tr>
             <td>
-              <input type="button" class="btn btn-secondary" value="Edit" onclick="javascript:editNotice('${notification.escapedName}')"/>
+              <input type="button" class="btn btn-secondary" value="Edit" onclick="javascript:editNotice('${e:forJavaScript(notification.escapedName)}')"/>
             </td>
             <td>
-              <input type="button" class="btn btn-secondary" value="Delete"  onclick="javascript:deleteNotice('${notification.escapedName}')"/>
+              <input type="button" class="btn btn-secondary" value="Delete"  onclick="javascript:deleteNotice('${e:forJavaScript(notification.escapedName)}')"/>
             </td>
             <td>
             <c:choose>
               <c:when test="${notification.isOn}">
-                <input type="radio" value="Off" onclick="javascript:setStatus('${notification.escapedName}','off')"/>Off
-                <input type="radio" value="On" CHECKED onclick="javascript:setStatus('${notification.escapedName}','on')"/>On
+                <input type="radio" value="Off" onclick="javascript:setStatus('${e:forJavaScript(notification.escapedName)}','off')"/>Off
+                <input type="radio" value="On" CHECKED onclick="javascript:setStatus('${e:forJavaScript(notification.escapedName)}','on')"/>On
               </c:when>
               <c:otherwise>
-                <input type="radio" value="Off" CHECKED onclick="javascript:setStatus('${notification.escapedName}','off')"/>Off
-                <input type="radio" value="On" onclick="javascript:setStatus('${notification.escapedName}','on')"/>On
+                <input type="radio" value="Off" CHECKED onclick="javascript:setStatus('${e:forJavaScript(notification.escapedName)}','off')"/>Off
+                <input type="radio" value="On" onclick="javascript:setStatus('${e:forJavaScript(notification.escapedName)}','on')"/>On
               </c:otherwise>
             </c:choose>
             </td>
             <td>
-              ${notification.name}
+            ${fn:escapeXml(notification.name)}
             </td>
             <td>
-              ${notification.eventLabel}
+              ${fn:escapeXml(notification.eventLabel)}
             </td>
             <td>
-              ${notification.displayUei}
+              ${fn:escapeXml(notification.displayUei)}
             </td>
           </tr>
           </c:forEach>

programming_language: HTML
filename: groupDetail.jsp
change_type: ModificationType.MODIFY
diff: @@ -40,6 +40,7 @@
 %>
 
 <%@page import="org.opennms.web.group.WebGroup"%>
+<%@ page import="org.opennms.core.utils.WebSecurityUtils" %>
 
 <%
     WebGroup group = (WebGroup)request.getAttribute("group");
@@ -60,13 +61,13 @@
   <div class="col-md-6">
     <div class="card">
       <div class="card-header">
-        <span>Details for Group: <%=group.getName()%></span>
+        <span>Details for Group: <%=WebSecurityUtils.sanitizeString(group.getName())%></span>
       </div>
       <table class="table table-sm">
         <tr>
           <th>Comments:</th>
           <td width="75%">
-            <%=group.getComments()%>
+            <%=WebSecurityUtils.sanitizeString(group.getComments())%>
           </td>
         </tr>
         <tr>
@@ -79,7 +80,7 @@
             <% } else { %>
               <ul class="list-unstyled">
               <% for (String user : users) { %>
-               <li> <%=user%> </li>
+               <li> <%=WebSecurityUtils.sanitizeString(user)%> </li>
               <% } %>
               </ul>
             <% } %>

programming_language: HTML
filename: list.jsp
change_type: ModificationType.MODIFY
diff: @@ -30,6 +30,7 @@
 --%>
 
 <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
+<%@taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
 
 <jsp:include page="/includes/bootstrap.jsp" flush="false" >
   <jsp:param name="title" value="Group Configuration" />
@@ -77,8 +78,11 @@
     {
         var newName = prompt("Enter new name for group.", groupName);
 
-        if (newName != null && newName != "")
-        {
+        if (newName != null && newName != "") {
+          if (/.*[&<>"`']+.*/.test(newName)) {
+            alert("The group ID must not contain any HTML markup.");
+            return;
+          }
           document.allGroups.newName.value = newName;
           document.allGroups.groupName.value=groupName;
           document.allGroups.operation.value="rename";
@@ -113,37 +117,37 @@
           <th>Comments</th>
         </tr>
          <c:forEach var="group" varStatus="groupStatus" items="${groups}">
-         <tr class="divider ${groupStatus.index % 2 == 0 ?  'even' : 'odd'}" id="group-${group.name}">
+         <tr class="divider ${groupStatus.index % 2 == 0 ?  'even' : 'odd'}" id="group-${fn:escapeXml(group.name)}">
           <td width="5%" class="text-center">
             <c:choose>
-              <c:when test='${group.name != "Admin"}'>
-                <a id="${group.name}.doDelete" href="javascript:deleteGroup('${group.name}')" onclick="return confirm('Are you sure you want to delete the group ${group.name}?')"><i class="fa fa-trash-o fa-2x"></i></a>              
+              <c:when test='${fn:escapeXml(group.name) != "Admin"}'>
+                <a id="${group.name}.doDelete" href="javascript:deleteGroup('${fn:escapeXml(group.name)}')" onclick="return confirm('Are you sure you want to delete the group ${fn:escapeXml(group.name)}?')"><i class="fa fa-trash-o fa-2x"></i></a>
               </c:when>
               <c:otherwise>
-                <i class="fa fa-trash-o fa-2x" onclick="alert('Sorry, the ${group.name} group cannot be deleted.')"></i>
+                <i class="fa fa-trash-o fa-2x" onclick="alert('Sorry, the ${fn:escapeXml(group.name)} group cannot be deleted.')"></i>
               </c:otherwise>
             </c:choose>
           </td>
           <td width="5%" class="text-center">
-            <a id="${group.name}.doModify" href="javascript:modifyGroup('${group.name}')"><i class="fa fa-edit fa-2x"></i></a>
+            <a id="${fn:escapeXml(group.name)}.doModify" href="javascript:modifyGroup('${fn:escapeXml(group.name)}')"><i class="fa fa-edit fa-2x"></i></a>
           </td>
           <td width="5%" class="text-center">
             <c:choose>
               <c:when test='${group.name != "Admin"}'>
-                <button id="${group.name}.doRename" type="button" class="btn btn-secondary" name="rename" onclick="renameGroup('${group.name}')">Rename</button>
+                <button id="${fn:escapeXml(group.name)}.doRename" type="button" class="btn btn-secondary" name="rename" onclick="renameGroup('${fn:escapeXml(group.name)}')">Rename</button>
               </c:when>
               <c:otherwise>
-                <button id="${group.name}.doRename" type="button" class="btn btn-secondary" name="rename" onclick="alert('Sorry, the Admin group cannot be renamed.')">Rename</button>
+                <button id="${fn:escapeXml(group.name)}.doRename" type="button" class="btn btn-secondary" name="rename" onclick="alert('Sorry, the Admin group cannot be renamed.')">Rename</button>
               </c:otherwise>
             </c:choose>
           </td>
           <td>
-            <a href="javascript:detailGroup('${group.name}')">${group.name}</a>
+            <a href="javascript:detailGroup('${fn:escapeXml(group.name)}')">${fn:escapeXml(group.name)}</a>
           </td>
             <td>
               <c:choose>
                 <c:when test="${group.comments.isPresent()}">
-                  ${group.comments.get()}
+                  ${fn:escapeXml(group.comments.get())}
                 </c:when>
                 
                 <c:otherwise>

programming_language: HTML
filename: modifyGroup.jsp
change_type: ModificationType.MODIFY
diff: @@ -39,6 +39,7 @@
 	"
 %>
 <%@page import="org.opennms.web.group.WebGroup"%>
+<%@ page import="org.opennms.core.utils.WebSecurityUtils" %>
 
 <%
   	WebGroup group = (WebGroup)session.getAttribute("group.modifyGroup.jsp");
@@ -463,7 +464,7 @@
     private String createSelectList(String name, String[] categories) {
         StringBuffer buffer = new StringBuffer("<select class=\"form-control custom-select\" multiple=\"multiple\" name=\""+name+"\" size=\"10\">");
         for(String category : categories){
-            buffer.append("<option>" + category + "</option>");
+            buffer.append("<option>" + WebSecurityUtils.sanitizeString(category) + "</option>");
         }
         buffer.append("</select>");
         

programming_language: HTML
filename: newGroup.jsp
change_type: ModificationType.MODIFY
diff: @@ -43,7 +43,7 @@
 </jsp:include>
 
 <script type="text/javascript">
-  function validateFormInput() 
+  function validateFormInput()
   {
     var id = new String(document.newGroupForm.groupName.value);
     if (id.toLowerCase()=="admin")
@@ -51,7 +51,18 @@
         alert("The group ID '" + document.newGroupForm.groupName.value + "' cannot be used. It may be confused with the administration group ID 'Admin'.");
         return false;
     }
-    
+
+    if (/.*[&<>"`']+.*/.test(id)) {
+        alert("The group ID must not contain any HTML markup.");
+        return false;
+    }
+
+    var comment = new String(document.newGroupForm.groupComment.value);
+    if (/.*[&<>"`']+.*/.test(comment)) {
+        alert("The group comment must not contain any HTML markup.");
+        return false;
+    }
+
     document.newGroupForm.action="admin/userGroupView/groups/modifyGroup";
     document.newGroupForm.operation.value="addGroup";
     return true;

programming_language: HTML
filename: detail.jsp
change_type: ModificationType.MODIFY
diff: @@ -449,7 +449,7 @@
     </tr>
     <% for (OnmsAcknowledgment ack : acks) {%>
     <tr class="severity-<%=alarm.getSeverity().getLabel().toLowerCase()%>">
-        <td><%=ack.getAckUser()%></td>
+        <td><%=WebSecurityUtils.sanitizeString(ack.getAckUser())%></td>
         <td><%=ack.getAckAction()%></td>
         <td><onms:datetime date="<%=ack.getAckTime()%>" /></td>
     </tr>

programming_language: Scala
filename: detail.jsp
change_type: ModificationType.MODIFY
diff: @@ -107,7 +107,7 @@
           <th class="col-1">Node</th>
           <td ${acknowledgeEvent ? '' : 'colspan="3"'} class="${acknowledgeEvent ? 'col-3' : 'col-7'}">
             <% if( event.getNodeId() > 0 ) { %>
-              <a href="element/node.jsp?node=<%=event.getNodeId()%>"><%=event.getNodeLabel()%></a>
+              <a href="element/node.jsp?node=<%=event.getNodeId()%>"><%=WebSecurityUtils.sanitizeString(event.getNodeLabel())%></a>
             <% } else {%>
               &nbsp;
             <% } %>

programming_language: HTML
filename: detail.jsp
change_type: ModificationType.MODIFY
diff: @@ -72,7 +72,7 @@
           <th class="col-2">Node</th>
           <td class="col-2">
             <% if( outage.getNodeId() > 0 ) { %>
-              <a href="element/node.jsp?node=<%=outage.getNodeId()%>"><%=outage.getNodeLabel()%></a>
+              <a href="element/node.jsp?node=<%=outage.getNodeId()%>"><%=WebSecurityUtils.sanitizeString(outage.getNodeLabel())%></a>
             <% } else {%>
               &nbsp;
             <% } %>

programming_language: Scala
filename: nodelist.tag
change_type: ModificationType.MODIFY
diff: @@ -3,6 +3,7 @@
 <%@ 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}">
@@ -12,7 +13,7 @@
     <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.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>
+          <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.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: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>

programming_language: HTML
filename: choosePath.jsp
change_type: ModificationType.MODIFY
diff: @@ -39,6 +39,10 @@
 		org.opennms.netmgt.config.*
 	"
 %>
+<%@ page import="org.opennms.core.utils.WebSecurityUtils" %>
+
+<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn"%>
+
 
 <%!
     public void init() throws ServletException {
@@ -116,7 +120,7 @@
   
 </script>
 
-<h2><%=(newNotice.getName()!=null ? "Editing notice: " + newNotice.getName() + "<br/>" : "")%></h2>
+<h2><%=(newNotice.getName()!=null ? "Editing notice: " + WebSecurityUtils.sanitizeString(newNotice.getName()) + "<br/>" : "")%></h2>
 
 <form method="post" name="info"
       action="admin/notification/noticeWizard/notificationWizard">
@@ -135,7 +139,7 @@
             <label>Name:</label>
           </td>
           <td valign="top" align="left">
-            <input type="text" class="form-control" name="name" value='<%=(newNotice.getName()!=null ? newNotice.getName() : "")%>'/>
+            <input type="text" class="form-control" name="name" value='<%=(newNotice.getName()!=null ? WebSecurityUtils.sanitizeString(newNotice.getName()) : "")%>'/>
           </td>
         </tr>
         <tr>
@@ -143,7 +147,7 @@
             <label>Description:</label>
           </td>
           <td valign="top" align="left">
-            <input type="text" class="form-control" name="description" value='<%=newNotice.getDescription().orElse("")%>'/>
+            <input type="text" class="form-control" name="description" value='<%=WebSecurityUtils.sanitizeString(newNotice.getDescription().orElse(""))%>'/>
           </td>
         </tr>
         <tr>
@@ -153,10 +157,10 @@
           <td valign="top" align="left">
             <div class="row">
               <div class="col-md-6">
-                <label>Name:</label> <input type="text" class="form-control" size="30" name="varbindName" value='<%=varbindName%>'/>
+                <label>Name:</label> <input type="text" class="form-control" size="30" name="varbindName" value='<%=WebSecurityUtils.sanitizeString(varbindName)%>'/>
               </div>
               <div class="col-md-6">
-                <label>Value:</label> <input class="form-control" type="text" size="30" name="varbindValue" value='<%=varbindValue%>'/>
+                <label>Value:</label> <input class="form-control" type="text" size="30" name="varbindValue" value='<%=WebSecurityUtils.sanitizeString(varbindValue)%>'/>
               </div>
             </div>
           </td>
@@ -174,7 +178,7 @@
             <label>Text Message:</label>
           </td>
           <td valign="top" align="left">
-            <textarea rows="3" class="form-control" name="textMsg"><%=(newNotice.getTextMessage()!=null ? newNotice.getTextMessage() : "")%></textarea>
+            <textarea rows="3" class="form-control" name="textMsg"><%=(newNotice.getTextMessage()!=null ? WebSecurityUtils.sanitizeString(newNotice.getTextMessage()) : "")%></textarea>
           </td>
          </tr>
          <tr>
@@ -182,7 +186,7 @@
             <label>Short Message:</label>
           </td>
           <td valign="top" align="left">
-            <textarea rows="1" class="form-control" name="numMsg"><%=newNotice.getNumericMessage().orElse("")%></textarea>
+            <textarea rows="1" class="form-control" name="numMsg"><%=WebSecurityUtils.sanitizeString(newNotice.getNumericMessage().orElse(""))%></textarea>
           </td>
          </tr>
          <tr>
@@ -190,7 +194,7 @@
             <label>Email Subject:</label>
           </td>
           <td valign="top" align="left">
-            <input type="text" class="form-control" name="subject" value='<%=newNotice.getSubject().orElse("")%>'/>
+            <input type="text" class="form-control" name="subject" value='<%=WebSecurityUtils.sanitizeString(newNotice.getSubject().orElse(""))%>'/>
           </td>
          </tr>
          <tr>

programming_language: HTML
filename: list.jsp
change_type: ModificationType.MODIFY
diff: @@ -36,6 +36,10 @@
 <%@page import="java.util.*" %>
 <%@page import="org.opennms.netmgt.config.*" %>
 <%@page import="org.opennms.netmgt.config.users.*" %>
+<%@ page import="org.opennms.core.utils.WebSecurityUtils" %>
+
+<%@taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
+
 <%
 	UserManager userFactory;
   	Map<String,User> users = null;
@@ -95,9 +99,13 @@
     {
         document.allUsers.userID.value=userID;
         var newID = prompt("Enter new name for user.", userID);
-        
-        if (newID != null && newID != "")
-        {
+
+        if (newID != null && newID != "") {
+          if (/.*[&<>"`']+.*/.test(newID)) {
+            alert("The user ID must not contain any HTML markup.");
+            return;
+          }
+
           document.allUsers.newID.value = newID;
           document.allUsers.action="admin/userGroupView/users/renameUser";
           document.allUsers.submit();
@@ -150,11 +158,12 @@
 	      String textService = userFactory.getTextPage(userid);
 	      String numericPin = userFactory.getNumericPin(userid);
 	      String textPin = userFactory.getTextPin(userid);
+	      String sanitizedUserId = WebSecurityUtils.sanitizeString(curUser.getUserId());
          %>
          <tr id="user-<%= userid %>">
           <% if (!curUser.getUserId().equals("admin") && !curUser.getUserId().equals("rtc")) { %>
           <td rowspan="2" class="text-center"> 
-            <a id="<%= "users("+curUser.getUserId()+").doDelete" %>" href="javascript:deleteUser('<%=curUser.getUserId()%>')" onclick="return confirm('Are you sure you want to delete the user <%=curUser.getUserId()%>?')"><i class="fa fa-trash-o fa-2x"></i></a> 
+            <a id="<%= "users("+sanitizedUserId+").doDelete" %>" href="javascript:deleteUser('<%=sanitizedUserId%>')" onclick="return confirm('Are you sure you want to delete the user <%=sanitizedUserId%>?')"><i class="fa fa-trash-o fa-2x"></i></a>
           </td>
           <% } else { %>
           <td rowspan="2" class="text-center">
@@ -162,42 +171,42 @@
           </td>
           <% } %>
           <td rowspan="2" class="text-center">
-            <a id="<%= "users("+curUser.getUserId()+").doModify" %>" href="javascript:modifyUser('<%=curUser.getUserId()%>')"><i class="fa fa-edit fa-2x"></i></a>
+            <a id="<%= "users("+sanitizedUserId+").doModify" %>" href="javascript:modifyUser('<%=sanitizedUserId%>')"><i class="fa fa-edit fa-2x"></i></a>
           </td>
           <td rowspan="2" class="text-center">
             <% if ( !curUser.getUserId().equals("admin")) { %>
-                <button id="<%= "users("+curUser.getUserId()+").doRename" %>" class="btn btn-secondary"  name="rename" onclick="renameUser('<%=curUser.getUserId()%>')">Rename</button>
+                <button id="<%= "users("+sanitizedUserId+").doRename" %>" class="btn btn-secondary"  name="rename" onclick="renameUser('<%=sanitizedUserId%>')">Rename</button>
               <% } else { %>
-                <button id="<%= "users("+curUser.getUserId()+").doRename" %>" class="btn btn-secondary"  name="rename" onclick="alert('Sorry, the admin user cannot be renamed.')">Rename</button>
+                <button id="<%= "users("+sanitizedUserId+").doRename" %>" class="btn btn-secondary"  name="rename" onclick="alert('Sorry, the admin user cannot be renamed.')">Rename</button>
               <% } %>
           </td>
           <td>
-            <a id="<%= "users("+curUser.getUserId()+").doDetails" %>" href="javascript:detailUser('<%=curUser.getUserId()%>')"><%=curUser.getUserId()%></a>
+            <a id="<%= "users("+sanitizedUserId+").doDetails" %>" href="javascript:detailUser('<%=sanitizedUserId%>')"><%=sanitizedUserId%></a>
           </td>
           <td>
-           <div id="<%= "users("+curUser.getUserId()+").fullName" %>">
+           <div id="<%= "users("+sanitizedUserId+").fullName" %>">
 		    <%= (curUser.getFullName().orElse("")) %>
 	      </div>
           </td>
           <td>
-            <div id="<%= "users("+curUser.getUserId()+").email" %>">
+            <div id="<%= "users("+sanitizedUserId+").email" %>">
             <%= ((email == null || email.equals("")) ? "&nbsp;" : email) %>
             </div>
           </td>
           <td>
-           <div id="<%= "users("+curUser.getUserId()+").pagerEmail" %>">
+           <div id="<%= "users("+sanitizedUserId+").pagerEmail" %>">
             <%= ((pagerEmail == null || pagerEmail.equals("")) ? "&nbsp;" : pagerEmail) %>
             </div>
           </td>
           <td>
-           <div id="<%= "users("+curUser.getUserId()+").xmppAddress" %>">
+           <div id="<%= "users("+sanitizedUserId+").xmppAddress" %>">
             <%= ((xmppAddress == null || xmppAddress.equals("")) ? "&nbsp;" : xmppAddress) %>
            </div>
           </td>
           </tr>
           <tr>
             <td colspan="5">
-             <div id="<%= "users("+curUser.getUserId()+").userComments" %>">
+             <div id="<%= "users("+sanitizedUserId+").userComments" %>">
              <%= (curUser.getUserComments().orElse("No Comments")) %>
 	        </div>
             </td>

programming_language: HTML
filename: modifyUser.jsp
change_type: ModificationType.MODIFY
diff: @@ -40,6 +40,7 @@
 <%@page import="org.opennms.netmgt.config.users.*"%>
 <%@page import="org.opennms.web.api.Util"%>
 <%@page import="org.opennms.web.api.Authentication"%>
+<%@ page import="org.opennms.core.utils.WebSecurityUtils" %>
 <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
 
 <%!
@@ -253,7 +254,7 @@
 </script>
 
 <form role="form" class="form-horizontal" id="modifyUser" method="post" name="modifyUser">
-<input id="userID" type="hidden" name="userID" value="<%=user.getUserId()%>"/>
+<input id="userID" type="hidden" name="userID" value="<%=WebSecurityUtils.sanitizeString(user.getUserId())%>"/>
 <input id="password" type="hidden" name="password"/>
 <input id="redirect" type="hidden" name="redirect"/>
 
@@ -261,7 +262,7 @@
   <div class="col-md-6">
     <div class="card">
       <div class="card-header">
-        <span>Modify User: <%=userid%></span>
+        <span>Modify User: <%=WebSecurityUtils.sanitizeString(userid)%></span>
       </div>
       <div class="card-body">
         <h3>User Password</h3>

programming_language: JavaScript
filename: newUser.jsp
change_type: ModificationType.MODIFY
diff: @@ -51,7 +51,12 @@
         alert("The user ID '" + document.newUserForm.userID.value + "' cannot be used. It may be confused with the administration user ID 'admin'.");
         return false;
     }
-    
+
+    if (/.*[&<>"`']+.*/.test(id)) {
+        alert("The user ID must not contain any HTML markup.");
+        return false;
+    }
+
     if (document.newUserForm.pass1.value == document.newUserForm.pass2.value) 
     {
       document.newUserForm.action="admin/userGroupView/users/addNewUser";

programming_language: PHP
filename: userDetail.jsp
change_type: ModificationType.MODIFY
diff: @@ -39,6 +39,8 @@
 		org.opennms.web.servlet.MissingParameterException
 	"
 %>
+<%@ page import="org.opennms.core.utils.WebSecurityUtils" %>
+<%@ page import="java.util.stream.Collectors" %>
 
 <%
 	User user = null;
@@ -72,7 +74,7 @@
   <div class="col-md-6">
     <div class="card">
       <div class="card-header">
-        <span>Details for User: <%=user.getUserId()%></span>
+        <span>Details for User: <%=WebSecurityUtils.sanitizeString(user.getUserId())%></span>
       </div>
       <table class="table table-sm">
         <tr>

programming_language: HTML
filename: node.jsp
change_type: ModificationType.MODIFY
diff: @@ -310,7 +310,7 @@ function confirmAssetEdit() {
 
 <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>

programming_language: Java
filename: detail.jsp
change_type: ModificationType.MODIFY
diff: @@ -114,7 +114,7 @@
       </c:choose>
     </td>
     <th class="col-md-1">Responder</th>
-    <td class="col-md-2"><%=notice.getResponder()!=null ? notice.getResponder() : "&nbsp;"%></td>
+    <td class="col-md-2"><%=notice.getResponder()!=null ? WebSecurityUtils.sanitizeString(notice.getResponder()) : "&nbsp;"%></td>
     <th class="col-md-1">Location</th>
     <td class="col-md-2">
       <c:choose>
@@ -237,7 +237,7 @@
   <%  for (NoticeSentTo sentTo : notice.getSentTo()) { %>
 
     <tr class="severity-<%=eventSeverity.toLowerCase()%>">
-      <td><%=sentTo.getUserId()%></td>
+      <td><%=WebSecurityUtils.sanitizeString(sentTo.getUserId())%></td>
 
       <td>
         <c:choose>

programming_language: HTML
filename: NotificationsPageIT.java
change_type: ModificationType.MODIFY
diff: @@ -38,6 +38,7 @@ import org.openqa.selenium.By;
 
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 public class NotificationsPageIT extends OpenNMSSeleniumIT {
+
     @Before
     public void setUp() throws Exception {
         notificationsPage();
@@ -86,5 +87,4 @@ public class NotificationsPageIT extends OpenNMSSeleniumIT {
         findElementByLink("Respond Time");
         assertElementDoesNotHaveText(By.xpath("//span[@class='label label-default']"), "admin was notified [-]");
     }
-
 }

programming_language: Java
filename: UserIT.java
change_type: ModificationType.MODIFY
diff: @@ -29,7 +29,10 @@
 package org.opennms.smoketest;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 
+import java.time.Duration;
+import java.time.temporal.ChronoUnit;
 import java.util.List;
 
 import org.junit.Before;
@@ -121,4 +124,86 @@ public class UserIT extends OpenNMSSeleniumIT {
         assertElementDoesNotExist(By.id(USER_NAME));
     }
 
+    @Test
+    public void testInvalidUserIds() {
+        testInvalidUserId("John<b>Doe</b>",true);
+        testInvalidUserId("Jane'Doe'",true);
+        testInvalidUserId("John&Doe",true);
+        testInvalidUserId("Jane\"\"Doe",true);
+    }
+
+    @Test
+    public void testValidUserIds() {
+        testInvalidUserId("John-Doe",false);
+        testInvalidUserId("Jane/Doe",false);
+        testInvalidUserId("John.Doe",false);
+        testInvalidUserId("Jane#Doe", false);
+        testInvalidUserId("John@Döe.com", false);
+        testInvalidUserId("JohnDoé", false);
+    }
+
+    @Test
+    public void testInvalidGroupIds() {
+        testInvalidGroupId("John<b>Doe</b>",true);
+        testInvalidGroupId("Jane'Doe'",true);
+        testInvalidGroupId("John&Doe",true);
+        testInvalidGroupId("Jane\"\"Doe",true);
+    }
+
+    @Test
+    public void testValidGroupIds() {
+        testInvalidGroupId("John-Doe",false);
+        testInvalidGroupId("Jane/Doe",false);
+        testInvalidGroupId("John.Doe",false);
+        testInvalidGroupId("Jane#Doe", false);
+        testInvalidGroupId("John@Döe.com", false);
+        testInvalidGroupId("JohnDoé", false);
+    }
+
+    public void testInvalidUserId(final String userId, final boolean mustFail) {
+        adminPage();
+        findElementByLink("Configure Users, Groups and On-Call Roles").click();
+        findElementByLink("Configure Users").click();
+        findElementByLink("Add new user").click();
+
+        enterText(By.id("userID"), userId);
+        enterText(By.id("pass1"), "SmokeTestPassword");
+        enterText(By.id("pass2"), "SmokeTestPassword");
+        findElementByXpath("//button[@type='submit' and text()='OK']").click();
+
+        if (mustFail) {
+            try {
+                final Alert alert = wait.withTimeout(Duration.of(5, ChronoUnit.SECONDS)).until(ExpectedConditions.alertIsPresent());
+                alert.dismiss();
+            } catch (final Exception e) {
+                LOG.debug("Got an exception waiting for a 'invalid user ID' alert.", e);
+                throw e;
+            }
+        } else {
+            wait.until(ExpectedConditions.elementToBeClickable(By.name("finish")));
+        }
+    }
+
+    public void testInvalidGroupId(final String groupId, final boolean mustFail) {
+        adminPage();
+        findElementByLink("Configure Users, Groups and On-Call Roles").click();
+        findElementByLink("Configure Groups").click();
+        findElementByLink("Add new group").click();
+
+        enterText(By.id("groupName"), groupId);
+        enterText(By.id("groupComment"), "SmokeTestComment");
+        findElementByXpath("//button[@type='submit' and text()='OK']").click();
+
+        if (mustFail) {
+            try {
+                final Alert alert = wait.withTimeout(Duration.of(5, ChronoUnit.SECONDS)).until(ExpectedConditions.alertIsPresent());
+                alert.dismiss();
+            } catch (final Exception e) {
+                LOG.debug("Got an exception waiting for a 'invalid group ID' alert.", e);
+                throw e;
+            }
+        } else {
+            wait.until(ExpectedConditions.elementToBeClickable(By.name("finish")));
+        }
+    }
 }

programming_language: Java