AngularJS web apps for Spring-based REST services security: the server side part 2 (CSRF)

Previous posts in this series:


Try to guess how many times per day you click on an HTML link…

Overwhelming, isn’t it?I don’t know about you but I certainly do not check every link I click on, especially if the link is from a website that I trust and use regularly. But what if the website has been compromised? Or what if the link has been sent to you by someone you’re chatting with?

What if someone manages to make you click on this code’s link (from OWASP’s page on CSRF):

<a href="http://bank.com/transfer.do?acct=MARIA&amount=100000">View my Pictures!</a>
...or...
<img src="https://bank.com/transfer.do?acct=MARIA&amount=100000" width="0" height="0" border="0">

In the first case, the user is tricked on clicking on a link that tries to perform a fake bank transfer. In the second case, the fake bank transfer request is performed as soon as the browser tries to load that picture.

Scary isn’t it? You think you’ll get a LOLcat and you get robbed instead!

What are cross-site request forgeries?

Okay, so the code above is a bit naive, but the point is that a CSRF attack “forces an end user to execute unwanted actions on a web application in which they’re currently authenticated“. To exploit CSRF attacks the attacker needs (among others):

  • to know the URL to the API that allows performing that action.
  • to use a request that has side effects (that is, which changes the state of the server)
  • to know your account, or how you are identified on the bank’s application (such as an identifying ID or token, or a username)
  • to reuse a cookie set up by the bank’s application to keep track of the user’s authentication. This can happen if your web app does not expire the cookies at logout… or if the user does not log out!

In other words: if an app you use has a public API and uses cookies to identify you, then you might be at risk.

And that’s not the only CSRF attack possible. Login CSRF is when an attacker forges a request to log the victim into a target website using the attacker’s credentials. The victim believes she’s using her account, but she’s in fact using the attacker’s account. Everything she does during that session can then be read by the attacker by simply logging in with his account!

If you want to know more about CSRF attacks, check OWASP’s excellent page.

So how can we prevent those?

We’re going to basically apply two techniques: the synchronizer token pattern and the cookie-to-header mechanism.

Synchronize your tokens!

We generate a unique, secret, random token: a CSRF token.

With session-based authentication using Spring Security, the server will associate this CSRF token with the user’s session after he logged in. The token is then sent to the web client.

Every time the client needs to perform a sensitive request that modifies the state (POST, PUT, PATCH…) it ensures that the token is sent along the request. This way the server compares the received token with the one he has in store for the authenticated user. If the tokens do not match, a 403 is returned and the request is rejected.

We shall NOT enforce this approach on GET, OPTIONS, HEAD or TRACE requests: if you have used your REST verbs correctly, these methods should not perform updates and therefore they are “safe“. Furthermore we’d rather prevent leaking the token information as much as possible (see OWASP’s cheat sheet about this).

So, to summarize: we use a CSRF token (unique, random, renewed at each user login) to ensure that a modifying request is indeed coming from a given authenticated user.

Spring Security already knows how to generate CSRF tokens. In fact, they are enabled by default when using Java Configuration. But since the default configuration is aimed at JSP-based applications, we’ll need to tweak it a little:

// CSRF
http.csrf().requireCsrfProtectionMatcher(
  new AndRequestMatcher(
    // Apply CSRF protection to all paths that do NOT match the ones below

    // We disable CSRF at login/logout, but only for OPTIONS methods
    new NegatedRequestMatcher(new AntPathRequestMatcher("/login*/**", HttpMethod.OPTIONS.toString())),
    new NegatedRequestMatcher(new AntPathRequestMatcher("/logout*/**", HttpMethod.OPTIONS.toString())),

    new NegatedRequestMatcher(new AntPathRequestMatcher("/rest*/**", HttpMethod.GET.toString())),
    new NegatedRequestMatcher(new AntPathRequestMatcher("/rest*/**", HttpMethod.HEAD.toString())),
    new NegatedRequestMatcher(new AntPathRequestMatcher("/rest*/**", HttpMethod.OPTIONS.toString())),
    new NegatedRequestMatcher(new AntPathRequestMatcher("/rest*/**", HttpMethod.TRACE.toString())),
    new NegatedRequestMatcher(new AntPathRequestMatcher("/rest/open*/**"))
  )
);

The above specified which methods and URL paths will require a CSRF token to be allowed. Basically, this is done by telling our application which paths or methods can be excluded, and all the rest will be protected.

The line:

new NegatedRequestMatcher(new AntPathRequestMatcher("/rest*/**", HttpMethod.GET.toString()))

means “apply CSRF protection to all requests that do NOT match that Ant path and method“.

We start by disabling CSRF on OPTIONS requests… Why? Because those are “safe” requests that do not modify anything server-side, and because we need OPTIONS for CORS to work!!! Indeed, remember that in some cases CORS uses OPTIONS requests to obtain the server’s agreement on sharing its resources… that’s the preflight (feel free to check my previous post about CORS).

Everything under “/rest” is protected for request methods other than the safe ones. However we leave “/rest/open” APIs unprotected for our tutorial, to see the difference in handling when we’ll examine the web client side. In practice, I would avoid doing that in a real project though!

You might have noticed that we still protect login and logout URLs. This is because login and logout processes are ALSO vulnerable to CSRF attacks, so we have to submit a CSRF token along our credentials (or our logout request). How? We ask the server for one? How do we ask? We send an OPTIONS request to the server, and we’ll get back a response which includes a CSRF token. OPTIONS do not need a CSRF token to be accepted, but they return one we can use for further operations.

Wait a second… the user is not logged yet when we do a login. And we know that the CSRF token is stored with the user’ session. So how do we actually get a CSRF token back?
What Spring Security does is that it sets up a temporary session for that. So basically it goes like this:

  1. The client asks a token with an OPTIONS request.
  2. The server creates a temporary session, stores the token and sends back a JSESSIONID and the token to the client.
  3. The client submits the login credentials using that JSESSIONID and CSRF token.
  4. The server matches the CSRF stored for the received JSESSIONID and, if all is green-lighted, creates a new definitive JSESSIONID and a new session-based CSRF token for the client to validate its requests after the login.

So it’s a bit trickier for login/logout, but it makes sense from a protection perspective. Check Spring Security’s reference manual for further explanations.

Header, cookies knees and toes

Now the next question is how to send the CSRF token from the server to the client?

If we adopt the cookie-to-header technique, we need to send the token to the client as a cookie…So the client stores the CSRF token in a cookie, and submits it back to server in the request as a header.

Why do that?

Well, remember that the same origin policy prevents JavaScript from reading a given site’s cookies if the script file is not of the same origin. So an attacker’s script will not be able to read the cookie’s value.
You would think he can reuse the CSRF cookie the same way he “hijacks” the authentication cookie of the user that did not logout. BUT the server expects the token in a header or it won’t accept the request! The malicious script cannot read the token value, so it cannot copy it to the header, and so the server will block the malicious request!

So let’s put this into practice. We implement a filter that provides the CSRF token as a cookie:

public class CsrfTokenResponseCookieBindingFilter extends OncePerRequestFilter {

  protected static final String REQUEST_ATTRIBUTE_NAME = "_csrf";

  @Override
  protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
    throws ServletException, IOException {

    CsrfToken token = (CsrfToken) request.getAttribute(REQUEST_ATTRIBUTE_NAME);

    Cookie cookie = new Cookie(CSRF.RESPONSE_COOKIE_NAME, token.getToken());
    cookie.setPath("/");

    response.addCookie(cookie);

    filterChain.doFilter(request, response);
  }
}

By implementing this as a filter, we really let Spring Security do the work. We merely expose the generated token as a cookie, and that’s it.

Of course we must not forget to register this filter in the ApplicationSecurity class:

http.addFilterAfter(new CsrfTokenResponseCookieBindingFilter(), CsrfFilter.class);

And that’s that!

Server-side wrap up

With this we have described a pretty decent security solution for servers exposing REST APIs. We have Spring Security’s authentication, CORS configuration and an adequate CSRF attacks’ prevention. That’s a good start!

Remember though: one application does not resemble any other, and software security is constantly adapting to new attacks and vulnerabilities. So if you’re serious about security, keep your eyes open and visit OWASP’s website regularly.

Next post we’ll move to the other side (the web client) and see how the web client is supposed to handle these security aspects.

Until then… Cheers!

Leave A Comment

Please be polite. We appreciate that. Your email address will not be published and required fields are marked

This site uses Akismet to reduce spam. Learn how your comment data is processed.