HES2011 - joernchen - Ruby on Rails from a Code Auditor Perspective
1. Ruby on Rails from a code auditor's
perspective
0x0b4dc0de the RoR way
9th April 2011
Hackito Ergo Sum
2. Meta / Disclaimer
● It's an attempt to share my experience in reading Ruby on
Rails code with the aim to find nice¹ bugs
● You can expect some code and practical examples from
● Redmine
– Open Source project management software
● CCCMS
– http://ccc.de
● I'm not a coder
● Rather, I enjoy reading other people's code
– So don't expect a RoR development tutorial
¹) as in: security
5. MVC
● Model-View-Controller: a software architecture
pattern isolating different domains of the
software into three parts
● Model: handling data of the application as well as
state changes
● View: user interface elements
● Controller: I/O, application logic calling methods of
the model and view
6. RoR – Controller
● Located in $railsapp/app/controllers
class PostsController < ActionController::Base
[…]
1 def show
2 @post = Post.find(params[:id])
3 respond_to do |format|
4 format.html # show.html.erb
5 format.xml { render :xml => @post }
6 end
7 end
[…]
7. RoR – Model
● Located in $railsapp/app/models
1 class User < ActiveRecord::Base
2 has_many :posts
3 verifies_presence_of :name
4 verifies_uniqueness_of :name
5 end
8. RoR – View
● Located in $railsapp/app/views
● Typically written in ERB
● Mixture of HTML and Ruby
01 <% if @posts.blank? %>
02 <p>There are no posts yet.</p>
03 <% else %>
04 <ul id="posts">
05 <% @posts.each do |c| %>
06 <li><%= link_to c.title, {:action => 'show', :id => c.id} =%></li>
07 <% end %>
08 </ul>
09 <% end %>
10 <p><%= link_to "Add new Post", {:action => 'new' }%></p>
11. RoR – Reading the Code
● Ruby tends to be easy to read, so does RoR
● There are at least three layers (MVC)
● All layers have to be covered when reading the
source code (for finding bugs)
● Additionally, there's libs, helpers, etc.
● There might be checks somewhere you don't
expect
● There might be bugs somewhere you don't expect
12. RoR – Database
● Database is configured in
● $railsapp/config/database.yml
● Migrations are used to describe the database
tables (in Ruby)
● These are then deployed on the database using
rake
14. RoR - Filters
● Filter example taken from Redmine
● app/controllers/issues_controller.rb
1 class UsersController < ApplicationController
2 layout 'admin'
3
4 before_filter :require_admin, :except => :show
15. RoR – Filters
● So mainly, there are
● before_filter
● after_filter
● skip_before_filter
● skip_after_filter
● around_filter
16. RoR – User Input
● Look for params[:something] in the
controller
● Take a look at the model/migration/DB to know
which fields you might potentially influence
● Post it like: something=foo
● params[:something][:bar] is posted like
something[bar]=foo
17. RoR – User Input
● RoR also takes automagically user input as
● XML
– Post with Content-Type text/xml:
<user>
<firstname>chunky</firstname>
</user>
● JSON
– Post with Content-Type application/json:
{
User:{
lastname:'bacon'
}
}
20. The Usual Web Application
Suspects
● SQL Injection
● XSS
● CSRF
21. SQL Injection the RoR way
● Rarely found
● Per se, RoR hides away plain SQL
– User.where(:first_name => “Chunky”, :last_name =>
“Bacon”)
● Look for the typical concatenation patterns
1 def sqlinjectme
2 User.find(:all, :conditions => "id = #{params[:id]}")
3 end
● Unfortunately stacked queries do not work
22. XSS the RoR way
● In order to find XSS bugs
● Look at the views
– <%= @post.title %>
vs.
– <%= h @post.title %>
● Look at formatters
● Just try to find XSS scripted/manually
23. Redmine persistent XSS
● Somewhat hard to spot
● Found it by chance ;)
● Problem in the syntax highlighter:
● lib/redcloth3.rb
27. Bugs the RoR way
● Rails has a lot of fancy automagic
● … which might eventually blow up in your face
● “Most of you are familiar with the virtues of a
programmer. There are three, of course: laziness,
impatience, and hubris.” – Larry Wall
28. Automagic – Mass Assignments
● When there is an assignment like
● user[name] = “Chunky Bacon”
● This is typically saved with
● 1 user = @params[:user]
● 2 user.save
29. Automagic – Mass Assignments
● When there is an assignment like
● user[name] = “Chunky Bacon”
● This is typically saved with
● 1 user = @params[:user]
● 2 user.save
● So what if you posted
● user[name]= “Chunky Bacon”
● user[admin]= true
30. Mass Assignment – CCC Website
● CCCMS – “feature” allowing regular users
promoting themselves to admin
1 def update
2
3 if @user.update_attributes(params[:user])
[…]
31. Mass Assignment – CCC Website
● CCCMS – patch preventing regular users
promoting themselves to admin
1 def update
2 params[:user].delete(:admin) unless current_user.is_admin?
3 if @user.update_attributes(params[:user])
[…]
32. Preventing Mass Assignments
● To be fixed in the model
● Example taken from Redmine:
1 class User < Principal
[…]
2 attr_protected :login, :admin, […]
33. Laziness – Infoleaks
● All those fanciness of RoR doesn't help against
lazy developers
● If you're having a second controller accessing a
model, you have to implement proper filters as
well
34. Redmine – Journals Infoleak
● Leaks info about issue descriptions, even if they are not visible
to the current user
● app/controllers/journals_controller.rb
1 class JournalsController < ApplicationController
2 before_filter :find_journal, :only => [:edit]
3 before_filter :find_issue, :only => [:new]
4 before_filter :find_optional_project, :only => [:index]
5
[…]
35. Redmine – Journals Infoleak
● Leaks info about issue descriptions, even if they are not visible
to the current user
● app/controllers/journals_controller.rb
1 class JournalsController < ApplicationController
2 before_filter :find_journal, :only => [:edit]
3 before_filter :find_issue, :only => [:new]
4 before_filter :find_optional_project, :only => [:index]
5 before_filter :authorize, :only => [:new, :edit]
[…]
37. Digging a bit deeper
● There is more than just the MVC code
● $railsapp/lib/
● $railsapp/vendor/plugins
● $railsapp/app/helpers
● There is RoR code itself
41. XXX¹
● Open Source Rails app
● Developed by xxx.com
● Was suspicious to me due to heavily using send
statements on user input
● send(symbol, [args...])
● Invokes the method identified by symbol, passing it any
arguments specified.
● Allows private methods to be called
¹) sorry had to censor this
42. send, my new best friend
● In XXX's controllers I didn't find anything
directly exploitable :-(
● But then a search helper lib got my attention:
● some/lib/path/search.rb:
01 values.each do |condition, value|
02 mass_conditions[condition.to_sym] = value
03 value.delete_if { |v| ignore_value?(v) } if value.is_a?(Array)
04 next if ignore_value?(value)
05 @current_scope = @current_scope.send(condition, value)
43. send, my new best friend
● How about:
GET /triggerpath?search[instance_eval]=
%60touch%20%2ftmp%2fcommand_exec%60
HTTP/1.1
● Or just msfupdate in a couple of days ;-)
44. Rails itself
● Of course, there is the RoR code itself
● Didn't look into it deeply enough (yet)
45. Security Mechanisms –
CSRF Tokens
● Short recap:
● protect_from_forgery
● But wait a minute
● Thumbs up to Felix Gröbert (Google Sec. Team)
– CVE-2011-0447
– Fixed in Rails 3.0.4
49. RoR Generic CSRF
Protection Bypass
● Post to yoursite.com with flash¹
● Let yoursite.com redirect via 307 to target.com
● Supply application/json with proper json params
● authenticity_token should also be present
(arbitrary string)
● Resend popup in Firefox
¹) Cross-domain POST header manipulation
details: http://bit.ly/hc65g3
50. Session Cookies
● Session cookie holds all session information
● Accessed like: session[:user_id]
● _twitter_sess=$base64blob$sha1hmac
51. Session Cookies
● Can be loaded after base64 decoding with
● Marshal.load token
● => {:logged_in_after_phx_default=>false,
:created_at=>1293669570258, :in_new_user_flow=>nil,
:show_help_link=>nil, :user=>19395266,
:password_token=>"censored",
"show_discoverability_for_joernchen"=>nil,
"flash"=>{}, :id=>"censored", :csrf_id=>"censored"}
52. Some Thoughts on Session Cookies
● Looked at the RoR handling of Session
Cookies → looked fine to me
● Maybe you find something I missed
● But keep in mind:
● What has been HMACed can't be un-HMACed
53. What has been HMACed
● A fictional example of some RoR controller:
01 def grant_token # called only once for a user
02 session[:token] = true
03 end
04 def invalidate # called in do_the_magic
05 session[:token] = false
06 end
07 def check # check if user has used token
08 if session[:token] == true
09 do_the_magic
10 else
11 do_not_do the magic
12 end
13 end
54. What has been HMACed
● The before made example is vulnerable to simple replay
attacks
● Once you have a HMACed session cookie with special
capabilities in a naïve implementation noone stops you
from reusing that cookie.
● Simple experiment:
● Go to twitter.com and login (without “Remember Me”).
● Save your _twitter_sess cookie
● Logout
● Restore the _twitter_sess cookie
● Be logged in again :-)
56. Kudos to Jean-Philippe Lang
● Initial notification of
● Infoleak issue
● Persistent XSS
● Multiple CMD-Exec bugs
● ~ 2:00 PM
● Respone “I'll fix it and let you know”
● ~ 6:00 PM
● Response “It's fixed and there will be a new release tomorrow”
● ~ 8:00 PM
● 2h for a complete fix.
● So in case you use Redmine
● Update at least to version 1.0.5 =)