Software Testing Blog

3 Hurdles to Getting CSRF Protection Correct

Preventing cross-site request forgery (CSRF) is a challenge for enterprise web applications.  A CSRF attack is executed when a user loads some malicious web content that silently hijacks their browser to perform unwanted actions on the target (i.e. your) website. An attack can even leverage the user’s active session cookies to bypass authentication checks. See here for an example. A user legitimately expects that no third party should be so easily able to update their password, post comments in their name, or transfer funds from their bank account.

The current best practice is to pass synchronizer (a.k.a. anti-forgery) tokens to and from the client.  These tokens are included in the page content and are not accessible to third-party web sites due to browser same-origin policies. Unlike session cookies, they can not be hijacked to forge a user’s credentials.

Correctly and consistently adding synchronizer tokens to the proper transactions is hard to get right.  The real security hardening comes with determining exactly what requests should and should not be protected. To a first approximation, all requests that change the “state” of the application are vulnerable to CSRF attacks.  These are actions that have side effects that could be forged by an attacker.  Any controllers that update the database, write to the filesystem, or set session or application attributes need to be scrutinized; controllers that simply serve content are safe.

Hurdle 1: Hidden behaviors

The open-source code below is a real-life example of code that requires CSRF protection.  Unfortunately it would be easy for a reviewer to overlook the vulnerability.  Here, we have a Struts entry point named doRootContent.  It turns out that the innocuously-named getRootContentVO can actually create new content in the database.  The last argument– the createIfNonExisting parameter– enables this behavior.

public String doRootContent() throws Exception
{
    Document doc = DocumentHelper.createDocument();

    ContentVO rootContent =
        ContentController.getContentController()
                         .getRootContentVO(repositoryId,
                                           getInfoGluePrincipal().getName(),
                                           true);

    doc.add(getPlainContentElement(rootContent));
}

A human auditor is likely to focus on method names and URLs; they’d have to be super-human to look into every line of code. It’s too easy to miss hidden side-effects in poorly-named methods.

Hurdle 2: Using the correct HTTP verbs

The HTTP request method verbs (GET, POST, PUT, etc.) provide one useful way to differentiate transactions with and without side effects.  The HTTP specification even dictates how servers should be using the verbs.  But like a law with no police to enforce it, web developers break these rules with abandon.  At least adherents of the popular RESTful design approach strive to correctly use these verbs, and conformant applications should have an easier time identifying where protection should be deployed.  But it’s still rare to find an application that doesn’t make some exception here or there.

As an example, the following Spring 3.0 controller is using a GET request to saveReview.  Tsk.

@Controller("blRatingController")
@RequestMapping("/rating")
public class RatingController {

    @RequestMapping(value = "/saveReview.htm",
                    method = {RequestMethod.GET})
    public String saveReview(HttpServletRequest request) {
        ...
    }
}

This is dangerous, because Spring security, OWASP CSRF-guard, and many custom CSRF protection schemes totally rely on web developers using the correct verbs. Because GET requests are not supposed to have side effects, they are commonly left unprotected. This amounts to a door that may be left unlocked and wide open by a careless developer.

Even more insidious is the misconception that confuses this common solution strategy– using verbs to identify transactions with side effects– with the root cause of the problem. No, GET requests are not safe simply by virtue of using that verb. (They’re only safe in an application that has been carefully architected so that GET requests are never used for any actions with side effects.) Any programmer or auditor who thinks otherwise is going to be leaving CSRF vulnerabilities in their wake.

Hurdle 3: Rule exceptions and cheating

It’s also very tempting for developers to start making “exceptions” instead of doing the harder work of re-architecting the web transactions.  If your application has resorted to similar bandages, know that you have good company– we’ve seen a lot of similar duct tape in enterprise applications. Here is some XML configuration from a security-minded open-source application for whitelisting certain URLs.  It’s grown so complex that the developers introduced their own exception language!

,/,/index.jsp,/login.jsp,/organizations,/wafs,/configuration,/reports,
    /j_spring_security_check,/j_spring_security_logout,/images/*,
    /styles/*,/scripts/*,/jasper/*,/rest/*,
    regex ^/rest/,
    regex ^/organizations/[0-9]+/applications/[0-9]+/scans/new/ajax_cwe$,
    regex ^/organizations/[0-9]+/applications/[0-9]+/scans/new/ajax_url$,
    regex ^/organizations/[0-9]+/applications/[0-9]+/table$,
    regex ^/organizations/[0-9]+/applications/[0-9]+/defectTable$,
    regex ^/organizations/[0-9]+/applications/jsontest$,
    regex ^/organizations/[0-9]+/applications/[0-9]+/scans/[0-9]+/table$
    regex ^/organizations/[0-9]+/applications/[0-9]+/falsepositives/table$
    ...

With such complex logic about where to apply CSRF protection, it can be hard to reason about which entry points are and are not protected and thus easy to miss vulnerabilities.

Better living through automation

Grappling with the exceptions and tricky cases– both in what must be protected and in what is actually protected– makes locking down a web application truly difficult.  As we’ve shown, it’s not always easy for an auditor to understand the protection obligations, and it’s easy for developers to “cheat”, misunderstand the issue, and subvert CSRF protection schemes.

This is where program analysis tools provide benefit.  A vulnerable request handler can be automatically identified by evaluating a deep model of its behavior– including any non-obvious or corner cases. These can then be checked against any existing protection schemes to ensure that their code, URL, and/or HTTP request method is secured with a synchronizer token check.

So you may have a CSRF filter… so what? Are you really sure that you’re protected?

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Current day month ye@r *