As the web becomes more and more pervasive, so do web-based security vulnerabilities. I talked a little bit about the most common web vulnerability, cross-site scripting, in Protecting Your Cookies: HttpOnly. Although XSS is incredibly dangerous, it's a fairly straightforward exploit to understand. Do not allow users to insert arbitrary HTML on your site. The name of the XSS game is sanitizing user input. If you stick to a whitelist based approach -- only allow input that you know to be good, and immediately discard anything else -- then you're usually well on your way to solving any XSS problems you might have.
I thought we had our website vulnerabilies licked with XSS. I was wrong. Steve Sanderson explains:
Since XSS gets all the limelight, few developers pay much attention to another form of attack that's equally destructive and potentially far easier to exploit. Your application can be vulnerable to cross-site request forgery (CSRF) attacks not because you the developer did something wrong (as in, failing to encode outputs leads to XSS), but simply because of how the whole Web is designed to work. Scary!
It turns out I didn't understand how cross-site request forgery, also known as XSRF or CSRF, works. It's not complicated, necessarily, but it's more.. subtle.. than XSS.
Let's say we allow users to post images on our forum. What if one of our users posted this image?
<img src="http://foo.com/logout">
Not really an image, true, but it will force the target URL to be retrieved by any random user who happens to browse that page -- using their browser credentials! From the webserver's perspective, there is no difference whatsoever between a real user initiated browser request and the above image URL retrieval.
If our logout page was a simple HTTP GET that required no confirmation, every user who visited that page would immediately be logged out. That's XSRF in action. Not necessarily dangerous, but annoying. Not too difficult to envision much more destructive versions of this technique, is it?
There are two obvious ways around this sort of basic XSRF attack:
Easy fix, right? We probably should never have never done either of these things in the first place. Duh!
Not so fast. Even with both of the above fixes, you are still vulnerable to XSRF attacks. Let's say I took my own advice, and converted the logout form to a HTTP POST, with a big button titled "Log Me Out" confirming the action. What's to stop a malicious user from placing a form like this on their own website ..
<body onload="document.getElementById('f').submit()">
<form id="f" action="http://foo.com/logout" method="post">
<input name="Log Me Out" value="Log Me Out" />
</form>
</body>
.. and then convincing other users to click on it?
Remember, the browser will happily act on this request, submitting this form along with all necessary cookies and credentials directly to your website. Blam. Logged out. Exactly as if they had clicked on the "Log Me Out" button themselves.
Sure, it takes a tiny bit more social engineering to convince users to visit some random web page, but it's not much. And the possibilities for attack are enormous: with XSRF, malicious users can initiate any arbitrary action they like on a target website. All they need to do is trick unwary users of your website -- who already have a validated user session cookie stored in their browser -- into clicking on their links.
So what can we do to protect our websites from these kinds of cross site request forgeries?
XmlHttpRequest function.
XmlHttpRequest calls can't read cookies. If either of the values don't match, discard the input as spoofed. The only downside to this approach is that it does require your users to have JavaScript enabled, otherwise their own form submissions will be rejected.
If your web site is vulnerable to XSRF, you're in good company. Digg, GMail, and Wikipedia have all been successfully attacked this way before.
Maybe you're already protected from XSRF. Some web frameworks provide built in protection for XSRF attacks, usually through unique form tokens. But do you know for sure? Don't make the same mistake I did! Understand how XSRF works and ensure you're protected before it becomes a problem.
One of the issues I ran into trying to use Django's built-in CSRF protection was that it would insert tokens only on sites with form elements. This means that on pages with no forms, but lots of AJAX sending POST requests, they would all be blocked. Luckily, Javascript's same-domain rules mean I can have a page like /auth/get-csrf-token/ that simply returns the user's current token, download it with an AJAX GET, and then freely use it in POSTS.
John Millikin on September 23, 2008 4:55 AMconvincing other users to click on it?
Your example shows auto-submitting the form via JavaScript. No user needed.
Julian on September 23, 2008 5:04 AMI disagree with your suggestion to whitelist inputs, in order to prevent XSS attacks. I think the better solution is to sanitize OUTPUTS.
You never know where your inputs are going to go -- sometimes they'll be sent to a database, or exported to a file, or output as HTML. Each of those destinations will require different sanitization to make the data safe. At the input stage, here's no way to sanitize data so it will be safe for any possible destination. If you try, you'll just end up corrupting your data.
I always stick to this rule: only encode data right before you're about to send it to another layer, and then only encode it to make it safe for that layer. Otherwise, keep your data in its purest possible form.
JW on September 23, 2008 5:06 AMThank you for explaining another common attack.
Cristian on September 23, 2008 5:10 AMThis would imply that REST implemented sites would be very vulnerable to this type of attack.
Does traditional ASP.NET forms protect you because the ViewState Mac value is embedded in forms?
Josh Hurley on September 23, 2008 5:11 AMWeb development is scary by default.
Hoffmann on September 23, 2008 5:13 AMInteresting stuff. Never heard about it before. It will take quite a lot of extra work to make your websites XRSF proof. Thanks!
Sander Versluys on September 23, 2008 5:30 AMJW: yes, you're outputting it, but it's still the user's input. Just a matter of point of view. Other than that, I agree with you.
Daniel Luz on September 23, 2008 5:34 AMPerhaps you should spend some time in the PHP community you seem to despise so; they've been talking about preventing cross site attacks for YEARS (ok, ill grant it's probably only some of the smarter ones and not the unwashed masses). See http://shiflett.org/articles/foiling-cross-site-attacks as an example
some jackass on September 23, 2008 5:53 AMPerhaps you should spend some time in the PHP community you seem to despise so
I really think it is a gross mischaracterization to say that I despise the PHP community.
This would imply that REST implemented sites would be very vulnerable to this type of attack. Does traditional ASP.NET forms protect you because the ViewState Mac value is embedded in forms?
Yes, and yes!
Jeff Atwood on September 23, 2008 6:16 AMYour example shows auto-submitting the form via JavaScript. No user needed.
They have to click on the URL containing that form, first. The form is *not* running on your site, hence cross-site.. scripting vulnerability.
Jeff Atwood on September 23, 2008 6:17 AMI would say JW/Daniel/Jeff are all right. Ha!
I was just reading More Joel On Software last night, and this topic came up in a 2005 article ( http://www.joelonsoftware.com/articles/Wrong.html ). Joel promotes the idea of using Hungarian notation to put emphasis on the kind/use of the variable. A user specified variable is $usPizza for example, and when we've done our safe-for-SQL encoding, $sqPizza, and when we've done our safe-for-html encoding $htPizza (these examples could be poor - read more to inform yourself!).
A string encoded to be HTML safe isn't the same as a string encoded to be SQL safe, which isn't the same as a string to be Query String safe. An approach where you encode as soon as possible, like say PHP's addslashes-to-incoming-vars approach, only solves one problem.
And looping back to Jeff's cross site issue, this attack was commented on (by Brett first, then a couple of others) http://www.codinghorror.com/blog/archives/001100.html - which led me to read about token passing like Jeff's second option. If you must provide proof you are the trustworthy web application with each request, through the querystring for GET or post var with POST, I'm fine with that. The approach is also extendable _for_ cross site uses, where your application passes out a token to another web app so it can take actions using this approach. Which you want to be careful with, blah blah blah.
sigh. web safety. you can cover every hole you want, but users will always find a way to screw up (here's an example: http://digg.com/tech_news/Fake_popup_study_sadly_confirms_most_users_are_idiots ).
/mp
Mauricio Pastrana on September 23, 2008 6:51 AMUsers are the most vulnerable security flaw I can think of!
asperous on September 23, 2008 6:56 AMCheck the referrer.
Beware, the referrer header is optional, so this won't work for a [very] small number of users who have chosen not to send it.
Ted Percival on September 23, 2008 7:09 AMiThey have to click on the URL containing that form, first./i
The bad guy could send browsers to that url via the IMG trick the post opened up with, so the human subject need not actually click on anything, just view a trusted page.
Jeffrey Friedl on September 23, 2008 7:12 AMFor the IMG trick, what harm is it to GET something? Assuming your website doesn't have anything destructive happen without a POST. I don't see how that other POST trick would get around ASP.NET default security.
Please enlighten me.
I want to vote up JW's comment. The problem is output, not input.
More specifically, the problem is data types. We fall into the trap of treating all strings like strings. In fact, an HTML string is different from a SQL string, which is different from a Javascript string. The same data would be represented differently in each of these types. Just like the number 3 is represented by different bits when treated as an integer, a double, or a character. They are all binary, right? Wrong. They are different types, each with a unique binary code.
The string is the new byte array.
If I want to post a question on Stack Overflow about Javascript, I can just start typing scriptvar blah = 'blah, blah';/script. That's valid input. When stored in the database, it should be stored using those characters. The apostrophes (sorry, single quotes) are not illegal. When shown on a web page, the user should see it in exactly that form. The less-than and greater-than (I mean, angle brackets) are perfectly OK.
The only thing I need to do with that piece of data is to convert it from the string data type to the SQL data type (escape the apostrophes) in order to generate a query. And then to convert it to the HTML data type (escape the inequalities) in order to generate instructions that cause the browser to display the data correctly.
This is not a new problem. This is exactly the same as XSS. Convert strings to the proper data types, and this vulnerability goes away.
Michael L Perry on September 23, 2008 9:08 AMThis would imply that REST implemented sites would be
very vulnerable to this type of attack.
How would a REST implemented site be any more or less vulnerable than a non-REST implemented site? It depends entirely upon the implementation doesn't it?
Garren on September 23, 2008 10:32 AM@Michael L Perry: Someone has solved this problem two years ago, using Haskell's type system.
http://blog.moertel.com/articles/2006/10/18/a-type-based-solution-to-the-strings-problem
You can't not escape things properly because the type system will punch you in the head if you try.
Jack on September 23, 2008 10:41 AMFor the IMG trick, what harm is it to GET something? Assuming your website doesn't have anything destructive happen without a POST.
This depends on what you consider destructive. Logging a user out (being the given example) doesn't typically alter data (maybe logging and maybe some other dull bookkeeping) on the server—it can, however, alter data on the client (cause the user to lose unsaved/unsent work).
This would imply that REST implemented sites would be very vulnerable to this type of attack.
Only if you consider Log out a Resource or destination. Somewhat more correctly, but still incorrectly, it would be a query on a resource (say, Authentication Controller). Correctly, it would be an action, which you would POST, to a resource—it alters your session and the data associated therewith.
There's all sorts of other ways that this can go haywire, especially if you do a lot on the client. That's why... you build the application with safeguards for this sort of stuff.
In other words, to quote Garren above: It depends entirely upon the implementation doesn't it?
In further other words: Requiring a POST to do actions like log out and requiring the POST originate from the site is not inconsistent with REST. None of what the article above discussed as a solution can't be done with REST.
Trevor on September 23, 2008 12:00 PM@JW, @Michael L Perry: From reading your posts, I think that either you haven't read Jeff's post at all, or you massively misunderstand CSRF and think it's the same as XSS.
You both might want to go and read up on CSRF/XSRF and make sure you're clear on how it's different to XSS. It's nothing to do with encoding anything.
Steven Sanderson on September 23, 2008 12:12 PMThe form is *not* running on your site, hence cross-site.. scripting vulnerability.
As you have pointed out that the form does not run on the original website, in that case wont the browser dis-allow such a request? As the request is being originated from javascript to a different domain shouldn't the browser catch it and throw a javascript error?
Amit on September 23, 2008 12:27 PMI'm not a javascript wizard, but is it possible to load a remote page in a hidden iframe on the malicious page, parse it with javascript to find the hidden token and then populate the form the user is (presumably) about to submit with the right values?
David Goodwin on September 24, 2008 2:35 AMhttp://www.owasp.org (Open Web Application Security Project) has many great articles, best practices, how tos, and checklists for securing web sites.
Mark on September 24, 2008 2:36 AM@David Goodwin: No, the same-origin policy would prevent the malicious page from reading the contents of the iframe.
Rico on September 24, 2008 3:03 AMYour comment on the referrer check is a bit of a cop-out, Jeff:
Furthermore, spoofing the referrer value is extremely easy. All in
all, a waste of time. Don't even bother with referrer checks.
Spoofing the referrer is extremely easy for a malicious *user agent*, yes. However, in this case you are trying to trick a normal user's browser, and a regular browser will not spoof the referrer string, so for protection against XSRF checking it will work.
I agree that it may still be a bad idea because the referrer field can be stripped, but your argument there was flawed.
RiX0R on September 24, 2008 3:07 AMgreat post! I finally understand what are those protect_from_forgery keys all about..
Fernando on September 24, 2008 3:26 AMAnother good way to avoid these type of attacks to your restful APIs is to not use cookies for authentication. Instead force the client App to insert an HTTP Authentication header in all requests (even if its just reusing a value stored in a cookie). This essentially forces your AJAX requests to be made using XMLHTTPRequests, and prevents requests from third party domains.
Dave B. on September 24, 2008 4:07 AMWhat a uncanny coincidence, Simon Willison posted a link regarding robust CSRF today: http://simonwillison.net/2008/Sep/24/robust/
Alistair Lattimore on September 24, 2008 4:19 AMThe digg link is nice, but it seems that it's author wasn't smart enough to remove or prevent comment spam :(
Manu on September 24, 2008 5:48 AMIf the only thing you ever deal with is comments on a website, blogs, or other word documents, then yes, you need to preserve what is displayed = what was input. But don't fool anyone into thinking that because it 'looks' the same, it needs to be stored exactly as the user typed it. JW and MLP made a big fuss about preserving input exactly, but it's not a universal truth. There's nothing sacred about user input. The user can be malicious.
The problem is that the abstraction of html leaks out, and it's the leaky abstraction that is exploited.
roflmao on September 24, 2008 5:52 AMI'm no expert (and am likely missing something obvious), but your logout example seems to defy the REST paradigm because a GET operation changes the state (logs you out). If you truely follow the REST paradigm, then it would already be a POST.
Also, I'm a little confused on about the comment: force the target URL to be retrieved by any random user who happens to browse that page -- using their browser credentials!
which credentials are you referring to? wouldn't the cookies and other form fields within the page NOT be sent during the image fetch (since they are in a different domain) ? only referring url might give away a URL embedded session id?
Perhaps you are referring to some malformed HTML that makes the browser reveal values in other form fields on the original page?
Can anyone shed some light on what I'm missing?
Jeff, please do not complicate the already complicated Web with more cookies and obscure POST forms.
There are at least two much better solutions than what you propose:
The first is based on understanding that exposing a generic URL like
/logout means whoever uses it, is logged out. The authentication is essentially missing. The website simply assumes some things internally when the URL is called. Since all websites use cookies or server-side sessions of sorts, what such URLs do is they logout the user they have information on either in these cookies or session variables. What is a much better idea, and is essentially transparent to the user (ONLY if he uses site navigation, and not third party request by URL) is to use a sort of authentication that cannot be forged. Since the default /logout URL request, wherever from and by whomever submitted, already carries all necessary user id data, it may be misused. If one requires that such request comes with a POST data generated prior to request, or a query string variable, both identifying the user, an attacker will not be able to trick anybody into logging themselves out accidentally, because a complimentary user authorization data is not sent beside the request.
Another solution is to think twice when processing HTML input on forums and such containing IMG objects that carry a src attribute that points to anything else than image mime-types. Again, a whitelist of such mime types. And this is done by sending a HEAD request first, prior to embedding such images which will be requested with GET and thus trigger server-side script processing such as the /logout URL would. Issue a HEAD request, check the mime type header, and if it is not an image type, reject the tag input.
Funny how it reads no HTML in this very form field I am typing this text in. Talk about FUD.
amn on September 24, 2008 6:43 AMSpoofing the referrer is extremely easy for a malicious *user agent*, yes. However, in this case you are trying to trick a normal user's browser, and a regular browser will not spoof the referrer string, so for protection against XSRF checking it will work.
Interesting you should say that, because I use a FireFox addon named RefControl on my home computer that doesn't strip out the referrer, but changes it to whatever I want (the domain's root url usually).
Incidentally, thank you for reminding me that I hadn't installed it on my work computer. *goes to install it*
R. Bemrose on September 24, 2008 6:55 AMSteven Sanderson Said: You both might want to go and read up on CSRF/XSRF and make sure you're clear on how it's different to XSS. It's nothing to do with encoding anything.
I think that they are simply referring to Jeff's comment: The name of the XSS game is sanitizing user input. This was just a side note to the conversation and they picked up on it. They understand the difference between XSS and XSRF.
Matt on September 24, 2008 6:59 AMThis would imply that REST implemented sites would be very vulnerable to this type of attack.
What does REST have to with this issue? It seems like any site is vulnerable if this issue is not considered during design. I don't see how a RESTful architecture makes you more or less vulnerable.
FWIW, Placing a verb in the URI, e.g. http://foo.com/logout, is not considered RESTful and in the original example it violates the usage of HTTP GET, as it's not safe.
This type of URI design is the same thing that got 37 signals in trouble with Google Web Accelerator.
Chuck M on September 24, 2008 7:04 AMWhat if you just removed http:// from input? And then htmlEncode everything else? If users want to use links, setup your page to have them enter [IMG]http://www.mylink.com[/IMG] instead.
If then can't create tags, they can't do this. Unless I'm missing something.
Kris on September 24, 2008 7:25 AM[URL]http://www.mylink.com[/URL] - meant that instead of using IMG tags.
Kris on September 24, 2008 7:25 AMWow! I can't beleive I'd never considered this as a vulnerability. I've been aware of XSS for awhile but this must have been around since the start of the web?
How come it's got such a low profile?
Another solution is to think twice when processing HTML input on forums and such containing IMG objects that carry a src attribute that points to anything else than image mime-types. Again, a whitelist of such mime types. And this is done by sending a HEAD request first, prior to embedding such images which will be requested with GET and thus trigger server-side script processing such as the /logout URL would. Issue a HEAD request, check the mime type header, and if it is not an image type, reject the tag input.
That is sensible but then I could put load on your server and possibly bring it down by linking to hugggge image files! :)
Rob Stevenson-Leggett on September 24, 2008 7:27 AMScratch that... I just read the HEAD bit... Man do I feel silly.
Rob Stevenson-Leggett on September 24, 2008 7:28 AMXSS, CSRF, you just need one more article (session high-jacking) to have spoken about every simple and common attack vector used against website.
Arkh on September 24, 2008 7:51 AMJeff,
I really love your blog. I appreciate that you are open about the mistakes you make, where other places are so prideful that they try to cover up vulnerabilities.
Just yesterday I was doing XSS attacks on my own company intranet! I never thought of doing XSRF. (We have an away comment field for our status, that does not get scrubbed in any way. Mostly just having fun here to pass time. Nothing malicious.)
Tim Reynolds on September 24, 2008 8:14 AMJeff so was stackoverflow found to be vulnerable to xsrf attacks by that one user 'erratic' or whatever that asked to do security testing on the site? Man I certainly hope not.
o.s. on September 24, 2008 8:18 AMYou say So what can we do to protect our websites from these kinds of cross site request forgeries?
Number one (most important) is Check the referrer. Then a couple sentences later you say Don't even bother with referrer checks.
Will you make up your mind please...?
Josh Stodola on September 24, 2008 8:44 AM1) I want to confirm that checking UrlRefeffer [hoping to prevent XSRF attack] is a waste of time. It can be spoofed by malicious user. That can be done by combination of XSS and XSRF attack: injecting javascript into HTML output (XSS) on one web page and producing forged request (XSRF) pointing to another page of the same web site.
That's also correct that legitimate users may have empty UrlReferrer. Rejecting doing business with such users is a mistake.
2) I agree that introducing parameters cuts off the most obvious XSRF attacks.
But if one of your pages is XSS vulnerable (allows javascript injection), then even if you have dynamic parameters to prevent XSRF (on another page), javascript can still read these dynamic parameters and re-submit them, so the request would succeed.
That's how Gmail was hacked -- the hacker used XSS vulnerability on some obscure Google's web site site in order to exploit XSRF vulnerability in Gmail).
amn, interesting ideas.
That head thing doesn't really fix things. While the web server may test a link and get an image, the client's browser may do the request and get a redirect. No problem with the first idea (attaching extra data to post or quesrystring, just as Jeff used in point 2), though if it's externally guessable or discoverable information (like Jeff's username, or to be topical the town where Palin first met her husband), it becomes possible to attack. Userids and usernames would be out.
For example, if a forum was targetted where the current logged in browsing users are listed, this list can be automatically checked and used as a basis when serving the image from the attacker's website. Got a johnsmith45 logged in? okay, instead of the image we'll redirect to /logout?confirmuser=johnsmith45
If it is possible to send private messages to individuals, an attack could be individually tailored. Ideally, the correct approach for choosing what to use as the second proof would have to be some data not externally visible, and probably per session - the session id used in the cookie would probably do - if the cookie session id is already externally known, hijacking is already possible. Sigh.
Sleepy Matt on September 24, 2008 9:15 AMUsers are the most vulnerable security flaw I can think of!
Absolutely. That is why I'm designing a site that no user will use.
Charles on September 24, 2008 9:25 AMSome web frameworks provide built in protection for XSRF attacks, usually through unique form tokens.
So what does ASP.NET MVC do?
John Topley on September 24, 2008 10:41 AMi am aware that the author probably does not read comments this far down, but this font looks like ASS on my screen .
yossi on September 24, 2008 11:02 AMUsers are the most vulnerable security flaw I can think of!
Which is why I teach my less technologically inclined family and friends the basics of user responsibility.
...Or I install Mozilla, AdBlocker, and NoScript to keep them from bad habits, hehe.
Ironically, the double-submitted cookie solution is prevented by your earlier security measure of making your cookies HttpOnly.
Rico on September 24, 2008 11:28 AMif you're running an asp.net site you can set the viewstatekey property during the page_init to sign the viewstate with a unique user value and avoid CSRF. You should also use viewstatemac to ensure the viewstate's not tamperable and enable viewstate encryption so it can't be decrypted (http://msdn.microsoft.com/en-us/library/aa479501.aspx)
Cathal on September 24, 2008 12:18 PMthx for the OLDNEWS post
this blog is NOOB CENTRAL
Just a quick check of some of my banking websites shows they are certainly vulnerable to an XSRF logout 'attack'. I'm not sure how bad that is though. Most online applications can and should err to the side of logging a person out.
However, I think the deeper problem here is one I've struggled with as a website developer. When a request coming in to the server, one can't tell whether it's from an IMG tag, iframe tag or click. (or script tag vs entering url in location bar).. It would be nice to have a browser header that specifies what the URL request came from... or maybe what type of media the browser is expecting. Have any solutions of this type been proposed?
Dave on September 24, 2008 12:53 PM As you have pointed out that the form does not run on the original
website, in that case wont the browser dis-allow such a request? As
the request is being originated from javascript to a different domain
shouldn't the browser catch it and throw a javascript error?
@Amit: Nope, this request is perfectly valid. Imagine, for example, submit a query to Google Search from any page, or posting a request to Facebook to 'Share This' from any page. The problem, I believe lies in the underlying concept of REST architecture, which is why the discussion about tokens above, which represent the state information missing from a typical HTTP request.
K' on September 24, 2008 1:43 PM@Jono
That's why it's generally a bad idea to access information using just Request[SomeVar].
dhany on September 25, 2008 3:44 AMSleepy Matt:
I dispute your arguments. I hardly can see how a web server checking a link with a HEAD request might get an image, but the browser doing a GET request get a redirect? Especially with a /logout type of URL. A web server might compose a custom HEAD request, designed to mimic the more popular web browsers, exactly for the purpose of foreward-checking the kind of response users will get, and if they do not get an image mime type, then reject the URL as image source.
Also, a confirmuser=jeff12345 is as bad idea as no idea at all. When I said authentication, that is what I meant. A query string variable confirmuser=jeff12345 is not a form of authentication. A better variable in such case would be a temporary session id, if any (one of examples), it is private, and it is temporary.
Jeff's proposition even though looked like mine, had a DIFFERENT PURPOSE and DIFFERENT METHOD. He is proposing to use hidden form fields with a unique server key, which will essentially disable the API for other websites. I am advocating for cross site API. I DO WANT developers to target my site API, by HTTP. He proposes a solution which disables it altogether, since only the server itself knows the unique key and embeds it in its own pages. Nobody else has a chance of knowing what this unique key is. In my case I am proposing to just send a temporary session id, possibly over HTTPS, to a logout script, so that it may authenticate the user that the script is supposed to log out, instead of it assuming it should just log out the default user - the one a cookie points at. A cookie that is sent by the browser intrinsically, a fact that is exactly the pillar of exploit by an attacker that wishes people click on a link. An attacker will have to know the session id, if it wants to achieve anything of importance, and the session id may only be displayed to the user, and retrieved with an API in exchange for a valid username and password. I do not expect end users to be aware of their session ids, but a username and password pair, sent over HTTPS might do too. Regarding passwords, it is always questionable though, because they are the weakest link. If HTTPS somehow fails and password becomes public knowledge, it is a gravely serious security breach.
Another thing Jeff proposes, which has little to do with my ideas, is the double cookie method. I do not see any good reason (over mine) to use more cookies. I am tired of cookies. I do not like so much cookies. Web has diabetes by now. Please stop with obscure non-solutions to simple non-problems. Thank you.
amn on September 25, 2008 5:20 AMExcellent post! Thanks for sharing!!
JMinadeo on September 25, 2008 5:36 AMFourth method to prevent XSRF: Disable JavaScript for all sites except the ones you opt-in, e. g. using NoScript on Firefox. A pity this solution is currently levered by the fact that about every site moronically insists on using JS, even for such basic things as links :-(( Thanks you infantile webdesigners and PR experts!
Murphy on September 25, 2008 7:12 AM@o.s. - That's why it's _still_ in beta, this is the time to work these things out
Also, people suggesting to check the referrer, unless I'm misunderstanding the post, since the page is being injected with a fake url in an IMG tag, wouldn't the referrer still be the correct site? The content is in fact coming off the page you are viewing targeting a logout URL on the same site...?
HB on September 25, 2008 7:18 AM@amn:
I dispute your arguments. I hardly can see how a web server checking
a link with a HEAD request might get an image, but the browser doing
a GET request get a redirect? Especially with a /logout type of
URL. A web server might compose a custom HEAD request, designed to
mimic the more popular web browsers, exactly for the purpose of
foreward-checking the kind of response users will get, and if they
do not get an image mime type, then reject the URL as image source.
Yeah, but the server does have a specific IP (or range of IPs). Serving different content based on the IP wouldn't be hard. Similarly, the server makes HEAD requests, and browsers don't. Differentiating content based on the request type is easy as well.
That's not to say that your approach is impossible... just that it ends up exploding in complexity to make it a strong technique.
Jess Sightler on September 25, 2008 10:14 AMQuite a few people seem to say that using POSTs instead of GETs will solve your problems, but I'd like to illustrate some examples where it wouldn't:
1. Many frameworks abstract the retrieving of request parameters, whether they are in the querystring or form, e.g. in asp.net, the developer may have written his code like:
String myVar = Request[somevar];
expecting the request to be posted, but sending this variable via a GET will work just as well.
2. Not all actions require parameters, e.g. the running logout example, so it actually doesn't matter whether the request was sent with a GET or POST unless specific parameters are expected from the request. Frameworks like asp.net may help here as things like button events are implicitly tied to POSTs.
So the point is, if you want to be really careful, you would need to actually *check* that the request method is a POST instead and not a GET.
Jono on September 25, 2008 11:38 AMSo basically don't allow any user input that is used with requests. Like href or src and encode everything else. Problem solved.
Andreas on September 26, 2008 4:04 AMI used log people out years ago on phpBB forums, along with worse exploits.
They have now made any critical action requires the session id sent via GET. Works well, everyone has a unique logout link, but wreaked all my fun. :[
Mitch on September 26, 2008 9:01 AMhow about changing your REST URL to include the user identity?
http://restfull.url/username/logout
Simple and efficient ...
Pierre Spring on September 28, 2008 2:25 AMYou people weaks agains XSRF do not deserve to be called webdesigner/webdevelopper. It's a VERY WELL KNOW security issue since the web begins.
Use POST, flag your forms, period.
I'd think you'd be ok if you let users upload their images to your own webserver? Its only the external sources that would have this vulnerability?
Holding aside the vulnerabilities attendant upon allowing files to be uploaded to your server.
What I like about sollution #2 (Secret hidden form value) is that it also helps to handle spam bots, the kind of scripts that flood every unprotected shoutbox with ads, links to various 'adult' sites and so on.
Anyway, nice post there, Jeff.
Excellent explanation and reccommendations. There are more XSS, SQL injection and XSRF-type threats out there than one can shake a stick at.
There's a good post at Vaclav's Blog about the related issue of insecure web browsing. Organizations can try to secure their code against XSRF and XSS all they like, but if a single member of the group starts browsing on infected websites, it puts their operation at risk of a breach. Post: The Web Browser, Security Threat Number One. http://www.pcis.com/web/vvblog.nsf/dx/the-web-browser-security-threat-number-one
Jonathon on October 2, 2008 10:24 AMYeah - I looked at stackoverflow and didn't like the concept nor the implementation. But I still hope it is popular for him.
Philip on October 10, 2008 1:04 PMLook at http://amareswar.blogspot.com/2008/10/interesting-findings-on-csrf-cross-site.html
amareswar on October 21, 2008 1:06 PMThis is a problem that had me stumped at work for a while!
We have our own closed source CMS system and on sites that had comments modules attached we were getting users complaining of being randomly logged out all the time. I spend hours trying to figure out what was going on!
We used to destroy sessions if the user went to any page with ?logout=true in the url, that has since changed and more care is taken over what is allowed in comment boxes!
Roon on October 23, 2008 10:55 AMSure, Having a hidden secret value in the form will help against some attack vectors, but what about a javascript that first requests an entire HTML page and then parses out the hidden key?
Sure, its makes it slightly more complex, but still not a 100% solution to the problem!
interesting blog, thank you for sharing. One might want to point out that a lot of CSRF attacks stem from the user's browser. Some browsers (Chrome for example) have built in sandboxes to prevent CSRF... just a thought. Again thanks for sharing.
anonymous on December 10, 2008 9:27 AMWeb development is scary by default.
Hoffmann on September 23, 2008 04:13 PM
Absolutely!
StingyJack on December 11, 2008 7:44 AMSo basically don't allow any user input that is used with requests. Like href or src and encode everything else. Problem solved.
http://megavibor.ru/
Dude, Don't Click: http://tinyurl.com/amgzs6
Benjamin W. Smith on February 12, 2009 9:56 AMIt's not CSRF, it's Clickjacking:
The page loads an iframe that prepopulates the twitter status form,
repositions the frame, and makes it transparent (and positions it in
the z-index above the button). When the user clicks the button,
they're actually clicking the iframe, which clicks the button ON
twitter, bypassing the CSRF protection. Nice (-:
Simplest solution: twitter shouldn't allow the form to be populated
from the URL.
S
erm.. that previous comment was about the Twitter worm going around today. Sorry about getting my wires crossed, there (-:
S
Hiii
interesting blog, thank you for sharing. One might want to point out that a lot of CSRF attacks stem from the user's browser. Some browsers (Chrome for example) have built in sandboxes to prevent CSRF... just a thought. Again thanks for sharing.
a href=http://ioffersearch.comIoffersearch.com Blogs - Just another Ioffersearch.com weblog/a
Hii
It's amazing how many security breeches there are out there. Just makes you wonder why sites still display those useless secure icons. How often is a site actually secure!?
a href=http://ioffersearch.comIoffersearch.com Blogs - Just another Ioffersearch.com weblog/a
ioffersearch001 on March 7, 2009 4:50 AMinteresting blog, thank you for sharing. One might want to point out that a lot of CSRF attacks stem from the user's browser. Some browsers (Chrome for example) have built in sandboxes to prevent CSRF... just a thought. Again thanks for sharing.
Olaf on May 13, 2009 1:48 PMThank you for article. I took great pleasure to read
site ekle on June 9, 2009 2:24 AMreally thanks.I was looking for this kind of infos on the net.finally after my some search I found here.this article is the right place I search mostly
beylikdüzü halı yıkama on July 3, 2009 7:30 AM"The trick here is that remote XmlHttpRequest calls can't read cookies."
That's not entirely correct. It depends.
What about Cross Site Tracing (XST)?
http://www.owasp.org/index.php/Cross_Site_Tracing
Do not let us forget that cookies STILL get sent along with ALL requests even though cookies are flagged as httpOnly.
httpOnly only prevents an attacker from using XSS flaws to perform attacks such as session hijacking through the use of document.domain etc.
Whereas document.domain cannot read cookies belonging to another domain (I am thinking of a malicious page which, as in some of your examples, could execute some JavaScript code without the user noticing), it is still possible in some circumstances to access that information even with an XmlHttpRequest, but performing a TRACE request instead.
The response to TRACE request will return (because of the debugging purpose of TRACE requests), in the body, the same text the server received with the request. Therefore, in the response text you will find the content of the cookie, regardless of the httpOnly flag, and regardless of whatever controls you peform on GET/POST requests.
This, because as said browsers still send all cookies with all requests.
Luckily, nowadays this only works with certain browsers, but what I meant to say is that in some cases *it is* possible to read cookies with remote XML HTTP requests. It is enough to think of how many companies still use older versions of Internet Explorer (affected) and do not allow staffs do update their systems...
I certainly don't usually forget, among other things (some of which you have also mentioned in this great post), to disable the TRACE method on my webservers.
But how many do as well? :)
I think this is still today something which we can't yet completely ignore.
Vito Botta on July 18, 2009 5:14 AMOf course I meant document.cookie, BTW
Vito Botta on July 18, 2009 5:21 AMThank you very good ;)
www.sonsayfam.com
Thank you for article. I took great pleasure to read
Sohbet on July 21, 2009 10:41 AMThe article written by your very good, I like it very much.
wholesale China on August 10, 2009 2:57 AMThis is my favourite web app security vulnerability. Thanks for helping get the message out.
Simon Willison on February 6, 2010 10:38 PMIt always amazes me the sites that people will exploit. Can you think of one good reason to take advantage of SO?
I think that when a hacker is discovered, his entire IP block should be blocked. Hopefully 209.x.y.z won't do anything bad! :)
Jason P-R (stalepretzel) on February 6, 2010 10:38 PMDennis: If you have an XSS hole in your site, there's absolutely nothing you can do to protect against CSRF. Referer protection isn't the issue - if an attacker can inject JavaScript in to your site they can force your users to perform any action they want.
Anyone interested in CSRF (which should be any professional web developer by now) should read this paper:
http://www.adambarth.com/papers/2008/barth-jackson-mitchell-b.pdf
One of the interesting revelations in there is that between 3 and 11% of hits to popular ad networks have had their referrer header stripped. Referrer stripping is much more common than most people realise.
The paper also introduces login CSRF attacks, where an attacker uses CSRF to trick a user to log in an account on a site belonging to the attacker. For example, trick a user in to signing in to PayPal using the attacker's account, then wait for them to save their credit card details. Pretty scary, and it means that you can't assume a user is already logged in when you implement CSRF protection.
I had to add CSRF protection to an existing PHP application today, and ended up using an output buffering trick to do it (rather than rewrite dozens of existing forms across many thousands of lines of source code). If anyone's interested, the code is here:
http://simonwillison.net/2008/Sep/24/csrfprotect/
Simon Willison on February 6, 2010 10:38 PMIronically, the double-submitted cookie solution is prevented by your earlier security measure of making your cookies HttpOnly.
Now, I really don't get this. JavaScript isn't needed. The idea is to send the value in the cookie also in the GET/POST vars, well when the web server app sends the page to the browser it knows the cookie data and can place the GET/POST var in any form or link - which is protected from scraping as noted in Jeff's point 2.
So really, point 2 and point 3 are largely identical: point 2 wants you to send user specific form data, and point 3 sends cookie specific form data. In point 2, you match the form data with the user identified by cookie data, and in point 3 you match the form data with cookie data (no need to look up user info). Point 3 goes through the extra pain of dynamically adding the cookie to the form, but doesn't need to.
If it's just a cookie/form match that's used, vulnerabilities could exist when adding cookie headers is allowed (older Flash definitely, and maybe older XMLHTTPRequest?).
It's amazing how many security breeches there are out there. Just makes you wonder why sites still display those useless secure icons. How often is a site actually secure!?
Jason P-R (stalepretzel) on February 6, 2010 10:38 PMThe comments to this entry are closed.
|
|
Traffic Stats |