Session fixation in Rails
document.cookie='_session_id=16d5b78abb28e3d6206b60f22a03c8d9';
<meta http-equiv=Set-Cookie content="_session_id=
4cf69dc5fee46251bdc1f99ef55f52b6">
session :off # turn it off for any action
# or turn it off for any but these actions
session :off, :except => [ :change_password, :edit, :delete ]
reset_session
Session hijacking
Ruby on Rails sessions – introduction and expiry
The session identifier is a 32 bytes long MD5 hash value of the current time, a random number between 0 and 1, the process id number of the Ruby interpreter (also basically a (less) random number) and a constant string (foobar). This keeps the risk of colliding (i.e. occurring twice) session identifiers very low.
Session expiry
Don’t trust primary key parameters
By default Ruby on Rails URLs have the following format: http://www.domain.com/project/show/1, whereas "show" is the action to be performed and "1" is the project id, which is the primary key of the project table (i.e. a project's main identifier is the id, but it could be something else, such as the name). It will be used in the controller to load the project with the id 1 and present it to the user in some form or another.
You have to keep in mind that a user access control may hinder unauthorized people to log in to the application. But if you want to prevent users from viewing or altering certain information, you will have to take additional precautions. For example, even though you don't have a link in your application to show the project with the id 2, you cannot hinder the user from entering the URL http://www.domain.com/ project/show/2. That means you have to check permissions every time someone wants to access an object, for example by verifying ownership:
# find the project with the id from the
# URL which is owned by the logged in user
@project = Project.find(params[:id], :conditions => \\
["user_id = ?", session[:user_id]]
Also, the Rails Way has a good post on this:
http://www.therailsway.com/2007/3/26/association-proxies-are-your-friend
Working with files in Rails
In many cases web applications save user entered data to files and deliver file uploads. You should always filter file names that come directly from the user, as an attacker could use a malicious file name to download or overwrite any file on the server. If you use a file name that the user entered without filtering, for example, in a send_file() method, which sends files from the server to the client, then any file can be downloaded:
send_file( params[‘filename’] )
Even if you use send_file( ‘/var/www/uploads/’ + params[‘filename’] ), you can download any file, as every directory in Unix has a link to its parent directory, and a file name of ../../../etc/passwd downloads the system’s login information.
When it comes to filtering file names, don’t try to remove malicious user input, as there are very many possibilities to hide, for example, the parent directory link “../” to your filter. An attacker could use a different encoding, such as “%2e%2e%2f”, which the operating system might or might not understand. Or think of a situation where you delete all “../” and an attacker uses a string such as “….//”. So it is best to use the whitelist approach, which checks for validity of a file name with a set of accepted characters. And in case it isn’t a valid file name, reject it, and don’t try to filter out malicious parts.
A second step is to validate that the file name is in the expected directory. Example in a controller storing the files with attachment_fu:
basename = File.expand_path(File.join(File.dirname(__FILE__), ‘../../files’))
filename = File.expand_path(File.join(basename, @file.public_filename))
raise if basename =!
File.expand_path(File.join(File.dirname(filename), ‘../../../’))
send_file filename, :disposition => ‘inline’
Another approach is to store the file names in the database and name the files on the disk after the ids in the database. This is also a good approach to avoid possible code in an uploaded file to be executed. Think of a situation where an attacker uploads a file “file.fcgi” with code in it, which will then be executed when someone downloads the file. Of course, this does only happen if you store the uploaded files in a subdirectory of Apache’s DocumentRoot directory (Rails “public” directory normally).
On the other hand, when you allow users to name the file on disk, an attacker could overwrite important files or add new ones. That’s why web server processes should be run as non-root user, so an attacker cannot overwrite any file. FastCGI, for example, as set up in the Apache chapter, runs as the “apache” user, which shouldn’t have write permissions on files and directories. Only an upload directory (and some Rails directories as described in the Apache chapter) should be writable to that user.
Filtering Sensitive Logs
Railscasts has an interesting screencast on how to filter sensitive data from logs:
Are you accepting sensitive user data? Passwords, credit card numbers, etc. By default, Rails stores all submitted parameters in plain text in the logs. This episode will show you how to filter this sensitive input so it doesn't show up in the log file.
More on logins
Thanks for your comments. Scott writes in a comment:
Assigning things individually is only marginally more secure, given the fact that your example contains no server side validation of data or authorization. Almost invariably, your application would have some sort of administrative interface to choose set admins, and that action could be divined in the same way that the admin attribute could be. Granted, guessing that you post to /users/make_admin/1 to make an administrator is more obscure than guessing the user[admin] field, but we know better than security through obscurity.
Here's some more on logins:
When you create your own access control or modify generated ones, you have to keep in mind that MySQL requests are case insensitive by default, and thus at least user names will be the same though in a different case. MySQL provides a BINARY operator, which makes statements case sensitive, for example:
User.find(:first, :conditions => ["BINARY login = ? AND \\
BINARY password = ?", login, pass])
# finds the first user in the database which
# matches the login information case sensitively
This maybe desired or not, but you have to use the same method when signing up and logging in users, or user names might occur more than once with different cases.
Also if the login process failed, you should not present too much information as to why it failed. If you tell an attacker whether user name or password was wrong he can focus on finding the other part. You should also be aware of the possibility of brute force attacks on the login page. As a countermeasure you can save the number of failed login attempts on an account and disable the account or require to enter a CAPTCHA to log in after a certain number of failed logins.
LoginGenerator and LoginSugar security vulnerabilities
Quote: This is the popular generator for the rails framework which will outfit your application with a complete user management. It offers login, signup pages as well as great security and technology to protect certain areas of the application.
A fork of Salted Hash Login Generator which quit working around rails 1.1.3. It is a user system providing signup, login, email validation and forgotten password facilities. Sugar works with rails 1.2.3
- Restful_authentication, here is a screencast
Recommended: This is a basic restful authentication generator for rails, taken from acts as authenticated. Currently it requires Rails 1.2 (or edge).
<input id="user[verified]" name="user[verified]" type="hidden" value="1" />
<input id="user[role]" name="user[role]" type="hidden" value="admin" />
Do not create records directly from form parameters
The scaffold generator creates code like the following, which is allowedly easier to handle, but vulnerable:
@user = User.new(params[:user])
With this code, Rails will create a new user based on the values that the user entered. Any corresponding attributes in the parameter hash params will be set in the user model. Arbitrary properties of the new user can be set by an attacker, the user's privileges, for example. Given you have a user registration form like this:
<form method="post" action="http://www.website.domain/user/register">
<input type="text" name="user[name]" />
…
</form>
An attacker could change the form (by saving it to disk, for example) to the following:
<form method="post" action="http://www.website.domain/user/register">
<input type="text" name="user[name]" />
<input type="text" name="user[admin]" value="1" />
…
</form>
If the attacker knows that the User model has an “admin” column, the newly created user will have administrator rights. One solution to this problem is, not to use mass-assignment and assign each value individually.
User.new(:first_name => params[:user][:first_name])
Another solution is, to protect several properties so they can't be assigned using mass-assignment, but have to be set individually. The following line in your model will protect the “admin” attribute, i.e. it will be ignored during mass-assignment.
attr_protected :admin
If you want to set a protected attribute, you will to have to assign it individually:
@user = User.new(params[:user])
@user.admin = false
You can also use the whitelist approach (highly recommended), which allows attributes to be mass-assigned, instead of forbidding access to them. Use attr_accessible with the attributes you want to allow access to, instead of attr_protected to do this.