• CSRF: Cross-Site Request Forgeries – Basic Security Part 3

    by  • 19 July 2012 • Security

    NB: This is the third post in a series of posts on web application security.

    The quintessential example of a CSRF (sometimes pronounced “sea-surf”) is a bank that naively does transfers over a GET request without any other security:


    Ignoring how many other bad things are going on here, there’s no validation that the request is coming from a site. I could include an image tag somewhere with that URL and every time someone visited the site, I’d get another hundred grand.

    Django, and most frameworks, have built-in CSRF protection that uses the concept of a nonce, or one-time-use number. These are submitted with a form (over POST, hopefully) and compared to a number on the server-side. If the number doesn’t match, the request is prohibited.

    Update: Because of an insightful comment on HN, I want to make it clear that using POST does not guarantee safety from CSRF. Someone can use an <iframe> and execute a POST request. But it does two things:

    • It reduces the surface area of attack. There are a lot more places I can stick an <img> tag that can trigger a GET request on high-traffic parts of the web than there are places I can stick some code to create an iframe and do a POST.
    • It’s a better practice to do make any change a POST because all the actors, from the browser/user-agent, to proxies, to load-balancers, to web servers, understand that the POST verb means something is changing and treat it differently than most GET requests.

    Whatever your framework provides for this, use it.

    If you’re using Django, you may also want to look at django-session-csrf, which provides a bit more security than the default implementation, which uses cookies. It stores the actual CSRF token (nonce) in the session, instead of in its own cookie. This provides better protection against compromised apps on different subdomains and MITM attacks that can alter cookie values.

    Testing CSRF Protection

    Testing CSRF protection is tricky, especially in Django. The built-in test client ignores CSRF protections, so you may break parts of your site without knowing.

    Using something like Selenium to drive tests through form submissions in actual browsers can help guarantee that you both have CSRF protection in place and that you’re providing all the credentials and nonces correctly to the browser.

    (And yes, we’ve stumbled over and broken that before. It took us a few days to track down all the forms we broke.)

    • http://www.tvspiegel.com Wandspiegel

      Is it displaying as grey text on a brown background for everyone else? That’s nigh-on unreadable… maybe not grey but some colour which doesn’t contrast with brown at all

    • http://jamessocol.com/ James

      Sometimes something takes a while to load and the white background never shows up. Maybe this weekend I’ll fix the theme, it’s been driving me nuts. Try reloading if you see that.

    • Carl Meyer

      There’s a useful intermediate layer between the Django test client and Selenium for integration testing of views and templates that do form handling: use WebTest [1].

      Unlike the Django test client, WebTest can parse HTML forms and submit them as a browser would (including the csrf token hidden input), which not only allows you to test that CSRF protection isn’t breaking your forms, but also tests that you haven’t broken them by e.g. omitting a crucial field in the template, which most tests people write using the Django test client and asserting against the template context won’t catch. And WebTest tests will run at a speed comparable to Django-test-client tests, unlike Selenium tests.

      I pretty much never use the Django test client for anything; as far as I’m concerned WebTest is strictly superior for that type of testing.

      Django-webtest (http://pypi.python.org/pypi/django-webtest/) provides Django integration for WebTest.

      [1] http://webtest.pythonpaste.org/en/latest/index.html

    • http://jamessocol.com/ James

      Carl, that’s pretty awesome. We should share that with the WebQA team, and WebQA teams everywhere!