Web Application Security Checklist
Cross-site Scripting (XSS)
Is all user input sanitized before entering the database? Are user-defined values escaped on output? Also, watch out for reflected XSS where a user parameter is directly output in the view (even if it never gets stored anywhere -- for example, a search term).
- Escape output in views with html_escape (h)
- Use Erubis or another auto-escaping alternative for rendering views
- Strip tags before saving values in db. Plugins: sanitize_params, xss-terminate, acts_as_santized
SQL Injection
Is all user input sanitized before entering the database?
NOTE: We get this for free in Rails, just make sure you're using ActiveRecord's DSL and not directly interpolating anything in traditional Ruby fashion. Example:
GOOD
User.find(:all, :conditions => ["name = ?", params[:name]])
BAD
User.find(:all, :conditions => ["name = #{params[:name]}")
Parameter Injection
Mass assignment or attribute values is something used extensively in Rails code for form targets; however, we need to be cautious here as a clever hacker can easily inject parameters into a request. For example, we may expect the following request:
POST http://site.com/users user[name]=Brian&user[email]=brian@site.com
And our controller could be setup the following way:
def create
@user = User.create(params[:user])
...
A malevolent user can add any other parameters he/she wants to the request and our User.create code will call <param>= for each of those parameters. This means a user can easily change foreign keys, etc -- in fact, they can call any method that ends in an equals sign and takes one argument. Fortunately, Rails gives us a very simple mechanism for protecting these values: you can use attr_protected or attr_accessible in your models to disallow mass-assignment for particular attributes. Personally, I recommend always using attr_accessible over attr_protected since it follows the principle of implicitly disallowing everything that isn't explicitly allowed.
Cross-site Request Forgery (CSRF)
Use a token to ensure that all forms (and any non-GET requests) are coming from the site rather than an external request. Rails 2+ gives you this functionality by default.
HTTPS/SSL
Not much to say here. Use SSL! With the power of processors ever-increasing and the cost ever-decreasing, there's not much of an excuse for not using SSL everywhere on your site. After all, scaling at the load balancer level really isn't that hard anyway.
Cookies
Use http-only on your cookies everywhere possible. If a user finds an XSS hole on the site, it gives you one more layer of protection against session hijacking. Also, for authenticated sessions, you probably want to set the secure flag on your cookies to prevent the session_id from being passed around in clear-text.
Authenticated Sessions / Login
A few easy things to mitigate session hijacking:
- Destroy session on login and logout (generating a new session id)
- Temporarily lock accounts that have multiple failed login attempts to prevent brute force attacks
- Require strong passwords (8 characters+, at least one letter and one number)
Logging
Ensure that sensitive data is filtered out before being logged. This includes credit card info, passwords, etc.
Data Enumeration
Is there an easy way for a user to determine whether a particular username exists in the system? Any other sensitive data that would be easy to enumerate. Naturally, this only matters in certain situations, so there are no hard and fast rules here. Sample targets include:
- Does forget password give a different message if a username/email does or doesn't exist?
- Is the message on a failed login different if the username/password doesn't exist?