Note: This article doesn’t contain anything new or ground breaking. This is the stuff that you should know already because it’s been written about before by people smarter than me. Tragically, despite lots of great material on subject people keep messing it up.

So, cross site request forgeries are a pretty common topic these days; they’re in almost every security talk, book, site etc. This is okay; they’re important (but I wish people would concentrate on security as a whole rather than just worrying about problems with nifty acronyms). Most of the sites, and all of the books I’ve read demonstrate things correctly, but when it comes to actual implementation, time and time again, I see code that’s just wrong. This CSRF Demonstration page will be used throughout this post.

In order to effectively use the common transparent defense against CSRF attacks you need to generate an unpredictable token, and confirm its presence in both the session and form submission upon receipt. Only two essentials there, but I’ve seen lots of live code that fails on at least one of the two.


So, the point of CSRF is to confirm that the CSRF token in the session is equal to the one received with the form post. This is critical and easy, though people seem to manage to screw it up. They write code that looks remarkably like this:
if ($_POST['csrf'] == $_SESSION['csrf'])

Do you see the fatal flaw? If both the session and post variable are empty, they’re also equal. So if you’re attacking the form you simply omit the post variable. I demonstrate this attack on my negligence csrf attack page. Not only do you need to ensure they’re equal, you also need to ensure that they’re both set and non-empty.

Note I’ve left the warnings in on purpose. I could have turned off display errors, or suppressed them, but I really wanted to show what was happening.

Unpredictable Token.

This part should also be easy; generate a token that the attack can’t guess. I haven’t actually said random here, though random is good. The important part is that the attacker can’t guess it. I have a piece of information for you that shouldn’t be news, but it might be. Time isn’t random. Time increases by one, once a second, every second. MD5 also isn’t random. Thanks to the snowball effect the hash of very similar values are actually very different, but they are in no way random. Now combining these two non-random values also happens to give you a non-random result. Yet, time and time again I see code exactly like this:
$csrfToken = md5(time());

This is idiotic. Throwing a hash function at the problem doesn’t solve anything. It’s obfuscation, and weak obfuscation at that.

Now I’ve mentioned this at talks before, and the audience has politely nodded, then privately told me that it doesn’t actually matter. It does, so take a look at the md5 time attack page.

Now adding a little bit of salt also doesn’t solve the problem, it’s just more obfuscation.

If you’re interested you can also see the source code for my weak csrf page.

Stefan Esser’s talk at ZendCon Lesser Known Security Problems in PHP discusses a few other issues related to sessions you may want to take a look at.

So please go home and fix your CSRF pages.


Comments »

Stop Messing up CSRF Protection by Paul Reinheimer
The author of the website wrote a nice article on cross site request forgeries (CSRF), what to watch out for when implementing such security measure. You may want to have a look at his post at:
Weblog: Server-Side Magazine
Tracked: Nov 09, 08:28
"Not only do you need to ensure they're equal, you also need to ensure that they’re both set and non-empty."

$_SESSION['token'] should always be initialized when you start a session. Verifying this is a good practice (defense in depth), but it doesn't replace the need for initialization.

Stated differently, this is a safeguard that is best addressed at the source. Assuming a non-zero chance of failure every time you implement a safeguard, one that can be implemented once has a lower risk of failure. The practice you suggest should be considered defense in depth, not the primary safeguard.

Hope this helps.
#1 Chris Shiflett (Homepage) on 2008-11-09 02:27 (Reply)

I feel that there's a lot of theory on CSRF out there, I believe its important to also highlight how to do things right vs. what not to do..

How do you deal with multiple browsers open in a single session? how do you deal with the browser back-button? As long as there's no good guides out there, we'll see a lot of people repeating mistakes.
#2 Evert (Homepage) on 2008-11-09 03:45 (Reply)

Hi Paul,

In the section on the unpredictable token, you've told folks how what they are doing is wrong, and then told them to go and fix their code. Feels like you missed out an important middle bit - how to fix their code and generate a truly decent random token :-)

Best regards,
#3 Stuart Herbert (Homepage) on 2008-11-09 09:02 (Reply)

I tend to use digital signatures. If the form has any hidden inputs sign them, along with perhaps the time sometimes client ip address, with hash_hmac().

That way don't need to use $_SESSION, gives me a random token, which can also add another layer of validation to the hidden input values.
#4 Jared Williams on 2008-11-09 13:17 (Reply)

random should obviously read unpredictable.
#5 Jared Williams on 2008-11-09 13:29 (Reply)

Hi Mate,

Just because I lost all of that time doesn't mean you should have to.
You Have Just Discovered The Fastest Way To Dramatically Accelerate Your Internet Business.
Stop wasting time & money trying to teach yourself. Act now...

The Best Part about this is that this Powerful Technology is Absolutely Free.

You can find out more by visiting SpiderAdd Marketing System Website.
All you have to do is to Grab the Username and Password,
login and take advantage of all the amazing Features!

Give it a shot to your success
#6 Isombocavioma on 2008-11-10 01:45 (Reply)

Thanks for the advice? Is this rapid internet business built on spam? Because spam is tasty.
#7 Paul Reinheimer (Homepage) on 2008-11-10 03:24 (Reply)

Would it be a problem to use the session ID as the token?

if ($_POST['csrf'] == session_id()) ...
#8 Michael on 2008-11-19 20:03 (Reply)

"if ($_POST['csrf'] == $_SESSION['csrf'])

Do you see the fatal flaw? If both the session and post variable are empty, they’re also equal."

Maybe I'm missing something but how is $_SESSION['csrf'] going to be empty?
#9 Simon on 2010-02-01 23:58 (Reply)

If the user's first request against the site is the page checking the session variable it will be uninitialized/empty.
#10 Paul Reinheimer (Homepage) on 2010-02-11 22:00 (Reply)

Enclosing asterisks marks text as bold (*word*), underscore are made via _word_.
Standard emoticons like :-) and ;-) are converted to images.
Submitted comments will be subject to moderation before being displayed.

Hi, I’m Paul Reinheimer, a developer working on the web.

I co-founded WonderProxy which provides access to over 200 proxies around the world to enable testing of geoip sensitive applications. We've since expanded to offer more granular tooling through Where's it Up

My hobbies are cycling, photography, travel, and engaging Allison Moore in intelligent discourse. I frequently write about PHP and other related technologies.