Module: Msf::DBManager::Import::MetasploitFramework::Zip

Included in:
Msf::DBManager::Import::MetasploitFramework
Defined in:
lib/msf/core/db_manager/import/metasploit_framework/zip.rb

Instance Method Summary collapse

Instance Method Details

#import_msf_collateral(args = {}, &block) ⇒ Object

Imports loot, tasks, and reports from an MSF ZIP report. XXX: This function is stupidly long. It needs to be refactored.


4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/msf/core/db_manager/import/metasploit_framework/zip.rb', line 4

def import_msf_collateral(args={}, &block)
  data = ::File.open(args[:filename], "rb") {|f| f.read(f.stat.size)}
  wspace = Msf::Util::DBManager.process_opts_workspace(args, framework).name
  args = args.clone()
  args.detele(:workspace)
  bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []
  basedir = args[:basedir] || args['basedir'] || ::File.join(Msf::Config.data_directory, "msf")

  allow_yaml = false
  btag = nil

  doc = Nokogiri::XML::Reader.from_memory(data)
  case doc.first.name
  when "MetasploitExpressV1"
    m_ver = 1
    allow_yaml = true
    btag = "MetasploitExpressV1"
  when "MetasploitExpressV2"
    m_ver = 2
    allow_yaml = true
    btag = "MetasploitExpressV2"
  when "MetasploitExpressV3"
    m_ver = 3
    btag = "MetasploitExpressV3"
  when "MetasploitExpressV4"
    m_ver = 4
    btag = "MetasploitExpressV4"
  when "MetasploitV4"
    m_ver = 4
    btag = "MetasploitV4"
  else
    m_ver = nil
  end
  unless m_ver and btag
    raise Msf::DBImportError.new("Unsupported Metasploit XML document format")
  end

  host_info = {}

  doc.each do |node|
    if ['host', 'loot', 'task', 'report'].include?(node.name)
      unless node.inner_xml.empty?
        send("parse_zip_#{node.name}", Nokogiri::XML(node.outer_xml).at("./#{node.name}"), wspace, bl, allow_yaml, btag, args, basedir, host_info, &block)
      end
    end
  end
end

#import_msf_zip(args = {}, &block) ⇒ Object

Import a Metasploit Express ZIP file. Note that this requires a fair bit of filesystem manipulation, and is very much tied up with the Metasploit Express ZIP file format export (for obvious reasons). In the event directories exist, they will be reused. If target files exist, they will be overwritten.

XXX: Refactor so it's not quite as sanity-blasting.


166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
# File 'lib/msf/core/db_manager/import/metasploit_framework/zip.rb', line 166

def import_msf_zip(args={}, &block)
  data = args[:data]
  wspace = Msf::Util::DBManager.process_opts_workspace(args, framework).name
  bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []

  new_tmp = ::File.join(Dir::tmpdir,"msf","imp_#{Rex::Text::rand_text_alphanumeric(4)}",@import_filedata[:zip_basename])
  if ::File.exist? new_tmp
    unless (::File.directory?(new_tmp) && ::File.writable?(new_tmp))
      raise Msf::DBImportError.new("Could not extract zip file to #{new_tmp}")
    end
  else
    FileUtils.mkdir_p(new_tmp)
  end
  @import_filedata[:zip_tmp] = new_tmp

  # Grab the list of unique basedirs over all entries.
  @import_filedata[:zip_tmp_subdirs] = @import_filedata[:zip_entry_names].map {|x| ::File.split(x)}.map {|x| x[0]}.uniq.reject {|x| x == "."}

  # mkdir all of the base directories we just pulled out, if they don't
  # already exist
  @import_filedata[:zip_tmp_subdirs].each {|sub|
    tmp_subdirs = ::File.join(@import_filedata[:zip_tmp],sub)
    if File.exist? tmp_subdirs
      unless (::File.directory?(tmp_subdirs) && File.writable?(tmp_subdirs))
        # if it exists but we can't write to it, give up
        raise Msf::DBImportError.new("Could not extract zip file to #{tmp_subdirs}")
      end
    else
      ::FileUtils.mkdir(tmp_subdirs)
    end
  }

  data.entries.each do |e|
    # normalize entry name to an absolute path
    target = File.expand_path(File.join(@import_filedata[:zip_tmp], e.name), '/').to_s

    # skip if the target would be extracted outside of the zip
    # tmp dir to mitigate any directory traversal attacks
    next unless is_child_of?(@import_filedata[:zip_tmp], target)

    e.extract(target)

    if target =~ /\.xml\z/
      target_data = ::File.open(target, "rb") {|f| f.read 1024}
      if import_filetype_detect(target_data) == :msf_xml
        @import_filedata[:zip_extracted_xml] = target
      end
    end
  end

  # Import any creds if there are some in the import file
  Dir.entries(@import_filedata[:zip_tmp]).each do |entry|
    if entry =~ /^.*#{Regexp.quote(Metasploit::Credential::Exporter::Core::CREDS_DUMP_FILE_IDENTIFIER)}.*/
      manifest_file_path = File.join(@import_filedata[:zip_tmp], entry, Metasploit::Credential::Importer::Zip::MANIFEST_FILE_NAME)
      if File.exist? manifest_file_path
        import_msf_cred_dump(manifest_file_path, wspace)
      end
    end
  end

  # This will kick the newly-extracted XML file through
  # the import_file process all over again.
  if @import_filedata[:zip_extracted_xml]
    new_args = args.dup
    new_args[:filename] = @import_filedata[:zip_extracted_xml]
    new_args[:data] = nil
    new_args[:ifd] = @import_filedata.dup
    if block
      import_file(new_args, &block)
    else
      import_file(new_args)
    end
  end

  # Kick down to all the MSFX ZIP specific items
  if block
    import_msf_collateral(new_args, &block)
  else
    import_msf_collateral(new_args)
  end
end

#is_child_of?(target_dir, target) ⇒ Boolean

Returns:

  • (Boolean)

248
249
250
# File 'lib/msf/core/db_manager/import/metasploit_framework/zip.rb', line 248

def is_child_of?(target_dir, target)
  target.downcase.start_with?(target_dir.downcase)
end

#parse_zip_host(host, wspace, bl, allow_yaml, btag, args, basedir, host_info, &block) ⇒ Object

Parses host Nokogiri::XML::Element


53
54
55
# File 'lib/msf/core/db_manager/import/metasploit_framework/zip.rb', line 53

def parse_zip_host(host, wspace, bl, allow_yaml, btag, args, basedir, host_info, &block)
  host_info[host.at("id").text.to_s.strip] = nils_for_nulls(host.at("address").text.to_s.strip) unless host.at('address').nil?
end

#parse_zip_loot(loot, wspace, bl, allow_yaml, btag, args, basedir, host_info, &block) ⇒ Object

Parses loot Nokogiri::XML::Element


58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/msf/core/db_manager/import/metasploit_framework/zip.rb', line 58

def parse_zip_loot(loot, wspace, bl, allow_yaml, btag, args, basedir, host_info, &block)
  return 0 if bl.include? host_info[loot.at("host-id").text.to_s.strip]
  loot_info              = {}
  loot_info[:host]       = host_info[loot.at("host-id").text.to_s.strip]
  loot_info[:workspace]  = args[:workspace]
  loot_info[:ctype]      = nils_for_nulls(loot.at("content-type").text.to_s.strip)
  loot_info[:info]       = nils_for_nulls(unserialize_object(loot.at("info"), allow_yaml))
  loot_info[:ltype]      = nils_for_nulls(loot.at("ltype").text.to_s.strip)
  loot_info[:name]       = nils_for_nulls(loot.at("name").text.to_s.strip)
  loot_info[:created_at] = nils_for_nulls(loot.at("created-at").text.to_s.strip)
  loot_info[:updated_at] = nils_for_nulls(loot.at("updated-at").text.to_s.strip)
  loot_info[:name]       = nils_for_nulls(loot.at("name").text.to_s.strip)
  loot_info[:orig_path]  = nils_for_nulls(loot.at("path").text.to_s.strip)
  loot_info[:task]       = args[:task]
  tmp = args[:ifd][:zip_tmp]
  loot_info[:orig_path].gsub!(/^\./,tmp) if loot_info[:orig_path]
  if !loot.at("service-id").text.to_s.strip.empty?
    unless loot.at("service-id").text.to_s.strip == "NULL"
      loot_info[:service] = loot.at("service-id").text.to_s.strip
    end
  end

  # Only report loot if we actually have it.
  # TODO: Copypasta. Separate this out.
  if ::File.exist? loot_info[:orig_path]
    loot_dir = ::File.join(basedir,"loot")
    loot_file = ::File.split(loot_info[:orig_path]).last
    if ::File.exist? loot_dir
      unless (::File.directory?(loot_dir) && ::File.writable?(loot_dir))
        raise Msf::DBImportError.new("Could not move files to #{loot_dir}")
      end
    else
      ::FileUtils.mkdir_p(loot_dir)
    end
    new_loot = ::File.join(loot_dir,loot_file)
    loot_info[:path] = new_loot
    if ::File.exist?(new_loot)
      ::File.unlink new_loot # Delete it, and don't report it.
    else
      report_loot(loot_info) # It's new, so report it.
    end
    ::FileUtils.copy(loot_info[:orig_path], new_loot)
    yield(:msf_loot, new_loot) if block
  end
end

#parse_zip_report(report, wspace, bl, allow_yaml, btag, args, basedir, host_info, &block) ⇒ Object

Parses report Nokogiri::XML::Element


155
156
157
# File 'lib/msf/core/db_manager/import/metasploit_framework/zip.rb', line 155

def parse_zip_report(report, wspace, bl, allow_yaml, btag, args, basedir, host_info, &block)
  import_report(report, args, basedir)
end

#parse_zip_task(task, wspace, bl, allow_yaml, btag, args, basedir, host_info, &block) ⇒ Object

Parses task Nokogiri::XML::Element


105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/msf/core/db_manager/import/metasploit_framework/zip.rb', line 105

def parse_zip_task(task, wspace, bl, allow_yaml, btag, args, basedir, host_info, &block)
  task_info = {}
  task_info[:workspace] = args[:workspace]
  # Should user be imported (original) or declared (the importing user)?
  task_info[:user] = nils_for_nulls(task.at("created-by").text.to_s.strip)
  task_info[:desc] = nils_for_nulls(task.at("description").text.to_s.strip)
  task_info[:info] = nils_for_nulls(unserialize_object(task.at("info"), allow_yaml))
  task_info[:mod] = nils_for_nulls(task.at("module").text.to_s.strip)
  task_info[:options] = nils_for_nulls(task.at("options").text.to_s.strip)
  task_info[:prog] = nils_for_nulls(task.at("progress").text.to_s.strip).to_i
  task_info[:created_at] = nils_for_nulls(task.at("created-at").text.to_s.strip)
  task_info[:updated_at] = nils_for_nulls(task.at("updated-at").text.to_s.strip)
  if !task.at("completed-at").text.to_s.empty?
    task_info[:completed_at] = nils_for_nulls(task.at("completed-at").text.to_s.strip)
  end
  if !task.at("error").text.to_s.empty?
    task_info[:error] = nils_for_nulls(task.at("error").text.to_s.strip)
  end
  if !task.at("result").text.to_s.empty?
    task_info[:result] = nils_for_nulls(task.at("result").text.to_s.strip)
  end
  task_info[:orig_path] = nils_for_nulls(task.at("path").text.to_s.strip)
  tmp = args[:ifd][:zip_tmp]
  task_info[:orig_path].gsub!(/^\./,tmp) if task_info[:orig_path]

  # Only report a task if we actually have it.
  # TODO: Copypasta. Separate this out.
  if ::File.exist? task_info[:orig_path]
    tasks_dir = ::File.join(basedir,"tasks")
    task_file = ::File.split(task_info[:orig_path]).last
    if ::File.exist? tasks_dir
      unless (::File.directory?(tasks_dir) && ::File.writable?(tasks_dir))
        raise Msf::DBImportError.new("Could not move files to #{tasks_dir}")
      end
    else
      ::FileUtils.mkdir_p(tasks_dir)
    end
    new_task = ::File.join(tasks_dir,task_file)
    task_info[:path] = new_task
    if ::File.exist?(new_task)
      ::File.unlink new_task # Delete it, and don't report it.
    else
      report_task(task_info) # It's new, so report it.
    end
    ::FileUtils.copy(task_info[:orig_path], new_task)
    yield(:msf_task, new_task) if block
  end
end