Every web developer out there knows there is a good chance that his or her web application will become the target of someone trying to do harm to your website or its users. If you built a popular website, it seems like hackers are willing to spend lots of hours to find even the smallest hole in your digital walls. Sadly, it isn’t enough to simply keep your site out of the Moz top 500 to remain safe. That’s because low traffic sites might also be stumbled upon by one of the zillion robots scanning these sites for vulnerabilities.
The Open Web Application Security Project (OWASP) publishes a list with the most common threats every year. Here’s last year’s list:
- A1 Injection
- A2 Broken Authentication and Session Management
- A3 Cross-Site Scripting (XSS)
- A4 Insecure Direct Object References
- A5 Security Misconfiguration
- A6 Sensitive Data Exposure
- A7 Missing Function Level Access Control
- A8 Cross-Site Request Forgery (CSRF)
- A9 Using Components with Known Vulnerabilities
- A10 Unvalidated Redirects and Forwards
If any of these make you scratch your head you should probably take some time to read about them first. Injection attacks and XSS vulnerabilities are very well known and most webdevelopers take at least some measures to prevent them. This article is about another common but many times overlooked threat on this list: the cross-site request forgery (CSRF).
How the internet works
When someone visits a webpage, the browser composes a HTTP request and sends it to the webserver. The webserver then responds accordingly. The response might contain a webpage (html), a PDF document, an image or whatever you requested. After the response is sent back, the webserver has completed its task and can now close the connection. In most cases it is perfectly acceptable that the webserver has forgotten all about you at this point.
Browser: hey server, I would like to see “nice_page.html”
Server: let me look for that file…oh here it is, catch! I’m going to sleep now.
Sometimes it’s necessary that the server remembers you for subsequent requests. A typical example is when someone logs in and browses to a protected page. This is usually accomplished by passing around a secret token called a session ID. The server generates the session ID as soon as the first request is received and usually remembers it for the next 20 minutes. The session ID is sent back to the browser during the first response. When the next request is composed, the browser automatically sends the token along with the request.
Browser: hey server, I would like to see “protected_page.html”. My Session ID is ‘12345’
Server: Hi ‘12345’, let me check if I have seen you in the last couple of minutes…yes, I still remember you! You are allowed to view ‘protected_page.html’, so here you go.
Even though the server correctly verified the session ID, it cannot know for sure where the request originated from. It trusts the browser because it was able to give a session ID for a user that is allowed to view the requested page. This simple fact is exploited during a CSRF attack.
The target page
Take a look at a document called change_password.html with the following markup:
<h1>Change your password</h1> <form method=’post’> New password: <input type=’password’ name=’newpassword’/> <input type= ‘submit’ value= ‘Change password’> </form>
This renders to a pretty page like this, hosted on our imaginary domain: http://www.greatdomainname.com/change_password.html
When a logged in user hits the submit button, the browser knows it has to post the new password to greatdomainname.com. By convention, it will send the session ID along with the post request as explained earlier. The server can now process the password change request for the user.
The attackers page
Now here’s a slightly different version of the html that an attacker can host anywhere on the internet.
<h1>Free ice cream!</h1> <form method="post" action="http://www.greatdomainname.com/change_password.html"> <img src="icecream.jpg" /> <input type="hidden" name="newpassword" value="h4ck3d!" /><br /> <br /> <input type="submit" value="Click here for your free ice cream!" /> </form>
Which renders to something like this:
Notice that the attacker has instructed the form to submit to another domain then it is hosted on.
When a user is logged in at greatdomainname.com and later on visits the evildomain.com, he/she might be tempted by the delicious free ice cream and therefore clicks the button. What will happen now, is that the browser composes a request with a valid session ID, but the attacker has chosen his own password by using a hidden input field with the same name!
In general terms, to make a CSRF attack succeed there are two prerequisites:
- The user must be logged in at the trusted domain – thanks to single sign on lots of popular sites, this is often true.
- The browser must be triggered to submit the malicious form. It isn’t always as obvious as the free ice cream sample. In fact, the whole Ice cream page isn’t event necessary, since the attacker could post the form directly in the onload event of the document with some javascript. In most cases, an attacker uses another exploit like XSS to inject the form into another website. The victim couldn’t possibly have a clue of what’s going on.
Ok, you scared me. What can I do to prevent CSRF ?
Depending on the platform you’re using there are pretty good solutions available. The key is to add an extra (hidden) input field to the form, which value is set to a value that only the server knows about. By checking this field when the request is received, the server is able to check the origin of the request.
Here are some platform related links regarding this subject:
ASP.NET MVC: http://www.asp.net/mvc/overview/security/xsrfcsrf-prevention-in-aspnet-mvc-and-web-pages
PHP: http://www.wikihow.com/Prevent-Cross-Site-Request-Forgery-(CSRF)-Attacks-in-PHP
Ruby: http://ruby.about.com/od/security/a/forgeryprotect.htm
Java: http://java.dzone.com/articles/preventing-csrf-java-web-apps