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.
Apache 2 file privileges and modules
File privileges
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
# 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
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
# User apache
# Group apache
# apache2ctl start # use stop to stop it
# ps aux | grep apache # maybe use “grep httpd” if your Apache
# binary has a different name