I am looking at making a version of the Form component that supports the
"action token" pattern for securing forms against cross-site request forgery
(XSRF) and cross-site script includes (XSSI). The basic idea is to have the
form generate a unique id that must be submitted along with the form. This
verifies that the form was not forged and generated outside of the
application.

I would love your input as to whether this will work (I am not  an expert on
all the versioning and pagemap stuff yet, but I think the form should always
be submitted to the same instance regardless of back button, etc.) and
whether this should be part of the base form component.

Below is the version of the form that I have created. The verification of
the token happens in an overriden validate() method. I would have preferred
to override onFormSubmitted, but it is marked as final (at least in
1.2.5which is what I am using). In
2.0 there appears to be "fake submit" handling, but it is not clear how this
should work. If this is already being handled, let me know...

Thanks for your time.


public class SecureForm extends Form
{

   private final transient Logger logger = LoggerFactory.getLogger(
SecureForm.class);

   private String actionToken;

   public SecureForm(final String id) {
       this(id, null);
   }

   public SecureForm(final String id, IModel model)
   {
       super(id, model);

       //generate a unique action token stored with this form
       actionToken = UUID.randomUUID().toString();
   }

   @Override
   protected void onComponentTagBody(final MarkupStream markupStream, final
ComponentTag openTag)
   {
       // render the hidden field
       AppendingStringBuffer buffer = new AppendingStringBuffer("<div
style=\"display:none\"><input type=\"hidden\" name=\"");
       buffer.append(getActionTokenHiddenFieldId())
               .append("\" id=\"")
               .append(getActionTokenHiddenFieldId())
               .append("\" value=\"")
               .append(actionToken)
               .append("\" /></div>");
       getResponse().write(buffer);

       // do the rest of the processing
       super.onComponentTagBody(markupStream, openTag);
   }

   @Override
   protected void validate() {
       //verify that the token was provided
       String token =
getRequest().getParameter(getActionTokenHiddenFieldId());

       if (!actionToken.equals(token)) {
           logger.warn("Attempted unauthorized form submission.");
           throw new UnauthorizedActionException(this, new Action("
SECUREFORM.SUBMIT"));
       }

       super.validate();
   }

   private String getActionTokenHiddenFieldId() {
       return "_actiontoken";
   }
}

Reply via email to