Over a million developers have joined DZone.

Preventing CSRF with JSF 2.0

DZone 's Guide to

Preventing CSRF with JSF 2.0

· Java Zone ·
Free Resource

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.

@Overridepublic 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 stringSecureRandom random = SecureRandom.getInstance("SHA1PRNG");// inizialize a MessageDigestMessageDigest sha = MessageDigest.getInstance("SHA-1");// create a MessageDigest of the random numberbyte[] randomDigest = sha.digest(new Integer(random.nextInt()).toString().getBytes());// encode the byte[] into some textual representationhexEncode(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.

@Overridepublic void encodeBegin(FacesContext context) throws IOException {// initialize the new TokenInputCSRFTokenInput cSRFToken = new CSRFTokenInput();// set the clientIdcSRFToken.setId(this.getClientId() + "_CSRFToken");// add the component to the formgetChildren().add(cSRFToken);super.encodeBegin(context);As you can see, we also need the CSRFTokenInput class which extends UIComponentBase and represents our own hidden field.@Overridepublic 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 sessionString token = (String) session.getAttribute(CSRFTOKEN_NAME);// write the component html to the responseResponseWriter 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 componentString clientId = getClientId(context);// access the hidden input field valueExternalContext external = context.getExternalContext();Map requestMap = external.getRequestParameterMap();String value = String.valueOf(requestMap.get(clientId));// access the session and get the tokenHttpSession session = (HttpSession) context.getExternalContext().getSession(false);String token = (String) session.getAttribute(CSRFTOKEN_NAME);// check if the token existsif (value == null || "".equals(value)) {throw new RuntimeException("CSRFToken is missing!");}// check the values for equalityif (!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!


From http://blog.eisele.net/2011/02/preventing-csrf-with-jsf-20.html


Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}