HES2011 - joernchen - Ruby on Rails from a Code Auditor Perspective

58
Ruby on Rails from a code auditor's perspective 0x0b4dc0de the RoR way 9 th April 2011 Hackito Ergo Sum

Transcript of HES2011 - joernchen - Ruby on Rails from a Code Auditor Perspective

Page 1: 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

Page 2: HES2011 - joernchen - Ruby on Rails from a Code Auditor Perspective

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

Page 3: HES2011 - joernchen - Ruby on Rails from a Code Auditor Perspective

Intro

Page 4: HES2011 - joernchen - Ruby on Rails from a Code Auditor Perspective

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

Page 5: HES2011 - joernchen - Ruby on Rails from a Code Auditor Perspective

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

Page 6: HES2011 - joernchen - Ruby on Rails from a Code Auditor Perspective

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 

[…]

Page 7: HES2011 - joernchen - Ruby on Rails from a Code Auditor Perspective

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

Page 8: HES2011 - joernchen - Ruby on Rails from a Code Auditor Perspective

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>

Page 9: HES2011 - joernchen - Ruby on Rails from a Code Auditor Perspective

Looks shiny, huh?

Page 10: HES2011 - joernchen - Ruby on Rails from a Code Auditor Perspective

Reading the code and (ab)using the webapp

Page 11: HES2011 - joernchen - Ruby on Rails from a Code Auditor Perspective

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

Page 12: HES2011 - joernchen - Ruby on Rails from a Code Auditor Perspective

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

Page 13: HES2011 - joernchen - Ruby on Rails from a Code Auditor Perspective

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

[…]

 

Page 14: HES2011 - joernchen - Ruby on Rails from a Code Auditor Perspective

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

Page 15: HES2011 - joernchen - Ruby on Rails from a Code Auditor Perspective

RoR – Filters

● So mainly, there are● before_filter● after_filter● skip_before_filter● skip_after_filter● around_filter

Page 16: HES2011 - joernchen - Ruby on Rails from a Code Auditor Perspective

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

Page 17: HES2011 - joernchen - Ruby on Rails from a Code Auditor Perspective

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'

    }

}

Page 18: HES2011 - joernchen - Ruby on Rails from a Code Auditor Perspective
Page 19: HES2011 - joernchen - Ruby on Rails from a Code Auditor Perspective

Good old friends

Page 20: HES2011 - joernchen - Ruby on Rails from a Code Auditor Perspective

The Usual Web Application Suspects

● SQL Injection● XSS● CSRF

Page 21: HES2011 - joernchen - Ruby on Rails from a Code Auditor Perspective

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

Page 22: HES2011 - joernchen - Ruby on Rails from a Code Auditor Perspective

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

Page 23: HES2011 - joernchen - Ruby on Rails from a Code Auditor Perspective

Redmine persistent XSS

● Somewhat hard to spot ● Found it by chance ;)

● Problem in the syntax highlighter:● lib/redcloth3.rb

Page 24: HES2011 - joernchen - Ruby on Rails from a Code Auditor Perspective

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 }"

Page 25: HES2011 - joernchen - Ruby on Rails from a Code Auditor Perspective

CSRF

● There is some RoR magic to protect_from_forgery:

1 class FooController < ApplicationController

2   protect_from_forgery :except => :index

  

Page 26: HES2011 - joernchen - Ruby on Rails from a Code Auditor Perspective

To be a bit more specific

Page 27: HES2011 - joernchen - Ruby on Rails from a Code Auditor Perspective

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

Page 28: HES2011 - joernchen - Ruby on Rails from a Code Auditor Perspective

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

Page 29: HES2011 - joernchen - Ruby on Rails from a Code Auditor Perspective

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

Page 30: HES2011 - joernchen - Ruby on Rails from a Code Auditor Perspective

Mass Assignment – CCC Website

● CCCMS – “feature” allowing regular users promoting themselves to admin

1 def update

2  

3  if @user.update_attributes(params[:user])

[…] 

Page 31: HES2011 - joernchen - Ruby on Rails from a Code Auditor Perspective

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])

[…] 

Page 32: HES2011 - joernchen - Ruby on Rails from a Code Auditor Perspective

Preventing Mass Assignments

● To be fixed in the model ● Example taken from Redmine:

1 class User < Principal

[…]

2   attr_protected :login, :admin, […] 

Page 33: HES2011 - joernchen - Ruby on Rails from a Code Auditor Perspective

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

Page 34: HES2011 - joernchen - Ruby on Rails from a Code Auditor Perspective

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

[…]

Page 35: HES2011 - joernchen - Ruby on Rails from a Code Auditor Perspective

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]

[…]

Page 36: HES2011 - joernchen - Ruby on Rails from a Code Auditor Perspective

Digging a bit deeper

Page 37: HES2011 - joernchen - Ruby on Rails from a Code Auditor Perspective

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

Page 38: HES2011 - joernchen - Ruby on Rails from a Code Auditor Perspective

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

Page 39: HES2011 - joernchen - Ruby on Rails from a Code Auditor Perspective

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)}"

Page 40: HES2011 - joernchen - Ruby on Rails from a Code Auditor Perspective

Redmine – Command Execution

● http://redminehost/projects/$project/repository/diff/?rev=`cmd`

Page 41: HES2011 - joernchen - Ruby on Rails from a Code Auditor Perspective

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

Page 42: HES2011 - joernchen - Ruby on Rails from a Code Auditor Perspective

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)

Page 43: HES2011 - joernchen - Ruby on Rails from a Code Auditor Perspective

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 ;-)

Page 44: HES2011 - joernchen - Ruby on Rails from a Code Auditor Perspective

Rails itself

● Of course, there is the RoR code itself● Didn't look into it deeply enough (yet)

Page 45: HES2011 - joernchen - Ruby on Rails from a Code Auditor Perspective

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

Page 46: HES2011 - joernchen - Ruby on Rails from a Code Auditor Perspective

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

Page 47: HES2011 - joernchen - Ruby on Rails from a Code Auditor Perspective

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]

Page 48: HES2011 - joernchen - Ruby on Rails from a Code Auditor Perspective

RoR Generic CSRF Protection Bypass

Page 49: HES2011 - joernchen - Ruby on Rails from a Code Auditor Perspective

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

Page 50: HES2011 - joernchen - Ruby on Rails from a Code Auditor Perspective

Session Cookies

● Session cookie holds all session information● Accessed like: session[:user_id]● _twitter_sess=$base64blob­­$sha1hmac

Page 51: HES2011 - joernchen - Ruby on Rails from a Code Auditor Perspective

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"}

Page 52: HES2011 - joernchen - Ruby on Rails from a Code Auditor Perspective

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

Page 53: HES2011 - joernchen - Ruby on Rails from a Code Auditor Perspective

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

Page 54: HES2011 - joernchen - Ruby on Rails from a Code Auditor Perspective

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 :-)

Page 55: HES2011 - joernchen - Ruby on Rails from a Code Auditor Perspective

Outro

Page 56: HES2011 - joernchen - Ruby on Rails from a Code Auditor Perspective

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 =)

Page 57: HES2011 - joernchen - Ruby on Rails from a Code Auditor Perspective

Thanks for Listening

Any Questions?

Page 58: HES2011 - joernchen - Ruby on Rails from a Code Auditor Perspective

Cheers

● astera <3 ● xilef ● opti ● til ● tina

● all @●

● Recurity Labs● Zynamics (RIP ;-))● Das Labor● Timecoderz● Dangerous Drums● HES-Orga