Web Security: XSS and CSRF
20 Oct 2011
At work I've been making a foray into web development. I've always been one to be very interested in "how to do it right" rather than just "get it done" and stop when it looks like it works. Security is one of the things where it works perfectly for the user -- but also for the attacker if you don't do it right. I've had a lot of experience with securing things with encryption, but the web is an entirely new (and scary) world.
The two attack vectors I looked into is XSS (Cross-site scripting) and CSRF (cross-site request forgery, aka XSRF).
I know this topic is covered in detail in other sites (more authoritative than mine), but I hope that this article helps provide another perspective or at least increases the awareness of these issues in the field.
Cross-Site Scripting
This exploit happens when someone can inject script code into your site. The primary way an application is vulnerable to this is when you provide unescaped code to a client that would be interpreted as HTML. An obvious way would be a forum post that allowed users to post a <script>. This script could steal information, the most important being the session cookie information and sending that to the attacker's site who can then "log in" as you.
OK, everyone knows to protect forum posts, of course. A good programmer protects any user-developed content on your site. But keep thinking -- one place you might not realize: The error handling pages. Everyone loves a good "404" page with a message "Oh, the URL ABC you tried to visit doesn't exist." Typically some web admin set that up once and no one thought about it again. Well, what if someone gets your user to visit (encoded of course) http://mysite.example.com/?<script>....? Now they have just injected a script into your site through your 404 page. What about a login page? "The user ABC doesn't exist". What about the user <script>bad_code();</script>? Does he exist?
There is plenty of more information on the Internet, and I'm not trying to outdo Wikipedia here, but here are some tips:
- Don't echo back ANYTHING to the user without escaping potential HTML code on the server side. In the case of the 404 and login examples above, consider not even echoing anything back at all.
- If you have client-side JavaScript reading from your server, escape there as well for another layer of precaution when it is possible.
- Consider a cookie value that wouldn't work if requested from a different machine (IP address) or browser. This isn't bulletproof if the attacker has control of the network to spoof this information, but it makes it a lot harder.
Another resource is this OpenAjax site page, which also covers CSRF.
Cross-Site Request Forgery
Learning about this really blew my mind, because I understood XSS but I didn't realize CSRF. CSRF occurs when an attacker causes an operation on your site. Even though the "request" came from the attacker's site, the browser will send the cookies (or HTTP authentication credentials) for your site to your site. This makes sense. Of course, the attacker can't see your cookies or see what you are doing on your site, but it might not matter.
In order to exploit a CSRF vulnerability, your application needs to have a request that performs an action beneficial to the user that is the same for all users. The typical example is the bank one, where visiting http://mybank.example.com/transfer?to=attacker@evil.com&amount=1234 will result in a money transfer. The attacker doesn't need the user's credentials nor does he even need to observe the request or response. That URL can be placed anywhere on the Internet easily, because most sites will let you publish content with images linked to any URL. Put that URL into the "img src" and the user will see a broken image but not know what happened. The link also needs to be put on a site that your visitors would visit as well, so of course large targets like Facebook and Google are far more likely targets.
OK, so HTTP "GET" requests with side-effects are bad style anyway. You can fix the img problem but not allowing this, but it doesn't fix the problem. If the evil (or compromised) site contains a hidden HTML form and javascript, it can perform a POST as well.
Unfortunately, to me it appears the browser cookie model and HTTP authentication models are broken in browsers, because they provide authentication information without prompting you. This makes sense otherwise every single page you click on would require the browser to ask you to confirm first. There's no way for a user to stop this except to always "log out" of your site and not view any other sites in that browser while logged in.
The solution is to foil the attacker by making it so that each user has a unique request that can only be generated by that user that does not involve solely the browser's cookie or HTTP authentication system. My understanding through reading is that any of the following ways to add user-specific data to the request would work:
-
If using AJAX:
- Take the value of the user's auth/session cookie; put it into the XML/JSON/form data. Since the evil site can't read your cookies, they can't form that request
- Put the cookie's value into an HTTP header. Headers can be forged by attackers but they still can't see the cookie.
- You could use a token from the server other than the auth cookie, but has to be specific to that user. But I'm not sure it provides any more protection.
- If not using scripting, generate an HTML form with a hidden field with a random value, saved to that user or session. Verify that the value is the same on submit.
You can also read more at Wikipedia.