ABSTRACT

It's easy for Web application users to inadvertently resubmit forms with bookmarks or the reload and back buttons. Because of this potential for mischief, web applications must guard against resubmission of sensitive forms. This document proposes using the delegation event model and tokens to restrict access to sensitive pages.

Note: This proposal depends on action events as proposed in 'Proposal: Retrofit Struts with the Delegation Event Model'.
 

CREDITS

   'Command Token Implementation', posted to struts-dev by Robert Leland
   Struts Web Application Safety' on struts-dev, by Craig McClanahan
   'What about This Model?', posted to struts-user by Dan Cancro
   Web Development with JavaServer Pages, by Kolb and Fields, p 270
 

INTRODUCTION

Web browsers were designed for browsing the Web, not for applications. Some browser features, such as the back button, reload button, and bookmarks, are difficult for web applications to deal with because they disrupt the normal flow of an application. This proposal shows how to trap those browser features in Struts using the delegation event model and tokens.
 

THE PROBLEM

Consider the following sequence of actions, as Timothy creates a new account. First, Timothy tries to login, but since he does not have an account, he's forwarded to the Login Failed page:


Figure 1. The Login Failed Page

Timothy clicks on the open new account link and is forwarded to this page:


Figure 2. The Open New Account Page

Timothy fills in the form shown in  Figure 2, clicks the create account button, and is forwarded to the login page:


Figure 3. The Login Page

At this point, Timothy's account has been created and he can login, but what if he reloads the page instead? Or what if he hits the back button, and then clicks on the create account button without changing the form's data? In both cases, a duplicate request will be sent to the new-account-action. That action might detect the duplicate, but it's a lot to ask all actions to test for duplicate submissions. Let's see how Struts can trap such illicit access at a higher level.
 

TRAPPING ILLICIT ACCESS WITH STRUTS
 

WHAT IT DOES

Guard against sensitive form resubmission. If a sensitive form is resubmitted, Struts throws an exception.
 

HOW IT'S IMPLEMENTED

Trapping illicit access is implemented in this proposal with tokens, as described by Fields and Kolb (see credits). Here's how it works:

From the discussion above, it's apparent that some actions, such as new-account-action.do, are sensitive to access by the back button, the reload button, or bookmarks. In addition to sensitive actions, we will also speak of sensitive forms, which are forms that forward to sensitive actions; for example, the form shown in Figure 2 is a sensitive form.

When Struts performs an action with a sensitive form, the following sequence of events takes place:

1. The action's perform method is invoked.
2. A token (a unique string) is stored in the session, and a copy of that token is stored in request scope.
3. When the sensitive form is submitted, the token in request scope stows away in the form as a hidden form variable.*

Before the corresponding sensitive action is performed by Struts:

1. The token, stored as a hidden form parameter, is compared to the token in the session.
2. Iff the tokens match, the sensitive action is performed; otherwise, an exception is thrown.

* The original token stored in request scope in step #2 is lost when the sensitive form is submitted, because that submission results in a new request. That's why the token is stored in a hidden form parameter.
 

HOW YOU USE IT

Protecting pages with sensitive actions is a three-step process:

1. Specify sensitive='true' in struts-config.xml for sensitive actions; for example, for the new-account-action:

   <action path='/new-account-action' ... sensitive='true' ...> ... </action>

2. Specify hasSensitiveForms='true' in struts-config.xml for actions that have sensitive forms; for example, for the query-account-action:

   <action path='/query-account-action' ... hasSensitiveForms='true' ...> ... </action>

3. Add a hidden field to each sensitive form. That field's value is obtained from the "token" request attribute, like this:

   <%@ taglib uri='/WEB-INF/struts-bean.tld' prefix='bean' %>
   ...
   <bean:token/>

Or, alternatively:

   <input type='hidden' value='<%= request.getAttribute("token") %>'/>
 

THE CODE

Two new classes--Token and SensitiveActionListener--and a custom tag, <bean:token>, are added to Struts and minor modifications are made to Action.java and ActionServlet.java. The Token class, which maintains a unique, ecrypted string, is listed below:

public class Token {
   private String token;

   public Token(HttpServletRequest req) throws ServletException {
      HttpSession session = req.getSession(true);
      long systime = System.currentTimeMillis();
      byte[] time  = new Long(systime).toString().getBytes();
      byte[] id = session.getId().getBytes();
      try {
         MessageDigest md5 = MessageDigest.getInstance("MD5");
         md5.update(id);
         md5.update(time);
         token = toHex(md5.digest());
      }
      catch(Exception ex) {
         throw new ServletException(ex);
      }
   }
   public String getToken() {
      return token;
   }
   private String toHex(byte[] digest) {
      StringBuffer buf = new StringBuffer();

      for(int i=0; i < digest.length; i++)
         buf.append(Integer.toHexString((int)digest[i] & 0x00ff));

      return buf.toString();
   }
}

SensitiveActionListener, which monitors actions for both sensitive actions and actions with sensitive forms, is listed below:

public class SensitiveActionListener implements ActionListener {
   public void beforeActionPerform(ActionEvent event) throws ServletException {
      Action action = (Action)event.getSource();
      HttpServletRequest request = event.getRequest();
      HttpSession session = request.getSession();
      String sessionToken = (String)session.getAttribute("token");

      if(action.isSensitive()) {
         String requestToken = (String)request.getParameter("token");
         if(sessionToken == null || requestToken == null ||
              !sessionToken.equals(requestToken)) {
            throw new ServletException("Sorry, but this is a sensitive page " +
               "that can't be accessed with " +
           "bookmarks or the back button");
         }
      }
   }
   public void afterActionPerform(ActionEvent event) throws ServletException {
      Action action = (Action)event.getSource();
      HttpServletRequest request = event.getRequest();
      HttpSession session = request.getSession();

      if(action.hasSensitiveForms()) {
         Token token = new Token(request);

         session.setAttribute("token", token.getToken());
         request.setAttribute("token", token.getToken());
      }
      if(action.isSensitive()) {
         session.removeAttribute("token");
      }
   }
}

Sensitive action listeners implement the Struts ActionListener interface. The methods defined in that interface--beforeActionPerform and afterActionPerform--are called just before, and immediately after an action's perform method is invoked.

The SensitiveActionListener listing indicates new Action methods: isSensitive(), and hasSensitiveForms(). A third
method, setSensitive(), is added to the Action class to set that property.

ActionServlet.processActionCreate adds sensitive action listener to each action:

protected Action processActionCreate(ActionMapping mapping,
                                         HttpServletRequest request) {
        ...
        // Add other listeners here as appropriate
        actionInstance.addActionListener(new SensitiveActionListener());
        return (actionInstance);
}

With the addition of the addActionListener to processActionCreate, every action's peform method is preceded with a check to see whether that action is sensitive or has sensitive forms. If the action has sensitive forms, tokens are created after the action's perform method returns. If the action is sensitive, tokens are checked before that action's perform method is invoked.
 

CONCLUSION

This proposal has illustrated how Struts can guard against sensitive form resubmissions by using a proposed delegation event model and tokens. See the 'Retrofit Struts with the Delegation Event Model' proposal for more information concerning the proposed Struts event model.

Reply via email to