Preventing CSRF with JSF 2.0

Markus Eisele
19
Have you ever had the need for higher security in one of your applications? Than you are probably familiar with the following topic. If not, I am going to tell you a little bit about attacks and web application security before we move over to implement a CSRF prevention approach with JSF 2.0.

What is Application Security? And why should I care?
Have you ever heard about attacks? Attacks are the techniques that attackers use to exploit the vulnerabilities in applications. That is not necessarily done by a real hacker but from nearly anybody with some kind of knowledge in the field of security and programming. There are a couple of basic principles your application should comply to. See OWASP Principles for more details. For each of the principles you could and should employ some prevention methods in your application.

What is a Cross-Site Request Forgery Attack?
Cross-Site Request Forgery (CSRF) is a type of attack that occurs when a malicious web site, email, blog, instant message, or program causes a user’s Web browser to perform an unwanted action on a trusted site for which the user is currently authenticated. The impact of a successful cross-site request forgery attack is limited to the capabilities exposed by the vulnerable application. For example, this attack could result in a transfer of funds, changing a password, or purchasing an item in the user's context. In affect, CSRF attacks are used by an attacker to make a target system perform a function (funds Transfer, form submission etc.) via the target's browser without knowledge of the target user, at least until the unauthorized function has been committed.
This attack can happen even if the user is logged into a Web site using strong encryption (HTTPS). Utilizing social engineering, an attacker will embed malicious HTML or JavaScript code into an email or Website to request a specific 'task url'. The task then executes with or without the user's knowledge. For more details please have a look at the owasp page.

How to prevent CSRF Attacks?
The only successful way of protection against CSRF attacks is to decide weather an issued user action is valid and allowed in the actual context. There basically are two ways to achieve this: You have to issue a secure random token and assign it to the requests issued by a client. This could be done by either assigning one token per HttpSession or, if you need an even higher level of security by issuing a token per request. If the token is send back from the client you have to check it's validity somehow and allow or reject the request.

Where to start? - Generating and placing the token
If you are looking at a standard JSF 2.0 application you have a couple of places where you can think about integrating the described solution. The following is only one approach focusing on the per HttpSession token, as this will be the sufficient one for most of the requirements. Let's start with the token generation and placement. We are in need of generating a unique token per HttpSession and placing it there. For developer convenience this should be done transparently and centralized. So you first need to implement your own CSRFSessionListener implementing the HttpSessionListener interface. Overwrite the sessionCreated method and place the token in the created session.

@Override
public void sessionCreated(HttpSessionEvent event) {
HttpSession session = event.getSession();
String randomId = generateRandomId();
session.setAttribute(CSRFTOKEN_NAME, randomId);
}

As you can see, there is no real magic here. The magic happens in the generateRandomId method. Again there are many ways to generate truly unique identifiers. Since Java SE 5 you have the handy UUID Class. This is a class that represents an immutable universally unique identifier (UUID) which represents a 128-bit value. The second option is to use SecureRandom and MessageDigest classes. Their use is far more expensive but you have a couple of options available which make your token a little bit more secure. You basically have to take the following steps:

// Generate a random string
SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
// inizialize a MessageDigest
MessageDigest sha = MessageDigest.getInstance("SHA-1");
// create a MessageDigest of the random number
byte[] randomDigest = sha.digest(new Integer(random.nextInt()).toString().getBytes());
// encode the byte[] into some textual representation
hexEncode(randomDigest)

For a more complete example please see this article about Generating unique IDs

And the JSF parts? - placing the token to a form
Up to know we didn't even use JSF. But we will. The idea is to have the token printed as a hidden textfield to every form. To prevent developers from additional coding, we should introduce a new CSRFForm component which does this for us. So the next step is to implement your CSRFForm which should extend the standard HtmlForm.

@Override
public void encodeBegin(FacesContext context) throws IOException {

// initialize the new TokenInput
CSRFTokenInput cSRFToken = new CSRFTokenInput();

// set the clientId
cSRFToken.setId(this.getClientId() + "_CSRFToken");

// add the component to the form
getChildren().add(cSRFToken);
super.encodeBegin(context);

As you can see, we also need the CSRFTokenInput class which extends UIComponentBase and represents our own hidden field.

@Override
public void encodeEnd(FacesContext context) throws IOException {

// get the session (don't create a new one!)
HttpSession session = (HttpSession) context.getExternalContext().getSession(false);

// get the token from the session
String token = (String) session.getAttribute(CSRFTOKEN_NAME);

// write the component html to the response
ResponseWriter responseWriter = context.getResponseWriter();
responseWriter.startElement("input", null);
responseWriter.writeAttribute("type", "hidden", null);
responseWriter.writeAttribute("name", (getClientId(context)), "clientId");
responseWriter.writeAttribute("value", token, "CSRFTOKEN_NAME");
responseWriter.endElement("input");

}

Perfect. Now we placed the token which was generated with the HttpSessionListener to the form. If you now register your components with the custom-taglib.xml you can now use the new form within your pages <cu:csrff>. Looking at the page source you will see, that the token has been placed.

<input type="hidden" name="j_idt7:j_idt7_CSRFToken" value="0c776040ff77d3af5acce4d4c59a51411eb960bd" />


... and checking the validity!
Fine. But what about checking the validity of the token. Spend a minute and add a decode() method to your CSRFTokenInput component.

public void decode(FacesContext context) {
// get the client id of the component
String clientId = getClientId(context);
// access the hidden input field value
ExternalContext external = context.getExternalContext();
Map requestMap = external.getRequestParameterMap();
String value = String.valueOf(requestMap.get(clientId));

// access the session and get the token
HttpSession session = (HttpSession) context.getExternalContext().getSession(false);
String token = (String) session.getAttribute(CSRFTOKEN_NAME);

// check if the token exists
if (value == null || "".equals(value)) {
throw new RuntimeException("CSRFToken is missing!");
}

// check the values for equality
if (!value.equalsIgnoreCase(token)) {
throw new RuntimeException("CSRFToken does not match!");
}

}

Done finally. We have the token in place, we can check it and this is done more or less transparently from the page designers. The only thing they have to take care of is the new form.

What's next?
It's quite drastic to throw a RuntimeException here. This is done to keep the example short. The JSF way of things would be to register and implement a converter that does the checks. But you probably know how to do this, so I skip this step here. Another part is also missing. You have to audit and alert the attack. But how to do this the right way in the context of your actual implementation is a post of it's own. Thanks for reading!

UPDATE: 01.03.11
Some more research and reading input for the fellow readers:
JSF does currently have such a token (the javax.faces.ViewState), which is
currently generated as a sequential token (easy to guess). A specification request to the EG was issued, implemented and backed out. Some more example code for the "Synchronizer Token" pattern (avoiding double submits) with JSF which will be targeted to JSF >=2.1

Post a Comment

19Comments

  1. A nice email asked me about why I did not simply replace/decorate the standard h:form component.
    Here is a simple answer: You seldom have the need for introducing this kind of prevention into any form in your application. You should focus on the application parts that are critical. In general one thing is most important for application security: Don't think about universal weapons! Know your vulnerabilities!

    ReplyDelete
  2. Without a proper view state, no action could be performed at all (at least for faces requests). Invalid requests will result in a ViewExpiredException. So there is no need to add another random id.

    Regards, Sven

    ReplyDelete
  3. I haven't had a chance to read this yet, but I wanted to mention that the soon-to-be-formed JSF 2.1 expert group has been talking about baking this functionality into the ViewState field which appears on every page. I believe Kito Mann is leading this effort.

    ReplyDelete
  4. Hi Sven, thanks for the comment. If you want to facilitate the ViewState for that, you have to use STATE_SAVING_METHOD client and encrypt it with a password. This is could lead to med CPU consumption and (if skipping compression) high bandwidth usage. Further on, each JSF impl tends to handle this different. So you have to check your solution against the needs and choose wisely. I presented a very simple but effective solution to the problem which works with any implementation.
    - Markus

    ReplyDelete
  5. Hi Ryan,
    thanks for the head-ups! I didn't know about that. I should try to follow the EG a little closer ;)
    Thanks,
    - Markus

    ReplyDelete
  6. As stated in http://en.wikipedia.org/wiki/XSRF
    On of the most important coutermeasures is "Referer validation". This will prevent attackers success by tricking the victim to click on a link in an email. In combination with a session secret I have not seen a successfull penetration yet.
    - Winfried

    ReplyDelete
  7. hi, I tried to configure the last version of CSRF Guard (http://www.owasp.org/index.php/Category:OWASP_CSRFGuard_Project) with JSF but i couldn't!
    Now i would like to try your solution, im using jsf 1. How should i declarate the new tag ?
    Thanks!
    Sebastian

    ReplyDelete
  8. Hi Sebastian,

    thanks for asking. I also had troubles setting up the CSRF Guard project. It's not build for JSF ;)
    There are a couple of good documentations available e.g.
    http://www.theserverside.com/news/1364786/Building-Custom-JSF-UI-Components

    ReplyDelete
  9. Thanks, i found the same link that you posted, great article!
    Well, now i have the tag in my jsp but the HttpSessionListener method is not being called!

    I created a class implementing HttpSessionListener and i declared it in web.xml using tag.
    Maybe im doing something wrong, when you wrote "@Override
    public void sessionCreated(HttpSessionEvent event)" is an error? @Override isn't appropriate for interfaces, i should extend some class and override a method?

    thanks
    Sebastian

    ReplyDelete
  10. A very nice article. It is nice to see all of the comments. It proves that this is an important issue, and I like the JSF solution.

    Like you I need to pay more attention to the JSF EG.

    ReplyDelete
  11. i tried to implement ur simplified approach. But once the CSRFForm is in templates, it fails to display SpringSecurity & ajax validations. This validations are bypassed allowing empty fields to be saved, which inturn throws DataAccessexceptions

    ReplyDelete
  12. Hi Marjan,

    working with AJAX needs a little more. you have to provide the hidden token field via the execute attribute, too. Typically this is set to @this.
    Didn't try SpringSecurity integration.

    Thanks,
    M

    ReplyDelete
  13. This comment has been removed by the author.

    ReplyDelete
  14. Marjan,

    sorry. No chance without looking at the code. Sorry.

    M

    ReplyDelete
  15. The code is an implementation of ur method. i can send the whole package,if an email is given

    ReplyDelete
  16. Hi, could you please guide me how to redirect a user to an error page if CSRF Token is not matching, I tried to use

    context.getExternalContext().redirect("error.jsp");
    context.responseComplete();

    in the decode method but getting IvalidState Exceptions.

    ReplyDelete
  17. This comment has been removed by the author.

    ReplyDelete
  18. This comment has been removed by a blog administrator.

    ReplyDelete
Post a Comment