You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@geode.apache.org by jm...@apache.org on 2022/08/29 17:06:53 UTC

[geode] branch support/1.14 updated: GEODE-10411: fix XSS vulnerability in pulse (#7836)

This is an automated email from the ASF dual-hosted git repository.

jmelchior pushed a commit to branch support/1.14
in repository https://gitbox.apache.org/repos/asf/geode.git


The following commit(s) were added to refs/heads/support/1.14 by this push:
     new c2783dd93c GEODE-10411: fix XSS vulnerability in pulse (#7836)
c2783dd93c is described below

commit c2783dd93c95085590f687a374452198bda97348
Author: Joris Melchior <jo...@gmail.com>
AuthorDate: Mon Aug 29 12:27:53 2022 -0400

    GEODE-10411: fix XSS vulnerability in pulse (#7836)
    
    * GEODE-10411: fix XSS vulnerability in pulse
    
    - html encode data coming from Geode queries
    - add cookie parameters to increase browsing security
    
    * Fix spotless check errors
---
 .../tools/pulse/tests/DataBrowserResultLoader.java |  77 ++++++-----
 .../geode/tools/pulse/tests/PulseTestData.java     |   5 +-
 .../testQueryResultClusterSmallJSInject.txt        |  23 ++++
 geode-pulse/src/main/webapp/META-INF/context.xml   |  26 ++++
 geode-pulse/src/main/webapp/WEB-INF/web.xml        |   8 ++
 .../scripts/pulsescript/pages/DataBrowserQuery.js  | 151 ++++++++++-----------
 .../pulsescript/pages/DataBrowserQueryHistory.js   |  28 ++--
 .../tools/pulse/tests/ui/PulseAutomatedTest.java   |  65 +++++++--
 8 files changed, 243 insertions(+), 140 deletions(-)

diff --git a/geode-pulse/geode-pulse-test/src/main/java/org/apache/geode/tools/pulse/tests/DataBrowserResultLoader.java b/geode-pulse/geode-pulse-test/src/main/java/org/apache/geode/tools/pulse/tests/DataBrowserResultLoader.java
index 6c50fbc996..392f0c9c48 100644
--- a/geode-pulse/geode-pulse-test/src/main/java/org/apache/geode/tools/pulse/tests/DataBrowserResultLoader.java
+++ b/geode-pulse/geode-pulse-test/src/main/java/org/apache/geode/tools/pulse/tests/DataBrowserResultLoader.java
@@ -17,12 +17,11 @@
 package org.apache.geode.tools.pulse.tests;
 
 import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
-import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.util.stream.Collectors;
 
 public class DataBrowserResultLoader {
   /* Constants for executing Data Browser queries */
@@ -32,9 +31,10 @@ public class DataBrowserResultLoader {
   public static final String QUERY_TYPE_FOUR = "query4";
   public static final String QUERY_TYPE_FIVE = "query5";
   public static final String QUERY_TYPE_SIX = "query6";
-  public static final String QUERY_TYPE_SEVENE = "query7";
+  public static final String QUERY_TYPE_SEVEN = "query7";
+  public static final String QUERY_TYPE_EIGHT = "query8";
 
-  private static DataBrowserResultLoader dbResultLoader = new DataBrowserResultLoader();
+  private static final DataBrowserResultLoader dbResultLoader = new DataBrowserResultLoader();
 
   public static DataBrowserResultLoader getInstance() {
     return dbResultLoader;
@@ -42,41 +42,46 @@ public class DataBrowserResultLoader {
 
   public String load(String queryString) throws IOException {
 
-    URL url = null;
-    InputStream inputStream = null;
-    BufferedReader streamReader = null;
-    String inputStr = null;
-    StringBuilder sampleQueryResultResponseStrBuilder = null;
+    String fileName;
+    String fileContent = "";
 
     try {
-      ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
 
-      if (queryString.equals(QUERY_TYPE_ONE)) {
-        url = classLoader.getResource("testQueryResultClusterSmall.txt");
-      } else if (queryString.equals(QUERY_TYPE_TWO)) {
-        url = classLoader.getResource("testQueryResultSmall.txt");
-      } else if (queryString.equals(QUERY_TYPE_THREE)) {
-        url = classLoader.getResource("testQueryResult.txt");
-      } else if (queryString.equals(QUERY_TYPE_FOUR)) {
-        url = classLoader.getResource("testQueryResultWithStructSmall.txt");
-      } else if (queryString.equals(QUERY_TYPE_FIVE)) {
-        url = classLoader.getResource("testQueryResultClusterWithStruct.txt");
-      } else if (queryString.equals(QUERY_TYPE_SIX)) {
-        url = classLoader.getResource("testQueryResultHashMapSmall.txt");
-      } else if (queryString.equals(QUERY_TYPE_SEVENE)) {
-        url = classLoader.getResource("testQueryResult1000.txt");
-      } else {
-        url = classLoader.getResource("testQueryResult.txt");
+      switch (queryString) {
+        case QUERY_TYPE_ONE:
+          fileName = "testQueryResultClusterSmall.txt";
+          break;
+        case QUERY_TYPE_TWO:
+          fileName = "testQueryResultSmall.txt";
+          break;
+        case QUERY_TYPE_THREE:
+          fileName = "testQueryResult.txt";
+          break;
+        case QUERY_TYPE_FOUR:
+          fileName = "testQueryResultWithStructSmall.txt";
+          break;
+        case QUERY_TYPE_FIVE:
+          fileName = "testQueryResultClusterWithStruct.txt";
+          break;
+        case QUERY_TYPE_SIX:
+          fileName = "testQueryResultHashMapSmall.txt";
+          break;
+        case QUERY_TYPE_SEVEN:
+          fileName = "testQueryResult1000.txt";
+          break;
+        case QUERY_TYPE_EIGHT:
+          fileName = "testQueryResultClusterSmallJSInject.txt";
+          break;
+        default:
+          fileName = "testQueryResult.txt";
+          break;
       }
 
-      File sampleQueryResultFile = new File(url.getPath());
-      inputStream = new FileInputStream(sampleQueryResultFile);
-      streamReader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
-      sampleQueryResultResponseStrBuilder = new StringBuilder();
-
-      while ((inputStr = streamReader.readLine()) != null) {
-        sampleQueryResultResponseStrBuilder.append(inputStr);
-      }
+      InputStream inputStream = getClass().getResourceAsStream("/" + fileName);
+      assert inputStream != null;
+      BufferedReader streamReader =
+          new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
+      fileContent = streamReader.lines().collect(Collectors.joining(System.lineSeparator()));
 
       // close stream reader
       streamReader.close();
@@ -85,6 +90,6 @@ public class DataBrowserResultLoader {
       ex.printStackTrace();
     }
 
-    return sampleQueryResultResponseStrBuilder.toString();
+    return fileContent;
   }
 }
diff --git a/geode-pulse/geode-pulse-test/src/main/java/org/apache/geode/tools/pulse/tests/PulseTestData.java b/geode-pulse/geode-pulse-test/src/main/java/org/apache/geode/tools/pulse/tests/PulseTestData.java
index 219c63f0b4..edbc0e1b5e 100644
--- a/geode-pulse/geode-pulse-test/src/main/java/org/apache/geode/tools/pulse/tests/PulseTestData.java
+++ b/geode-pulse/geode-pulse-test/src/main/java/org/apache/geode/tools/pulse/tests/PulseTestData.java
@@ -94,6 +94,9 @@ public class PulseTestData {
     public static final String partialRgnName = "R";
     public static final String chkRgnClassName = "bttn chk checkbox_true_full";
     public static final String notChkRgnClassName = "bttn chk checkbox_false_full";
+    public static final String resultClusterHeadingsXPath = "//div[@id='clusterDetails']/div/div";
+    public static final String resultClusterCellXPath =
+        "//tr/td[contains(@title, '<script>alert')]";
 
     public static final String regName = "R1";
     public static final String query1Text = "select * from " + SEPARATOR + "R1";
@@ -101,6 +104,4 @@ public class PulseTestData {
     public static final String datePattern = "EEE, MMM dd yyyy, HH:mm:ss z";
 
   }
-
-
 }
diff --git a/geode-pulse/geode-pulse-test/src/main/resources/testQueryResultClusterSmallJSInject.txt b/geode-pulse/geode-pulse-test/src/main/resources/testQueryResultClusterSmallJSInject.txt
new file mode 100644
index 0000000000..25a0a2cdeb
--- /dev/null
+++ b/geode-pulse/geode-pulse-test/src/main/resources/testQueryResultClusterSmallJSInject.txt
@@ -0,0 +1,23 @@
+{"result":[
+    ["org.apache.geode.cache.query.data.PortfolioDummy",
+     {"type":["java.lang.String","type0"],"ID":["int",0],"active":["boolean",true],"pk":["java.lang.String","0"],"collectionHolderMapDummy":["java.util.HashMap",{"3":["org.apache.geode.cache.query.data.CollectionHolder",{"arr":["java.lang.String[]",["0","1","2","3","4","SUN","IBM","YHOO","GOOG","MSFT"]]}],"2":["org.apache.geode.cache.query.data.CollectionHolder",{"arr":["java.lang.String[]",["0","1","2","3","4","SUN","IBM","YHOO","GOOG","MSFT"]]}],"1":["org.apache.geode.cache.query.data. [...]
+
+    ["org.apache.geode.cache.query.data.Portfolio",
+     {"type":["java.lang.String","type0"],"ID":["int",0],"active":["boolean",true],"pk":["java.lang.String","0"],"collectionHolderMap":["java.util.HashMap",{"3":["org.apache.geode.cache.query.data.CollectionHolder",{"arr":["java.lang.String[]",["0","1","2","3","4","SUN","IBM","YHOO","GOOG","MSFT"]]}],"2":["org.apache.geode.cache.query.data.CollectionHolder",{"arr":["java.lang.String[]",["0","1","2","3","4","SUN","IBM","YHOO","GOOG","MSFT"]]}],"1":["org.apache.geode.cache.query.data.Colle [...]
+
+    ["org.apache.geode.cache.query.data.Portfolio",
+     {"type":["java.lang.String","type1"],"ID":["int",1],"active":["boolean",false],"pk":["java.lang.String","1"],"collectionHolderMap":["java.util.HashMap",{"3":["org.apache.geode.cache.query.data.CollectionHolder",{"arr":["java.lang.String[]",["0","1","2","3","4","SUN","IBM","YHOO","GOOG","MSFT"]]}],"2":["org.apache.geode.cache.query.data.CollectionHolder",{"arr":["java.lang.String[]",["0","1","2","3","4","SUN","IBM","YHOO","GOOG","MSFT"]]}],"1":["org.apache.geode.cache.query.data.Coll [...]
+
+    ["org.apache.geode.cache.query.data.Portfolio",
+     {"type":["java.lang.String","type2"],"ID":["int",2],"active":["boolean",true],"pk":["java.lang.String","2"],"collectionHolderMap":["java.util.HashMap",{"3":["org.apache.geode.cache.query.data.CollectionHolder",{"arr":["java.lang.String[]",["0","1","2","3","4","SUN","IBM","YHOO","GOOG","MSFT"]]}],"2":["org.apache.geode.cache.query.data.CollectionHolder",{"arr":["java.lang.String[]",["0","1","2","3","4","SUN","IBM","YHOO","GOOG","MSFT"]]}],"1":["org.apache.geode.cache.query.data.Colle [...]
+
+    ["org.apache.geode.cache.query.data.Portfolio",
+     {"type":["java.lang.String","type0"],"ID":["int",3],"active":["boolean",false],"pk":["java.lang.String","3"],"collectionHolderMap":["java.util.HashMap",{"3":["org.apache.geode.cache.query.data.CollectionHolder",{"arr":["java.lang.String[]",["0","1","2","3","4","SUN","IBM","YHOO","GOOG","MSFT"]]}],"2":["org.apache.geode.cache.query.data.CollectionHolder",{"arr":["java.lang.String[]",["0","1","2","3","4","SUN","IBM","YHOO","GOOG","MSFT"]]}],"1":["org.apache.geode.cache.query.data.Coll [...]
+
+    ["org.apache.geode.cache.query.data.PortfolioDummy",
+     {"type":["java.lang.String","type1"],"ID":["int",4],"active":["boolean",true],"pk":["java.lang.String","4"],"collectionHolderMap":["java.util.HashMap",{"3":["org.apache.geode.cache.query.data.CollectionHolder",{"arr":["java.lang.String[]",["0","1","2","3","4","SUN","IBM","YHOO","GOOG","MSFT"]]}],"2":["org.apache.geode.cache.query.data.CollectionHolder",{"arr":["java.lang.String[]",["0","1","2","3","4","SUN","IBM","YHOO","GOOG","MSFT"]]}],"1":["org.apache.geode.cache.query.data.Colle [...]
+
+    ["org.apache.geode.cache.query.data.Portfolio",
+     {"type":["java.lang.String","<script>alert('xss')</script>"],"ID":["int",5],"active":["boolean",false],"pk":["java.lang.String","5"],"collectionHolderMap":["java.util.HashMap",{"3":["org.apache.geode.cache.query.data.CollectionHolder",{"arr":["java.lang.String[]",["0","1","2","3","4","SUN","IBM","YHOO","GOOG","MSFT"]]}],"2":["org.apache.geode.cache.query.data.CollectionHolder",{"arr":["java.lang.String[]",["0","1","2","3","4","SUN","IBM","YHOO","GOOG","MSFT"]]}],"1":["org.apache.geo [...]
+    ]
+}
\ No newline at end of file
diff --git a/geode-pulse/src/main/webapp/META-INF/context.xml b/geode-pulse/src/main/webapp/META-INF/context.xml
new file mode 100644
index 0000000000..e70eb6ceca
--- /dev/null
+++ b/geode-pulse/src/main/webapp/META-INF/context.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+
+<Context>
+
+    <!-- Add SameSite to the cookies for Tomcat -->
+    <CookieProcessor
+            sameSiteCookies="Strict" />
+
+</Context>
\ No newline at end of file
diff --git a/geode-pulse/src/main/webapp/WEB-INF/web.xml b/geode-pulse/src/main/webapp/WEB-INF/web.xml
index 5da192888e..e16a8dd389 100644
--- a/geode-pulse/src/main/webapp/WEB-INF/web.xml
+++ b/geode-pulse/src/main/webapp/WEB-INF/web.xml
@@ -43,6 +43,14 @@
     <param-name>spring.profiles.default</param-name>
     <param-value>pulse.authentication.default</param-value>
   </context-param>
+
+  <session-config>
+    <cookie-config>
+      <http-only>true</http-only>
+      <comment>__SAME_SITE_STRICT__</comment>
+    </cookie-config>
+  </session-config>
+
   <filter>
     <filter-name>springSecurityFilterChain</filter-name>
     <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
diff --git a/geode-pulse/src/main/webapp/scripts/pulsescript/pages/DataBrowserQuery.js b/geode-pulse/src/main/webapp/scripts/pulsescript/pages/DataBrowserQuery.js
index 2a59382892..ef1cbc67eb 100644
--- a/geode-pulse/src/main/webapp/scripts/pulsescript/pages/DataBrowserQuery.js
+++ b/geode-pulse/src/main/webapp/scripts/pulsescript/pages/DataBrowserQuery.js
@@ -68,10 +68,10 @@ function executeDBQuery(){
   }
   
   // Determine selected members query to be execute on 
-  if($("#membersList").html() != ""){
+  if($("#membersList").html() !== ""){
     var selectedMembers = $( "input[type=checkbox][name=Member]:checked" );
     for(var i=0; i< selectedMembers.length; i++){
-      if(selectedMemberNames == ""){
+      if(selectedMemberNames === ""){
         selectedMemberNames = selectedMembers[i].value;
       }else{
         selectedMemberNames += ","+selectedMembers[i].value;
@@ -169,7 +169,7 @@ function executeDBQuery(){
 
   }).error(resErrHandler);
   
-  return;
+
 }
 
 // This function displays error if occurred 
@@ -181,7 +181,7 @@ function resErrHandler(data){
   }else{
     console.log(data);
   }
-};
+}
 
 // This function creates complete result panel html
 function createHtmlForQueryResults(){
@@ -458,8 +458,8 @@ function createResultGrid(member, memberResultObject){
   }*/
   
   // Determine table columns
-  var columnName = new Array();
-  var columnModel = new Array();
+  var columnName = [];
+  var columnModel = [];
   for(var cnt=0; cnt<objectResults.length; cnt++){
     for(key in objectResults[cnt]){
       if(-1 == columnName.indexOf(key)){
@@ -803,14 +803,14 @@ function formDataForPopUpGrid(data){
 //Function for converting raw response into expected format
 function convertRawResponseToExpectedFormat(rawResponeData){
   
-  if(rawResponeData == null || rawResponeData == undefined){
+  if(rawResponeData === null || rawResponeData === undefined){
     return;
   }
   
   var finalResponseData = {};
-  var finalResponseResults = new Array();
+  var finalResponseResults = [];
   
-  if(rawResponeData.result != null || rawResponeData.result != undefined){
+  if(rawResponeData.result != null || rawResponeData.result !== undefined){
     var rawResponeDataResult = rawResponeData.result;
     
     for(var i=0; i<rawResponeDataResult.length; i++){
@@ -821,7 +821,7 @@ function convertRawResponseToExpectedFormat(rawResponeData){
             finalResponseResults = convertToExpectedObjectsFormat(rawResponeDataResult, "");
             break;
             
-          }else if(rawResponeDataResult[i].member != null && rawResponeDataResult[i].member != undefined){
+          }else if(rawResponeDataResult[i].member != null && rawResponeDataResult[i].member !== undefined){
             
             var responseForMember = {};
             responseForMember.member = rawResponeDataResult[i].member[0];
@@ -842,31 +842,25 @@ function convertRawResponseToExpectedFormat(rawResponeData){
 
 // Function for converting raw response into expected object wise results format
 function convertToExpectedObjectsFormat(rawResponseResult, prefixForId){
-  
-  var expResponseResult = new Array();
-  
-  if(rawResponseResult != null && rawResponseResult != undefined ){
+
+  let entry;
+  let objectResults;
+  const expResponseResult = [];
+
+  if(rawResponseResult != null ){
     
-    for(var i=0; i< rawResponseResult.length; i++){
+    for(let i=0; i < rawResponseResult.length; i++){
       if(rawResponseResult[i] != null){
         
         if(expResponseResult.length > 0){
           // search expected object type in expResponseResult
-          var flagObjectFound = false;
-          for(var j=0 ; j < expResponseResult.length ; j++){
-            if(expResponseResult[j].objectType == rawResponseResult[i][0]){
+          let flagObjectFound = false;
+          for(let j=0 ; j < expResponseResult.length ; j++){
+            if(expResponseResult[j].objectType === rawResponseResult[i][0]){
               // required object found
               flagObjectFound = true;
-              var objectResults = expResponseResult[j].objectResults;
-              var type = rawResponseResult[i][0];
-              var entry = rawResponseResult[i][1];
-
-              // if entry is not object then convert it into object
-              if(typeof(entry) != "object" ){
-                var entryObj = {};
-                entryObj[type] = rawResponseResult[i][1];
-                entry = entryObj;
-              }
+              objectResults = expResponseResult[j].objectResults;
+              entry = htmlEncodeEntry(rawResponseResult[i]);
 
               // add unique id for new entry
               entry.uid = generateEntryUID(prefixForId, expResponseResult[j].objectType, objectResults.length);
@@ -875,64 +869,67 @@ function convertToExpectedObjectsFormat(rawResponseResult, prefixForId){
               break;
             }
           }
-          
-          if(!flagObjectFound){  // required object not found in expResponseResult 
-            
-            var objectResults = new Array();
-            var type = rawResponseResult[i][0];
-            var entry = rawResponseResult[i][1];
-
-            // if entry is not object then convert it into object
-            if(typeof(entry) != "object" ){
-              var entryObj = {};
-              entryObj[type] = rawResponseResult[i][1];
-              entry = entryObj;
-            }
-
-            // add unique id for new entry
-            entry.uid = generateEntryUID(prefixForId, type, objectResults.length);
-            
-            objectResults.push(entry);
-            
-            var newResultObject = {};
-            newResultObject.objectType = type;
-            newResultObject.objectResults = objectResults;
-            
-            expResponseResult.push(newResultObject);
+          if(!flagObjectFound){  // required object not found in expResponseResult
+            expResponseResult.push(addToExpResponseResult(rawResponseResult[i], prefixForId));
           }
-          
         }else{  // expResponseResult is empty
-          
-          var objectResults = new Array();
-          var type = rawResponseResult[i][0];
-          var entry = rawResponseResult[i][1];
-
-          // if entry is not object then convert it into object
-          if(typeof(entry) != "object" ){
-            var entryObj = {};
-            entryObj[type] = rawResponseResult[i][1];
-            entry = entryObj;
-          }
-
-          // add unique id for new entry
-          entry.uid = generateEntryUID(prefixForId, type, objectResults.length);
-          
-          objectResults.push(entry);
-          
-          var newResultObject = {};
-          newResultObject.objectType = type;
-          newResultObject.objectResults = objectResults;
-          
-          expResponseResult.push(newResultObject);
+          expResponseResult.push(addToExpResponseResult(rawResponseResult[i], prefixForId));
         }
-        
       }
     }
   }
-  
+
   return expResponseResult;
 }
 
+// Add results to the expected responseResults
+function addToExpResponseResult(rawResponseResultEntry, prefixForId) {
+  let objectResults = [];
+  let type = rawResponseResultEntry[0];
+  let entry = htmlEncodeEntry(rawResponseResultEntry, prefixForId);
+
+  // add unique id for new entry
+  entry.uid = generateEntryUID(prefixForId, type, objectResults.length);
+
+  objectResults.push(entry);
+
+  let newResultObject = {};
+  newResultObject.objectType = type;
+  newResultObject.objectResults = objectResults;
+
+  return newResultObject;
+}
+
+// Ensure that strings are HTML encoded to reduce likelihood of XSS attacks
+function htmlEncodeEntry(rawResponseResultEntry, prefixForId) {
+  let type = htmlEncodeStringsAndObjects(rawResponseResultEntry[0]);
+  let entry = rawResponseResultEntry[1];
+
+  let entryObj = {};
+
+  // if entry is not object then convert it into object
+  if(typeof(entry) == "object" ){
+    entryObj = htmlEncodeStringsAndObjects(entry);
+  } else {
+    entryObj[type] = htmlEncodeStringsAndObjects(entry)
+  }
+
+  return entryObj;
+}
+
+function htmlEncodeStringsAndObjects(raw) {
+  switch(typeof(raw)) {
+    case "string":
+      return $('<pre/>').text(raw).html();
+    case "object":
+      let objectAsString = JSON.stringify(raw);
+      objectAsString = $('<pre/>').text(objectAsString).html();
+      return JSON.parse(objectAsString);
+    default:
+      return raw
+  }
+}
+
 // Function to generate unique idetifier for entry
 function generateEntryUID(prefixForId, type, len) {
 
diff --git a/geode-pulse/src/main/webapp/scripts/pulsescript/pages/DataBrowserQueryHistory.js b/geode-pulse/src/main/webapp/scripts/pulsescript/pages/DataBrowserQueryHistory.js
index 1c6000ba8c..c41381644e 100644
--- a/geode-pulse/src/main/webapp/scripts/pulsescript/pages/DataBrowserQueryHistory.js
+++ b/geode-pulse/src/main/webapp/scripts/pulsescript/pages/DataBrowserQueryHistory.js
@@ -21,25 +21,25 @@
 // updateQueryHistory()
 function updateQueryHistory(action,queryId) {
   
-  requestData = {
-    action:action,
-    queryId:queryId
+  let requestData = {
+    action: action,
+    queryId: queryId
   };
 
   $.getJSON("dataBrowserQueryHistory", requestData, function(data) {
-    
-    var queries = new Array();
-    if(data.queryHistory != undefined && data.queryHistory != null){
+
+    let queries = [];
+    if(data.queryHistory !== undefined && data.queryHistory != null){
       queries = data.queryHistory;
     }
-    var refHistoryConatiner = $("#detailsHistoryList");
-    var queryListHTML = "";
-    if(queries.length == 0){
+    const refHistoryContainer = $("#detailsHistoryList");
+    let queryListHTML = "";
+    if(queries.length === 0){
       // no queries found
       queryListHTML = "No Query Found";
     }else{
       queries.sort(dynamicSort("queryId", "desc"));
-      for(var i=0; i<queries.length && i<20; i++){
+      for(let i=0; i < queries.length && i < 20; i++){
         // add query item
         queryListHTML += "" +
           "<div class=\"container\">" +
@@ -50,7 +50,7 @@ function updateQueryHistory(action,queryId) {
               "<div class=\"remove\">" +
                 "<a href=\"#\" onclick=\"updateQueryHistory('delete','"+ queries[i].queryId +"');\">&nbsp;</a>" +
               "</div>" +
-              "<div class=\"wrapHistoryContent\"  ondblclick=\"queryHistoryItemClicked(this);\">" + queries[i].queryText +
+              "<div class=\"wrapHistoryContent\"  ondblclick=\"queryHistoryItemClicked(this);\">" + queries[i].queryText.replaceAll("\"", "") +
               "</div>" +
               "<div class=\"dateTimeHistory\">" + queries[i].queryDateTime +
               "</div>" +
@@ -59,7 +59,7 @@ function updateQueryHistory(action,queryId) {
       }
     }
     
-    refHistoryConatiner.html(queryListHTML);
+    refHistoryContainer.html(queryListHTML);
     //$('.queryHistoryScroll-pane').jScrollPane();/*Custome scroll*/    
 
     // Set eventsAdded = false as list is refreshed and slide events 
@@ -73,13 +73,13 @@ function updateQueryHistory(action,queryId) {
 // This function displays error if occurred 
 function resErrHandler(data){
   // Check for unauthorized access
-  if (data.status == 401) {
+  if (data.status === 401) {
     // redirect user on Login Page
     window.location.href = "login.html?error=UNAUTH_ACCESS";
   }else{
     console.log(data);
   }
-};
+}
 
 // This function is called when any query from history list is double clicked 
 function queryHistoryItemClicked(divElement){
diff --git a/geode-pulse/src/uiTest/java/org/apache/geode/tools/pulse/tests/ui/PulseAutomatedTest.java b/geode-pulse/src/uiTest/java/org/apache/geode/tools/pulse/tests/ui/PulseAutomatedTest.java
index 7732e6d0fa..fdf0dac0de 100644
--- a/geode-pulse/src/uiTest/java/org/apache/geode/tools/pulse/tests/ui/PulseAutomatedTest.java
+++ b/geode-pulse/src/uiTest/java/org/apache/geode/tools/pulse/tests/ui/PulseAutomatedTest.java
@@ -13,12 +13,6 @@
  * the License.
  *
  */
-/**
- * This test class contains automated tests for Pulse application related to 1. Different grid data
- * validations for example - Topology, Server Group, Redundancy Zone 2. Data Browser 3.
- *
- * @since GemFire 2014-04-02
- */
 package org.apache.geode.tools.pulse.tests.ui;
 
 import static org.apache.geode.tools.pulse.tests.ui.PulseTestUtils.assertMemberSortingByCpuUsage;
@@ -59,13 +53,16 @@ import static org.apache.geode.tools.pulse.tests.ui.PulseTestUtils.verifyElement
 import static org.apache.geode.tools.pulse.tests.ui.PulseTestUtils.verifyTextPresrntById;
 import static org.apache.geode.tools.pulse.tests.ui.PulseTestUtils.verifyTextPresrntByXpath;
 import static org.apache.geode.tools.pulse.tests.ui.PulseTestUtils.waitForElementWithId;
+import static org.assertj.core.api.Assertions.assertThat;
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 
 import java.text.DateFormat;
 import java.text.SimpleDateFormat;
+import java.util.Collection;
 import java.util.List;
+import java.util.stream.Collectors;
 
 import org.junit.Assert;
 import org.junit.Before;
@@ -84,6 +81,12 @@ import org.apache.geode.tools.pulse.tests.rules.ScreenshotOnFailureRule;
 import org.apache.geode.tools.pulse.tests.rules.ServerRule;
 import org.apache.geode.tools.pulse.tests.rules.WebDriverRule;
 
+/**
+ * This test class contains automated tests for Pulse application related to 1. Different grid data
+ * validations for example - Topology, Server Group, Redundancy Zone 2. Data Browser 3.
+ *
+ * @since GemFire 2014-04-02
+ */
 public class PulseAutomatedTest extends PulseBase {
 
   @ClassRule
@@ -851,7 +854,7 @@ public class PulseAutomatedTest extends PulseBase {
     clickElementUsingXpath(PulseTestLocators.DataBrowser.btnClearXpath);
     String editorTextAfterClear = getTextUsingId(PulseTestLocators.DataBrowser.queryEditorTxtBoxId);
 
-    assertFalse(PulseTestData.DataBrowser.query1Text.equals(editorTextAfterClear));
+    assertNotEquals(PulseTestData.DataBrowser.query1Text, editorTextAfterClear);
   }
 
   @Ignore("WIP") // Data Browser's Query History not showing any data on button click, therefore
@@ -892,10 +895,50 @@ public class PulseAutomatedTest extends PulseBase {
     System.out.println("Query Text from History Table: " + queryText);
     System.out.println("Query Time from History Table: " + historyDateTime);
     // verify the query text, query datetime in history panel
-    assertTrue(DataBrowserResultLoader.QUERY_TYPE_ONE.equals(queryText));
-    assertTrue(historyDateTime.contains(queryTime[0]));
-
+    assertThat(queryText).isEqualTo(DataBrowserResultLoader.QUERY_TYPE_ONE);
+    assertThat(historyDateTime).contains(queryTime[0]);
   }
 
+  @Test
+  public void testDataBrowserHTMLEncode() {
+    // navigate to Data browser page
+    loadDataBrowserpage();
+
+    WebDriver driver = webDriverRule.getDriver();
+    List<WebElement> numOfReg = driver
+        .findElements(By.xpath(PulseTestLocators.DataBrowser.divDataRegions));
 
+    for (int i = 1; i <= numOfReg.size(); i++) {
+      if (getTextUsingId("treeDemo_" + i + "_span").equals(PulseTestData.DataBrowser.regName)) {
+        searchByIdAndClick("treeDemo_" + i + "_check"); // driver.findElement(By.id("treeDemo_" + i
+        // + "_check")).click();
+      }
+    }
+
+    sendKeysUsingId(PulseTestLocators.DataBrowser.queryEditorTxtBoxId,
+        DataBrowserResultLoader.QUERY_TYPE_EIGHT);
+    clickElementUsingId(PulseTestLocators.DataBrowser.btnExecuteQueryId);
+
+    clickElementUsingId(PulseTestLocators.DataBrowser.historyIcon);
+    String queryText = findElementByXpath(PulseTestLocators.DataBrowser.historyLst)
+        .findElement(By.cssSelector(PulseTestLocators.DataBrowser.queryText)).getText();
+
+    assertThat(queryText).isEqualTo(DataBrowserResultLoader.QUERY_TYPE_EIGHT);
+
+    List<WebElement> elements =
+        driver.findElements(By.xpath(PulseTestData.DataBrowser.resultClusterHeadingsXPath));
+    List<WebElement> filteredElements = elements.stream().filter(webElement -> webElement.getText()
+        .equals("org.apache.geode.cache.query.data.Portfolio")).collect(
+            Collectors.toList());
+    List<WebElement> finalElements = filteredElements.stream().map(webElement -> {
+      webElement.click();
+      return webElement.findElements(By.xpath(PulseTestData.DataBrowser.resultClusterCellXPath));
+    }).flatMap(Collection::stream).collect(Collectors.toList());
+
+    // confirm script text is displayed
+    assertThat(finalElements).hasSize(2);
+    finalElements.forEach(webElement -> {
+      assertThat(webElement.getAttribute("title")).isEqualTo("<script>alert('xss')</script>");
+    });
+  }
 }