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.

Apache 2 file privileges and modules

File privileges

On Unix systems, the file and directory access privileges are crucial for security. If you let other people write files, that the root user also writes on or executes, then your root account could be compromised. For example, an attacker could modify the apache2ctl starting script and execute arbitrary code, next time the root user starts Apache. Someone with a write privilege on the log file directory could create a link to another file on the system, which will then be overwritten (if he overwrites /etc/passwd, nobody can login anymore). And if the log files itself are writable to non-root users, an attacker could cover his tracks. So important files, directories and its parents must be writable only by root, or the Apache user, respectively.
 

The following table shows which ownership and privileges the Apache files and directories should have. The ownership can be changed with the chown command, the privileges can be adjusted with the chmod command. Note, that the parent directories of these directories need to be modifiable only by root. All changes need to be performed in this order.

Subject

Ownership (user:group)

Privileges

Binary directory

root:root

755 (rwxr-xr-x)

Binary files, such as the httpd executable

root:root

511 (r-x–x–x)

Configuration directory and files

root:root

755 (rwxr-xr-x)

Log files and its directory

root:root

700 (rwx——)

Content files and directories

apache:apache

500 (r-x——)

Rails log and tmp directories and subdirectories

apache:apache

700 (rwx——)

Modules
Modules have to be chosen when compiling Apache, but, with the help of the mod_so module, they can be dynamically loaded or deactivated afterwards. It’s best to compile Apache with the required modules. You can use the following command to see which modules Apache has been compiled with, i.e. which are always activated:

# apache2 -l # or httpd -l

The following modules are a good basic:

  • Core, Http_core and Mpm_common: these are always needed
  • Prefork or Worker MPM: read the first part to learn more about them
  • Mod_alias, everything with mod_auth…, Mod_log_config, Mod_mime, Mod_negotiation, Mod_setenvif: see the Apache documentation for more on these modules.
  • These are extensions, but you need them Mod_rewrite (if you use FastCGI, for example), Mod_so (to load modules dynamically)
  • you can generally disable these: Mod_cgi, Mod_cgid, Mod_actions, Mod_env (for CGI scripts), Mod_dir, Mod_autoindex (directory listings!), Mod_info, Mod_status (they provide sensitive information!)

To be continued…

Apache 2 setup

Apache 2 introduced the multi-processing modules (MPMs), which provide networking features, accept requests and dispatch them to children to handle the request. You can choose from several MPMs at compile time in order to suit your needs.

The pre-forking server mode, which was the standard behavior in Apache 1.3, lives on in the prefork MPM, which is the default for Unix operating systems. The prefork MPM is a non-threaded, pre-forking web server, which is for compatibility with non-thread-safe modules.

The worker MPM is highly efficient serving static pages, but needs thread-safe libraries for dynamic content. Popular modules, such as mod_php and mod_perl, are not thread-safe and thus can't be used. But you can use any language interpreter with FastCGI, because it moves the workoutside of Apache.

 

Installation
The installation process differs from distribution to distribution: you can either use the apt-get command to install a package, or download the source code directly from the Apache web site. If you don't trust the distribution server, or you need a special version or MPM, it is better to download the source code and compile it yourself. You should also check the integrity and authenticity of the source code by means of the digital signature. Before compiling it, you can choose where to install it to, which MPM and which other modules you want to use, third-party modules, however, can be added afterwards.
Normally, Apache will be installed into /usr/local/apache2, but you should configure it following the conventions of your Unix distribution. On Debian, for example, the binaries go into /usr/sbin, modules into /usr/lib/apache2, configuration files into /etc/apache2, log files into /var/log/apache2 and the actual web sites into /var/www. The top of the directory tree must be indicated with the ServerRoot directive in the configuration file (see configuration section)

Configuration

The configuration of Apache usually happens in the httpd.conf file. For better organization, you can, however, move some configuration to other files and include the file in httpd.conf, using the Include directive.

 

User
It is not recommended to run the Apache server with the privileges of the Unix root user, as an attacker would have full access to the system, if he could exploit a security hole. Apache can be configured to answer requests as an unprivileged user, the main, parent process, however, will remain running as root. So at first add a new new user and group:

# groupadd apache
# useradd apache -c "Apache" -d /dev/null -g apache -s /bin/false

Then edit your configuration file, and add the user name:
# User apache
# Group apache

There is the apache2ctl script in Apache's binary folder, which is the preferred way to start and stop the server. Switch to the root user and start the server:
# apache2ctl start   # use stop to stop it

Now review your process list in order to verify that the server has switched to the apache user. The column USER should be apache for each of the possibly many apache processes:

# ps aux | grep apache # maybe use “grep httpd” if your Apache
# binary has a different name
 
To be continued…