About the Author

Chris Shiflett

Hi, I’m Chris: entrepreneur, community leader, husband, and father. I live and work in Boulder, CO.

Cross-Site Request Forgeries

  • Published in PHP Architect on 13 Dec 2004
  • Last Updated 13 Dec 2004

Welcome to another edition of Security Corner. This month's topic is cross-site request forgeries, an attack vector that enables an attacker to send arbitrary HTTP requests from a victim user. That's worth reading a couple of times, and it will likely not be until you've seen your first example attack that you can fully understand or appreciate the danger.

The typical scenario involves a victim that has an established level of privilege with the target site, and this allows an attacker to initiate unauthorized actions.

This article introduces cross-site request forgeries (CSRF, pronounced "sea surf") and provides a few simple steps to help prevent these types of attacks in your own applications.

Where Is the Trust?

CSRF attacks exploit the trust that a site has for a particular user. The site is the target of the attack, and the user is both the victim and an unknowing accomplice.

Because the victim sends the request (not the attacker), it can be very difficult to determine that the request represents a CSRF attack. In fact, if you have not taken specific steps to mitigate the risk of CSRF attacks, your applications are most likely vulnerable.

When developing an application, challenging tasks include authentication, identification, and authorization. Assuming that you have hypothetically achieved maximum security regarding these tasks, a CSRF attack can still be successful, because it allows an attacker to bypass traditional safeguards.

Sample Application

Every good example needs a sample application, and this article uses one that allows users to buy stocks. So that the actual buying process doesn't complicate the various examples, I use a hypothetical function called buy_stocks(). For my security conscious readers, you can assume that this function contains sufficient input filtering. The form handling is the important part for this discussion.

The interface that allows users to buy stocks includes an HTML form:

<form action="buy.php" method="POST">
<p>Symbol: <input type="text" name="symbol" /></p>
<p>Shares: <input type="text" name="shares" /></p>
<p><input type="submit" value="Buy" /></p>

This form allows the user to specify the stock symbol and the number of shares. It sends this information to buy.php:

if (isset($_REQUEST['symbol'] &&

Keep this application in mind as you continue reading.

Example Exploit

The simplest example exploit uses an image. To understand the exploit, it is first necessary to understand how a browser requests images. Consider a very simple page:

<p>Here is my sample image:
<img src="http://example.org/example.png" /></p>

When a browser requests this page, it cannot know that the page has an image. The browser only realizes this once it parses the HTML within the response. It is at this point that the browser requests the image, and it uses a standard GET request to do so. This is the important characteristic. It is impossible for the target site to distinguish between a request for an image and a request for any other resource.

When requesting an image, some browsers alter the value of the Accept header to give a higher priority to image types. Resist the urge to rely upon this behavior for protection - I show you a more reliable approach at the end of this article.

Now, imagine that the image in a page is the following:

<img src="http://example.org/buy.php?symbol=SCOX&shares=1000" />

Every user that visits this page sends a request to example.org just as if the user clicked a link to the same URL. Because the sample application uses $_REQUEST instead of the more specific $_POST, it cannot distinguish between data sent in the URL from data provided in the proper HTML form.

This is an intentional mistake that I want to highlight. Using $_REQUEST unnecessarily increases your risk. In addition, if you perform an action (such as buying stocks) as a result of a GET request, you are violating the HTTP specification. Section 9.1.1 of RFC 2616 states the following:

In particular, the convention has been established that the GET and HEAD methods SHOULD NOT have the significance of taking an action other than retrieval. These methods ought to be considered "safe". This allows user agents to represent other methods, such as POST, PUT and DELETE, in a special way, so that the user is made aware of the fact that a possibly unsafe action is being requested.

POST requests can also be forged, so do not consider a strict use of $_POST to be sufficient protection.

Safeguarding Against CSRF

There are a few steps you can take to mitigate the risk of CSRF attacks. Minor steps include using POST rather than GET in HTML forms that perform actions, using $_POST instead of $_REQUEST, and requiring verification for critical actions (convenience typically increases risk, and it's up to you to decide the appropriate balance).

The most important thing you can do is to try to force the use of your own forms. If a user sends a request that looks like it is the result of a form submission, doesn't it make sense to be a little suspicious if the user has not recently requested the form?

Consider the following replacement for the HTML form in the sample application:

$token = md5(uniqid(rand(), TRUE));
$_SESSION['token'] = $token;
$_SESSION['token_time'] = time();
<form action="buy.php" method="post">
<input type="hidden" name="token" value="<?php echo $token; ?>" />
Symbol: <input type="text" name="symbol" /><br />
Shares: <input type="text" name="shares" /><br />
<input type="submit" value="Buy" />

Because this form does not represent an entire script, I do not include the call to session_start(). You can safely assume that this required step takes place prior to the form.

With this simple modification, a CSRF attack must include a valid token (anti-CSRF token) in order to perfectly mimic the form submission. Because you store the user's token in the session, it is also necessary that the attacker uses the token unique to the victim. This effectively limits any attack to a single user, and it requires the attacker to obtain a valid token for another user (obtaining your own token is useless when it comes to forging requests from someone else).

This token should be initialized just like any other session variable:

if (!isset($_SESSION['token']))
    $_SESSION['token'] = md5(uniqid(rand(), TRUE));

The token can be checked with a simple conditional statement:

if ($_POST['token'] == $_SESSION['token'])
    /* Valid Token */

The validity of the token can also be limited to a small window of time, such as five minutes:

$token_age = time() - $_SESSION['token_time'];
if ($token_age <= 300)
    /* Less than five minutes has passed. */

Until Next Time...

CSRF attacks are very dangerous, and most applications that do not take specific steps to prevent CSRF attacks are vulnerable. Because the requests originate from the victim, it is possible for an attacker to target sites that only the victim can access, such as ones on a local network.

If you use a token in all of your forms as I have suggested, you can eliminate CSRF from your list of concerns. While no safeguard can be considered absolute (an attacker can theoretically guess a valid token), this approach mitigates the majority of the risk. Until next month, be safe.

About this article

Cross-Site Request Forgeries was last updated on 13 Dec 2004. Follow me on Twitter.


1.kendall wrote:

Well, I consider myself a newbie at this stuff so I apologize if these are uninformed questions with obvious answers!

I guess I'm trying to understand why these attacks are so dangerous probably because I don't see how a user's browser runs somebody else's modified HTML code (perhaps a link from a malicious email?). Maybe I just don't understand the full context of how this works... I can see that the implementation of a solution is fairly straightforward (token exists? yours? nope - 'token expired.. reload please.') but I want to really know why I need to do this. :)

And, just wanted to say I've been reading your site for awhile and truly appreciate the articles you've posted. Much of my learning about security for the web has come from you. Thanks!

Tue, 11 Oct 2005 at 15:23:49 GMT Link

2.saberworks wrote:


I think he is saying this: If you are logged into amazon.com, and then you visit some other page which happens to contain one of those malicious image links (initiating a purchase through amazon.com for this case), the "attacker" can initiate a purchase on your account. Of course, you'll get a confirmation email message and the item would be addressed to you - but consider whether they have also figured out how to forge the submission of an address change form as well.

It's an interesting theory, but I'm curious how they can forge a post request as an embedded image? In this case, it would have to be done with javascript or something.

It's an interesting angle but I think the ramifications aren't huge. It affects one person at a time, and it only allows actions that are pre-defined anyway.

The idea of form submission tokens is interesting. I don't like the idea of time-limiting forms though.

Thu, 13 Oct 2005 at 15:26:22 GMT Link

3.Chris Shiflett wrote:

I'd like to clarify a few points.

1. Yes, the attack is that you (the victim) send HTTP requests of an attacker's choosing. The attacks can go completely unnoticed.

2. You're right that you can't forge a POST request with an embedded image. However, there are many JavaScript techniques to do so. CSRF is a label for many techniques. Using an image is just an example.

3. The ramifications are huge. Just this morning, it is being reported that CSRF played a part in this "friendly" but revealing hack:


4. CSRF attacks are not limited to one person at a time. Only when you use a token in your forms can you limit the scope of the attack. In fact, most CSRF attacks rely on heavy traffic.

5. Regarding timeouts, I'm not suggesting that a user's form data to that point be discarded. Although it's an implementation detail that I don't discuss, there are elegant ways of handling suspicious activity. If you prompt the user for a password, that's usually enough to prevent attacks without affecting your legitimate users. It's up to you to make sure that you don't lose their form data in the process.

Hope that clears a few things up. Thanks for the comments. :-)

Thu, 13 Oct 2005 at 15:44:24 GMT Link

4.Alek Andreev wrote:

That's stupid! The whole $_REQUEST being less secure than $_POST argument is bogus. The <img> tag is just as easy to write as a hidden form that autosubmits itself to a hidden iframe or something. Or you could do it with AJAX... Obscurity is not security. There's no point for $_SESSION['token'] or the "token" form variable, as the session ID is a token in itself. If you open the same page in two windows your tokens are going to get all messed up. And you can set the session expiration to something low, like 5 minutes. All you have to do is set $_SESSION['authenticated'] to true and you are all set. PHP is going to take care of the rest for you.

Reinventing the wheel is so 1999.

Thu, 13 Oct 2005 at 21:47:01 GMT Link

5.Chris Shiflett wrote:

Alek writes:

The whole $_REQUEST being less secure than $_POST argument is bogus.

No, it's not. You really want to argue that lowering the barrier of entry has no affect? I think you'll be hard-pressed to find anyone who agrees with you. That being said, I explicitly state that POST requests can also be forged.

There's no point for $_SESSION['token'] or the "token" form variable, as the session ID is a token in itself.

You should really try this yourself, because it's clear you don't understand the nature of the attack. Using a token in the form requires the attacker to use the correct token. The session identifier is automatically provided in a cookie, so no action is required. In other words, your suggested "protection" presents no obstacle to an attacker. If it did, CSRF would not exist. Here is proof that it does:


Hope that helps.

Thu, 13 Oct 2005 at 21:55:51 GMT Link

6.Örjan Persson wrote:

Good work, this helped me to raise some concerns!

If you also unset the session variable when it have been filled it's purpose, you will also have a cheap protection against double posting / attack.

Fri, 14 Oct 2005 at 09:24:20 GMT Link

7.Stéphane Boisvert wrote:

I must comment on you reply to &quot;Alek Andreev&quot; (the that's stupid! one)

I must say it is admirable that you are very professional in dealing with someone who may not understand fully the ramifications of this.

Although I do agree somewhat with his Request/Post it is easier to write a Get than a Post but if someone is looking for a way in its definitely not a safe barrier.

But his session thing lacks of thought. i don't not think he understand that the token is only to be used for that form and a different token for every form.

I do have one real question concerning the tokens. Would it not be possible to read the token and submit it with the attack? This would mean that the XSS whole would be on your form page and then grab the value and post it via javascript with our evil values?

Also would requiring confirmation add any safety? Ie after a request is taken you report another page with another token and ask for confirmation? or would this be as penetrable as the above example of using Javascript to receive the information sent back find the token and confirm?

Fri, 14 Oct 2005 at 13:41:46 GMT Link

8.Chris Shiflett wrote:

Stéphane writes:

I do agree somewhat with his Request/Post it is easier to write a Get than a Post but if someone is looking for a way in its definitly not a safe barrier.

I agree with that as well. :-) The article states:

POST requests can also be forged, so do not consider a strict use of $_POST to be sufficient protection.

The only point the article makes is that using $_REQUEST increases your risk. It's similar to enabling register_globals. By itself, register_globals does not create a vulnerability, but it makes one more likely.

Would it not be possible to read the token and submit it with the attack?

Whose token are you reading? :-) Keep in mind that for this to be successful, you would have to be able to read someone else's (the victim's) token.

One way to do this is with XMLHttpRequest - you inject some JavaScript that first requests the page with the form (from the victim's browser), parses the results to find the victim's token, then submits the forged request (valid token included). This attack is limited by the inherent limitations in XMLHttpRequest - only requests from the same domain are allowed, etc. For most, this means that this scenario is impossible, but if you have a XSS vulnerability, that all changes.

Also would requiring confirmation add any safety?

Yes, anything that raises the bar or makes things more difficult for an attacker increases security, even if the increase is slight. Just remember that safeguards become less valuable and more expensive as they are accrued. If you get overzealous, you might unnecessarily complicate your application and make it more error-prone.

Hope that helps.

Fri, 14 Oct 2005 at 13:58:40 GMT Link

9.Ivo Jansch wrote:

The proposed solution incurs a 'single window' problem. If you are a user that browses a site in multiple windows, all forms but the one in the last window will fail as the last window will overwrite the previous token. Of course, this can be countered too, but is a lot harder as you would need multiwindow detection.

Sun, 23 Oct 2005 at 07:47:27 GMT Link

10.wesley wrote:

Can't you just do the following:

when the user logs in, set his unique token in the session:

$_SESSION['token'] = 'blabla';

use this token for every form. Do not reset the token after an action but use the same token during the entire visit.

Seems good to me. I don't think there's a reason why you should limit the token to 5 minutes or so.

Thu, 27 Oct 2005 at 09:17:30 GMT Link

11.Tomek wrote:

I agree with wesley - to my understanding one token per session is secure enough.

Yet it is possible to stick to Chris' solution and not get into 'single window' problem. Just give form unique_id too:

$token      = md5(uniqid(rand(), TRUE));
$form_id   = md5(uniqid(rand(), TRUE));
$_SESSION[$form_id]['token']  = $token;

And in form:

<input type="hidden" name="token" value="<?php echo $token; ?>">
<input type="hidden" name="id_form" value="<?php echo $form_id; ?>">

Then we can validate it this way:

if ($_SESSION[$_POST['id_form']]['token'] == $_POST['token']) {
 //token ok

Thu, 27 Oct 2005 at 11:53:50 GMT Link

12.Chris Shiflett wrote:

Wesley writes:

Do not reset the token after an action but use the same token during the entire visit.

Yes, many developers actually use an auth token for this, because they already have one associated with each user to strengthen their session mechanism. This still offers strong protection against CSRF, and there's very little additional work required.

Using the same token also takes care of Ivo's concern, although another option is to allow different tokens for different forms instead of having only one valid token per user.

Thanks for the comments.

Fri, 28 Oct 2005 at 06:32:20 GMT Link

13.Julio César Carrascal wrote:

What happens if I:

1. add a hidden iframe that loads your sample form

2. fill the fields using JavaScript (frame.document.forms["blah"].field.value = 'MSFT' or something)

3. submit the form: frame.document.forms["blah"].submit()

I think it'll get a valid token and the attack will be executed.

Fri, 28 Oct 2005 at 16:48:46 GMT Link

14.matt wrote:

Does anyone know of any hacks that can get you the username and password of a myspace user without using a keylogger?

Sun, 30 Oct 2005 at 19:11:31 GMT Link

15.Travis wrote:

In experimenting with this solution, I found one issue. As explained, the unique hash is generated, stored to session, and is output as a hidden form field for each page load. The problem I encountered is that if someone loads the form, submits the form, then, resubmits the form (via F5 - i.e., a repost), it then is re-sending the PREVIOUS hash from the hidden form field, which of course won't match the most recent hash that was just stored to session. Any suggestions on how to get around this issue? Although I'd like to believe that users will always click my "Submit" form button, it's just not the case. Thanks!

Thu, 04 May 2006 at 19:22:09 GMT Link

16.Matthieu wrote:


In most case you should not allow your user to re-submit a form. What kind of action is your form triggering ? In the case of a BB, it would send the message twice for example. Not good ...

I came across the same problem as the multi-window browsers using Ajax as it's producing asynchronous form submissions. Chris's solutions to generate a token by form is perfect if as he said, you allow more than one token per session. Simply create your token as explained and store it in an array in the session:


# generate your token
$_SESSION['validTokens'][] = '561asd51asd5';
#validate your token
if (in_array($_POST['token'], $_SESSION['validTokens'])) {
    # do your action
    # delete your token

Mon, 08 May 2006 at 18:28:00 GMT Link

17.Travis wrote:

The key thing is that the action is not happening twice...I'm referring to a situation where the user posts the form, but the server prevents the "end goal action" from happening due to invalid input. In the case of a bulletin board post, the message never would have been posted the first time.

Here is a scenario to help explain my situation:

1) A customer loads a form that is used to register for a new account on an e-com web site. The customer enters their email address & desired password for their new account.

2) The customer submits the form to PHP_SELF (HTTP POST)

3) The PHP page checks to make sure the email address isn't already in use by another user and determines that it IS in use by someone else. A flag variable is set so that the page knows to show an error message about the situation... the key thing here is that we're on the same .php page the whole time.

4) The customer remains on the form page, they see the error message informing them that the email address is already in use. Also, all of the data they entered (except their password perhaps) are still populated into the form so they don't have to re-enter it.

At this point, a customer in a perfect world would change their email and would click "Submit" again. But, some users might reload the page for whatever reason (I assume users will do random things), which will popup the typical "are you sure you want to repost" dialog box.

My point is that if the situation illustrated above happens, the token they are reposting is from the original page load -- i.e., it is no longer the one in session, since a new token would have just been generated after the user's first form post.

Is there an alternative way of getting around this issue? I've thought about only generating a new form token when the page is not loaded via a form post (i.e., when it's loaded for the first time), but that seems to open the form up for an easier attack.




Tue, 09 May 2006 at 21:40:41 GMT Link

18.bruno (france) wrote:

Hi Chris

Thank you for your site .

I've found another solution but i don't know if it's really secure

in my form , i put an elseif like this , just a try in Easyphp,

elseif ($_SERVER['SERVER_NAME'] != '') {
    $err="Error , ";



Sat, 01 Jul 2006 at 06:48:07 GMT Link

19.wei wrote:

how about

1) generated a random user token "A" per session

2) for each form display, generate a random string "B"

3) generate a form token as C = md5(A + B) + B

4) on form submit, D = C - B, verify D == md5( A+ B)

So, A is the secret string, and B is a salt.

Thu, 20 Jul 2006 at 03:13:37 GMT Link

20.DataBoy wrote:

Very nice article, and some comments are very helpful (notably those by wei -- I've used the hmac extension to encrypt/decrypt as well as create a hash using MD5) as opposed to "that's stupid" above -- now I have a new lesson in life: "don't be the 'that's stupid' commenter, but if you should meet him, still try to act out of professional courtesy!"

Whenever I've used PHP to create online exams, I try to capture as much data about the session as I think I need to document who (or what) is attempting to login, such as standard identifying data such as IP and client string info. Then, I match all future session traffic for that one connection against the original: but I know that I'm being stricter than a typical application! While a store will allow someone to consider their options for more than 20 minutes between selections, my exams won't dally any longer than the default session_timeout amount...

Thu, 20 Jul 2006 at 12:22:14 GMT Link

21.edu wrote:

I think several of the comments above miss the point, because posters do not understand that this is a CROSS-SITE vulnerability:

* You visit 'good site' A, where you log-in and get a cookie to identify your session.

* You leave site A, but forget to close your open session.

* You visit 'bad site' B, where there is a malicious GET request hidden as an IMG.

* Now, you are inadvertly executing an acction on site A, using the credentials from the cookie generated when you visited site A.

Wed, 09 Aug 2006 at 09:12:19 GMT Link

22.Tedjn wrote:


From what I understand, users reposting an erroneous form should not pose a problem for authenticating the token, although it may depend on the structure of your site and how you display your error messages.

For instance, if you have a separate function to display your form that is called both by the input filtering code on error and when the form is not submitted, you can test for a form submission and only set the token if there was not one (i.e. you are just displaying the form for the first time to the user). In this way, as long as the token does not timeout, it will still be available when you repopulate the form.

Of course, if you must reset the token--perhaps your input filtering sets an error flag and setting the token is bundled with your form display code--you just need to repopulate the token field with the new token using an echo statement.

I'm not expert on any of these topics (hence why I found this article so enlightening), but I believe this will address your issue.

Thu, 10 Aug 2006 at 17:53:30 GMT Link

23.SlashBoot wrote:

Although I mainly use tokens to combat automated comment spam posts, my implementation is also very effective against CSRF. I have a db table for tokens, each token has various aspects of the HTTP request stored with them. Because proxies can incur variations of IP address, my scripts watch for changes in User-Agent, which shouldn't change. The source of the token request, in the form that the token applies to, is also recorded. If anything varies that I don't expect to, then the 'transaction' is not allowed. I have the skimpy but effective O'Reilly PHP Security book, which is where I get some of my inspiration for attention to detail. My getToken() and verifyToken() functions are very effective and flexible enough to cover all the forms I use and process on my site.

Sat, 26 Aug 2006 at 22:34:12 GMT Link

24.Alberto wrote:


maybe I misunderstood the nature of your suggestions, so bear patience with me: this job is made also of bogus alerts.

However, the check you suggest:

$_POST['token'] == $_SESSION['token'];

does not derive its security from how complicated or hashed the token is.

I applied your technique, for arguuably the little I understood, to my site and then I attacked my own site with XSS.

I entered the token protected page with the greatest ease. Your readers should be warned, unless I am wrong of course which may be, that a token does NOT implement ENOUGH security.

I attacked my own site with a mere php from another page that went (quoting by hearth)


<form action="http:www.mysite.com/1.php">
<input type="token" value="whocares" />
<input type="submit" value="Buy Safe" />

==hack ends==

==legitimate 1.php file==

if($_SESSION['token'] == $_POST['token']){print 'safe access';}
else{print 'naughty guy!';};

==legitimate file ends==

Result of submission from hack.php from another domain:

"safe access"

It is quite possible I misunderstood your advice here, at any rate if I did, it may be worth stressing to your readers that either this implementation is absolutely what you did NOT mean, or that a token alone would never suffice - bold text lol



Tue, 29 Aug 2006 at 17:16:22 GMT Link

25.Alberto wrote:

Sorry i post the complete hack.php - apparently what makes it work is that I leave the POST data empty by giving no name to the input fields.

I know it shouldn't have worked - what amazes me, it works.

==hack php==

//header('Location: http://localhost/FULLPOSTER/admin/ajax_nations.php?id=28&cityPrefix=AB&token=whocares');
<form action="http://www.unitedscripters.com/1.php" method="post">
<input type="token" value="whocares">
<input type="submit">

Tue, 29 Aug 2006 at 17:22:09 GMT Link

26.Alberto wrote:

Let's talk of creative errors. The fact I am prone to such things even after 10 years of programming like overlooking the nature of an html tag (needless to say I know no type="token" exists, but that's why the hack worked - I THOUGHT I had put in place the... name property), apparently triggered a successful bypassing of the token.

Besides, the fact I also overlooked in my post the link to my POST data hacks repository (I attack myself, NOT others - to be very clear about it), remembers us of another thing: 'localhost' is a word that URIs posted by clients should never carry!

Ok, enough, ciao and thank you - and bear patience in case I posted a bogus alert.


Tue, 29 Aug 2006 at 17:42:27 GMT Link

27.Alberto wrote:

Bogus alert, as I after all suspected: the session file I was using today was the one that did not initialize the token in the protected page. That's the problem with having too many files! You test on the wrong files!

if(isset($_SESSION['token']) && isset($_POST['token']) && $_POST['token'] === $_SESSION['token']){print 'safe access';}
else{print 'naughty guy!';};

However this still raises a minor issue: check that you do have a token in case you are performing the check about a token generated from another included file:


or your token 'protected' pages may actually be unprotected.

And eliminate from your URIs the localhost parts or one may end up seeing code potentially executed on his own machine.

Tue, 29 Aug 2006 at 18:14:34 GMT Link

28.Chris Shiflett wrote:

Hi Alberto,

The article stresses the importance of initializing $_SESSION['token'], but I think it's a good idea to also check to be sure that it exists (and is not empty) when checking to see whether it is valid.

I will make this enhancement in a future revision of this article.

Wed, 20 Sep 2006 at 08:25:34 GMT Link

29.Özgür wrote:

Hi Chris,

Do you still believe using a token in the form will secure this example? If I'm not mistaken, in the recent Myspace worm, the attacker was able to make a pre-request, parse the page to get the token and use that token in his attack to make a valid request.

Mon, 16 Oct 2006 at 09:04:08 GMT Link

30.Chris Shiflett wrote:

Hi Özgür,

The MySpace worm taught us that this particular CSRF safeguard is ineffective if your application has any XSS vulnerabilities. Protecting against CSRF isn't enough, which is why web application security can be challenging. A chain is only a strong as its weakest link. :-)

There is a recent entry in my blog that discuss this a bit more:


Mon, 16 Oct 2006 at 14:29:56 GMT Link

31.Wes Mahler wrote:

Do you have to have the timelimit for this hack?

Fri, 08 Dec 2006 at 11:14:15 GMT Link

32.Wes Mahler wrote:

This script is not working, maybe I'm confused.

When its verifying that the post token and the session token are the same, they will always be different because:

When I load the script, token is set, and session token is set to token.

then, when I submit the form, it takes token.

but because the page reloads, now my post token, was set to the old token, where as because how it is scripted, token is being reset again, so is session token.

So when I try to see if they are equal

post token, was always the old session token.

Does that make sense? please help. thank you! I just subscribed to your RSS feed btw, great site!

Fri, 08 Dec 2006 at 11:33:40 GMT Link

33.Chris Shiflett wrote:

Hi Wes,

Thanks for the kind words about the site. I really appreciate it.

Instead of regenerating the token on every page, it's only necessary that you do so when displaying a form.

There are some other challenges you might want to think about as well. How do you handle users who use multiple tabs? You can overcome this challenge by using the same token for the lifetime of the session, but this slightly weakens the safeguard.

Now is a good time to point out my philosophy regarding the articles I write:


The "teach a man to fish" philosophy means that this article tries to explain CSRF well enough that you completely understand the fundamentals of the attack. I demonstrate the anti-CSRF token as an effective safeguard, but ultimately, no one knows your application as well as you do.

Fri, 08 Dec 2006 at 16:10:31 GMT Link

34.Wes Mahler wrote:

Hey Chris quick question, I now understand the token idea.

So, if I have a form, that sends the information back to itself you can still use the tokens right? Using one page to handle everything rather than sending it to another page.

Does the token thing still provide security?

Thanks, love the site again, fixing alot of things now!

Wed, 13 Dec 2006 at 14:20:15 GMT Link

35.Wes Mahler wrote:

Ok Here is what I did I wonder if this is secure:

IF valid token



......run script

CLOSE //post if


......set token, and place in form.

CLOSE //valid token.

That way if they force a submission the 1st thing, it will never set the token, and not post.

If they open the form how it should, it will set the token.


But now I'm confused, if they never set the token to begin with, I won't have any variables to test the validitly of the token, I guess I can use another variable to see if it appears.

Am I on the right path here, thanks again!

Wed, 13 Dec 2006 at 14:42:02 GMT Link

36.Wes Mahler wrote:

Sorry I think I got lost, I was thinking the goal was to prevent someone from sending table data from another source randomly.

But it appears we want to prevent scripts like images, links, or anything that will steal the users' data to the hacker.

So I'd want to strip the code of the <>'s, Help!


Wed, 13 Dec 2006 at 14:45:24 GMT Link

37.Ken Bloom wrote:

What prevents a malicious site from AJAXing the original form that it's attacking to get the token, and then submitting that in its malicious post request.

Tue, 02 Jan 2007 at 20:03:44 GMT Link

38.Chris Shiflett wrote:

Hi Ken,

The same-domain restriction on Ajax and other client-side technologies offers protection from this scenario.

I have more information on cross-domain Ajax and other risks in my blog. Here is a good place to begin:


Hope that helps, and thanks for commenting.

Tue, 02 Jan 2007 at 20:47:24 GMT Link

39.Roc wrote:

It seems the point raised by Julio César Carrascal didn't get addressed. If I made a page and showed it to UserX:


[iframe url="your site"] your form here[/iframe]

[javascript]Get Token From Iframe[/javascript]


I could end up with UserX's token. If the site was using the same token in all forms for multiple window support, as suggested above, its back to square one.

I'm not very familiar with Javascript, so perhaps the above scenario doesn't apply?

Sun, 21 Jan 2007 at 10:01:21 GMT Link

40.Chris Shiflett wrote:

Hi Roc,

The scenario you describe should not work due to the same-domain restriction on JavaScript and other client-side technologies.

Sun, 21 Jan 2007 at 14:58:00 GMT Link

41.Sarah wrote:


You've been praised by Neil Bertram for a phpug presentation on UTF encoding. Have you got an article on this? I miss all the summer meetings, sadly, but would love to have the information.


Fri, 02 Feb 2007 at 08:46:34 GMT Link

42.Chris Shiflett wrote:

Hi Sarah,

Tell Neil thanks. I'll have to pay him later. :-)

I don't have a very in-depth article on character encoding, but there are a couple of blog posts on the topic:



Hope that helps!

Fri, 02 Feb 2007 at 22:28:45 GMT Link

43.Colin McKinnon wrote:

As several correspondents have pointed out, the single token thing breaks when the user has multiple windows open.

The solution I used previously was to encrypt the parameters along with a timestamp.

Like Chris's solution, this does not prevent replay attacks but shortens the window of opportunity - but unlike Chris solution it protects against man-in-the-middle request rewriting.

Since the resulting value only has to be opaque to the user, it doesn't need assymetric encryption (and therefore not *too* expensive) and does not rely on having a session on the server.


Wed, 07 Feb 2007 at 14:08:42 GMT Link

44.Chris Shiflett wrote:

Hi Colin,

The solution I used previously was to encrypt the parameters along with a timestamp.

Are you sure this offers sufficient protection against CSRF? I'd need to see an example to be sure, but it sounds like I could create a script that:

1. Reads a form from your site every minute or two.

2. Uses the encrypted form data to launch a CSRF attack.

What are you doing to tie the form data to a particular user?

I could speak about MITM attacks in this article and discuss safeguards for that as well, but I'm afraid it would fragment the focus of the article and make CSRF seem more complicated than it is.

Thanks for the comment.

Wed, 07 Feb 2007 at 14:48:32 GMT Link

45.Colin McKinnon wrote:

What are you doing to tie the form data to a particular user?

Fair point. However, it has made your life a lot more difficult as an attacker since you need to re-seed your attack URL, and you are limited to replay attacks only.

If you are have an open session and you're happy its robust against the session attacks, then by all means include the session ID in the encrypted payload.

Wed, 07 Feb 2007 at 15:19:10 GMT Link

46.Chris Shiflett wrote:

Hi Colin,

I agree that every hindrance helps, but since this article is about CSRF, suggestions should at least solve the CSRF problem.

Adding the session identifier to the encrypted payload should be sufficient.

Thanks again.

Wed, 07 Feb 2007 at 15:36:23 GMT Link

47.Howard Young wrote:

This method works quite well in preventing comment spam or email spam (in terms of a contact form). I've eliminated 100% of the spam sent via a contact form generated via bots.



Mon, 19 Feb 2007 at 21:43:29 GMT Link

48.Martin P. wrote:

I'm wondering, why create a unique token rather than using the current session id? For example:


<input type="hidden" name="token" value="<?= session_id(); ?>">


The third party "malicious" site can attempt to perform actions on my behalf, sure, but they still can't read my cookies. Do the extra lines of code generating a unique ID really make that much of a difference?

Tue, 06 Mar 2007 at 16:11:00 GMT Link

49.shay wrote:

hi Chris.

another thing u can do, is to check in the header if the page comes from reffer://url.

it will eliminate the CSRF.

Tue, 10 Apr 2007 at 17:05:22 GMT Link

50.Chris Shiflett wrote:

Martin wrote:

I'm wondering, why create a unique token rather than using the current session id?

As a general rule, I don't like to expose the session identifier any more than is necessary.

Shay wrote:

another thing u can do, is to check in the header if the page comes from reffer://url.

Although I don't mention it in this article, an attacker can potentially forge headers (including Referer) from a victim:


Tue, 10 Apr 2007 at 17:33:56 GMT Link

51.Dukemhr wrote:

First up thanks for the site.

I was just wondering your opinion on these techniques to eliminate the CSRF problem. It is a PHP site

We force $_POST, we create a unique id some 32 chars long which is written to the database and the session cookie when the user logs in. Every time a form submit is done $session[unique_code] is chked with the database unique code for that user. We also use code like this to remove all tags ereg_replace('<([^>])*>', '', $_POST[subject]).

But I noticed that symbols can be written with ascii code and well my head is starting to hurt.

Also somewhere I read it is good to make sure all html pages have the line

<meta http-equiv="content-type" content="text/html;charset=ISO-8859-1">

which most web creation tools should do automatically. From an article on the same problem at


Also I suppose the login should be done on a secure https page or am I getting paranoid about this

Thanks in advance

Wed, 02 May 2007 at 09:42:41 GMT Link

52.Joran wrote:

heh thanks :)

I needed to silently send GET(nothing malicious : ) ) vars to a script and this works great

Mon, 23 Jul 2007 at 20:32:58 GMT Link

53.Wes Mahler wrote:

Hi Chris,

I have purchased your book, the section on BRUTE FORCE attacks. Doesn't having a token in the form like you recommend stop auotmated brute force attacks?

It seems like the brute force example in the book would be eliminated by this, because the user would have to be on your website to submit data, making the brute force extremely difficult because they wouldn't be able to setup a script that loops?

Am I thinking correctly here?

Sat, 22 Sep 2007 at 20:12:11 GMT Link

54.Joppe wrote:

You´re using MD5 for token generation. Wouldn´t it be better to use a stronger like SHA?

Sun, 14 Oct 2007 at 23:15:23 GMT Link

55.Joppe wrote:

...and you should use:

$token = uniqid(md5(mt_rand()), true);

instead of

$token = md5(uniqid(rand(), true));

because md5 has collision leaks.

Sun, 14 Oct 2007 at 23:26:13 GMT Link

56.Arcane wrote:

Is there a way for PHP to detect where the POST data comes from?

If so, couldn't the php script that processes the POST data check to see if the submitted values came from the same domain that it currently resides?

Seems simple enough, or can this be forged as well?

Tue, 06 Nov 2007 at 05:22:42 GMT Link

57.Syntaxineer wrote:

There is a way for PHP to get the domain in which the request was made from.


However this shouldn't be relied upon, because the form can be spoofed, or anyone can send decieving http headers, therefore it's not a very good idea to rely upon this from a security approach.

Sun, 09 Dec 2007 at 07:03:28 GMT Link

58.Ivaylo wrote:

This type of attack doesn't work at all (token or no token) in Opera (9.25). I guess that Opera checks what's requested in img.

But Firefox (, Safari (3.0.3) and IE 7 doesn't prevent you from CSRF. So be careful with what you browse out there...

Mon, 07 Jan 2008 at 11:23:04 GMT Link

59.Said Bakr wrote:


The token set as hidden input text field has no extra effect in-order to prevent spoofing forms. Simply copy its value from the form then paste it into the spoofed form.

Tue, 18 Mar 2008 at 02:23:12 GMT Link

60.Chris Shiflett wrote:

Said, it seems you don't understand the nature of CSRF. I'll try to explain why I think so, and perhaps you'd be kind enough to help me improve the article to be clearer.

The fundamental thing you seem to be missing is that a CSRF attack is a forged request sent by someone else. Knowing your own token does nothing to help you forge a request from someone else. To do that, you'd need to know theirs.

Hope that helps.

Tue, 18 Mar 2008 at 20:19:32 GMT Link

61.Chris Shiflett wrote:

Wes, that's a good idea.

This technique wouldn't prevent enumeration attacks, but it could be used to require an extra request for every attempt. In order to implement this technique for that purpose, it's important to be sure that an attacker can't simply reuse a token or omit it entirely. Requiring an active session is one option.

Tue, 18 Mar 2008 at 20:29:35 GMT Link

62.gvre wrote:

Hi Chris,

At first, I would like to congratulate you for the nice site.

In my opinion, a token based solution is not the perfect one. It's easy for the attacker to get the token's value with javascript and append it to submitted data.

Sun, 17 Aug 2008 at 16:15:57 GMT Link

63.Pete Carr wrote:

First off, I'm very impressed.

Three years ago with this blog post and you're still helping users out thank you! It has been very beneficial for me to read through and learn more about CSRF.

One question I did have:

Couldn't I just take the IP of the initial session created by the user and use that as a check?

IE: User a comes to website -> session created off his ip.

If another user tries to jack that session it would check their ip, and notice it was different.

Delete Session?

Tue, 19 Aug 2008 at 18:32:05 GMT Link

64.gvre wrote:

@Pete Carr

IP check is correct to prevent session hijacking but not CSRF. Read again this article to fully understand what CSRF is.

Wed, 20 Aug 2008 at 21:01:24 GMT Link

65.Pat wrote:

Hi Chris, Great site, still struggling a bit, but i think you have put me on the right path. Thanks man.

ps: my head hurts too. lol.


Thu, 11 Sep 2008 at 21:55:29 GMT Link

66.Arran Schlosberg wrote:

For anyone who uses the PEAR package HTML_QuickForm here is an extension HTML_QuickFormS that I wrote to operate in the same manner as QuickForm but with built in CSRF mitigation. It's in its early stages but has proved to be stable so far.

Just put it in QuickFormS.php in the HTML/ directory of your PEAR files.

 * @uses HTML_QuickForm
 * @desc Add automatic CSRF mitigation to all forms by incorporating a token that must be matched in the session and forcing the use of POST method
require_once "QuickForm.php";
class HTML_QuickFormS extends HTML_QuickForm {
     * @property string $_sessionTokenKey The name of the session variable containing the token
    private $_sessionTokenKey;
     * @method HTML_QuickFormS
     * @desc Override the method to always use post and pass it on to the parent constructor. Create a session key for the token based on the form name.
     * @param string $formName
     * @param string $method
     * @param string $action
     * @param string $target
     * @param mixed $attributes
     * @param boolean $trackSubmit
    public function HTML_QuickFormS($formName='', $method='post', $action='', $target='', $attributes=null, $trackSubmit=false){
        $this->_sessionTokenKey = "QuickFormS_".md5($formName);
        parent::HTML_QuickForm($formName, 'post', $action, $target, $attributes, $trackSubmit);
     * @method display
     * @desc Create a token if necessary and place a hidden field in the form before displaying
     * @return void
    public function display(){
        //A token hasn't been created so do so
            $_SESSION[$this->_sessionTokenKey] = md5(uniqid(rand(), true).session_id()); //requires the session id to be known in order to add extra difficulty to compromising
        //Hide the token at the end of the form
        $this->addElement("hidden", "qfS_csrf", $_SESSION[$this->_sessionTokenKey]);
     * @method validate
     * @desc Check if the passed token matches the session before allowing validation
     * @return boolean
    public function validate(){
        //The token was not passed or does not match
        if(!isset($this->_submitValues['qfS_csrf']) || $this->_submitValues['qfS_csrf']!=$_SESSION[$this->_sessionTokenKey]){
            $this->setElementError("qfS_csrf", "Anti-CSRF token does not match");
        return parent::validate();

Fri, 19 Sep 2008 at 05:47:08 GMT Link

67.Ian E wrote:

Thanks for this article.

For me the core issue of CSRF is:

Why can't the attacker make a pre-request in order to get the token first?

Your answer is:

You cannot use XMLHttpRequest for this (unless there is a XSS vulnerability),

because only requests from the same domain are allowed.

Fine. But this only answers to Özgür and Ken Bloom.

It does not answer to Stéphane Boisvert, Julio César Carrascal, Roc and me on this:

What if you load the form inside an iframe or frameset, that you manage to minimize or somehow hide from the user?

I am no javascript programmer, but I believe javascript can read a frame's content. Then all you have to do

is read the token and dinamically create a form, or even post the very form you loaded in the frame.

I have searched google with: CSRF using frames

And found this in 2nd position:

"Cool CSRF using nothing but CSS and iframes"


This is a very recent article, and it confirmed my fears.

Then I found this:


and this:


So apparently you can prevent your form from being loaded inside someone's frame on the client side.

But I still have a bad feeling with regard to frame possibilities.

If you could complete the article with a paragraph on this issue, it would become the definitive webpage on CSRF,

and it would save you the work of giving the same answer over and over.

As the number of comments increase, some people will not read all of them and post this same question again.

Thu, 18 Dec 2008 at 23:24:34 GMT Link

68.Roman wrote:

Very interesting article and discussion. Very interesting website, for that matter.

Hm, this article gave me an idea several days ago. I use a certain technique to check my CAPTCHAS. I think it can be applied to this problem as well.

class FormVerifyer{
    protected $secret;
    public $seed;
    public $seal;
    function __construct($secret){
        $this->secret = $secret;
    function generate($restriction){
        $this->seed = uniqid(rand(), TRUE);
        $this->seal = hash('sha256', $this->secret.$restriction.$this->seed);
    function verify($restriction, $seed, $seal){
        return $seal == hash('sha256', $this->secret.$restriction.$seed);

Here is how it works. You have a secret word that only your server knows. When the user requests a form, you invoke a function, that generates a random seed. It also generates a seal by hashing concatenated secret, restriction and the aforementioned random seed. The restriction can be anything, but for our purposes we assume that it is the id of the current user. We include both the seed and the seal in the form that the user receives.

When the user submits the form, we go through a similar process, we take the server secret, the restriction (current user id) and the seed from the form, hash them and compare the resulting hash with the one from the from.

Unlike the token methods above, this one does not require to store a specific token in user's session. Neither does it require user to maintain the session. I'm not sure whether that's a good or bad thing, but a user could open a form in one tab, log out in another, then login again and the form would still work. Opening consecutive forms is perfectly fine too.

Another thing. It is possible to use more than the user id as a restriction. You could, for example, add a target form id (path) there as well, or append it with an IP address of the person requesting the form.

Do you think this idea has any merit? I would appreciate any feedback.

Fri, 19 Dec 2008 at 18:17:19 GMT Link

69.Hey ho wrote:

This is what I use:

function init_session() {
   if (isset($_COOKIE[session_name()])) {
      // Protect against obvious XSRF attacks.
      // Doesn't work if the client is completely pwned; for example,
      // Flash versions before 9.0.18 allows the author of the SWF to
      // completely custom tailor the contents of the Referer header.
      $thishost = "https://" . $_SERVER["HTTP_HOST"] . "/";
      $referrer = substr($_SERVER["HTTP_REFERER"], 0, strlen($thishost));
      if ($referrer != $thishost) {

I've never used the unique-token-per-response->request-pair approach because storing all those unique tokens is peasky and an open invitation to getting your server DoS'ed, basing the tokens on cryptography is better but cumbersome to implement, and either way both approaches are subject to circumvention by client-side bugs anyway.

(The referrer approach is circumvented by finding a way to fake the Referer header, and the token approach is circumvented by finding a way to request a page and read the token.)

Thu, 19 Feb 2009 at 21:53:44 GMT Link

70.Mike H wrote:

Hi Chris,

Thanks for the succinct overview of CSRF attacks. Although it's been a while since I first read this article I have a question:

What is the benefit of having a timeout for the form token?

If the token ensures that it's the legitimate form being submitted (and not an invisible replica on a bad site) does it matter if the form was generated 5 minutes, an hour or 5 hours before it is submitted?


Thu, 05 Mar 2009 at 00:36:54 GMT Link

71.Nilima Kadam wrote:

Hi Good day Chris.

Thanks for the detailed explanation. Was totally new to this concept. Entire day spent in getting more infor abt it and ur site seemed understandable and practically.

Tried it for a system we r working on and got it more Secure. Thanks once again!

Mon, 13 Apr 2009 at 13:59:46 GMT Link

72.Xiao Feng Wu wrote:

Mike, CSRF only happen when a victim is logon a website. So set a short session timeout would reduce the probability of being hacked.

Tue, 12 May 2009 at 09:42:13 GMT Link

73.torg wrote:

is there any reason as to why tokens along with a http get does not suffice to prevent csrf attacks?

Wed, 10 Jun 2009 at 15:26:27 GMT Link

74.Ray C wrote:

Hi Chris,

I see the value of using a CSRF prevention token, but Ian E raised a very good point about an attacker possibly getting a handle to the token through an intial request to get form.

Could you provide a response to this?

Also, I'm interested in your take on Roman's proposed solution for stateless sessions.


Mon, 13 Jul 2009 at 23:50:49 GMT Link

75.RyanTheGreat wrote:

Well, I'm not Chris, but I will do my best to address the questions raised in the comments by Ian E and Ray C.

@Ian E - I'm not sure what you found on that first link called "Cool CSRF using nothing but CSS and iframes" because it no longer exists. However, I would venture to guess that if someone indeed did use css + iframes for CSRF it was either one of two things:

1) An example of the possibilities of CSRF against an unprotected form, not one that was using a nonce. From the title, it sounds like it was intended to be an example of the fact you can use only CSS + iframes to execute a CSRF. I do not believe it was meant to be an example of how to use CSS + iframes to bypass a protected form which uses a nonce.

2) If it was indeed an attack against a protected form, not just a vulnerable form, I would be forced to assume it was a particular version of a browser that was vulnerable, not an attack vector meant for all purposes.

I would be forced to assume this because your assumption: "I am no javascript programmer, but I believe javascript can read a frame's content." is incorrect. JavaScript has almost literally NO access to ANY information about a frame. Try it out for yourself, you can get essentially no information about a even a frames current URL, let alone the actual content within the frame - and with good reason.

Imagine how horribly insecure everyone would be if such attacks were possible? Monitoring entered keystrokes inside a frame with JavaScript to steal login credentials, redirecting users from a frame in which they thought they were on a legitimate site to an attack site and etc. If you could use JavaScript to manipulate/grab information from frames, CSRF would be the very least of our problems.


Thu, 11 Mar 2010 at 20:40:04 GMT Link

76.isogashii wrote:

Great article Chris,

For those asking about JavaScript ability to read iframes, actually, using javascript you can read iframe properties and content only if it is for the same website, if you load another website, it will become useless.

Also, I found this page that tells about possible attack using CSRF, it may help you get more info:


Thu, 04 Nov 2010 at 12:09:00 GMT Link

77.Samvel Gevorgyan wrote:

I've just seen your interesting topic about Cross-Site Request Forgery and I think..


Capthca 99% protects your web application

why 99%, because there are lots of online payed services to crack captchas... 1 cent for each cracked image.

But no one uses captchas most of the time, because it's secure but boring and asking to fill a captcha each time user takes an action will make him/her forget about your website.

2. Tokens

Tokens are not secure as Captchas, but they protect your web form from lots of attacks.

Why I lot's of attacks and not all of them?

Because javascript provides flexibility which attacker can use to get any token you provide to a user and send a get/post request from his custom form.



<?php session_start();?>







$token = md5(uniqid(rand(),true));

$_SESSION['token'] = $token;


<form name="messageForm" action="post.php" method="POST">

<input type="text" name="message">

<input type="submit" value="Post">

<input type="hidden" name="token" value="<?php echo $token?>">







if($_SESSION['token'] = $_POST['token']){

$message = $_POST['message'];

echo "<b>Message:</b><br/>".$message;

$file = fopen('messages.txt','a');




else {

echo 'Bad request.';







<script language="javascript">

function submitForm(){

var token = document.frames("hidden").document.forms("messageForm").elements("token").value;

var myForm = document.myForm;

myForm.token.value = token;





<body onLoad="submitForm();">

<div style="display:none">

<iframe id="hidden" src="http://good.com/index(good).php"></iframe>

<form name="myForm" target="hidden" action="http://good.com/post.php" method="POST">

<input type="text" name="message" value="I like www.bad.com" />

<input type="hidden" name="token" value="" />

<input type="submit" value="Post">






doesn't work on FF and CHROME :(

so you don't even need to use the click-jacking techniques and it may be working automated..

Sun, 28 Nov 2010 at 12:29:41 GMT Link

78.Christopher Hanson wrote:

good read! whilst I know how to do this it is always helpful to have a reference some where! :D


Wed, 27 Jul 2011 at 16:17:41 GMT Link

79.Philip Herbert wrote:

I have implemented the token on a page and it works.

My problem occurs with the multi page aspect. Can I use the same session token on different pages/forms?

What is happening is that I go to page1(with form1) set a session token, I don't submit anything, Click a link and go to page2(with form2), the session token already exists so I don't set the token var in the form, so the session token will never be the same as the token var in the form, if I came from a page where I set the token.

I have this at the top of all .php pages in question:

if (!isset($_SESSION['token']))


$token = md5(uniqid(rand(), TRUE));

$_SESSION['token'] = $token;


How should I go about using the same session token on different pages with different form?

Mon, 29 Aug 2011 at 16:31:57 GMT Link

80.Avalonica Sativa wrote:

What is the answer to Philip Herbert's query above? When you go from form page to form page?

Sun, 10 Jun 2012 at 20:10:44 GMT Link

81.Private Proxies wrote:

Great share.

Fri, 07 Jul 2017 at 13:56:03 GMT Link

Hello! What’s your name?

Want to comment? Please connect with Twitter to join the discussion.