In the previous blog, I’ve shown you a tip of the CSRF iceberg and a simple prevention mechanism. In this blog, I’ll elaborate a bit more on the implementation of a successful CSRF prevention mechanism.
Ad-hoc token per form implementation
As stated, the solution is simple, just include a hidden field in every form, store the same value in the session and compare the two when the form is submitted.
Although working, this fix has some disadvantages.
- First, security is not turned-on by default. Every developer needs to be aware of the concept of CSRF and may not forget it when adding new forms. This also applies to developers that have to maintain the website in the future. They may not even know CSRF in the first place and just remove ‘that silly token’ from the form.
- Second, it’s probably not the most elegant solution from a Separation of Concerns perspective. Now, every form needs to be aware of security and you need to perform a check in your side controller logic.
So let’s elaborate on some more mature solutions.
What are the variables?
Like always in computer science, there is no single, one-size-fits-all, perfect solution. It always depends on the context. Well, let’s give it a bit of context!
-
Token freshness
How often is a new token generated? In other words, what’s the time-to-live of a token?
-
Token quality
How random should the token be? What algorithm should you use?
-
Token storage
You’ll need to store the token in two places to compare them. First, the HTML where your forms and links reside. And somewhere else…
-
Back buttons and bookmarking
How to deal with users pressing the back button and seeing a cached web page which they submit? How to deal with bookmarks? Also, you can have Single Sign On, paving the way for deep linking into your system without re-authentication.
-
Ajax
When using Ajax, many server side requests may occur before a full page refresh happens (if any). What to do with token management?
-
Punishment
What to do with hack attempts? Do you log an error and short-circuit the server side processing? Or do you log the user off?
-
Logging
How do you log the event? What kind of information do you include in the logging?
As you can see, there are many variables and all of them may impact your implementation. Most of them even influences each other. In the next section, I’ll discuss all of them in isolation.
Token freshness
This is a major one. A choice needs to be made here. Do you generate one token when the user logs in or do you re-generate a new token each time an action is made?
While re-generating a new token with every request may be very secure, it’s not the most practical solution in reality. In practice, it doesn’t even add that much security, because the Same Origin Policy takes care of the integrity of the token. I also wouldn’t worry about brute force attacks for the token, since the user will probably be logged off long before the attacker guesses right. But when we throw back buttons, Ajax, double-clicking users, etc. into the mix, changing the token during a session has serious disadvantages.
So, unless you have really really really weak token (which will never happen unless you pick an integer between 0 and 1), I would generate the token once (@login) and reuse that value.
Token quality
First of all, we’re not talking about high end encryption stuff here. The only use of the token is turning the URL into a “random” URL. So simple tokens will do.
Important to note is that the attacker will probably never see any token, unless he logs into the system (the token is only used for secure pages). So we need something between an incremental counter and some heavy duty 1024 bit encryption.
I would say, keep it simple. Just use a Random generator or a simple AES key, if it makes your security department happy. When using something like AES, remember to encode it using Base64. It only needs to be one way encryption, since you can compare the encrypted values.
This is a simple AES token generator which also encodes the token using Base64 so it can be used in the HTML.
import java.util.Random;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import org.apache.commons.codec.binary.Base64;
public class IntegrityTokenGenerator {
public static void main(String[] args) {
IntegrityTokenGenerator integrityTokenGenerator = new IntegrityTokenGenerator();
System.out.println(integrityTokenGenerator.generateToken());
System.out.println(integrityTokenGenerator.generateToken());
System.out.println(integrityTokenGenerator.generateToken());
}
private static final String ENC_TYPE = "AES";
private final SecretKey key;
private final Cipher cipher;
private final Random random;
public IntegrityTokenGenerator() {
try {
KeyGenerator keyGen = KeyGenerator.getInstance(ENC_TYPE);
key = keyGen.generateKey();
cipher = Cipher.getInstance(ENC_TYPE);
cipher.init(Cipher.ENCRYPT_MODE, key);
random = new Random();
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("Error generating integrity token");
}
}
public String generateToken() {
try {
String tokenString = "" + System.nanoTime() + "" + random.nextInt();
byte[] tokenBytes = tokenString.getBytes("UTF-8");
byte[] encoded = cipher.doFinal(tokenBytes);
return new String(Base64.encodeBase64(encoded));
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("Error generating integrity token");
}
}
}
I used AES in this example, but you can use anything, like Blowfish. Performance is not an issue, because of the small token length. I’m talking about less than milliseconds. Also, encryption strength is not very important, because you only need a simple token. Remember to base64 encode binary tokens into plain text.
Also be sure to use a random value each time when you generate a token. This way, the token sequence becomes less predictable. To make it a bit more secure, include some personal data in the token, like a customer ID. The combination of data make the key even less likely to guess. Don’t put any confidential data in the token. It doesn’t add any security and only increases the risk of information leakage.
And make sure not to throw too specific exceptions. Just log them and throw a general exception. Details may never end up in the wrong hands!
One more note about the snippet. A big advantage of this approach is that you don’t have any key management. Keys may leak and key management puts another burden on the system administrators. This implementation generates a key once (when the class is instantiated) and reuses it. Java takes care of the details.
Token storage
There are many ways to keep track of the current token for the currently logged in user, but the HttpSession and Cookies are the most common. Cookies decrease the amount of state your server must manage, but for some reason, developers think cookies are unsafe.
When used for storing CSRF prevention tokens, cookies can be safe. Why? Because an attacker cannot see the contents of the cookies. It’s the browser that appends the cookies to an HTTP request, but the attacker cannot see them. A mechanism like HttpOnly makes cookies more secure, because it prevents scripts to access them. But storing security tokens on the client is inherently unsafe and is probably the prime reason not to use cookies to store the tokens. You CAN do it, but you must be very careful AND rely on the browser to do the right thing. For example, Firefox ignores HttpOnly, causing cookies to be quite unsafe.
But why not store the token in an HttpSession attribute? Generate it once, put it in the session and then compare this token with the submitted token. It has a little bit of overhead, but that will probably be nothing compared with the rest of the session data.
I prefer the HttpSession over cookies by a mile length. With session attributes, I don’t have to think about token confidentiality and with cookies I do. Easy choice.
Back buttons and bookmarking
For some reason, clients want us to build systems in web browsers that are not specifically designed for such a purpose. But it’s a fact of life, so we need to deal with it. Browsers have back/forward buttons, refresh buttons and bookmarks and allow users to navigate freely by using the address bar. If you want your users to be able to use these features, here are some notes.
Only check the CSRF prevention token on state changing actions. Users often don’t bookmark actions, but they bookmark pages. When you only check the CSRF token when the form is submitted, the user can use a bookmark to navigate to the form. The server then renders a token into the form. The user submits the form and it works. You just need to give the server a chance to render a form before the user triggers an action. Using post-redirect-get makes it even more robust, because it prevents users to bookmark the POST request.
Also, don’t re-generate tokens with each request. When you want to support back buttons, you’ll have to be aware of the possibility that the rendered page may be a cached (stale) page which contains an outdated token. So, use the same token for the duration of the session.
You might also want to consider storing the token per user in a persistent store, so that when the same user navigates to the site (after a time of inactivity, maybe days or weeks) he gets the same token, thus fixing possible bookmarking problems.
If you implement your own home brewn CSRF prevention mechanism you can redirect the user to a proper page when the integrity token is absent or invalid. This way, the user doesn’t see some strange error but a proper page instead.
Ajax
When securing your website, you also need to secure your Ajax actions. After all, an Ajax request is just the same (from the server perspective) as another request. So you need to include CSRF tokens in the Ajax requests as well.
Luckily, with most JavaScript libraries, this is easy, since they provide a generic abstraction for Ajax requests and often provide a way to append generic parameters to a request.
The implementation is really easy, just render the CSRF token to a script variable and append it to all requests, like shown below.
var globalIntegrityToken = '<%=(String)session.getAttribute("integrityToken")%>';
Yeah I know that scriptlets are ugly, but I don’t care in this example. You can use EL or whatever you like.
Appending the token to the URL is easy, for example using JQuery:
$.ajax({
type: 'POST',
url: '/changeEmail.do',
data: {
newEmail: $('emailaddress').val(),
integrityToken: globalIntegrityToken
}
});
Again, reusing the same token for a longer period of time makes your job a lot easier. Otherwise, if you re-generate the token per request, you need a way to update all tokens that exist in the DOM when the request is completed. This has performance impact on the client and makes the code a lot more complex. You’ll have to make sure you update all tokens, which include:
- The global variable, which is easy to update in a generic way
- All hidden fields that contain a token. If you can easily find the fields, this should also be easy
- Any tokens in querystrings. This may be a bigger challenge and you may end up using regular expressions to parse and replace the querystrings
So, when you reuse the same token for the duration of the session (or longer) Ajax will probably not be difficult.
Punishment
What do you do when you see someone fiddling with the CSRF prevention token? First of all, don’t proceed with the action the user invokes. But do you log the user off?
If you can, I would suggest making a switch to turn the logoff mechanism on and off. Turning it off makes it easier to test, but you need to turn it on in production. If you’re afraid that you (or the sysadmin) forgets to turn the mechanism on, you can skip this switch.
Just be sure your prevention mechanism doesn’t give false positives, because users will be very annoyed if they are logged off often.
Logging the user off has some other benefit. If there is a hacker messing around with your website, users will get logged off very often and complaints will come in often at your help desk. This way, you have the opportunity to detect hackers. If you let the user proceed working without notification, you will never know that something happened. Unless your sysadmins proactively scan the log files, but unfortunately I don’t see sysadmins do these kind of things very often…
Logging
How do you log the event? I would suggest creating a separate security log file so that sysadmins can react to hack attempts. It’s also useful as a proof in a lawsuit. I would log as much as possible. You can think about:
- Date/time of course and also the timezone.
- The entire HTTP request, with cookies, referrer, user agent, etc.
- The source IP address. However it may be spoofed or a proxy, at least you can see that there is a difference with the “real” user’s IP.
- The invoked action, along with it’s parameters.
- Any server side state, specific to your system.
It might be a good idea to also log the first two items when the user authenticates so you can compare the two values.
To summarize
In most cases, you’ll want to use a static CSRF prevention token for the duration of the session. It makes Ajax implementations easier and gives browser features like back buttons and bookmarks a more natural behavior. When you do, implementing a working CSRF prevention mechanism may not be a big issue anymore.
The other variables don’t have this much impact, but be sure to pick a good combination, because some variable combinations may harm each other.