HES2011 - joernchen - Ruby on Rails from a Code Auditor Perspective
-
Upload
hackito-ergo-sum -
Category
Technology
-
view
4.332 -
download
5
Transcript of HES2011 - joernchen - Ruby on Rails from a Code Auditor Perspective
Ruby on Rails from a code auditor's perspective
0x0b4dc0de the RoR way
9th April 2011Hackito Ergo Sum
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
Intro
RoR – WTF?
● Ruby on Rails● Blah² from the website:
– “Ruby on Rails © is an open-source web framework that's optimized for programmer happiness and sustainable productivity. It lets you write beautiful code by favoring convention over configuration.”
● Current version: 3.0.5● Model-View-Controller based webapp framework
written in Ruby
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
RoR – Controller
● Located in $railsapp/app/controllersclass 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
[…]
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
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>
Looks shiny, huh?
Reading the code and (ab)using the webapp
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
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
RoR – Migrations
●Example taken from Redmine● db/migrate/001_setup.rb
create_table "users", :force => true do |t|
t.column "login", :string, :limit => 30, :default => "", :null => false
t.column "hashed_password", :string, :limit => 40, :default => "", :null => false
t.column "firstname", :string, :limit => 30, :default => "", :null => false
t.column "lastname", :string, :limit => 30, :default => "", :null => false
t.column "mail", :string, :limit => 60, :default => "", :null => false
t.column "mail_notification", :boolean, :default => true, :null => false
t.column "admin", :boolean, :default => false, :null => false
[…]
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
RoR – Filters
● So mainly, there are● before_filter● after_filter● skip_before_filter● skip_after_filter● around_filter
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
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'
}
}
Good old friends
The Usual Web Application Suspects
● SQL Injection● XSS● CSRF
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
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
Redmine persistent XSS
● Somewhat hard to spot ● Found it by chance ;)
● Problem in the syntax highlighter:● lib/redcloth3.rb
Redmine persistent XSS
1 htmlesc( aftertag, :NoQuotes ) if aftertag &&
escape_aftertag && !
first.match(/<code\s+class="(\w+)">/)
2 line = "<redpre##{ @pre_list.length }>"
3 first.match(/<#{ OFFTAGS }([^>]*)>/)
4 tag = $1
5 $2.to_s.match(/(class\=\S+)/i)
6 tag << " #{$1}" if $1
7 @pre_list << "<#{ tag }>#{ aftertag }"
CSRF
● There is some RoR magic to protect_from_forgery:
1 class FooController < ApplicationController
2 protect_from_forgery :except => :index
To be a bit more specific
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
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
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
Mass Assignment – CCC Website
● CCCMS – “feature” allowing regular users promoting themselves to admin
1 def update
2
3 if @user.update_attributes(params[:user])
[…]
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])
[…]
Preventing Mass Assignments
● To be fixed in the model ● Example taken from Redmine:
1 class User < Principal
[…]
2 attr_protected :login, :admin, […]
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
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
[…]
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]
[…]
Digging a bit deeper
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
Redmine – SCM Adapters
● app/controllers/repositories_controller.rb
1 before_filter :find_repository, :except => :edit
[…]
2 def diff
3 if params[:format] == 'diff'
4 @diff = @repository.diff(@path, @rev, @rev_to)
[…]
5 def find_repository
[…]
6 @rev = params[:rev].blank? ? @repository.default_branch : params[:rev].strip
Redmine – SCM Adapters
● lib/redmine/scm/adapters/bazaar_adapter.rb
1 def diff(path, identifier_from, identifier_to=nil)
2 path ||= ''
3 if identifier_to
4 identifier_to = identifier_to.to_i
5 else
6 identifier_to = identifier_from.to_i 1
7 end
8 cmd = "#{BZR_BIN} diff
r#{identifier_to}..#{identifier_from}
#{target(path)}"
Redmine – Command Execution
● http://redminehost/projects/$project/repository/diff/?rev=`cmd`
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
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)
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 ;-)
Rails itself
● Of course, there is the RoR code itself● Didn't look into it deeply enough (yet)
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
Security Mechanisms – CSRF Tokens
● actionpack/lib/action_controller/metal/request_forgery_protection.rb1 def verified_request?
2 !protect_against_forgery? ||request.forgery_whitelisted? ||
3 form_authenticity_token == params[request_forgery_protection_token]
4 end
● actionpack/lib/action_dispatch/http/request.rb5 def forgery_whitelisted?
6 get? || xhr? || content_mime_type.nil? ||
!content_mime_type.verify_request?
7 end
Security Mechanisms – CSRF Tokens
● actionpack/lib/action_dispatch/http/mime_type.rb
1 def verify_request?
2 @@browser_generated_types.include?(to_sym)
3 end
● With4 @@browser_generated_types = Set.new [:html,
:url_encoded_form,:multipart_form, :text]
RoR Generic CSRF Protection Bypass
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
Session Cookies
● Session cookie holds all session information● Accessed like: session[:user_id]● _twitter_sess=$base64blob$sha1hmac
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"}
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
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
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 :-)
Outro
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 =)
Thanks for Listening
Any Questions?
Cheers
● astera <3 ● xilef ● opti ● til ● tina
● all @●
● Recurity Labs● Zynamics (RIP ;-))● Das Labor● Timecoderz● Dangerous Drums● HES-Orga