CVE-2018-0046: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: A reflected cross-site scripting vulnerability in OpenNMS included with Juniper Networks Junos Space may allow the stealing of sensitive information or session credentials from Junos Space administrators or perform administrative actions. This issue affects Juniper Networks Junos Space versions prior to 18.2R1.
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: 6.1
Published: 2018-10-10T18:29Z
Repository: https://github.com/OpenNMS/opennms
Language: Other
Files changed: opennms-webapp/src/main/webapp/asset/modify.jsp, opennms-webapp/src/main/webapp/asset/nodelist.jsp
Commit date: 2017-02-01 16:32:08 +0100
Commit message: NMS-9065: Fixed the Asset pages JSPs to handle ACL filtering for release-19.0.0

Diff: diff --git a/opennms-webapp/src/main/webapp/asset/modify.jsp b/opennms-webapp/src/main/webapp/asset/modify.jsp
index e60cf3d0c3..dd48763f31 100644
--- a/opennms-webapp/src/main/webapp/asset/modify.jsp
+++ b/opennms-webapp/src/main/webapp/asset/modify.jsp
@@ -26,11 +26,11 @@
  *     http://www.opennms.org/
  *     http://www.opennms.com/
  *******************************************************************************/
 --%>
 
-<%@ page contentType="text/html;charset=UTF-8" language="java" %>
+<%@ page contentType="text/html;charset=UTF-8" language="java" import="org.opennms.web.springframework.security.AclUtils"%>
 
 <% pageContext.setAttribute("nodeId", request.getParameter("node")); %>
 
 <jsp:include page="/includes/bootstrap.jsp" flush="false">
   <jsp:param name="norequirejs" value="true" />
@@ -45,74 +45,97 @@
   <jsp:param name="script" value='<script type="text/javascript" src="lib/angular-growl-v2/build/angular-growl.js"></script>' />
   <jsp:param name="script" value='<script type="text/javascript" src="lib/bootbox/bootbox.js"></script>' />
   <jsp:param name="script" value='<script type="text/javascript" src="js/onms-assets/app.js"></script>' />
 </jsp:include>
 
-<div class="container-fluid" ng-app="onms-assets" ng-controller="NodeAssetsCtrl" ng-init="init(${nodeId})">
+<%
+  AclUtils.NodeAccessChecker accessChecker = AclUtils.getNodeAccessChecker(getServletContext());
 
-  <div growl></div>
+  Integer nodeId = null;
 
-  <h4>
-    Node: <strong><a href="element/node.jsp?node=${nodeId}">{{ nodeLabel }}</a></strong>
-  </h4>
-  <p>
-    Last modified by {{ master['lastModifiedBy'] }} at {{ master['lastModifiedDate'] | date }}
-  </p>
+  try {
+    nodeId = Integer.valueOf(request.getParameter("node"));
+  } catch (NumberFormatException e) {
+%>
+    <h2>Error parsing node parameter.</h2>
+<%
+  }
 
-  <form name="assetForm" novalidate>
-    <div class="row" ng-repeat="row in config.rows">
-      <div ng-class="col.class" ng-repeat="col in row.columns">
-        <div class="panel panel-default" ng-repeat="panel in col.panels">
-          <div class="panel-heading">
-            <h3 class="panel-title">{{ panel.title }}</h3>
-          </div>
-          <div class="panel-body">
-            <div class="form-horizontal" ng-repeat="field in panel.fields"> 
-              <div class="form-group" ng-class="{ 'has-error': assetForm[field.model].$invalid && !assetForm[field.model].$pristine, 'has-warning': assetForm[field.model].$dirty }">
-                <label class="control-label col-md-3" for="{{ field.model }}" uib-tooltip="{{ field.tooltip  }}">{{ field.label }}</label>
-                <div class="col-md-9">
-                  <%-- Static/ReadOnly fields --%>
-                  <p class="form-control-static" ng-if="field.type=='static'">{{ asset[field.model] }}</p>
-                  <%-- Standard fields with typeahead suggestions --%>
-                  <input type="text" class="form-control" id="{{ field.model }}" name="{{ field.model }}" ng-model="asset[field.model]" ng-if="field.type=='text'"
-                    typeahead-editable="true" typeahead-min-length="0" ng-pattern="field.pattern"
-                    uib-typeahead="suggestion for suggestion in getSuggestions(field.model) | filter:$viewValue"></input>
-                  <%-- Password fields --%>
-                  <input type="password" class="form-control" ng-model="asset[field.model]" ng-if="field.type=='password'"></input>
-                  <%-- Textarea fields --%>
-                  <textarea class="form-control" style="height: 20em;" ng-model="asset[field.model]" ng-if="field.type=='textarea'"></textarea>
-                  <%-- Date fields with Popup Picker --%>
-                  <p class="input-group" ng-if="field.type=='date'">
-                    <input type="date" class="form-control" uib-datepicker-popup="{{ dateFormat }}" is-open="field.open" ng-model="asset[field.model]" placeholder="Specify date using this format: {{ dateFormat }}" />
-                    <span class="input-group-btn">
-                      <button type="button" class="btn btn-default" ng-click="field.open=true"><i class="glyphicon glyphicon-calendar"></i></button>
-                    </span>
-                  </p>
-                  <%-- List/Select fields --%>
-                  <select class="form-control" ng-model="asset[field.model]" ng-if="field.type=='select'">
-                    <option ng-repeat="value in field.options">{{value}}</option>
-                  </select>
+  if (nodeId != null) {
+    if (accessChecker.isNodeAccessible(nodeId)) {
+%>
+      <div class="container-fluid" ng-app="onms-assets" ng-controller="NodeAssetsCtrl" ng-init="init(${nodeId})">
+
+        <div growl></div>
+
+        <h4>
+          Node: <strong><a href="element/node.jsp?node=${nodeId}">{{ nodeLabel }}</a></strong>
+        </h4>
+        <p>
+          Last modified by {{ master['lastModifiedBy'] }} at {{ master['lastModifiedDate'] | date }}
+        </p>
+
+        <form name="assetForm" novalidate>
+          <div class="row" ng-repeat="row in config.rows">
+            <div ng-class="col.class" ng-repeat="col in row.columns">
+              <div class="panel panel-default" ng-repeat="panel in col.panels">
+                <div class="panel-heading">
+                  <h3 class="panel-title">{{ panel.title }}</h3>
+                </div>
+                <div class="panel-body">
+                  <div class="form-horizontal" ng-repeat="field in panel.fields">
+                    <div class="form-group" ng-class="{ 'has-error': assetForm[field.model].$invalid && !assetForm[field.model].$pristine, 'has-warning': assetForm[field.model].$dirty }">
+                      <label class="control-label col-md-3" for="{{ field.model }}" uib-tooltip="{{ field.tooltip  }}">{{ field.label }}</label>
+                      <div class="col-md-9">
+                        <%-- Static/ReadOnly fields --%>
+                        <p class="form-control-static" ng-if="field.type=='static'">{{ asset[field.model] }}</p>
+                        <%-- Standard fields with typeahead suggestions --%>
+                        <input type="text" class="form-control" id="{{ field.model }}" name="{{ field.model }}" ng-model="asset[field.model]" ng-if="field.type=='text'"
+                          typeahead-editable="true" typeahead-min-length="0" ng-pattern="field.pattern"
+                          uib-typeahead="suggestion for suggestion in getSuggestions(field.model) | filter:$viewValue"></input>
+                        <%-- Password fields --%>
+                        <input type="password" class="form-control" ng-model="asset[field.model]" ng-if="field.type=='password'"></input>
+                        <%-- Textarea fields --%>
+                        <textarea class="form-control" style="height: 20em;" ng-model="asset[field.model]" ng-if="field.type=='textarea'"></textarea>
+                        <%-- Date fields with Popup Picker --%>
+                        <p class="input-group" ng-if="field.type=='date'">
+                          <input type="date" class="form-control" uib-datepicker-popup="{{ dateFormat }}" is-open="field.open" ng-model="asset[field.model]" placeholder="Specify date using this format: {{ dateFormat }}" />
+                          <span class="input-group-btn">
+                            <button type="button" class="btn btn-default" ng-click="field.open=true"><i class="glyphicon glyphicon-calendar"></i></button>
+                          </span>
+                        </p>
+                        <%-- List/Select fields --%>
+                        <select class="form-control" ng-model="asset[field.model]" ng-if="field.type=='select'">
+                          <option ng-repeat="value in field.options">{{value}}</option>
+                        </select>
+                      </div>
+                    </div>
+                  </div>
                 </div>
               </div>
-            </div> 
+            </div>
           </div>
-        </div>
-      </div>
-    </div>
-    <div class="row">
-      <div class="col-md-6">
-        <div class="btn-group">
-          <button type="button" class="btn btn-default" ng-click="save()" id="save-asset" ng-disabled="assetForm.$invalid">Save Asset Record&nbsp;&nbsp;&nbsp;
-            <span class="glyphicon glyphicon-save"><span>
-          </button>
-          <button type="button" class="btn btn-default" ng-click="reset()" id="reset-asset">Reset&nbsp;&nbsp;&nbsp;
-            <span class="glyphicon glyphicon-refresh"><span>
-          </button>
-        </div>
+          <div class="row">
+            <div class="col-md-6">
+              <div class="btn-group">
+                <button type="button" class="btn btn-default" ng-click="save()" id="save-asset" ng-disabled="assetForm.$invalid">Save Asset Record&nbsp;&nbsp;&nbsp;
+                  <span class="glyphicon glyphicon-save"><span>
+                </button>
+                <button type="button" class="btn btn-default" ng-click="reset()" id="reset-asset">Reset&nbsp;&nbsp;&nbsp;
+                  <span class="glyphicon glyphicon-refresh"><span>
+                </button>
+              </div>
+            </div>
+          </div>
+        </form>
       </div>
-    </div>
-  </form>
-
-</div>
+<%
+    } else {
+%>
+      <h2>Access denied.</h2>
+<%
+    }
+  }
+%>
 
 <jsp:include page="/includes/bootstrap-footer.jsp" flush="false"/>
 
diff --git a/opennms-webapp/src/main/webapp/asset/nodelist.jsp b/opennms-webapp/src/main/webapp/asset/nodelist.jsp
index c2ef70c36f..491249ea5d 100644
--- a/opennms-webapp/src/main/webapp/asset/nodelist.jsp
+++ b/opennms-webapp/src/main/webapp/asset/nodelist.jsp
@@ -28,17 +28,15 @@
  *******************************************************************************/
 
 --%>
 
 <%@page language="java"
-	contentType="text/html"
-	session="true"
-	import="org.opennms.web.asset.*,
-		org.opennms.web.servlet.MissingParameterException 
-	"
-%>
-
+        contentType="text/html"
+        session="true"
+        import="org.opennms.web.asset.*,
+		    org.opennms.web.servlet.MissingParameterException,
+            org.opennms.web.springframework.security.AclUtils"%>
 <%
     final String ALL_NON_EMPTY = "_allNonEmpty";
     String column = request.getParameter("column");
     String search = request.getParameter("searchvalue");
     String requiredParameters[] = new String[] { "column", "searchvalue" };
@@ -50,10 +48,12 @@
     if( search == null && ! column.equals(ALL_NON_EMPTY)) {
         throw new MissingParameterException("searchvalue", requiredParameters);
     }
 
     AssetModel.MatchingAsset[] assets = column.equals(ALL_NON_EMPTY) ? AssetModel.searchNodesWithAssets() : AssetModel.searchAssets(column, search);
+
+    AclUtils.NodeAccessChecker accessChecker = AclUtils.getNodeAccessChecker(getServletContext());
 %>
 
 <jsp:include page="/includes/bootstrap.jsp" flush="false" >
   <jsp:param name="title" value="Asset List" />
   <jsp:param name="headTitle" value="Asset List" />
@@ -78,11 +78,15 @@
             <th>Matching Text</td>
             <th>Asset Link</td>
             <th>Node Link</td>
           </tr>
 
-        <% for( int i=0; i < assets.length; i++ ) { %>
+        <% for( int i=0; i < assets.length; i++ ) {
+            if (!accessChecker.isNodeAccessible(assets[i].nodeId)) {
+                continue;
+            }
+        %>
           <tr>
             <td><%=assets[i].matchingValue%></td>
             <td><a href="asset/modify.jsp?node=<%=assets[i].nodeId%>"><%=assets[i].nodeLabel%></a></td>
             <td><a href="element/node.jsp?node=<%=assets[i].nodeId%>"><%=assets[i].nodeLabel%></a></td>
           </tr>

Vulnerable code: <%@ page contentType="text/html;charset=UTF-8" language="java" %>
<div class="container-fluid" ng-app="onms-assets" ng-controller="NodeAssetsCtrl" ng-init="init(${nodeId})">
  <div growl></div>
  <h4>
    Node: <strong><a href="element/node.jsp?node=${nodeId}">{{ nodeLabel }}</a></strong>
  </h4>
  <p>
    Last modified by {{ master['lastModifiedBy'] }} at {{ master['lastModifiedDate'] | date }}
  </p>
  <form name="assetForm" novalidate>
    <div class="row" ng-repeat="row in config.rows">
      <div ng-class="col.class" ng-repeat="col in row.columns">
        <div class="panel panel-default" ng-repeat="panel in col.panels">
          <div class="panel-heading">
            <h3 class="panel-title">{{ panel.title }}</h3>
          </div>
          <div class="panel-body">
            <div class="form-horizontal" ng-repeat="field in panel.fields"> 
              <div class="form-group" ng-class="{ 'has-error': assetForm[field.model].$invalid && !assetForm[field.model].$pristine, 'has-warning': assetForm[field.model].$dirty }">
                <label class="control-label col-md-3" for="{{ field.model }}" uib-tooltip="{{ field.tooltip  }}">{{ field.label }}</label>
                <div class="col-md-9">
                  <%-- Static/ReadOnly fields --%>
                  <p class="form-control-static" ng-if="field.type=='static'">{{ asset[field.model] }}</p>
                  <%-- Standard fields with typeahead suggestions --%>
                  <input type="text" class="form-control" id="{{ field.model }}" name="{{ field.model }}" ng-model="asset[field.model]" ng-if="field.type=='text'"
                    typeahead-editable="true" typeahead-min-length="0" ng-pattern="field.pattern"
                    uib-typeahead="suggestion for suggestion in getSuggestions(field.model) | filter:$viewValue"></input>
                  <%-- Password fields --%>
                  <input type="password" class="form-control" ng-model="asset[field.model]" ng-if="field.type=='password'"></input>
                  <%-- Textarea fields --%>
                  <textarea class="form-control" style="height: 20em;" ng-model="asset[field.model]" ng-if="field.type=='textarea'"></textarea>
                  <%-- Date fields with Popup Picker --%>
                  <p class="input-group" ng-if="field.type=='date'">
                    <input type="date" class="form-control" uib-datepicker-popup="{{ dateFormat }}" is-open="field.open" ng-model="asset[field.model]" placeholder="Specify date using this format: {{ dateFormat }}" />
                    <span class="input-group-btn">
                      <button type="button" class="btn btn-default" ng-click="field.open=true"><i class="glyphicon glyphicon-calendar"></i></button>
                    </span>
                  </p>
                  <%-- List/Select fields --%>
                  <select class="form-control" ng-model="asset[field.model]" ng-if="field.type=='select'">
                    <option ng-repeat="value in field.options">{{value}}</option>
                  </select>
            </div> 
        </div>
      </div>
    </div>
    <div class="row">
      <div class="col-md-6">
        <div class="btn-group">
          <button type="button" class="btn btn-default" ng-click="save()" id="save-asset" ng-disabled="assetForm.$invalid">Save Asset Record&nbsp;&nbsp;&nbsp;
            <span class="glyphicon glyphicon-save"><span>
          </button>
          <button type="button" class="btn btn-default" ng-click="reset()" id="reset-asset">Reset&nbsp;&nbsp;&nbsp;
            <span class="glyphicon glyphicon-refresh"><span>
          </button>
        </div>
    </div>
  </form>

</div>
	contentType="text/html"
	session="true"
	import="org.opennms.web.asset.*,
		org.opennms.web.servlet.MissingParameterException 
	"
%>

        <% for( int i=0; i < assets.length; i++ ) { %>
Fixed code: <%@ page contentType="text/html;charset=UTF-8" language="java" import="org.opennms.web.springframework.security.AclUtils"%>
<%
  AclUtils.NodeAccessChecker accessChecker = AclUtils.getNodeAccessChecker(getServletContext());
  Integer nodeId = null;
  try {
    nodeId = Integer.valueOf(request.getParameter("node"));
  } catch (NumberFormatException e) {
%>
    <h2>Error parsing node parameter.</h2>
<%
  }
  if (nodeId != null) {
    if (accessChecker.isNodeAccessible(nodeId)) {
%>
      <div class="container-fluid" ng-app="onms-assets" ng-controller="NodeAssetsCtrl" ng-init="init(${nodeId})">

        <div growl></div>

        <h4>
          Node: <strong><a href="element/node.jsp?node=${nodeId}">{{ nodeLabel }}</a></strong>
        </h4>
        <p>
          Last modified by {{ master['lastModifiedBy'] }} at {{ master['lastModifiedDate'] | date }}
        </p>

        <form name="assetForm" novalidate>
          <div class="row" ng-repeat="row in config.rows">
            <div ng-class="col.class" ng-repeat="col in row.columns">
              <div class="panel panel-default" ng-repeat="panel in col.panels">
                <div class="panel-heading">
                  <h3 class="panel-title">{{ panel.title }}</h3>
                </div>
                <div class="panel-body">
                  <div class="form-horizontal" ng-repeat="field in panel.fields">
                    <div class="form-group" ng-class="{ 'has-error': assetForm[field.model].$invalid && !assetForm[field.model].$pristine, 'has-warning': assetForm[field.model].$dirty }">
                      <label class="control-label col-md-3" for="{{ field.model }}" uib-tooltip="{{ field.tooltip  }}">{{ field.label }}</label>
                      <div class="col-md-9">
                        <%-- Static/ReadOnly fields --%>
                        <p class="form-control-static" ng-if="field.type=='static'">{{ asset[field.model] }}</p>
                        <%-- Standard fields with typeahead suggestions --%>
                        <input type="text" class="form-control" id="{{ field.model }}" name="{{ field.model }}" ng-model="asset[field.model]" ng-if="field.type=='text'"
                          typeahead-editable="true" typeahead-min-length="0" ng-pattern="field.pattern"
                          uib-typeahead="suggestion for suggestion in getSuggestions(field.model) | filter:$viewValue"></input>
                        <%-- Password fields --%>
                        <input type="password" class="form-control" ng-model="asset[field.model]" ng-if="field.type=='password'"></input>
                        <%-- Textarea fields --%>
                        <textarea class="form-control" style="height: 20em;" ng-model="asset[field.model]" ng-if="field.type=='textarea'"></textarea>
                        <%-- Date fields with Popup Picker --%>
                        <p class="input-group" ng-if="field.type=='date'">
                          <input type="date" class="form-control" uib-datepicker-popup="{{ dateFormat }}" is-open="field.open" ng-model="asset[field.model]" placeholder="Specify date using this format: {{ dateFormat }}" />
                          <span class="input-group-btn">
                            <button type="button" class="btn btn-default" ng-click="field.open=true"><i class="glyphicon glyphicon-calendar"></i></button>
                          </span>
                        </p>
                        <%-- List/Select fields --%>
                        <select class="form-control" ng-model="asset[field.model]" ng-if="field.type=='select'">
                          <option ng-repeat="value in field.options">{{value}}</option>
                        </select>
                      </div>
                    </div>
                  </div>
            </div>
          <div class="row">
            <div class="col-md-6">
              <div class="btn-group">
                <button type="button" class="btn btn-default" ng-click="save()" id="save-asset" ng-disabled="assetForm.$invalid">Save Asset Record&nbsp;&nbsp;&nbsp;
                  <span class="glyphicon glyphicon-save"><span>
                </button>
                <button type="button" class="btn btn-default" ng-click="reset()" id="reset-asset">Reset&nbsp;&nbsp;&nbsp;
                  <span class="glyphicon glyphicon-refresh"><span>
                </button>
              </div>
            </div>
          </div>
        </form>
<%
    } else {
%>
      <h2>Access denied.</h2>
<%
    }
  }
%>
        contentType="text/html"
        session="true"
        import="org.opennms.web.asset.*,
		    org.opennms.web.servlet.MissingParameterException,
            org.opennms.web.springframework.security.AclUtils"%>

    AclUtils.NodeAccessChecker accessChecker = AclUtils.getNodeAccessChecker(getServletContext());
        <% for( int i=0; i < assets.length; i++ ) {
            if (!accessChecker.isNodeAccessible(assets[i].nodeId)) {
                continue;
            }
        %>