Cross-Site Request Forgery

Just like cross-site scripting, cross-site request forgery (CSRF) is essentially a way to bypass the defenses of the same-origin policy, but it works in a completely opposite way. The simplest way to describe the difference between these two attacks is to look at it from a perspective of trust. When you look at a web site, you trust that what you’re seeing actually came from that site. This is the trust that the XSS attacker exploits: he injects his own content that looks as if it came from the server. But there’s another trust relationship at work, too. Not only do you trust that what you’re seeing came from the web server, but the web server also trusts that what it’s seeing came from you. This is the trust that the CSRF attacker exploits.

Your Plan

By following these steps, you can prevent even the most highly skilled cross-site scripting attackers from injecting their malicious script and HTML into your web pages.

Image Remember that XSS comes in three flavors: reflected XSS (vanilla) is the most common; stored XSS (rocky road) is the most dangerous; and local XSS (French vanilla) is similar to reflected XSS but with the twist that the vulnerable code comes from the client-side code instead of the server-side code. A similar “fourth flavor,” HTML injection (frozen yogurt), works on a similar principle against the same vulnerabilities, but doesn’t require any JavaScript to exploit and can have even more damaging effects.

Image Encoding untrusted content before displaying it is the best way to prevent all forms of XSS. Since you’ll need to encode output in so many different ways depending on where it’s being written in the page (into HTML text, an HTML attribute, a URL, and so on), it’s best to avoid writing your own encoding library. Instead, use one of the freely available encoding libraries like the OWASP Enterprise Security API (ESAPI).

Image Another good XSS defense is to sanitize untrusted input to remove any potentially malicious script. Again, don’t reinvent the wheel; use a sanitization library like OWASP AntiSamy or the Microsoft Web Protection Library.

Image If you have a social web site and you want your users to be able to decorate their posts with certain markup elements like bold tags, italics tags, or tables, consider accepting a lightweight markup language like Wikitext instead of the full set of HTML. These types of markup languages will usually provide all the functionality you need with only a fraction of the attack surface of HTML.

Image Apply HttpOnly flags to any cookies that you don’t need to access through client-side script. If nothing else, be sure to protect your session identification and authentication cookies.

Image Refactoring your application to support Mozilla’s Content Security Policy (CSP) can provide a formidable defense against XSS attacks, but unless a significant number of your users are on Firefox 4, it may not be the best use of your resources.

The good news is that CSRF is a lot less common than XSS is. However, that’s about the only good thing you can say for CSRF. It’s easier for an attacker to write a CSRF exploit than an XSS exploit, since CSRF attacks don’t require any kind of technical expertise with scripting code. CSRF vulnerabilities can also be a lot harder to fix than XSS. Sometimes large features may need to be redesigned in order to prevent CSRF. This is a hard pill to swallow for many organizations, and they may end up taking easier half-measures that leave them still open to attack.

Let’s look deeper into just how CSRF attacks really work, how they differ from XSS, and why the same-origin policy doesn’t protect you. Next, we’ll discuss some popular yet ineffective CSRF defenses that you should avoid; and we’ll finish with some recommendations of better CSRF defenses that will hold up against attack.

Cross-Site Request Forgery Explained

When we were talking about cross-site scripting attacks in the last section, we showed some ways that attackers can use to “trick” a browser into making HTTP requests without any confirmation from the user. When a browser renders an HTML element like <img> or <object>, it automatically sends an HTTP GET request to the URL specified by that tag, even if that URL is in a completely different origin. Usually this is completely harmless, as the example in Figure 6-7 shows. But this functionality can be abused by attackers. As we showed earlier, an XSS attacker will use this technique to sneak confidential data like session tokens from a vulnerable page back to himself at his own server. CSRF attackers also use this same technique, but for a completely different purpose.

Image

Figure 6-7 A browser automatically makes a (nonmalicious) request for an image from a different-origin site.

Remember that whenever a browser sends an HTTP request, it will automatically add any cookies it has stored for that domain and path. This includes any authentication or session identification cookies, too. So for example, once you log in to www.bank.cxx, every request your browser sends to www.bank.cxx will include your authentication cookie.

CSRF attacks take advantage of this browser behavior to make your browser send requests with your cookies on behalf of the attacker. What kind of requests might an attacker want to send with your cookies? For a bank, he might want to make you send an account transfer request to move your money into his account. This request might look something like this:

Image

If the bank application only relies on an authentication cookie and/or a session ID cookie to validate the request, then this attack will succeed, and the attacker will be $1,000 richer at your expense. All he needs to do is to get your browser to make a request for this page, and as we saw before, there are lots of ways that he can do this, like <img src> or <script src>. Figure 6-8 shows an example of this attack in action. And unfortunately, the same-origin policy is no help here, for the same reason it wasn’t any help in preventing XSS attacks from sending data back to an attacker at a different domain: The only thing that’s important to making the attack work is sending the request, not reading the response.

Image

Figure 6-8 A browser automatically makes a malicious request to transfer funds from the user’s bank account at www.bank.cxx.

If you’d like to see a video of a more advanced, real-world example of this kind of attack, the page evilpacket.net/2011/may/17/stealing-bitcoins demonstrates a live exploit against the Mt Gox bitcoin exchange. MtGox.com is a site where users can exchange their virtual bitcoin currency for U.S. dollars (and vice versa). In the video, the attacker crafts a malicious page such that anyone who visits it will automatically send the attacker $10.30 worth of bitcoins. We’ll show exactly how to duplicate this kind of attack later in this chapter, but you might find the video entertaining and enlightening in its own right.

HTTP GET and the Concept of Safe Methods

Part of the problem with a web application design that allows requests like “transferFunds?amount=1000&to_account=12345678” is that it’s violating the principle that HTTP GET requests should be “safe requests.” In short, safe requests should have no persistent side effects on the server. Checking the balance of your bank account would be a safe action; just looking at the value doesn’t change it. But transferring funds from your bank account is definitely not a safe action; your account balance is permanently changed as a result of the action taking place.

ImageNote

You may have heard the term “idempotent” being used to refer to HTTP requests that have no persistent side effects. However, idempotence is actually a slightly different concept. An idempotent request is a request that can have persistent effects, but making the request multiple times has no additional effects after the first. For example, deleting a file is an idempotent request. The first time you make a request to delete a certain file, it has an effect—the file is erased. But if you make ten more requests to delete that same file, none of them will have any additional effects, since the file is already gone.

The W3C specification for HTTP says that HTTP GET (and HEAD) methods should only be used for safe actions. If you break this rule, not only will you make it easier for attackers to exploit CSRF vulnerabilities in your site as we just showed, but you can also inadvertently add other non-security-related usability problems to your site as well. One good example of this is the (now-discontinued) Google Web Accelerator.

Google Web Accelerator was an application to help speed up web browsing. If a user had the accelerator installed, whenever he visited a page, the accelerator would silently fetch all the URLs linked from that page. That way, once the user was done reading that page and decided to click on to another one, the content for that next page would already be loaded and the browser could just display it instantly. The problem was that some web sites broke the W3C specification and made page links to items with persistent effects. Google Web Accelerator would then call these links automatically.

For example, let’s say your bank added a link to its checking page so that users could click it to open a new savings account. If you were using Google Web Accelerator, when you went to view your checking balance, the accelerator would automatically request that link in order to pre-cache the results, but with the unfortunate side effect that you would open a new savings account whether you wanted to or not.

You should definitely use HTTP POST (and/or PUT and DELETE) whenever you need to allow users to take actions with persistent side effects, but this step alone won’t prevent CSRF. Let’s take a look at why, and also take this opportunity to debunk some other misconceptions around CSRF defenses.

Ineffective CSRF Defense: Relying on POST

In terms of defense, cross-site request forgery is probably the most misunderstood web application vulnerability. Many people try to prevent CSRF by taking shortcuts that end up leaving them still vulnerable or sometimes even worse off than they were in the first place. Just as some people mistakenly try to defend against XSS just by filtering out <script> tags from user input, other people mistakenly try to defend against CSRF just by changing their application to use POSTs and reject GETs. This is a good start, but it’s only a start, since as we’re about to see, it’s only slightly harder to exploit a POST-based CSRF vulnerability than a GET-based one.

Just as with the POST-based reflected XSS vulnerability, if an attacker has some help from an accomplice web site, he can create an automatically submitting form that posts to the CSRF-vulnerable page.

Image

Again, all he has to do is lure you to this page, and your browser will automatically send this POST request to the bank along with your cookies.

Ineffective CSRF Defense: Checking the Referer Header

Besides thinking that POST alone will save them, another common CSRF defense mistake is to validate the request based on the value of the referer header.

ImageNote

Yes, “referer” really is the way that particular header is spelled, despite the fact that “referrer” is the correct English spelling. If you’re interested in the history behind this bit of web trivia, check out the W3C mailing list archive page at http://lists.w3.org/Archives/Public/ietf-http-wg-old/1995JanApr/0107.html.

Browsers add referers to outgoing requests to let the web server know where the user is coming from. For example, if you’re checking out convertibles at www.sportcars.cxx/new_models.html, and you click a link on that page to take you to www.bank.cxx/loans.html to apply for a loan, the browser will add a referer header like this to your request:

Image

Some developers will try to use this feature to prevent CSRF. They check whether the incoming referer value is coming from the same domain as the page, and if it’s not, then they block the request. So if an attacker sets up a CSRF exploit page at www.badguy.cxx/evil.html that automatically posts to www.bank.cxx/transferfunds, the bank will see that the transfer-funds request is actually coming from www.badguy.cxx, and it won’t make the funds transfer. This would be a really elegant solution to solving the CSRF problem, except for two facts: First, referer headers aren’t sent for every request; and second, referer headers can be spoofed.

Whenever a user manually types a URL into their browser address bar, or when they click on a URL in an e-mail, or in a non-web-based native OS application like a word processor, the browser that services that request doesn’t add a referer header. It can’t; it wouldn’t really have any meaningful referer to apply. Even if you have your browser open to www.sportscars.cxx and you manually type in www.bank.cxx, your browser doesn’t add “www.sportscars.cxx” as the referer. So if you’re planning to check the referer header to validate requests, you have two options. You can either reject all requests without referers, which means users can’t type your URL into their address bar and can’t access it through e-mail links or native applications; or you can allow all requests without referers, which means you’ll be vulnerable to CSRF attacks from e-mail links and native applications.

You can work around this issue by creating a “landing page”—a welcome page that users can access before they log in to the site and that allows all access from any referer or no referer. Only requests to pages not marked as special landing pages would then need an appropriate referer. This would be a somewhat less elegant solution than just checking the referer, but at least it would be fairly easy to implement. Unfortunately, it won’t help in the case where an attacker can spoof, or change, the referer header value.

Some older browsers allow XMLHttpRequest script objects to set arbitrary header values, including the referer header. In this case, all an attacker has to do is specify a known good referer value, and the attack will go through. Older versions of Flash (7 and 8) also allow an attacker to spoof the referer. While this problem has been patched in newer versions of the applications, it’s still considered bad practice just to rely on the referer value.

Ineffective CSRF Defense: URL Rewriting

Since CSRF vulnerabilities happen because applications store authentication tokens in cookies, which get automatically submitted with every request, you could prevent CSRF by using cookieless authentication. Cookieless authentication uses a process called URL rewriting to put the authentication token into the URL instead of in a cookie, like this:

Image

Unlike referrer checking, this actually is a pretty good defense against CSRF. Assuming that the authentication token is a strong random number, it’d be pretty difficult for an attacker to guess a valid one. (However, the opposite is also true: if you’re not using a good, cryptographically strong random number generator [RNG], then an attacker might easily be able to come up with a valid token. So be sure to use strong cryptographic randomness for security features!) Even if an attacker did find a valid token, there wouldn’t be any point in tricking you into sending the request, since he could send the same request from his own browser just as easily.

Unfortunately, while URL rewriting does help prevent CSRF, in this case the cure is actually worse than the disease. It’s dangerous to pass authentication tokens around in URLs. Not only do you have the same risk of having the token sniffed by a man-in-the-middle as you do with cookie-based tokens, but you also open yourself to session fixation attacks as we talked about in the session management chapter.

Better CSRF Defense: Shared Secrets

We’ve talked about some wrong ways of preventing CSRF; now let’s talk about the right ways. The most thorough way of defending against CSRF is to implement a shared secret for each user session. The process is a little complicated, but it’s very effective, and just like XSS defenses, existing libraries are available that have already done most of the work for you.

This is how a shared-secret CSRF defense works: Whenever a user first logs in, the server-side code generates a cryptographically strong random number just for that user. (This number is also called a nonce, short for number-used-once.) It then associates the nonce with that particular user by adding it to his store of session state data. Now, whenever the server sends its responses back to the user, it includes the nonce as a hidden form field input. Figure 6-9 shows an example of this defense.

Image

Figure 6-9 A web server generates, stores, and sends back a unique nonce for the user.

One nice thing about this solution is that hidden form field inputs automatically get included when the user makes a request to the server, so there’s no extra work required on the user’s part or on the part of the client-side code. When the server gets the request, it checks for that hidden form field input value. If it’s missing, then it knows the request is a forgery. If it’s there but it doesn’t match the value it’s stored for that user, then it knows that request is a forgery, too. It only lets the request go through if the nonce is present and it does match the stored value. In Figure 6-10, you can see how a potential CSRF attack is blocked, because the attacker had no way of knowing a valid nonce for his intended victim.

Image

Figure 6-10 A CSRF attack is prevented by the shared-secret defense.

For a real-world analogy for this process, imagine that you and I want to send letters to each other in the mail. I’m afraid that someone else could send me a letter with your return address and forge your signature to it, and I wouldn’t know any better. So the very first time I write you a letter, I pull a random card from a deck of playing cards, which turns out to be the eight of hearts. I then drop the card in the envelope along with the letter and add a p.s. telling you to include the card when you write me back. The next time I get a letter from you, if the eight of hearts is there, then I’ll know it’s really you. If there’s no card, or if it’s the jack of clubs, then I’ll know someone is trying to trick me and I’ll just throw the letter away.

Of course, if someone steals one of our letters out of either of our mailboxes, or if they peek over our shoulder while we open our mail, then they could still forge a letter to me. The same is true of the nonce CSRF defense: It’s no help at all against a true man-inthe-middle attack. But otherwise, the forger only has a blind 1-in-52 chance of guessing the right card for the letter forgery, and assuming we use a strong enough cryptographic random value, we can reduce the odds that he’ll guess the right nonce for the CSRF forgery to 1 in hundreds of trillions.

ImageTip

Speaking of cryptography, you should be especially cautious when developing any security feature based on crypto. Crypto is tricky enough that even experts sometimes get little details wrong, and when you get little details wrong, the crypto gets broken. Leave cryptography to the professionals and just implement pre-existing libraries whenever possible.

If you’re looking for a shared-secret CSRF defense library, once again OWASP comes through with a freely downloadable version called CSRFGuard. (The OWASP ESAPI library we discussed earlier also includes some CSRF protections.) CSRFGuard is available for Java, .NET, and PHP and can be found with all the other OWASP libraries at www.owasp.org.

Better CSRF Defense: Double-Submitted Cookies

A downside of the shared-secret CSRF defense is that the server-side code needs to store a secret value for each user session. It may not sound like a big deal just to store a few bytes’ worth of nonce, but if this is the only thing you’re using server-side session state for, then you could be adding a significant amount of complexity to your application or limiting its scalability just for this one defense. In this case, a better alternative is to use a double-submitted cookie.

A double-submitted cookie defense works similarly to the shared-secret defense, but instead of storing a separate secret nonce, the session identifier itself is treated as the secret. Whenever the server sends responses back to the user, it takes the session identifier and writes it into a cookie as usual, but then also writes it into a hidden form field. When the user posts the form back to the server, both the cookie and the hidden form field automatically get included with the request. The server then simply checks to make sure the two values are the same.

This is a good defense, because there’s no real way for an attacker to be able to guess a valid session identifier for a user—and if there were a way, he wouldn’t have to go to the trouble of CSRF, he could just send the request himself. This defense also has the benefit of being completely stateless: there’s no need for the server to store anything, even a few bytes of nonce.

There is an added danger here because you’re passing session identifiers around in the bodies of the HTTP requests and responses, but there’s an easy way to reduce this risk. Instead of adding the session identifier value itself to the hidden form field, add a one-way hash of the session identifier.

ImageTip

Use a strong cryptographic hash algorithm. As of this writing, the SHA-2 algorithm is still considered strong, but SHA-1 is beginning to show some weakness. And while MD5 is still very popular and very widely supported, it’s been broken by security researchers and you should avoid using it.

Prevent XSS

One critical step for preventing cross-site request forgery in your applications is to first prevent cross-site scripting. If an attacker can write script for your browser to execute, it will be really easy for him to write script that will make requests. He could make XMLHttpRequest calls, or just dynamically add new <img src> elements to the page.

Worse still, XSS vulnerabilities will allow an attacker to bypass either the shared-secret or the double-submitted cookie defense. Since both of these defenses work by including a secret token in a hidden form field on the page, all the attacker has to do is add a little script to pull that value out of the page:

Image
Reauthentication

Both the shared-secret and double-submitted cookie defenses have the benefit of being transparent to the end user; there are no extra steps he needs to take and he’s probably completely unaware that anything different or unusual is going on. While it’s great when security can be invisible and unobtrusive, in some situations you might want to take some extra precautions that require a little more work from the user.

Whenever you’re doing something especially sensitive, such as buying or selling stocks or doing any other kind of financial transaction, it can be a good idea to ask the user to verify his identity by logging back in to the application. This can be a little irritating to some people, but it definitely makes exploiting CSRF much harder. Even if an attacker manages to get a user’s authentication token, he still won’t have the actual password, so he won’t be able to re-authenticate.

What Being “Logged In” Means

One thing that does help to reduce the danger of CSRF vulnerabilities is the fact that any potential victim has to already be logged in to the vulnerable site for the attack to work. But all that being “logged in” means is that you have a valid authentication cookie, and this isn’t always easy to determine. Let’s say you log in to your bank and check your balance to make sure that your paycheck was auto-deposited this month. You see that everything looks good, so now you head over to Amazon to do a little shopping. Are you still logged in to your bank? Yes you are, even though you don’t have a page open to the bank site. The authentication cookie that the bank gave you when you logged in is still there in your browser’s cookie store. The bank site has no way of knowing that you’ve left and moved on to Amazon, so the cookie remains active and valid.

What if you close the browser tab or window where you were visiting the bank site—will you still be logged in? Possibly, depending on whether you have other tabs or windows open. Most browsers will share cookies across all of their tabs and windows. As long as you have at least one browser window open at any given time, the cookie will still be there in the cookie store.

ImageNote

In OS X, even closing all the browser windows won’t clear out the cookie store. Unlike Windows, some OS X applications—including Safari, Chrome, and Firefox—don’t automatically quit when all of their windows are closed. You have to explicitly quit the application through the menu or with the imageQ shortcut. Until you do this, all your session cookies remain in the store.

Your Plan

There can be a little more work involved in preventing CSRF than XSS, but by following these steps you’ll get on the right path without wasting your time reinventing defenses.

Image Never allow HTTP GET requests for actions with significant persistent side effects, like making purchases. Allowing GET for these functions not only makes it easier for attackers to exploit CSRF vulnerabilities, but it can lead to other usability problems as well. Instead of GET, use POST, PUT, or DELETE.

Image Don’t rely on the referer header as a CSRF defense. Referers aren’t always sent, and even when they are, they can be spoofed.

Image Writing the session identifier into the URL (that is, URL rewriting) does provide a good CSRF defense, but at the cost of opening the application up to other attacks like session fixation. Avoid this.

Image The best defense against CSRF is to write a shared-secret nonce into a hidden form field and then check that value on the server whenever a new request is received. Take advantage of a library like the OWASP CSRFGuard that will do this work for you.

Image Another good alternative CSRF defense that doesn’t rely on any stored server state is to double-submit the user’s session identification token in both a cookie and in a hidden form field. The server can then just check that both tokens match in order to validate the request. For extra security, write a one-way hash of the token itself into the form field instead of the token value itself.

Image Be sure to apply XSS defenses to your code too: Any XSS vulnerabilities in an application will automatically allow CSRF exploits that can easily bypass both the shared-secret and double-submitted cookie defenses.

Image For especially sensitive transactions, consider asking the user to re-authenticate himself with his username and password. This is a little intrusive, but most users will understand given the circumstances, and it reduces the risk from several different forms of attack.

Image “Remember me” features that store authentication cookies as persistent cookies rather than session cookies increase the risk of CSRF. As both an application developer and as an end user yourself, be cautious when using this.

So now that you know that session cookies survive even if the original tab or window is closed, you close all of your browser windows, quit the browser applications, and reboot your machine for good measure. There’s no way you could still be logged in to the bank now, right? If the bank used a session cookie to store the authentication token, then no, you won’t be logged in any more. But if the bank used a persistent cookie, then even after a reboot it’s possible that you’re still logged in.

Some web sites have a “Remember Me” or “Stay Logged In” feature that keeps the user logged in even after they close their browser. These features work by setting the authentication cookie as a persistent cookie—a cookie with a specific expiration date—rather than as a session cookie that only lives until the user closes his browser. If a user takes advantage of this feature, he’s much more likely to be exploited by a CSRF attack. In effect, he’ll always be logged in to the site, whether he has the site open right now or whether he hasn’t visited it in a month. As a web application developer, you’ll have to weigh the convenience of providing your users with a “Remember Me” feature against the extra risk this incurs.

Final Thoughts on Cross-Site Request Forgery

While cross-site request forgery might not be quite as epidemic as cross-site scripting, it’s still a serious problem that web applications need to address. Always remember that there are two directions of trust necessary to make the web work: Users need to be able to trust that their content is really coming from the server, but the server needs to be able to trust that its requests are really coming from the users, too. The responsibility for ensuring both of these trust dependencies falls on you as the application owner. Don’t let them down!

We’ve Covered

Cross-site scripting

image The three flavors of cross-site scripting

image HTML injection

image Solving the problem: Encoding output

image Solving the problem: Sanitizing input

image Solving the problem: Using an alternative lightweight markup language

image Browser-specific defenses

Cross-site request forgery

image HTTP GET and the concept of “safe methods”

image Ineffective CSRF defenses

image Solving the problem: shared secrets

image Solving the problem: double-submitted cookies

image Reauthenticating before performing sensitive actions

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset