Tuesday, December 11, 2018

Struts2 : Handling double submission /CSRF attack using token interceptor

Recently I was working on J2EE Application Security related job tasks to address security vulnerabilities. so we given solutions and worked to address top 10 Web Application Vulnerabilities in J2EE. In this post explains you how to prevent multiple/duplicate form submissions and a Cross Site Request Forgery (CSRF) attack in a Struts2 web application by including a random token with each form submission request.

Multiple form submission occurs:
  • when an impatient user clicks the form Submit button more than once before the response is sent back
  • when a client accesses a view by returning to a previously bookmarked page
  • once submitted refresh the page causing issues
      This may result in inconsistent transactions and must be avoided for data integrity. This problem can be fixed by JavaScript that disable the submit button once the form has been submitted - a weakness of this is if clients have JavaScript disabled.

      Cross Site Request Forgery (CSRF or XSRF) attack, a type of malicious exploit whereby unauthorized commands are performed on behalf of an authenticated user. To prevent CSRF attacks on the server side, use a random dynamically generated token with each form submission request. This is a unique string that is generated for each session. We generate the token and then include it in every form as a hidden input. The system then checks if the form is valid by comparing the token with the one stored in the user's session variable. An attacker will be unable to generate a request without knowing the token value. This would make it more difficult for an attacker to get a hold of a client’s session.


      The following is a list of potential uses for CSRF:

      • Use a content management system to add/delete content from a website.
      • Transfer money from one bank account to another.
      • Change a user’s password.
      • Add items to a user’s shopping basket.
      • Change the delivery address of an order.

      Explanation of a CSRF attack:

      An example for a scam web page


      1. A user signs into www.trust-banking-site.com using forms authentication. The server authenticates the user and issues a response that includes an authentication cookie. The site is vulnerable to attack because it trusts any request that it receives with a valid authentication cookie.
      2. The user visits a malicious site, www.bad-luckywinner-site.com.
      The malicious site, www.bad-luckywinner-site.com, contains an HTML form similar to the following:

      <h1>Congratulations! You're a Winner!</h1>
      <form action="http://trust-banking-site.com/api/account" method="post">
          <input type="hidden" name="ToAccNumber" value="13-232323-34">
          <input type="hidden" name="Transaction" value="withdraw">
          <input type="hidden" name="Amount" value="1000000">
          <input type="submit" value="Click to collect your prize!">
      </form>
      
      Notice that the form's action posts to the vulnerable site, not to the malicious site. This is the "cross-site" part of CSRF.

      3. The user clicks on the submit button. The browser makes the request and automatically includes the authentication cookie for the requested domain, www.trust-banking-site.com.

      4.The request runs on the www.trust-banking-site.com server with the user's authentication context and can perform any action that an authenticated user is allowed to perform.
      In addition to the scenario where the user selects the button to submit the form, the malicious site could:

      •     Run a script that automatically submits the form.
      •     Send the form submission as an AJAX request.
      •     Hide the form using CSS.


      Note:  Struts2 Token interceptor can be used to address CSRF and multiple form submission issues. An application developer’s highest assurance solution for CSRF is secure form tokens, which are also called nonces. The qualities of a good token in a CSRF defense mechanism include the following:

      • The token value must be universally unique to user
      • The token value must be computationally difficult to guess
      • The token’s legitimacy must have a comparable lifetime to the user’s session
      The tokenSession interceptor, it extends the token interceptor by adding sophisticated logic to the duplicate token-processing checks.

      Struts2 token interceptor and how this works behind the scene 

      1. When a request is made to the update action, Struts2 tags API generates a unique token and set it to the session. The same token is sent in the HTML response as hidden field. 
        
        
        
      2. When the form is submitted with token, it is intercepted by token interceptor where it tries to fetch the token from the session and validate that it’s same as the token received in the request form. If token is found in session and validated then the request is forwarded to the next interceptor in the chain. Token interceptor also removes the token from the session. 
      3. When the same form is submitted again, token interceptor will not find it in the session. So it will add an action error message and return invalid.token result as response. You can see this message in above image for invalid_token.jsp response. This way token interceptor make sure that a form with token is processed only once by the action. 
      4. If we use tokenSession interceptor, rather than returning invalid token response, it tries to return the same response as the returned by the first action with same token. This implementation is done in the TokenSessionStoreInterceptor class that saves the response for each token in the session. 
      5. We can override the action error message sent by token interceptor through i18n support with key as “struts.messages.invalid.token”. 

        Form Data with post and Ajax/XHR calls using JQuery
        Technologies used in this example:
        • struts2-core 2.5.18 
        • JDK 1.8 
        • Apache Maven 3.6.0 
        • Eclipse Photon 4.8.0
        • apache-tomcat-9.0.8
        • jquery 3.3.1
        • jQuery Validation Plugin
        Create a new eclipse Dynamic Web Project and convert to maven project



        pom.xml
        <project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
         <modelVersion>4.0.0</modelVersion>
         <groupId>com.rmpk.prj</groupId>
         <artifactId>struts2-token</artifactId>
         <version>0.0.1-SNAPSHOT</version>
         <packaging>war</packaging>
         <properties>
          <struts2.version>2.5.18</struts2.version>
          <javaee.version>8.0</javaee.version>
          <servlet-api-version>2.5</servlet-api-version>
         </properties>
         <dependencies>
          <dependency>
           <groupId>org.apache.struts</groupId>
           <artifactId>struts2-core</artifactId>
           <version>${struts2.version}</version>
          </dependency>
         </dependencies>
         <build>
          <sourceDirectory>src</sourceDirectory>
          <plugins>
           <plugin>
            <artifactId>maven-war-plugin</artifactId>
            <version>3.2.1</version>
            <configuration>
             <warSourceDirectory>WebContent</warSourceDirectory>
            </configuration>
           </plugin>
           <plugin>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.8.0</version>
            <configuration>
             <source>1.8</source>
             <target>1.8</target>
            </configuration>
           </plugin>
          </plugins>
         </build>
        </project>
        


        web.xml
        <?xml version="1.0" encoding="UTF-8"?>
        <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee 
         http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         id="WebApp_ID" version="4.0">
         <display-name>struts2-token</display-name>
         <welcome-file-list>
          <welcome-file>employee_save.jsp</welcome-file>
         </welcome-file-list>
         <filter>
          <filter-name>struts2</filter-name>
          <filter-class>
          org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter
          </filter-class>
         </filter>
         <filter-mapping>
          <filter-name>struts2</filter-name>
          <url-pattern>/*</url-pattern>
         </filter-mapping>
        </web-app>
        
        Note: The Struts 2 support for eliminating duplicate transactions is powerful and easy touse. This support will protect your application against double-clicking and the pesky browser Back and Refresh buttons.
        struts.xml
        <?xml version="1.0" encoding="UTF-8" ?>
        <!DOCTYPE struts PUBLIC
            "-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"
            "http://struts.apache.org/dtds/struts-2.5.dtd">
        
        <struts>
         <package name="token-test" namespace="/employee" extends="struts-default">
          <action name="save" method="save" class="com.rmpk.prj.token.action.EmployeeMgmtAction">
           <interceptor-ref name="token" />
           <interceptor-ref name="defaultStack"/>
           <result name="success">/success.jsp</result>
           <result name="invalid.token">/invalid_token.jsp</result>
          </action>
         </package>
        </struts>
        
        Note:Although the token and tokenSession interceptors can help prevent duplicate posts from being submitted and processed, the other common problem with web applications is users who click too frequently. Long-running pages are often resubmitted multiple times. The tokenSession interceptor can transparently address this issue, but sometimes having a simple Please Wait page while the action executes gives the user a better sense of confidence with your application. The execAndWait interceptor does
        that for you.
        employee_save.jsp
        <%@ page language="java" contentType="text/html; charset=ISO-8859-1"
         pageEncoding="ISO-8859-1"%>
        <%@ taglib uri="/struts-tags" prefix="s"%>
        <!DOCTYPE html>
        <html>
        <head>
        <title>Employee Management System</title>
        <script src="<s:url value="/js/jquery-3.3.1.min.js" />"
             type="text/javascript"></script>
        <script src="<s:url value="/js/jquery.validate.min.js" />"
             type="text/javascript"></script>
        <script type="text/javascript">
         $(function() {
          $("#registerForm").validate({
           rules : {
            email : {
             required : true,
             email : true
            },
            firstName : {
             required : true
            },
            lastName : {
             required : true
            }
           }
          });
         });
        </script>
        </head>
        <body>
         <s:set var="namespace" scope="request">/employee</s:set>
         <s:form action="save" id="registerForm"
          namespace="%{#request.namespace}" method="post">
          <h3>Employee Registration Form</h3>
          <s:token />
          <div>
           <s:textfield name="firstName" id="firstName" placeholder="First name"
            label="First name"></s:textfield>
          </div>
          <div>
           <s:textfield name="lastName" id="lastName" placeholder="Last name"
            label="Last name"></s:textfield>
          </div>
          <div>
           <s:textfield name="email" id="email" placeholder="Email Address"
            label="Email :"></s:textfield>
          </div>
          <div>
           <s:submit name="submit" value="Save"></s:submit>
          </div>
         </s:form>
        </body>
        </html>
        


        invalid_token.jsp
        <%@ page language="java" contentType="text/html; charset=ISO-8859-1"
            pageEncoding="ISO-8859-1"%>
        <!DOCTYPE html>
        <html>
        <head>
        <meta charset="ISO-8859-1">
        <title>Employee Management System</title>
        </head>
        <body>
        <h3>Employee details are not updated, duplicate request detected.</h3>
         <h4>Possible Reasons are:</h4>
         <ul>
          <li>Back button usage to submit form again</li>
          <li>Double click on Submit button</li>
          <li>Using "Reload" Option in browser</li>
          <li>Cross-Site Request</li>
         </ul>
        </body>
        </html>
        


        success.jsp
        <%@ page language="java" contentType="text/html; charset=ISO-8859-1"
         pageEncoding="ISO-8859-1"%>
        <%@ taglib uri="/struts-tags" prefix="s"%>
        <!DOCTYPE html>
        <html>
        <head>
        <meta charset="ISO-8859-1">
        <title>Employee Management System</title>
        </head>
        <body>
         <h1>Employee registration success</h1>
         <h2>Employee Details</h2>
         First Name : <s:property value="firstName" /><br> 
         Last Name : <s:property value="lastName" /><br>
         Email : <s:property value="email" /><br>
        </body>
        </html>
        


        EmployeeMgmtForm.java
        package com.rmpk.prj.token.action;
        
        import java.io.Serializable;
        
        public class EmployeeMgmtForm implements Serializable{
         private static final long serialVersionUID = 1L;
         private String firstName;
         private String lastName;
         private String email;
         
         public String getFirstName() {
          return firstName;
         }
         public void setFirstName(String firstName) {
          this.firstName = firstName;
         }
         public String getLastName() {
          return lastName;
         }
         public void setLastName(String lastName) {
          this.lastName = lastName;
         }
         public String getEmail() {
          return email;
         }
         public void setEmail(String email) {
          this.email = email;
         }
        }
        


        EmployeeMgmtAction.java
        package com.rmpk.prj.token.action;
        
        import java.text.MessageFormat;
        
        import com.opensymphony.xwork2.ActionSupport;
        import com.opensymphony.xwork2.ModelDriven;
        
        public class EmployeeMgmtAction extends ActionSupport implements ModelDriven<EmployeeMgmtForm> {
         private static final long serialVersionUID = 1L;
         private EmployeeMgmtForm employeeMgmtForm = new EmployeeMgmtForm();
        
         public String index() {
          return SUCCESS;
         }
        
         @Override
         public EmployeeMgmtForm getModel() {
          return employeeMgmtForm;
         }
        
         public String save() {
          String name = MessageFormat.format("Employee First Name : {0}, Last Name {1}",
            employeeMgmtForm.getFirstName(), employeeMgmtForm.getFirstName());
          System.out.println(name);
          System.out.println("Email : " + employeeMgmtForm.getFirstName());
          return SUCCESS;
         }
        }
        







        Conclusion



        References