CVE-2018-25084: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: A vulnerability, which was classified as problematic, has been found in Ping Identity Self-Service Account Manager 1.1.2. Affected by this issue is some unknown functionality of the file src/main/java/com/unboundid/webapp/ssam/SSAMController.java. The manipulation leads to cross site scripting. The attack may be launched remotely. Upgrading to version 1.1.3 is able to address this issue. The patch is identified as f64b10d63bb19ca2228b0c2d561a1a6e5a3bf251. It is recommended to upgrade the affected component. VDB-225362 is the identifier assigned to this vulnerability.
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: 2023-04-10T18:15Z
Repository: https://github.com/pingidentity/ssam
Language: Other
Files changed: src/main/java/com/unboundid/webapp/ssam/HTTPHeaderFilter.java, src/main/java/com/unboundid/webapp/ssam/HTTPRequestParameterFilter.java, src/main/java/com/unboundid/webapp/ssam/SSAMController.java, src/main/resources/static/css/ssam.css, src/main/resources/static/js/ssam-security-patch.js, src/main/resources/templates/deletion-success.vm, src/main/resources/templates/error.vm, src/main/resources/templates/login.vm, src/main/resources/templates/recover-password-success.vm, src/main/resources/templates/recover-password-verify.vm, src/main/resources/templates/recover-password.vm, src/main/resources/templates/register.vm, src/main/resources/templates/registration-success.vm, src/main/resources/templates/registration-verify.vm, src/main/resources/templates/user.vm
Commit date: 2018-02-01 11:25:59 -0600
Commit message: Added html escaping to help with XSS. Added frame busting to help with XFS. Added a method override filter so that only GETs and POSTs are accepted. Disabled autocomplete for password fields. Added ability for customer to set their HPKP headers.

Diff: diff --git a/src/main/java/com/unboundid/webapp/ssam/HTTPHeaderFilter.java b/src/main/java/com/unboundid/webapp/ssam/HTTPHeaderFilter.java
new file mode 100644
index 0000000..74a4047
--- /dev/null
+++ b/src/main/java/com/unboundid/webapp/ssam/HTTPHeaderFilter.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2014-2017 Ping Identity Corporation
+ * All Rights Reserved.
+ */
+
+package com.unboundid.webapp.ssam;
+
+import org.springframework.stereotype.Component;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.FilterChain;
+import javax.servlet.http.HttpServletResponse;
+
+@Component
+public class HTTPHeaderFilter implements javax.servlet.Filter {
+    public FilterConfig filterConfig;
+
+    public void doFilter(final ServletRequest request,
+                         final ServletResponse response, FilterChain chain)
+            throws java.io.IOException, javax.servlet.ServletException {
+
+        HttpServletResponse res = (HttpServletResponse) response;
+
+        // Set this variable to the sha256 pin of your public key
+        // This value can be generated using the "openssl" command line tool
+        // https://developer.mozilla.org/en-US/docs/Web/HTTP/Public_Key_Pinning
+        String pinSha256 = "";
+
+        if(pinSha256.length() > 0)
+        {
+            res.setHeader("Public-Key-Pins", "max-age=518400; " +
+                    "pin-sha256=\"" + pinSha256 + "\"; " +
+                    "includeSubDomains");
+        }
+
+        chain.doFilter(request, response);
+    }
+
+    public void init(final FilterConfig filterConfig) { this.filterConfig = filterConfig; }
+
+    public void destroy() {}
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/unboundid/webapp/ssam/HTTPRequestParameterFilter.java b/src/main/java/com/unboundid/webapp/ssam/HTTPRequestParameterFilter.java
new file mode 100644
index 0000000..fff466d
--- /dev/null
+++ b/src/main/java/com/unboundid/webapp/ssam/HTTPRequestParameterFilter.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2014-2017 Ping Identity Corporation
+ * All Rights Reserved.
+ */
+
+package com.unboundid.webapp.ssam;
+
+import org.springframework.stereotype.Component;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.FilterChain;
+import javax.servlet.http.HttpServletRequest;
+
+@Component
+public class HTTPRequestParameterFilter implements javax.servlet.Filter {
+  public FilterConfig filterConfig;
+
+  public void doFilter(final ServletRequest request,
+    final ServletResponse response, FilterChain chain)
+    throws java.io.IOException, javax.servlet.ServletException {
+
+    String curMethod = ((HttpServletRequest) request).getMethod();
+    //only allow for GET and POST requests.
+    if (curMethod.equalsIgnoreCase("get") || curMethod.equalsIgnoreCase("post"))
+    {
+      chain.doFilter(request, response);
+    }
+  }
+
+  public void init(final FilterConfig filterConfig) {
+        this.filterConfig = filterConfig;
+    }
+  public void destroy() {}
+}
\ No newline at end of file
diff --git a/src/main/java/com/unboundid/webapp/ssam/SSAMController.java b/src/main/java/com/unboundid/webapp/ssam/SSAMController.java
index 053b1fe..26d0b30 100644
--- a/src/main/java/com/unboundid/webapp/ssam/SSAMController.java
+++ b/src/main/java/com/unboundid/webapp/ssam/SSAMController.java
@@ -42,10 +42,11 @@ import org.springframework.web.bind.annotation.ModelAttribute;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RequestMethod;
 import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.ResponseBody;
 import org.springframework.web.client.RestTemplate;
+import org.springframework.web.util.HtmlUtils;
 
 import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import com.unboundid.ldap.sdk.Attribute;
 import com.unboundid.ldap.sdk.Control;
@@ -969,11 +970,11 @@ public class SSAMController
   private void populateUserModel(String username, Entry entry, Model model)
   {
     model.addAttribute("username", username);
     for(Attribute attribute : entry.getAttributes())
     {
-      model.addAttribute(attribute.getName(), attribute.getValue());
+      model.addAttribute(attribute.getName(), HtmlUtils.htmlEscape(attribute.getValue()));
     }
     model.addAttribute("entry", entry);
   }
 
   /** Populates the provided model with the specified parameters. */
@@ -987,11 +988,11 @@ public class SSAMController
       if(!name.equals("userPassword") && !name.equals("_csrf"))
       {
         String value = parameter.getValue().trim();
         if(!value.isEmpty())
         {
-          model.addAttribute(name, value);
+          model.addAttribute(name, HtmlUtils.htmlEscape(value));
         }
       }
     }
   }
 
diff --git a/src/main/resources/static/css/ssam.css b/src/main/resources/static/css/ssam.css
index 03b0d02..44402c9 100644
--- a/src/main/resources/static/css/ssam.css
+++ b/src/main/resources/static/css/ssam.css
@@ -1,5 +1,14 @@
+/*
+ * None style added to help with Frame Busting
+ * intended for older browsers without 'X-Frame-Options' header support.
+ * This method comes from an OWASP write up by Gustav Rydstedt.
+ */
+html {
+  display: none;
+}
+
 .navbar-static-top {
   margin-bottom: 19px;
 }
 
 .brand-icon {
diff --git a/src/main/resources/static/js/ssam-security-patch.js b/src/main/resources/static/js/ssam-security-patch.js
new file mode 100644
index 0000000..2fbbb64
--- /dev/null
+++ b/src/main/resources/static/js/ssam-security-patch.js
@@ -0,0 +1,26 @@
+// frame busting
+function buster(document_in, top_in) {
+ if (self === top_in) {
+   document_in.documentElement.style.display = "block";
+ } else {
+   top_in.location = self.location;
+ }
+}
+
+// html escaping for potentially unsafe jQuery methods such as ".append()"
+var entityMap = {
+    '&': '&amp;',
+    '<': '&lt;',
+    '>': '&gt;',
+    '"': '&quot;',
+    "'": '&#x27;',
+    '/': '&#x2F;',
+    '`': '&#x60;',
+    '=': '&#x3D;'
+};
+
+function escapeHtml (string) {
+    return String(string).replace(/[&<>"'`=\/]/g, function (s) {
+      return entityMap[s];
+    });
+}
\ No newline at end of file
diff --git a/src/main/resources/templates/deletion-success.vm b/src/main/resources/templates/deletion-success.vm
index 1beee7e..ad891b7 100644
--- a/src/main/resources/templates/deletion-success.vm
+++ b/src/main/resources/templates/deletion-success.vm
@@ -23,11 +23,15 @@
             </div>
           </div>
         </div>
       </div>
     </div>
-    
+
+    <script src="js/ssam-security-patch.js"></script>
+    <script type="text/javascript">
+      buster(document, top);
+    </script>
     <script src="js/jquery-2.1.4.min.js"></script>
     <script src="js/bootstrap.min.js"></script>
     <script src="js/ssam.js"></script>
     <script type="text/javascript">
       $(document).ready(function() {
diff --git a/src/main/resources/templates/error.vm b/src/main/resources/templates/error.vm
index 2bf22fa..7c7f981 100644
--- a/src/main/resources/templates/error.vm
+++ b/src/main/resources/templates/error.vm
@@ -11,7 +11,11 @@
   <body>
     #parse("_nav.vm")
     <div class="container">
       Something went wrong: $!status $error
     </div>
+    <script src="js/ssam-security-patch.js"></script>
+    <script type="text/javascript">
+      buster(document, top);
+    </script>
   </body>
 </html>
\ No newline at end of file
diff --git a/src/main/resources/templates/login.vm b/src/main/resources/templates/login.vm
index ce0ec5e..66e3cb6 100644
--- a/src/main/resources/templates/login.vm
+++ b/src/main/resources/templates/login.vm
@@ -20,18 +20,22 @@
         <h2 class="form-signin-heading">Please sign in</h2>
         <input type="hidden" name="$_csrf.parameterName" value="$_csrf.token" />
         <label for="inputUsername" class="sr-only">Email/Username</label>
         <input type="text" id="inputUsername" name="username" class="form-control" placeholder="Email/Username" required autofocus>
         <label for="inputPassword" class="sr-only">Password</label>
-        <input type="password" id="inputPassword" name="password" class="form-control" placeholder="Password" required>
+        <input type="password" id="inputPassword" name="password" class="form-control" placeholder="Password" autocomplete="off" required>
         <button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
         <div style="margin-top: 5px;">
           <a href="recoverPassword">Forgot password?</a>
           <br>
           <a href="register">Sign up for an account</a>
         </div>
       </form>
     </div>
+    <script src="js/ssam-security-patch.js"></script>
+    <script type="text/javascript">
+      buster(document, top);
+    </script>
     <script src="js/jquery-2.1.4.min.js"></script>
     <script src="js/bootstrap.min.js"></script>
   </body>
 </html>
\ No newline at end of file
diff --git a/src/main/resources/templates/recover-password-success.vm b/src/main/resources/templates/recover-password-success.vm
index c66a7d2..33e418d 100644
--- a/src/main/resources/templates/recover-password-success.vm
+++ b/src/main/resources/templates/recover-password-success.vm
@@ -23,11 +23,15 @@
             </div>
           </div>
         </div>
       </div>
     </div>
-    
+
+    <script src="js/ssam-security-patch.js"></script>
+    <script type="text/javascript">
+      buster(document, top);
+    </script>
     <script src="js/jquery-2.1.4.min.js"></script>
     <script type="text/javascript">
       $(document).ready(function() {
         $("#finish").focus();  
       });
diff --git a/src/main/resources/templates/recover-password-verify.vm b/src/main/resources/templates/recover-password-verify.vm
index 231e74a..84751c0 100644
--- a/src/main/resources/templates/recover-password-verify.vm
+++ b/src/main/resources/templates/recover-password-verify.vm
@@ -88,10 +88,14 @@
           </div>
         </div>
       </div>
     </div>
 
+    <script src="js/ssam-security-patch.js"></script>
+    <script type="text/javascript">
+      buster(document, top);
+    </script>
     <script src="js/jquery-2.1.4.min.js"></script>
     <script src="js/bootstrap.min.js"></script>
     <script src="js/ssam.js"></script>
     ## reCAPTCHA script
         #if ($useRecaptcha && $settings.isRecaptchaEnabled())
diff --git a/src/main/resources/templates/recover-password.vm b/src/main/resources/templates/recover-password.vm
index b856f7b..a950187 100644
--- a/src/main/resources/templates/recover-password.vm
+++ b/src/main/resources/templates/recover-password.vm
@@ -49,10 +49,14 @@
           </div>
         </div>
       </div>
     </div>
 
+    <script src="js/ssam-security-patch.js"></script>
+    <script type="text/javascript">
+      buster(document, top);
+    </script>
     <script src="js/jquery-2.1.4.min.js"></script>
     <script src="js/bootstrap.min.js"></script>
     <script src="js/ssam.js"></script>
     ## reCAPTCHA script
     #if ($settings.isRecaptchaEnabled())
diff --git a/src/main/resources/templates/register.vm b/src/main/resources/templates/register.vm
index 1eee66c..e26cd5d 100644
--- a/src/main/resources/templates/register.vm
+++ b/src/main/resources/templates/register.vm
@@ -91,10 +91,14 @@
           </div>
         </div>
       </div>
     </div>
 
+    <script src="js/ssam-security-patch.js"></script>
+    <script type="text/javascript">
+      buster(document, top);
+    </script>
     <script src="js/jquery-2.1.4.min.js"></script>
     <script src="js/bootstrap.min.js"></script>
     <script src="js/ssam.js"></script>
     ## reCAPTCHA script
     #if ($settings.isRecaptchaEnabled())
diff --git a/src/main/resources/templates/registration-success.vm b/src/main/resources/templates/registration-success.vm
index 0e08cc7..1fca949 100644
--- a/src/main/resources/templates/registration-success.vm
+++ b/src/main/resources/templates/registration-success.vm
@@ -23,11 +23,15 @@
             </div>
           </div>
         </div>
       </div>
     </div>
-    
+
+    <script src="js/ssam-security-patch.js"></script>
+    <script type="text/javascript">
+      buster(document, top);
+    </script>
     <script src="js/jquery-2.1.4.min.js"></script>
     <script src="js/bootstrap.min.js"></script>
     <script src="js/ssam.js"></script>
     <script type="text/javascript">
       $(document).ready(function() {
diff --git a/src/main/resources/templates/registration-verify.vm b/src/main/resources/templates/registration-verify.vm
index da558b9..a7c6f04 100644
--- a/src/main/resources/templates/registration-verify.vm
+++ b/src/main/resources/templates/registration-verify.vm
@@ -54,11 +54,15 @@
             </form>       
           </div>
         </div>
       </div>
     </div>
-    
+
+    <script src="js/ssam-security-patch.js"></script>
+    <script type="text/javascript">
+      buster(document, top);
+    </script>
     <script src="js/jquery-2.1.4.min.js"></script>
     <script src="js/bootstrap.min.js"></script>
     <script src="js/ssam.js"></script>
     <script type="text/javascript">
       $(document).ready(function() {  
diff --git a/src/main/resources/templates/user.vm b/src/main/resources/templates/user.vm
index 93d6158..8d9a119 100644
--- a/src/main/resources/templates/user.vm
+++ b/src/main/resources/templates/user.vm
@@ -111,24 +111,24 @@
             <div id="passwordChangeAlerts"></div>
             <form class="form-horizontal">
               <div class="form-group">
                 <label class="col-sm-4 control-label">Current Password </label>
                 <div class="col-sm-8">
-                  <input id="currentPassword" type="password" class="form-control" placeholder="Enter Current Password">
+                  <input id="currentPassword" type="password" class="form-control" placeholder="Enter Current Password" autocomplete="off">
                 </div>
               </div>
               <div class="form-group">
                 <label class="col-sm-4 control-label">Password </label>
                 <div class="col-sm-8">
-                  <input id="password" type="password" class="form-control" placeholder="Enter New Password">
+                  <input id="password" type="password" class="form-control" placeholder="Enter New Password" autocomplete="off">
                     #parse("_password-requirements.vm")  
                 </div>
               </div>
               <div class="form-group">
                 <label class="col-sm-4 control-label">Confirm&nbsp;Password </label>
                 <div class="col-sm-8">
-                  <input id="confirmPassword" type="password" class="form-control" placeholder="Re-Enter New Password">
+                  <input id="confirmPassword" type="password" class="form-control" placeholder="Re-Enter New Password" autocomplete="off">
                 </div>
               </div>
             </form>
           </div>
           <div class="modal-footer">
@@ -159,10 +159,14 @@
           </form>
         </div>
       </div>
     </div>
   #end
+    <script src="js/ssam-security-patch.js"></script>
+   	<script type="text/javascript">
+   	  buster(document, top);
+   	</script>
     <script src="js/jquery-2.1.4.min.js"></script>
     <script src="js/bootstrap.min.js"></script>
     <script src="js/ssam.js"></script>
     <script type="text/javascript">
       $(document).ready(function() {
@@ -275,12 +279,12 @@
           if(first && last)
           {
             cn = first + " " + last;
           }
           var address = cn + '$' + $('input[name="street"]').val().trim() + '$' + $('input[name="l"]').val().trim() + ', ' + $('input[name="st"]').val().trim() + '  ' + $('input[name="postalCode"]').val().trim();
-          userForm.append('<input type="hidden" name="cn" value="' + cn + '">');
-          userForm.append('<input type="hidden" name="postalAddress" value="' + address + '">');
+          userForm.append('<input type="hidden" name="cn" value="' + escapeHtml(cn) + '">');
+          userForm.append('<input type="hidden" name="postalAddress" value="' + escapeHtml(address) + '">');
           userForm.submit();
         });
       });
     </script>
   </body>

Vulnerable code:       model.addAttribute(attribute.getName(), attribute.getValue());
          model.addAttribute(name, value);
    
        <input type="password" id="inputPassword" name="password" class="form-control" placeholder="Password" required>
    
    
    
                  <input id="currentPassword" type="password" class="form-control" placeholder="Enter Current Password">
                  <input id="password" type="password" class="form-control" placeholder="Enter New Password">
                  <input id="confirmPassword" type="password" class="form-control" placeholder="Re-Enter New Password">
          userForm.append('<input type="hidden" name="cn" value="' + cn + '">');
          userForm.append('<input type="hidden" name="postalAddress" value="' + address + '">');
Fixed code: /*
 * Copyright 2014-2017 Ping Identity Corporation
 * All Rights Reserved.
 */

package com.unboundid.webapp.ssam;

import org.springframework.stereotype.Component;
import javax.servlet.FilterConfig;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletResponse;

@Component
public class HTTPHeaderFilter implements javax.servlet.Filter {
    public FilterConfig filterConfig;

    public void doFilter(final ServletRequest request,
                         final ServletResponse response, FilterChain chain)
            throws java.io.IOException, javax.servlet.ServletException {

        HttpServletResponse res = (HttpServletResponse) response;

        // Set this variable to the sha256 pin of your public key
        // This value can be generated using the "openssl" command line tool
        // https://developer.mozilla.org/en-US/docs/Web/HTTP/Public_Key_Pinning
        String pinSha256 = "";

        if(pinSha256.length() > 0)
        {
            res.setHeader("Public-Key-Pins", "max-age=518400; " +
                    "pin-sha256=\"" + pinSha256 + "\"; " +
                    "includeSubDomains");
        }

        chain.doFilter(request, response);
    }

    public void init(final FilterConfig filterConfig) { this.filterConfig = filterConfig; }

    public void destroy() {}

}
/*
 * Copyright 2014-2017 Ping Identity Corporation
 * All Rights Reserved.
 */

package com.unboundid.webapp.ssam;

import org.springframework.stereotype.Component;
import javax.servlet.FilterConfig;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;

@Component
public class HTTPRequestParameterFilter implements javax.servlet.Filter {
  public FilterConfig filterConfig;

  public void doFilter(final ServletRequest request,
    final ServletResponse response, FilterChain chain)
    throws java.io.IOException, javax.servlet.ServletException {

    String curMethod = ((HttpServletRequest) request).getMethod();
    //only allow for GET and POST requests.
    if (curMethod.equalsIgnoreCase("get") || curMethod.equalsIgnoreCase("post"))
    {
      chain.doFilter(request, response);
    }
  }

  public void init(final FilterConfig filterConfig) {
        this.filterConfig = filterConfig;
    }
  public void destroy() {}
}
import org.springframework.web.util.HtmlUtils;
      model.addAttribute(attribute.getName(), HtmlUtils.htmlEscape(attribute.getValue()));
          model.addAttribute(name, HtmlUtils.htmlEscape(value));
/*
 * None style added to help with Frame Busting
 * intended for older browsers without 'X-Frame-Options' header support.
 * This method comes from an OWASP write up by Gustav Rydstedt.
 */
html {
  display: none;
}

// frame busting
function buster(document_in, top_in) {
 if (self === top_in) {
   document_in.documentElement.style.display = "block";
 } else {
   top_in.location = self.location;
 }
}

// html escaping for potentially unsafe jQuery methods such as ".append()"
var entityMap = {
    '&': '&amp;',
    '<': '&lt;',
    '>': '&gt;',
    '"': '&quot;',
    "'": '&#x27;',
    '/': '&#x2F;',
    '`': '&#x60;',
    '=': '&#x3D;'
};

function escapeHtml (string) {
    return String(string).replace(/[&<>"'`=\/]/g, function (s) {
      return entityMap[s];
    });
}

    <script src="js/ssam-security-patch.js"></script>
    <script type="text/javascript">
      buster(document, top);
    </script>
    <script src="js/ssam-security-patch.js"></script>
    <script type="text/javascript">
      buster(document, top);
    </script>
        <input type="password" id="inputPassword" name="password" class="form-control" placeholder="Password" autocomplete="off" required>
    <script src="js/ssam-security-patch.js"></script>
    <script type="text/javascript">
      buster(document, top);
    </script>

    <script src="js/ssam-security-patch.js"></script>
    <script type="text/javascript">
      buster(document, top);
    </script>
    <script src="js/ssam-security-patch.js"></script>
    <script type="text/javascript">
      buster(document, top);
    </script>
    <script src="js/ssam-security-patch.js"></script>
    <script type="text/javascript">
      buster(document, top);
    </script>
    <script src="js/ssam-security-patch.js"></script>
    <script type="text/javascript">
      buster(document, top);
    </script>

    <script src="js/ssam-security-patch.js"></script>
    <script type="text/javascript">
      buster(document, top);
    </script>

    <script src="js/ssam-security-patch.js"></script>
    <script type="text/javascript">
      buster(document, top);
    </script>
                  <input id="currentPassword" type="password" class="form-control" placeholder="Enter Current Password" autocomplete="off">
                  <input id="password" type="password" class="form-control" placeholder="Enter New Password" autocomplete="off">
                  <input id="confirmPassword" type="password" class="form-control" placeholder="Re-Enter New Password" autocomplete="off">
    <script src="js/ssam-security-patch.js"></script>
   	<script type="text/javascript">
   	  buster(document, top);
   	</script>
          userForm.append('<input type="hidden" name="cn" value="' + escapeHtml(cn) + '">');
          userForm.append('<input type="hidden" name="postalAddress" value="' + escapeHtml(address) + '">');