11.1. Security Considerations

It would be nice to be able to publish Web applications and sites without worrying about them being hacked, but it is not realistic. It's a jungle out there on the Internet, and unless you take serious precautions, your site is bound to become compromised sooner or later.

To make things more challenging, the security of an application is like a chain: it's only as strong as its weakest link. Covering the subject of securing a Web server is well beyond the scope of this book. If you are not familiar with the process, hosting companies and plans are available that will take care of this for you. What they cannot do, though, is guarantee that your application is secure as well. As a developer, application-level security is your responsibility, and this section should help you make more conscious choices in this regard.

11.1.1. Cross-Site Scripting (XSS)

Cross-site scripting (XSS) attacks take advantage of vulnerabilities in a Web application to inject malicious code that will be executed when other users view the page.

To better understand how XSS attacks work, let's take a look at one possible scenario. Assume that you have a blog application that allows comments. If that comment form is vulnerable to XSS attacks, a malicious user could publish a comment that includes JavaScript code. Once the comment is published on the site, every visitor who comes across that page executes the malicious JavaScript code (assuming JavaScript was enabled in their browsers).

This is dangerous because the vulnerable form enables a malicious user to publish arbitrary code that would be executed by other users. The malicious user could, for example, inject JavaScript code that would grab the cookie of a legitimate user and send it over to a server where it would be collected and processed. If the genuine user was logged in to the application, the malicious user would then be able to use the stolen cookie to gain access to the application as if he were the authenticated, genuine user. And that genuine user, who visited the compromised page on the application, could be the administrator of the site, granting the malicious user full control of the application and its data.

It's important to understand that this attack relies on vulnerable pages that publish unsafe values without properly escaping them first. While you're developing Web applications, you need to be ruthless when it comes to any value that could originate from a malicious user.

For example, the following is vulnerable to XSS attacks, because it displays the content coming from the user as it is:

7lt;%= params[:body] %>

If params[:body] contains a <script> tag, it will be published and executed by any future visitors to that page.

11.1.1.1. html_escape

In Rails, the easiest way is to escape any HTML tags is with the already amply discussed h helper (alias for html_escape). This helper will transform < and > occurrences within the argument into &lt; and &gt;. The correct way to display the previous example is as follows:

<%= h(params[:body]) %>

or as it will often appear in many code bases:

<%=h params[:body] %>

With this in place, all tags will be displayed instead of being interpreted/executed. Any <script> in input becomes an innocuous &lt;script&gt;, which is rendered as the string <script> by any browser.

11.1.1.2. sanitize

Escaping HTML tags with h is good practice from a security standpoint, but it's also very limiting when you need to allow certain tags to be displayed. These occasions warrant the use of sanitize, which takes a whitelist approach, by allowing only a specific set of tags and attributes.

Everything else is escaped (tags) or stripped (attributes). This process of sanitizing the input also strips dangerous protocols (for example, javascript:), preventing them from being used as values for href and src attributes. Finally, sanitize tries to combat any tricks that black-hat hackers may have up their sleeve, and their attempts to bypass the JavaScript filters with special characters (for example, hexadecimals).

Without optional arguments, sanitize is used as follows:

<%= sanitize(@comment.body) %>

You may be wondering what tags, attributes, and protocols are allowed by default. To verify which ones are, you need to dig into Rails' code in actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb, to discover the following snippet:

# A regular expression of the valid characters used to separate protocols like
# the ':' in 'http://foo.com'
self.protocol_separator     = /:|(&#0*58) |(##x70)  |(%|&#37;) 3A/

# Specifies a Set of HTML attributes that can have URIs.
self.uri_attributes         = Set.new(%w(href src cite action longdesc xlink:href
 lowsrc))

# Specifies a Set of 'bad' tags that the #sanitize helper will remove completely,
as opposed
# to just escaping harmless tags like <font>
self.bad_tags               = Set.new(%w(script))

# Specifies the default Set of tags that the #sanitize helper will allow unscathed.
self.allowed_tags           = Set.new(%w(strong em b i p code pre tt samp kbd var
sub sup dfn cite big small address hr br div span h1 h2 h3 h4 h5 h6 ul ol li dt dd
abbr acronym a img blockquote del ins))

# Specifies the default Set of html attributes that the #sanitize helper will leave
# in the allowed tag.
self.allowed_attributes     = Set.new(%w(href src width height alt cite datetime
title class name xml:lang abbr))

# Specifies the default Set of acceptable css properties that #sanitize and
#sanitize_css will accept.
self.allowed_protocols      = Set.new(%w(ed2k ftp http https irc mailto news gopher
nntp telnet webcal xmpp callto feed svn urn aim rsync tag ssh sftp rtsp afs))

The :tags and :attributes options are used to specify additional tags and attributes that can be permitted. For example:

<%= sanitize(@comment.body, :tags => %w{table tr td}, :attributes => %w{id class}) %>

These can also be specified globally for the application within an initializer (that is, in configenvironment.rb or in a file within configinitializers):

Rails::Initializer.run do |config|
  config.action_view.sanitized_allowed_tags = 'table', 'tr', 'td'
  config.action_view.sanitized_allowed_attributes = 'id', 'class'
end

You can also disallow some of the existing tags or attributes by deleting them from the default list:

Rails::Initializer.run do |config|
  config.after_initialize do
    ActionView::Base.sanitized_allowed_tags.delete('img')
  end
end

Alternatively, for a whitelist approach that removes all tags by default, consider the Sanitize gem. You can find more information about it online at http://wonko.com/post/sanitize.

XSS vulnerabilities are not limited to regular forms. Allowing users to upload files that become available to other users can also be dangerous. In fact, a malicious user could misrepresent the file's content type and try to let other users execute the file's content as opposed to simply downloading the file. Another risk is attempting to pass a relative path to the server to make sensitive files that are located on the server (for example, ../../config/database.yml) available for download. Using the Rails plugin Paperclip (http://www.thoughtbot.com/projects/paperclip) and its validations eases the process of working securely with attachments.

11.1.1.3. Cookie Security

In the scenario described in the previous section, the malicious user was able to log in as a different user thanks to both a vulnerable form and the application's reliance on the session data stored in the cookie to identify a user.

Along with escaping/stripping/validating the user input, a (admittedly less effective) step is attempting to render the stolen cookie useless. In the pursuit of this aim, a common countermeasure is to store the IP address of the legitimate user in the session data. This way when a malicious user tries to use the cookie from a different IP, the IP address won't match up and the controller will be able to invalidate the session data, preventing the attacker from getting through. This does not offer 100% protection, because the attacker could have the same IP (if for example, both legitimate and malicious users are behind the same NAT or Web Proxy). Furthermore, it's an inconvenience for those users whose dynamic IP addresses change regularly.

Because of the possibility of inconveniencing many users, a developer should think carefully about the pros and cons of adopting this countermeasure.

This is also a possible countermeasure against Session Fixation Attacks. In these kinds of attacks, the malicious user manages to assign a session id to a user through vulnerabilities, such as XSS, and then waits for the user to log in with that session id, allowing the malicious user to impersonate a legitimate user. Storing the IP address of the user in the session data within the cookie, reduces — but doesn't eliminate — the risk of this type of attack.

Another common technique that is very effective against Session Fixation Attacks is to issue new session data whenever a user logs in. This way, the attacker who's waiting for the "victim" to log in with the "fixed" session id will be disarmed, because the legitimate user will log in but doing so with a new session id that was assigned by the Rails application.

The session ids (SIDs) are larger in size and randomly generated by Rails on purpose, so as not to be simply guessed at.

11.1.2. SQL Injection

XSS attacks can be very dangerous, but when it comes to application-level security risks, SQL injection attacks take the cake. As the name implies, these attacks consist of injecting fragments of SQL containing meta-characters into legitimate queries, so as to gain access to the database and execute arbitrary queries.

It's important to understand that these attacks are not Rails-specific. Independently from the language and/or framework that's been employed, any application can be vulnerable to these types of attacks if the developer doesn't pay close enough attention and provide valid countermeasures. Conversely, these countermeasures are framework-specific.

Imagine for a moment that you created a login form without properly escaping the user input. A malicious user could provide the following input:

Login: admin
Password: anything' or 'a'='a

And with these simple strings the attacker would suddenly gain access to your application as an administrator. Let's see how this works and what you can do to prevent it from happening.

SQL injection attacks are database-specific and each RDBMS allows different SQL syntaxes, which means that attackers will try several variants of this exploit.

A developer would be expecting the database to execute queries as follows:

SELECT * FROM users WHERE username = 'someuser' AND password = 'somepwd';

When the previous malicious input is provided, the executed query becomes:

SELECT * FROM users WHERE username = 'admin' AND password = 'anything' or 'a'='a';

As you can see, the condition to the right of AND is always true, and therefore the attacker manages to log in as an administrator (provided the admin username was admin).

Thankfully this can easily be avoided by using Rails' built-in finders correctly. Whenever you retrieve a record based on its id, escaping of special characters like ' and " is done automatically for you. For example, look at the following SQL injection attempt:

User.find(params[:id]) # params[:id] is ' or 1--

It generates the harmless query:

SELECT * FROM "users" WHERE ("users"."id" = 0)

Similarly, using dynamic finders without manually adding SQL conditions is a safe move as well:

Book.find_by_title(params[:title])

Problems begin to arise when you adopt options in your finders that allow you to specify custom fragments of SQL. For example, one typical mistake is to evaluate expressions directly within a string that's been assigned to the key :conditions. The following line is vulnerable to SQL injection attacks:

# Don't do this
Account.find(:all, :conditions => "name LIKE '%#{params[:name]}%' AND active =
'#{params[:active]'")

You should never embed tainted expressions (ones that are coming from the user) in SQL fragments, because these strings are not escaped and are able to be exploited. You can pass an array to :conditions instead. The first element is a string with question marks in place of the actual values, and the remaining elements of the array are values in order of how they should be substituted:

Account.find(:first, :conditions => ["name LIKE ? AND active = ?", "%#{params[:name]}%",
params[:active])

This will automatically escape dangerous characters that could be used to hijack your queries. Likewise, you can opt for the hash form as well:

Account.find(:first, :conditions => { :username => params[:username],
                                      :password => params[:password] })

It is worth noticing that in the industry, parametric queries are often used as a means of optimizing performances and securing databases against SQL injection attacks. The current version of ActiveRecord does not make use of these however.

Check out the official documentation for santize* methods that you can use whenever you're defining your own model's methods (that involve potentially unsafe SQL strings).

Little Bobby Table

xkcd is an online webcomic that's very popular among developers. In what is now a famous comic strip called "Exploits of a Mom," a school runs into big trouble due to a student named Robert'; DROP TABLE Students; --, or "Little Bobby Table" as he's nicknamed at home. In the last frame of this hilarious strip, the school informs the mother that they've lost this year's student records because of his name. And the mother tells the school: "And I hope you've learned to sanitize your database inputs." Indeed, SQL injection is a serious threat and as Web developers it is our responsibility to carefully sanitize any input. You can find the strip online at http://xkcd.com/327/ and I also recommend that you check out the wealth of other xkcd comics that are available there, if you're into geeky humor.


11.1.3. Protecting Your Records

Another important security risk is leaving your records' attributes unprotected. Imagine that you have a User model with the following attributes: name, email, password, is_admin, and photo. For regular users, the form for editing one's profile will probably contain all of these fields but it will leave out the field is_admin. Unless you protect your records, by default the users will be able to save the page on their hard drive, modify it to add a checkbox for the is_admin boolean field, mark off that checkbox, and then click submit to grant themselves admin access to the application.

Alternatively, a malicious user could opt to manipulate the URL instead.

In fact, in your code you'd probably update the user like this:

current_user.update_attributes(params[:user])

Note that this will perform a mass-assignment for all the available model attributes that are contained within params[:user]. That's problematic because users can alter all the fields they want as well as the associated objects if associations between models were defined.

Mass-assignments are possible through methods such as update_attributes, new, and attributes=.

Rails provides two macro-like methods to prevent this risky default behavior. The first is attr_protected and the second is attr_accessible. The former adopts a blacklist approach, in which you specify attributes that should be protected from mass-assignments. The latter uses a whitelist approach, allowing you to specify which attributes can be modified through mass-assignment, and blocking all the rest.

In the previous example, you can protect the is_admin field when performing mass-assignments as follows:

class User < ActiveRecord::Base
  attr_protected :is_admin
end

Likewise, you could have specified a list of allowed fields:

class User < ActiveRecord::Base
  attr_accessible :name, :email, :password, :photo
end

This second approach is more verbose, but it protects all the attributes from mass-assignments by default, hence protecting attributes defined by associations as well. Furthermore, any future attributes added to the model at a later stage will automatically be protected as well.

11.1.4. Other Costly Mistakes

A common mistake made by beginners is to leave all the methods in their controllers as public. When a method defined in a controller, including ApplicationController, is public, it becomes an action that can be accessed by visitors to the site. Whenever you have a method used by other actions that shouldn't be accessible to the end user, you need to declare it as private (or protected).

Another typical mistake is to allow access to other users' data by simply accepting the id provided in input. Because the id appears in the URL, it doesn't take a hacker to increment or change it. The easiest way to protect your application from this type of vulnerability (which concerns privacy as well) is to perform your searches based on the id (or other parameters) as well as verify that the current user has the right to access it.

For example, in the show action of a BankAccountController you should retrieve the bank account through an id as well as the condition that the owner of the account is the current user. So don't run @account = BankAccount.find(params[:id]) or everyone will have access to the account of everyone else. Instead, add a condition like :conditions => ["user_id = ?", @user.id] where @user is an instance variable set in a private before filter method.

NOTE

Remember that on its own "security through obscurity" rarely works. Don't rely on hard-to-guess ids as a means of protecting your users' data.

When find (by id) fails to find a record an error is raised. This error could be rescued within a rescue clause, in which a redirect to the homepage or another appropriate page is performed.

11.1.5. Ruby on Rails Security Guide

The subject of security is very wide and it's beyond the scope of this book to provide you with a complete set of possible security countermeasures in Rails applications. It would also be a duplication of the excellent work carried out by the Rails community to provide informative online material.

My suggestion to you is to read the official Ruby on Rails Security Guide, which is available online at http://guides.rails.info/security.html or through rake doc:guides. This guide is extensive and covers all you realistically need to know.

I also recommend checking the Ruby on Rails Security Project at http://www.rorsecurity.info. Its blog has plenty of interesting articles related to Rails security and it also provides a short, free e-book that you may want to check out.

Finally, in order to keep your applications secure, I can't stress enough the importance of subscribing to the official Rails blog, Riding Rails, which is available at http://weblog.rubyonrails.org as well as keeping an eye on the Rails Security mailing list available at http://lists.rubyonrails.org/mailman/listinfo/rails-security. Any vulnerability or security concerns will be publicly announced there, as well as the availability of Rails upgrades.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.21.248.162