Cross-site request forgery (CSRF) is a type of security exploit where a user’s web browser is tricked by a third-party site into performing actions on websites that the user is logged into. It is often a difficult attack to pull off, as it requires a number of factors to line up at once. Protecting against it requires good discipline and good design practices, especially when it comes to protecting Web APIs.

Here’s a brief example of a fictitious CSRF attack against a bank:

  1. Sally logs into her bank, bank.example.com, with her account number and secure password.
  2. The bank sends a cookie to her web browser, identifying her “session”, which is the period for which she’s logged in to her account. Set-Cookie: SESSIONID=a804696f-93fc-48cf-9b02-267d9ed773c0
  3. Some time later, Sally is browsing other web sites not related to her bank. A search lands her on hungryhippos.ca.
  4. hungryhippos.ca hosts some malicious code in their web page:

    <form name="attack" enctype="text/plain"
        action="https://bank.example.com/api/transfer" METHOD="POST">
      <input type="hidden" name='{"from": "Savings", "to": "00302319550440", "amount": "100.00"}'>
    </form>
    <script>document.attack.submit();</script>
    
  5. Sally’s web browser reads the web page, and executes the code on the page, which includes sending a request to her bank to transfer $100.00 out of her savings account.
  6. Sally’s browser also includes her SESSIONID cookie with the request, because it is a request to bank.example.com.
  7. Sally’s bank sees an authorized request to transfer money come in, with a valid session cookie, from the same IP address that the session logged-in as. The transfer is authorized.

So, how do we protect against requests that come from our users, but aren’t actually performed by them?

Traditional UI Protection

In a traditional web application, where the user primarily interacts with the application server by performing link-based navigation and form-based modification actions, you’d protect against a CSRF attack by one or more of these approaches:

  • Check the Referer header of the HTTP request to ensure that requests are generated from your own site. Don’t stop reading! I know that this sounds stupid because Referer headers are easily spoofed, right? But a CSRF attack relies on tricking a user’s browser into performing actions, so it is not possible to spoof the Referer header, because you don’t control the HTTP client. (Edit: If you disagree with me, please, tell me how such an attack would work! I want to know!)
  • Embed unguessable user-specific data in every HTTP request that performs modification actions; primarily this would be HTML forms that perform POSTs. This protects against the CSRF attack because the malicious site (eg. hungryhippos.ca) doesn’t know the correct user-specific data to put into the POST payload. The backend server must validate that the user-specific data is correct for every POST.
    • The simplest choice for user-specific data is the user’s session ID. That is easy to validate on the server-side.
    • Bonus points: Another option is a version identifier or last-modified timestamp of the object being edited. Checking this server-side can also serve the purpose of preventing two edits of a resource at the same time from overwriting each other. Note that a version identifier would have to be unguessable for this to work, so an auto-incrementing number is not a good candidate.

Web API Protection

Protecting a Web API requires a slightly different approach, because the API won’t have an opportunity to tell it’s client, “here, send this data as well” for each request. Here are some mechanisms for protecting a Web API from a CSRF attack:

  • Don’t allow your API to be accessed with the same credentials that your interactive UI sessions use. In the example above, if the bank’s API didn’t accept a SESSIONID cookie as a valid credential, the transfer request would not have been possible.
    • Do: use an API-key based authentication, or a more sophisticated mechanism like OAuth.
    • Don’t: allow HTTP basic, digest, or NTLM authentication to your API. If Sally’s bank hadn’t accepted SESSIONID, and instead presented WWW-Authenticate: Basic realm="bank.example.com" in its response, Sally would’ve been prompted to enter her bank account credentials. There’s a chance any user might enter whatever is being asked for.
    • Except: HTTP authentication mechanisms are totally safe if they’re just being used to transport an API key.
  • Requiring user-specific data can also be used to mitigate API CSRF attacks, but it doesn’t work the same way as an HTML form. Instead, imagine if Sally’s bank’s API had required that the source bank account be specified as an account number, rather than an account name. The attackers at hungryhippos.ca wouldn’t have been able to supply Sally’s bank account number for the transfer.
    • Partial fix:This form of protection is less effective as the attack gets more targeted. If hungryhippos.ca were targeting Sally, rather than just any user wandering past their web site, they might be able to craft more specific attacks.

Hybrid API and UI Protection

If you’re protecting your UI and your API differently, then a real conundrum is presented for modern web application development. Many people want to be able to call their own API from their UI, to develop sophisticated single-page web applications and other highly interactive UIs. So, how can you allow your client-side UI code to call your API, while still protecting the API from CSRF attacks?

  • Allow your API to accept UI authentication, like the SESSIONID cookie, but require that API calls made with that form of authentication have additional security checks on them. They can either check the Referrer header, or have user-specific data inserted into the API payload.
    • Downside: This requires that your API and your UI are pretty tightly coupled, since they would share authentication mechanisms. That works just fine for a lot of projects, but it’s an architecture no-no for some people.
  • Proxy all your UI API requests through the UI to your API. The UI will perform its normal authentication of the requests, and then insert the user’s API-key into the API requests before sending it to the API implementation.
    • Bonus points: If you implement OAuth, you can make your UI have it’s own OAuth tokens for performing API requests on behalf of the user. This is a very decoupled architecture where your UI is an API integrating application no different than any other (except possibly in provisioning the OAuth token). It’s a bit architecture-astronaut-y for most applications, but there are some real benefits.

Summary

The various mechanisms for protecting against a CSRF attack aren’t particularly complex or difficult to implement. However, they require some discipline to follow. In my experience, retrofitting an application with proper CSRF protection is difficult; individual edge cases pop-up that lead to disabling protection here and there, and eventually the system is hardly protected at all.

Read more about Cross-site request forgery on Wikipedia. Thanks to pentestmonkey for their article on arbitrary-content CSRF POSTs, which is the technique that really exposes APIs to CSRF attacks.