6

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.

skaffman
  • 398,947
  • 96
  • 818
  • 769
Eric J.
  • 147,927
  • 63
  • 340
  • 553

2 Answers2

4

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.

dbreaux
  • 4,982
  • 1
  • 25
  • 64
rook
  • 66,304
  • 38
  • 162
  • 239
  • Note that some power users will have referer headers turned off (for privacy reasons) and that you have to be careful with bookmarks (although people really shouldn't bookmark POST pages), be sure to redirect the browser to a different page after processing the POST data. – Boy Baukema Oct 24 '11 at 12:19
  • I know this post is old, but it was a top Google hit for my search. Please do not follow this advice. Referer headers are spoofable. Please see this for more information: http://stackoverflow.com/questions/1413930/is-checking-the-referrer-enough-to-protect-against-a-csrf-attack – John.Larison Aug 24 '12 at 19:47
  • 4
    @John.Larison Yes it is very easy to spoof your referer on your own browser, however it is impossible to do so in a CSRF attack. Please refer to the OWASP CSRF prevention cheat sheet, and refrain from spreading clearly incorrect information. – rook Aug 24 '12 at 21:48
  • The difficulty is if you want to exclude some "pages" from such checking. For our non-Web-Flow apps, we do this by excluding "direct" page URLs. But with Web Flow, all "pages" share a single URL. – dbreaux Oct 25 '17 at 20:42
  • @dbreaux as long as all pages share the same **domain** then you can prevent domains owned by attackers from riding on user sessions with CSRF by simply checking the referer or origin. – rook Oct 26 '17 at 01:58
  • @rook unless I'm missing something, the point is that I want to allow /myapp/flow1 with any referer/origin, at the start of the flow. Bookmarks and search results are legitimate sources of starting the flow. But then I don't want any subsequent requests to /myapp/flow1 to have a different referer. So I need to perform that check at something other than the URL level. – dbreaux Oct 26 '17 at 14:00
  • Thinking about it further, though, I'm not sure how exposed flows are... execution keys, state machine... post-redirect-get – dbreaux Oct 26 '17 at 14:10
  • @dbreaux CSRF is one of the most common vulns on the net right now because nearly every platform is vulnerable by default. As a dev, you don't need to understand the SOP to write apps, and it is easy to void the protections that the SOP provides. – rook Oct 26 '17 at 22:03
  • I'm apparently not making my main point clear. /myapp/flow1 can sometimes allow any referer at all, sometimes not. So any remediation that looks at that can't be simply based on URL, which is how we've wanted to do it for other, non-Web Flow apps. – dbreaux Oct 27 '17 at 14:06
2

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.

Barry Pitman
  • 3,087
  • 1
  • 24
  • 32