Prevent Cross-Site Request Forgery in a Spring WebFlow Application
I'm开发者_开发技巧 looking for a (hopefully straightforward) way to add CSRF protection to an application build on Spring WebFlow 2.
An approach that migrates well to Spring WebFlow 3 (when released) is preferred.
The easiest way to prevent CSRF it to check the referer request.getHeader("referer");
to make sure the request is coming from the same domain. This method is covered by the CSRF Prevention Cheat Sheet.
Its common to see this CSRF protection system on embedded network hardware with limited memory requirements, Motorola uses this method on most of their hardware. This isn't the most secure CSRF protection, token based protection is better but both systems can still be bypassed with xss. The biggest problem with token based CSRF protection is that it takes alot of time to go back and fix every request and you will probably miss a few requests.
A secure way to implement this is to check the referer on all incoming POST requests, and use POST for sensitive functions like changing passwords, adding user accounts, executing code, making configuration changes. GET should only be used for navigation or searching, basically GET is safe for anything that doesn't cause a state change.
Make sure you test your site with a xss scanner.
OWASP has a good guide to preventing CSRF attacks here:
Checking the Referer header is certainly the easiest, and its a good idea to at least log instances where the Referer is a 3rd party or empty. There are however several drawbacks which make using the referer alone unreliable:
- Corporate firewalls might strip the referer header for privacy reasons
- The referer is not sent when moving from HTTPS to HTTP
- In CFRF attacks, it is generally difficult to 'forge' the referer, but it can be done using meta-refresh tags.
Fortunately WebFlow makes it easy to implement a custom unique token-per-flow-invocation CSRF filter (you might not have to modify any views/forms)!
First, use a FlowExectionListener to create a new random token whenever a flow starts and store it in the flowScope. Then, whenever an event is signalled, verify that the submitted token (submitted as a parameter in the request) is equal to the value stored in the flowScope.
Then, configure a custom FlowUrlHandler which appends the "_token" parameter to generated URLs, so if you have been using ${flowExecutionUrl} to reference your flows, the token will appended whenever you POST/GET back to your flow automatically. To fetch the token from the flowScope from inside the FlowUrlHandler, I had to resort to using RequestContextHolder
private String retrieveToken() {
RequestContext requestContext = RequestContextHolder.getRequestContext();
if (requestContext == null) {
return null;
}
return (String) requestContext.getFlowScope().get(CsrfTokenFlowListener.TOKEN_NAME);
}
...
This method will include the CSRF token whenever you output ${flowExecutionUrl} - for both GETs and POSTs, and if you are using post-redirect-get, you can ensure ensure that the CSRF token doesn't appear in the URL bar.
I would caution against only checking CSRF tokens for POSTs:
WebFlow and many other web frameworks don't distinguish between GET and POST - by default you generally can use a GET to do whatever you do with a POST, unless you verify the request method yourself (which would be a good idea anyway). So an attacker wanting to bypass your CSRF filter would just do a GET instead of a POST.
Edit: One disadvantage to be aware of in including CSRF tokens in the ${flowExecutionUrl} is that the CSRF token will likely always be sent as part of the request URL (because it would be part of the HTML form's 'action' attribute), and never in a POST body. Including sensitive information in the request URL isn't great, as it is more likely to be logged in server/ISP logs. The alternative is to add a hidden input in each form containing the CSRF token, and only enforce its presence for POST requests.
精彩评论