Ruby regular expression fun

I found several regular expressions to validate all sorts of things, URLs, names, email addresses, et cetera. Here is an example for an email address validation, I found:
 
/^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i 
 
How do you like the following email address, which validates fine with this filter?:
 
[email protected]%0A<script>alert('hello')</script>
 
%0A is a line break.
^$ in Ruby match LINE begin and end, not the overall begin and end, \A and \z does the job! The same JavaScript works in the part before the @. This is a first step to disallow HTML and line breaks:
 
/\A([^@\s<>'"]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i 
 
A whitelist approach is always better (are there other characters in a name?):
 
/\A([\w\.\-\+]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
 
Edit: This will match most of today's email addresses, without comments. For email addresses compliant to the RFC 822, you can use this regular expression.

Session fixation in Rails

These attacks focus on fixing a user's session identifier known to the attacker, and forcing the user's browser and the web application into using this identifier. The first step in such attacks is to create a valid session identifier. While other session managements (in PHP, for example) accept arbitrary identifiers, and create a valid session with the session identifier if it does not exist yet, this is not possible in Ruby on Rails.Rails accepts only session identifiers which have been generated by itself, and will issue a new one if you propose an arbitrary one. So with Rails applications, an attacker has to access the site of web application in order to obtain a valid session identifier.

The next step is to force a victim's web browser into using this identifier, which is know as the actual session fixation. According to RFC2965, a website cannot set a cookie for another domain. So the attacker cannot set a cookie for the web application by luring him on a site that he controls. One possibility to fixate the session, which requires the ability to sniff and modify data traffic between the user and the web application, is to change the session identifier in a response from the server. Or, if he has access to the user's DNS-server, he can redirect requests to a server taken over by the attacker, which forwards and modifies request to and from the original web server. The next two possibilities to fixate the session have to do with User Agent Injection (also: XSS) vulnerabilities. The first approach to do this is to change the cookie by setting document.cookie to a desired value, for example in JavaScript:

 

document.cookie='_session_id=16d5b78abb28e3d6206b60f22a03c8d9';

However, a web application administrator might know this vulnerability, and therefore has an appropriate flter for that. A less known approach is to set the cookie with an injected <META> HTML tag. Normally, <META> tags belong between the <HEAD> tags at the beginning of the document, however, it will be processed by the browser anywhere in the document. Inject this line, for example:

<meta http-equiv=Set-Cookie content="_session_id=
4cf69dc5fee46251bdc1f99ef55f52b6">

After the session identifier has been successfully injected into the user's browser and he logged in to the trap session, the attacker will be able to use the session, until the user logs out.

A good countermeasure against creating a valid session identifier is to not to issue them on pages that everyone can access. That means, for example the login page, should not send a session identifier to the yet unauthorized user, do so only after the user has been authorized. With the following you can turn the use of sessions in a specific controller off:

 

session :off # turn it off for any action
# or turn it off for any but these actions
session :off, :except => [ :change_password, :edit, :delete ]

However, you have to bear in mind that a request to any URL which issues a session identifier to logged in users also issues it to unauthorized users, but redirects then. And if the application is open to everyone, i.e. you can sign up on your own, you cannot prevent an attacker from signing up and retrieving a valid session identifier.

One of the most effective countermeasures is to issue a new session identifier after a successful login. That way, an attacker cannot use the fixed session identifier. By the way,this is also a good countermeasure against session hijacking. The following lines create a new session in Rails:
 

reset_session

Session hijacking

Session hijacking is a class of attacks where an attacker gets hold of a session identifer of another user. Consequently, he gets access to the web application, because the sessionidentifer serves as temporary login credential. The most popular way of hijacking a session is to steal the session identifer. There are several ways doing this.Most cross-site scripting attacks aim at stealing a user's session identifer.

By default, Rails stores the session identifer in a cookie. However, if one decides to use an URL parameter to keep track of the session identifer, he should be even more aware of the possibilityof session stealing. For example if your web applications contains an external link and a logged in user clicks on it, the target web site can see in its web server logs fromwhich URL (including the session identifer) the user came from.

A countermeasure against session id sniffing could be to encrypt the entire data traffc using SSL. However, if parts of the web site are not encrypted with SSL, such as the login or index page, the cookie will be transmitted nevertheless. To instruct the browser only to send the cookie over encrypted HTTPS and never over normal HTTP, you have to include the following line in the confg/environment.rb file.
 
ActionController::Base.session_options[:session_secure] = true

Another countermeasure is to save user-specifc properties in the session, verify themevery time a request comes in, and deny access, if the information does not match. Suchproperties could be the remote IP-address or the user agent (i.e. the web browser software's name), though the latter is less user-specifc. When saving the IP-address, you have tobear in mind that there are Internet access provider or large organizations that put their users behind proxies and these might change over the course of a session, so these users willnot be able to use your application or only in a limited way. Also, the attacker could be in the same local network and so both the victim and the attacker have the same external IP address. Although, if these drawbacks do not apply to your users, as it is the case for users of Intranet applications, for example, this will be an appropriate additional protection.However, the best countermeasure currently, is to expire sessions frequently.

Ruby on Rails sessions – introduction and expiry

As the HTTP protocol is stateless, a logged in client, for example, would have to provide his login name and password for every request he makes, because the server cannot maintain the state during subsequent user's requests. The idea of adding a state to requests is to save information about the exchanged data on the server (the session data), identified by a session identifier.

Rails saves the session data and identifier on the server, and advises the client side to store the same session identifier in a cookie. The cookie looks like the following:
 
# name = value
_session_id=16d5b78abb28e3d6206b60f22a03c8e8

 

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

In many web applications your session stays only valid either until you log out, or after a certain time after the creation of the session. It is not a good idea to use sessions that never expire, that allows an attacker unlimited time to brute-force a session identifier.Also the number of valid session identifiers rises over time and so rises the possibility of correct guessing of session identifiers.You could limit the time how long a session stays valid by setting the expiry time stamp of the _session_id cookie. However, as cookies in the web browser can be edited by the user, this is not the safest thing to do. It is safer to control the validity of a cookie on the server side.
 
Every user who accesses the web application creates a new session on the server which can eventually lead to a major performance drop or fll up your disk space and make your server incapable of acting. Not only to save disk space and for performance reasons, but also to let expire old sessions, you should remove old session data from time to time. Sessions are saved to files in Rails' /tmp/sessions, by default. When deciding which session files you can remove, the simplest way is to delete those files which were not changed within the last hour. However in a situation where sessions do not become invalid when the user logs out, or the user always forgets to log out, and an attacker got hold of the session cookie,he could write an automated script to access the web application every 10 minutes, for example. In such a case, the session would never expire.
 
You should use an automated script (via the cron command on Unix, for example) to clear expired sessions which were not accessed for some time AND the ones which were created a long time ago. Depending on the user behavior and on how much you want to protect the application, you should clear them every 5 minutes through to no more than 20 minutes.
 
If you use a database to store the sessions, of course the same rules apply.
 
To be continued…

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.

 http://www.railscasts.com/episodes/9

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

There are several login generators, user authentication plugins and access control frameworks for Rails, but most of them are declared beta. Here are the two major ones on RubyForge:

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

Recommended: This is a basic restful authentication generator for rails, taken from acts as authenticated. Currently it requires Rails 1.2 (or edge).

 

LoginSugar is based on LoginGenerator, so are several other generators. Restful_authentication is the recommended plugin as it doesn't have the vulnerabilities of the other generators. But you have to update to the newest version. Today I found several security holes in the other two:
 
1. When signing up for a new account, both of them can fall prey to mass assignment attacks (see previous post) which lets you bypass confirmation emails, get administrator privileges, basically set any value in the model. The controller contains the following line:
 
@user = User.new(@params['user'])
 
An attacker could save the signup page and add the following to the signup form:

<input id="user[verified]" name="user[verified]" type="hidden" value="1" />
<input id="user[role]" name="user[role]" type="hidden" value="admin" />

 
2. LoginSugar: If an attacker gets hold of a confirmation URL within the expiry time (usually 24 hours) he can use it to log into the application without knowing username or password. A countermeasure would be to let the link expire when it is being verified.
 
3. LoginSugar: If an attacker got access to the application, he can use the change_password method to set a new password without knowing the original one. This completely takes over the account. A countermeasure would be to ask for the original password when changing it.

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.