Thursday, August 23, 2007

Protecting a Wicket application against CSRF attacks

A security audit showed our site to be vulnerable to CSRF attacks. Here's an example of such an attack from Wikipedia:


"The attack works by including a link or script in a page that accesses a site to which the user is known to have authenticated. For example, one user, Bob, might be browsing a chat forum where another user, Alice, has posted a message with an image that links to Bob's bank. Suppose that, as the URL for the image tag, Alice has crafted a URL that submits a withdrawal form on Bob's bank's website. If Bob's bank keeps his authentication information in a cookie, and if the cookie hasn't expired, then Bob's browser's attempt to load the image will submit the withdrawal form with his cookie, thus authorizing a transaction without Bob's approval."


I got the idea for the solution from this post. It works by injecting a random string in a hidden field in a reusable form. When the form is submitted, the field value is checked against the server's record of it. It is difficult, if not impossible, for an attack to guess this random ID and so the form submission will be rejected.


Although in the same post Eelco comments that he think that Wicket is safe against such attacks, and that a better approach would be to use a technique like CryptedUrlWebRequestCodingStrategy, this seems easier to me and complementary to CryptedUrlWebRequestCodingStrategy.


MyForm.java


public class MyForm extends Form
{
/**
* A random id that's put in a hidden field and checked that it matches
* upon submit. This guards against CSRF attacks.
*/
private final String guidToken;
private static final String GUIDFIELD = "guidfield";

public MyForm(String id)
{
super(id);
// Generate a unique action token stored with this form
guidToken = UUID.randomUUID().toString();
add(new HiddenField(GUIDFIELD, new Model(guidToken)));
}
@Override
protected void validate()
{
super.validate();
// Check the random id in the hidden field. This guards against
// CSRF attacks.
FormComponent hidden = (FormComponent) this.get(GUIDFIELD);
String clientToken = hidden.getConvertedInput().toString();
String serverToken = this.getGuidToken();
if (!serverToken.equals(clientToken))
{
hidden.error(getString(GUIDFIELD));
}
}
public String getGuidToken()
{
return guidToken;
}
}

MyForm.html


<form wicket:id="myform">
<fieldset>
...
<input wicket:id="guidfield" type="hidden"/>
...
</fieldset>
</form>

MyForm.properties


guidfield=Detected an attack! Please inform Support

2 comments:

Eelco Hillenius said...

Here is the thing though, how would Alice be able to guess the internal - session relative - URL? Unless you made it a bookmarkable page, it's really guessing in the dark. However, I guess there is a remote possibility that the guess would be right, so this form looks like a good ultimate protection.

Thanks for sharing!

Jörn Zaefferer said...

This looks like a much more solid solution: http://issues.apache.org/jira/browse/WICKET-1782

The related commit is here: http://svnsearch.org/svnsearch/repos/ASF/search?logMessage=csrf#

Other opinions on that?