Retab all the things (except external/)

This commit is contained in:
Tab Assassin
2013-09-30 13:47:53 -05:00
parent 0ecba377f5
commit 2e8d19edcf
293 changed files with 32962 additions and 32962 deletions
+305 -305
View File
@@ -8,339 +8,339 @@ require 'rex/ui/text/table'
module Msf
class Plugin::Alias < Msf::Plugin
class AliasCommandDispatcher
include Msf::Ui::Console::CommandDispatcher
class AliasCommandDispatcher
include Msf::Ui::Console::CommandDispatcher
attr_reader :aliases
def initialize(driver)
super(driver)
@aliases = {}
end
attr_reader :aliases
def initialize(driver)
super(driver)
@aliases = {}
end
def name
"Alias"
end
def name
"Alias"
end
@@alias_opts = Rex::Parser::Arguments.new(
"-h" => [ false, "Help banner." ],
"-c" => [ true, "Clear an alias (* to clear all)."],
"-f" => [ true, "Force an alias assignment." ]
)
#
# Returns the hash of commands supported by this dispatcher.
#
def commands # driver.dispatcher_stack[3].commands
{
"alias" => "create or view an alias."
# "alias_clear" => "clear an alias (or all aliases).",
# "alias_force" => "Force an alias (such as to override)"
}.merge(aliases) # make aliased commands available as commands of their own
end
@@alias_opts = Rex::Parser::Arguments.new(
"-h" => [ false, "Help banner." ],
"-c" => [ true, "Clear an alias (* to clear all)."],
"-f" => [ true, "Force an alias assignment." ]
)
#
# Returns the hash of commands supported by this dispatcher.
#
def commands # driver.dispatcher_stack[3].commands
{
"alias" => "create or view an alias."
# "alias_clear" => "clear an alias (or all aliases).",
# "alias_force" => "Force an alias (such as to override)"
}.merge(aliases) # make aliased commands available as commands of their own
end
#
# the main alias command handler
#
# usage: alias [options] [name [value]]
def cmd_alias(*args)
# we parse args manually instead of using @@alias.opts.parse to handle special cases
case args.length
when 0 # print the list of current aliases
if @aliases.length == 0
return print_status("No aliases currently defined")
else
tbl = Rex::Ui::Text::Table.new(
'Header' => "Current Aliases",
'Prefix' => "\n",
'Postfix' => "\n",
'Columns' => [ '', 'Alias Name', 'Alias Value' ]
)
# add 'alias' in front of each row so that the output can be copy pasted into an rc file if desired
@aliases.each_pair do |key,val|
tbl << ["alias",key,val]
end
return print(tbl.to_s)
end
when 1 # display the alias if one matches this name (or help)
return cmd_alias_help if args[0] == "-h" or args[0] == "--help"
if @aliases.keys.include?(args[0])
print_status("\'#{args[0]}\' is aliased to \'#{@aliases[args[0]]}\'")
else
print_status("\'#{args[0]}\' is not currently aliased")
end
else # let's see if we can assign or clear the alias
force = false
clear = false
# if using -f or -c, they must be the first arg, because -f/-c may also show up in the alias
# value so we can't do something like if args.include("-f") or delete_if etc
# we should never have to force and clear simultaneously.
if args[0] == "-f"
force = true
args.shift
elsif args[0] == "-c"
clear = true
args.shift
end
name = args.shift
# alias name can NEVER be certain reserved words like 'alias', add any other reserved words here
# We prevent the user from naming the alias "alias" cuz they could end up unable to clear the aliases,
# for example you 'alias -f set unset and then 'alias -f alias sessions', now you're screwed. The byproduct
# of this is that it prevents you from aliasing 'alias' to 'alias -f' etc, but that's acceptable
reserved_words = [/^alias$/i]
reserved_words.each do |regex|
if name =~ regex
print_error "You cannot use #{name} as the name for an alias, sorry"
return false
end
end
#
# the main alias command handler
#
# usage: alias [options] [name [value]]
def cmd_alias(*args)
# we parse args manually instead of using @@alias.opts.parse to handle special cases
case args.length
when 0 # print the list of current aliases
if @aliases.length == 0
return print_status("No aliases currently defined")
else
tbl = Rex::Ui::Text::Table.new(
'Header' => "Current Aliases",
'Prefix' => "\n",
'Postfix' => "\n",
'Columns' => [ '', 'Alias Name', 'Alias Value' ]
)
# add 'alias' in front of each row so that the output can be copy pasted into an rc file if desired
@aliases.each_pair do |key,val|
tbl << ["alias",key,val]
end
return print(tbl.to_s)
end
when 1 # display the alias if one matches this name (or help)
return cmd_alias_help if args[0] == "-h" or args[0] == "--help"
if @aliases.keys.include?(args[0])
print_status("\'#{args[0]}\' is aliased to \'#{@aliases[args[0]]}\'")
else
print_status("\'#{args[0]}\' is not currently aliased")
end
else # let's see if we can assign or clear the alias
force = false
clear = false
# if using -f or -c, they must be the first arg, because -f/-c may also show up in the alias
# value so we can't do something like if args.include("-f") or delete_if etc
# we should never have to force and clear simultaneously.
if args[0] == "-f"
force = true
args.shift
elsif args[0] == "-c"
clear = true
args.shift
end
name = args.shift
# alias name can NEVER be certain reserved words like 'alias', add any other reserved words here
# We prevent the user from naming the alias "alias" cuz they could end up unable to clear the aliases,
# for example you 'alias -f set unset and then 'alias -f alias sessions', now you're screwed. The byproduct
# of this is that it prevents you from aliasing 'alias' to 'alias -f' etc, but that's acceptable
reserved_words = [/^alias$/i]
reserved_words.each do |regex|
if name =~ regex
print_error "You cannot use #{name} as the name for an alias, sorry"
return false
end
end
if clear
# clear all aliases if "*"
if name == "*"
@aliases.keys.each do |a|
deregister_alias(a)
end
print_status "Cleared all aliases"
else # clear the named alias if it exists
if @aliases.keys.include?(name)
deregister_alias(name)
print_status "Cleared alias #{name}"
else
print_error("#{name} is not a currently active alias")
end
end
return
end
# smash everything that's left together
value = args.join(" ")
value.strip!
# value can NEVER be certain bad words like 'rm -rf /', add any other reserved words here
# this is basic idiot protection, not meant to be impervious to subversive intentions
reserved_words = [/^rm +(-rf|-r +-f|-f +-r) +\/.*$/]
reserved_words.each do |regex|
if value =~ regex
print_error "You cannot use #{value} as the value for an alias, sorry"
return false
end
end
if clear
# clear all aliases if "*"
if name == "*"
@aliases.keys.each do |a|
deregister_alias(a)
end
print_status "Cleared all aliases"
else # clear the named alias if it exists
if @aliases.keys.include?(name)
deregister_alias(name)
print_status "Cleared alias #{name}"
else
print_error("#{name} is not a currently active alias")
end
end
return
end
# smash everything that's left together
value = args.join(" ")
value.strip!
# value can NEVER be certain bad words like 'rm -rf /', add any other reserved words here
# this is basic idiot protection, not meant to be impervious to subversive intentions
reserved_words = [/^rm +(-rf|-r +-f|-f +-r) +\/.*$/]
reserved_words.each do |regex|
if value =~ regex
print_error "You cannot use #{value} as the value for an alias, sorry"
return false
end
end
is_valid_alias = is_valid_alias?(name,value)
#print_good "Alias validity = #{is_valid_alias.to_s}"
is_sys_cmd = Rex::FileUtils.find_full_path(name)
is_already_alias = @aliases.keys.include?(name)
if is_valid_alias and not is_sys_cmd and not is_already_alias
register_alias(name, value)
elsif force
if not is_valid_alias
print_status "The alias failed validation, but force is set so we allow this. This is often the case"
print_status "when for instance 'exploit' is being overridden but msfconsole is not currently in the"
print_status "exploit context (an exploit is not loaded), or you are overriding a system command"
end
register_alias(name, value)
else
print_error("#{name} already exists as a system command, use -f to force override") if is_sys_cmd
print_error("#{name} is already an alias, use -f to force override") if is_already_alias
if not is_valid_alias and not force
print_error("\'#{name}\' is not a permitted name or \'#{value}\' is not valid/permitted")
print_error("It's possible the responding dispatcher isn't loaded yet, try changing to the proper context or using -f to force")
end
end
end
end
is_valid_alias = is_valid_alias?(name,value)
#print_good "Alias validity = #{is_valid_alias.to_s}"
is_sys_cmd = Rex::FileUtils.find_full_path(name)
is_already_alias = @aliases.keys.include?(name)
if is_valid_alias and not is_sys_cmd and not is_already_alias
register_alias(name, value)
elsif force
if not is_valid_alias
print_status "The alias failed validation, but force is set so we allow this. This is often the case"
print_status "when for instance 'exploit' is being overridden but msfconsole is not currently in the"
print_status "exploit context (an exploit is not loaded), or you are overriding a system command"
end
register_alias(name, value)
else
print_error("#{name} already exists as a system command, use -f to force override") if is_sys_cmd
print_error("#{name} is already an alias, use -f to force override") if is_already_alias
if not is_valid_alias and not force
print_error("\'#{name}\' is not a permitted name or \'#{value}\' is not valid/permitted")
print_error("It's possible the responding dispatcher isn't loaded yet, try changing to the proper context or using -f to force")
end
end
end
end
def cmd_alias_help
print_line "Usage: alias [options] [name [value]]"
print_line
print(@@alias_opts.usage())
end
def cmd_alias_help
print_line "Usage: alias [options] [name [value]]"
print_line
print(@@alias_opts.usage())
end
#
# Tab completion for the alias command
#
def cmd_alias_tabs(str, words)
if words.length <= 1
#puts "1 word or less"
return @@alias_opts.fmt.keys + tab_complete_aliases_and_commands
else
#puts "more than 1 word"
return tab_complete_aliases_and_commands
end
end
#
# Tab completion for the alias command
#
def cmd_alias_tabs(str, words)
if words.length <= 1
#puts "1 word or less"
return @@alias_opts.fmt.keys + tab_complete_aliases_and_commands
else
#puts "more than 1 word"
return tab_complete_aliases_and_commands
end
end
private
#
# do everything needed to add an alias of +name+ having the value +value+
#
def register_alias(name, value)
#TODO: begin rescue?
#TODO: security concerns since we are using eval
private
#
# do everything needed to add an alias of +name+ having the value +value+
#
def register_alias(name, value)
#TODO: begin rescue?
#TODO: security concerns since we are using eval
# define some class instance methods
self.class_eval do
# define a class instance method that will respond for the alias
define_method "cmd_#{name}" do |*args|
# just replace the alias w/the alias' value and run that
driver.run_single("#{value} #{args.join(' ')}")
end
# define a class instance method that will tab complete the aliased command
# we just proxy to the top-level tab complete function and let them handle it
define_method "cmd_#{name}_tabs" do |str, words|
# we need to repair the tab complete string/words and pass back
# replace alias name with the root alias value
value_words = value.split(/[\s\t\n]+/) # in case value is e.g. 'sessions -l'
# valwords is now [sessions,-l]
words[0] = value_words[0]
# words[0] is now 'sessions' (was 'sue')
value_words.shift # valwords is now ['-l']
# insert any remaining parts of value and rebuild the line
line = words.join(" ") + " " + value_words.join(" ") + " " + str
# define some class instance methods
self.class_eval do
# define a class instance method that will respond for the alias
define_method "cmd_#{name}" do |*args|
# just replace the alias w/the alias' value and run that
driver.run_single("#{value} #{args.join(' ')}")
end
# define a class instance method that will tab complete the aliased command
# we just proxy to the top-level tab complete function and let them handle it
define_method "cmd_#{name}_tabs" do |str, words|
# we need to repair the tab complete string/words and pass back
# replace alias name with the root alias value
value_words = value.split(/[\s\t\n]+/) # in case value is e.g. 'sessions -l'
# valwords is now [sessions,-l]
words[0] = value_words[0]
# words[0] is now 'sessions' (was 'sue')
value_words.shift # valwords is now ['-l']
# insert any remaining parts of value and rebuild the line
line = words.join(" ") + " " + value_words.join(" ") + " " + str
#print_good "passing (#{line.strip}) back to tab_complete"
# clear current tab_words
driver.tab_words = []
driver.tab_complete(line.strip)
end
# add a cmd_#{name}_help method
define_method "cmd_#{name}_help" do |*args|
driver.run_single("help #{value}")
end
end
# add the alias to the list
@aliases[name] = value
end
#print_good "passing (#{line.strip}) back to tab_complete"
# clear current tab_words
driver.tab_words = []
driver.tab_complete(line.strip)
end
# add a cmd_#{name}_help method
define_method "cmd_#{name}_help" do |*args|
driver.run_single("help #{value}")
end
end
# add the alias to the list
@aliases[name] = value
end
#
# do everything required to remove an alias of name +name+
#
def deregister_alias(name)
self.class_eval do
# remove the class methods we created when the alias was registered
remove_method("cmd_#{name}")
remove_method("cmd_#{name}_tabs")
remove_method("cmd_#{name}_help")
end
# remove the alias from the list of active aliases
@aliases.delete(name)
end
#
# do everything required to remove an alias of name +name+
#
def deregister_alias(name)
self.class_eval do
# remove the class methods we created when the alias was registered
remove_method("cmd_#{name}")
remove_method("cmd_#{name}_tabs")
remove_method("cmd_#{name}_help")
end
# remove the alias from the list of active aliases
@aliases.delete(name)
end
#
# Validate a proposed alias with the +name+ and having the value +value+
#
def is_valid_alias?(name,value)
#print_good "Assessing validay for #{name} and #{value}"
# we validate two things, the name and the value
#
# Validate a proposed alias with the +name+ and having the value +value+
#
def is_valid_alias?(name,value)
#print_good "Assessing validay for #{name} and #{value}"
# we validate two things, the name and the value
### name
# we don't check if this alias name exists or if it's a console command already etc as -f can override
# that so those need to be checked externally, we pretty much just check to see if the name is sane
name.strip!
bad_words = [/\*/] # add any additional "bad word" regexes here
bad_words.each do |regex|
# don't mess around, just return false in this case, prevents wasted processing
return false if name =~ regex
end
### name
# we don't check if this alias name exists or if it's a console command already etc as -f can override
# that so those need to be checked externally, we pretty much just check to see if the name is sane
name.strip!
bad_words = [/\*/] # add any additional "bad word" regexes here
bad_words.each do |regex|
# don't mess around, just return false in this case, prevents wasted processing
return false if name =~ regex
end
### value
# value is considered valid if it's a ref to a valid console cmd, a system executable, or an existing
# alias AND isn't a "bad word"
# Here we check for "bad words" to avoid for the value...value would have to NOT match these regexes
# this is just basic idiot protection
value.strip!
bad_words = [/^msfconsole$/]
bad_words.each do |regex|
# don't mess around, just return false if we match
return false if value =~ regex
end
### value
# value is considered valid if it's a ref to a valid console cmd, a system executable, or an existing
# alias AND isn't a "bad word"
# Here we check for "bad words" to avoid for the value...value would have to NOT match these regexes
# this is just basic idiot protection
value.strip!
bad_words = [/^msfconsole$/]
bad_words.each do |regex|
# don't mess around, just return false if we match
return false if value =~ regex
end
# we're only gonna validate the first part of the cmd, e.g. just ls from "ls -lh"
value = value.split(" ").first
if @aliases.keys.include?(value)
return true
else
[value, value+".exe"].each do |cmd|
if Rex::FileUtils.find_full_path(cmd)
return true
end
end
end
# we're only gonna validate the first part of the cmd, e.g. just ls from "ls -lh"
value = value.split(" ").first
if @aliases.keys.include?(value)
return true
else
[value, value+".exe"].each do |cmd|
if Rex::FileUtils.find_full_path(cmd)
return true
end
end
end
# gather all the current commands the driver's dispatcher's have & check 'em
driver.dispatcher_stack.each do |dispatcher|
next unless dispatcher.respond_to?(:commands)
next if (dispatcher.commands.nil?)
next if (dispatcher.commands.length == 0)
# gather all the current commands the driver's dispatcher's have & check 'em
driver.dispatcher_stack.each do |dispatcher|
next unless dispatcher.respond_to?(:commands)
next if (dispatcher.commands.nil?)
next if (dispatcher.commands.length == 0)
if dispatcher.respond_to?("cmd_#{value.split(" ").first}")
#print_status "Dispatcher (#{dispatcher.name}) responds to cmd_#{value.split(" ").first}"
return true
else
#print_status "Dispatcher (#{dispatcher.name}) does not respond to cmd_#{value.split(" ").first}"
end
end
if dispatcher.respond_to?("cmd_#{value.split(" ").first}")
#print_status "Dispatcher (#{dispatcher.name}) responds to cmd_#{value.split(" ").first}"
return true
else
#print_status "Dispatcher (#{dispatcher.name}) does not respond to cmd_#{value.split(" ").first}"
end
end
return false
end
return false
end
#
# Provide tab completion list for aliases and commands
#
def tab_complete_aliases_and_commands
items = []
# gather all the current commands the driver's dispatcher's have
driver.dispatcher_stack.each do |dispatcher|
next unless dispatcher.respond_to?(:commands)
next if (dispatcher.commands.nil? or dispatcher.commands.length == 0)
items << dispatcher.commands.keys
end
# add all the current aliases to the list
items.concat(@aliases.keys)
return items
end
#
# Provide tab completion list for aliases and commands
#
def tab_complete_aliases_and_commands
items = []
# gather all the current commands the driver's dispatcher's have
driver.dispatcher_stack.each do |dispatcher|
next unless dispatcher.respond_to?(:commands)
next if (dispatcher.commands.nil? or dispatcher.commands.length == 0)
items << dispatcher.commands.keys
end
# add all the current aliases to the list
items.concat(@aliases.keys)
return items
end
end # end AliasCommandDispatcher class
end # end AliasCommandDispatcher class
#
# The constructor is called when an instance of the plugin is created. The
# framework instance that the plugin is being associated with is passed in
# the framework parameter. Plugins should call the parent constructor when
# inheriting from Msf::Plugin to ensure that the framework attribute on
# their instance gets set.
#
def initialize(framework, opts)
super
#
# The constructor is called when an instance of the plugin is created. The
# framework instance that the plugin is being associated with is passed in
# the framework parameter. Plugins should call the parent constructor when
# inheriting from Msf::Plugin to ensure that the framework attribute on
# their instance gets set.
#
def initialize(framework, opts)
super
## Register the commands above
add_console_dispatcher(AliasCommandDispatcher)
end
## Register the commands above
add_console_dispatcher(AliasCommandDispatcher)
end
#
# The cleanup routine for plugins gives them a chance to undo any actions
# they may have done to the framework. For instance, if a console
# dispatcher was added, then it should be removed in the cleanup routine.
#
def cleanup
# If we had previously registered a console dispatcher with the console,
# deregister it now.
remove_console_dispatcher('Alias')
#
# The cleanup routine for plugins gives them a chance to undo any actions
# they may have done to the framework. For instance, if a console
# dispatcher was added, then it should be removed in the cleanup routine.
#
def cleanup
# If we had previously registered a console dispatcher with the console,
# deregister it now.
remove_console_dispatcher('Alias')
# we don't need to remove class methods we added because they were added to
# AliasCommandDispatcher class
end
# we don't need to remove class methods we added because they were added to
# AliasCommandDispatcher class
end
#
# This method returns a short, friendly name for the plugin.
#
def name
"alias"
end
#
# This method returns a short, friendly name for the plugin.
#
def name
"alias"
end
#
# This method returns a brief description of the plugin. It should be no
# more than 60 characters, but there are no hard limits.
#
def desc
"Adds the ability to alias console commands"
end
#
# This method returns a brief description of the plugin. It should be no
# more than 60 characters, but there are no hard limits.
#
def desc
"Adds the ability to alias console commands"
end
end ## End Plugin Class
end ## End Module
+27 -27
View File
@@ -5,37 +5,37 @@
module Msf
class Plugin::AutoAddRoute < Msf::Plugin
include Msf::SessionEvent
def name; 'auto_add_route'; end
include Msf::SessionEvent
def name; 'auto_add_route'; end
def desc
"Adds routes for any new subnets whenever a session opens"
end
def desc
"Adds routes for any new subnets whenever a session opens"
end
def on_session_open(session)
return if not session.type == 'meterpreter'
session.load_stdapi
sb = Rex::Socket::SwitchBoard.instance
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'
if not sb.route_exists?(route.subnet, route.netmask)
print_status("AutoAddRoute: Routing new subnet #{route.subnet}/#{route.netmask} through session #{session.sid}")
sb.add_route(route.subnet, route.netmask, session)
end
}
end
def on_session_open(session)
return if not session.type == 'meterpreter'
session.load_stdapi
sb = Rex::Socket::SwitchBoard.instance
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'
if not sb.route_exists?(route.subnet, route.netmask)
print_status("AutoAddRoute: Routing new subnet #{route.subnet}/#{route.netmask} through session #{session.sid}")
sb.add_route(route.subnet, route.netmask, session)
end
}
end
def initialize(framework, opts)
super
self.framework.events.add_session_subscriber(self)
end
def initialize(framework, opts)
super
self.framework.events.add_session_subscriber(self)
end
def cleanup
self.framework.events.remove_session_subscriber(self)
end
def cleanup
self.framework.events.remove_session_subscriber(self)
end
end
end
+80 -80
View File
@@ -9,109 +9,109 @@
module Msf
class Plugin::CredCollect < Msf::Plugin
include Msf::SessionEvent
include Msf::SessionEvent
class CredCollectCommandDispatcher
include Msf::Ui::Console::CommandDispatcher
class CredCollectCommandDispatcher
include Msf::Ui::Console::CommandDispatcher
def name
"credcollect"
end
def name
"credcollect"
end
def commands
{
"db_hashes" => "Dumps hashes (deprecated: use 'creds -s smb')",
"db_tokens" => "Dumps tokens (deprecated: use 'notes -t smb_token')"
}
end
def commands
{
"db_hashes" => "Dumps hashes (deprecated: use 'creds -s smb')",
"db_tokens" => "Dumps tokens (deprecated: use 'notes -t smb_token')"
}
end
def cmd_db_hashes()
print_error ""
print_error "db_hashes is deprecated. Use 'creds -s smb' instead."
print_error ""
end
def cmd_db_hashes()
print_error ""
print_error "db_hashes is deprecated. Use 'creds -s smb' instead."
print_error ""
end
def cmd_db_tokens()
print_error ""
print_error "db_tokens is deprecated. Use 'notes -t smb_token' instead."
print_error ""
end
def cmd_db_tokens()
print_error ""
print_error "db_tokens is deprecated. Use 'notes -t smb_token' instead."
print_error ""
end
end
end
def on_session_open(session)
def on_session_open(session)
return if not self.framework.db.active
return if not self.framework.db.active
print_status("This is CredCollect, I have the conn!")
print_status("This is CredCollect, I have the conn!")
if (session.type == "meterpreter")
if (session.type == "meterpreter")
# Make sure we're rockin Priv and Incognito
session.core.use("priv")
session.core.use("incognito")
# Make sure we're rockin Priv and Incognito
session.core.use("priv")
session.core.use("incognito")
# It wasn't me mom! Stinko did it!
hashes = session.priv.sam_hashes
# It wasn't me mom! Stinko did it!
hashes = session.priv.sam_hashes
# Target infos for the db record
addr = session.sock.peerhost
# This ought to read from the exploit's datastore.
# Use the meterpreter script if you need to control it.
smb_port = 445
# Target infos for the db record
addr = session.sock.peerhost
# This ought to read from the exploit's datastore.
# Use the meterpreter script if you need to control it.
smb_port = 445
# Record hashes to the running db instance
hashes.each do |hash|
data = {}
data[:host] = addr
data[:port] = smb_port
data[:sname] = 'smb'
data[:user] = hash.user_name
data[:pass] = hash.lanman + ":" + hash.ntlm
data[:type] = "smb_hash"
data[:active] = true
# Record hashes to the running db instance
hashes.each do |hash|
data = {}
data[:host] = addr
data[:port] = smb_port
data[:sname] = 'smb'
data[:user] = hash.user_name
data[:pass] = hash.lanman + ":" + hash.ntlm
data[:type] = "smb_hash"
data[:active] = true
self.framework.db.report_auth_info(data)
end
self.framework.db.report_auth_info(data)
end
# Record user tokens
tokens = session.incognito.incognito_list_tokens(0).values
# Meh, tokens come to us as a formatted string
tokens = tokens.join.strip!.split("\n")
# Record user tokens
tokens = session.incognito.incognito_list_tokens(0).values
# Meh, tokens come to us as a formatted string
tokens = tokens.join.strip!.split("\n")
tokens.each do |token|
data = {}
data[:host] = addr
data[:type] = 'smb_token'
data[:data] = token
data[:update] = :unique_data
tokens.each do |token|
data = {}
data[:host] = addr
data[:type] = 'smb_token'
data[:data] = token
data[:update] = :unique_data
self.framework.db.report_note(data)
end
end
end
self.framework.db.report_note(data)
end
end
end
def on_session_close(session,reason='')
end
def on_session_close(session,reason='')
end
def initialize(framework, opts)
super
self.framework.events.add_session_subscriber(self)
add_console_dispatcher(CredCollectCommandDispatcher)
end
def initialize(framework, opts)
super
self.framework.events.add_session_subscriber(self)
add_console_dispatcher(CredCollectCommandDispatcher)
end
def cleanup
self.framework.events.remove_session_subscriber(self)
remove_console_dispatcher('credcollect')
end
def cleanup
self.framework.events.remove_session_subscriber(self)
remove_console_dispatcher('credcollect')
end
def name
"db_credcollect"
end
def name
"db_credcollect"
end
def desc
"Automatically grabs hashes and tokens from meterpreter session events and stores them in the db"
end
def desc
"Automatically grabs hashes and tokens from meterpreter session events and stores them in the db"
end
end
end
+44 -44
View File
@@ -14,62 +14,62 @@ module Msf
class Plugin::DB_Tracer < Msf::Plugin
###
#
# This class implements a socket communication tracker
#
###
class DBTracerEventHandler
include Rex::Socket::Comm::Events
###
#
# This class implements a socket communication tracker
#
###
class DBTracerEventHandler
include Rex::Socket::Comm::Events
def on_before_socket_create(comm, param)
end
def on_before_socket_create(comm, param)
end
def on_socket_created(comm, sock, param)
# Ignore local listening sockets
return if not sock.peerhost
def on_socket_created(comm, sock, param)
# Ignore local listening sockets
return if not sock.peerhost
if (sock.peerhost != '0.0.0.0' and sock.peerport)
if (sock.peerhost != '0.0.0.0' and sock.peerport)
# Ignore sockets that didn't set up their context
# to hold the framework in 'Msf'
return if not param.context['Msf']
# Ignore sockets that didn't set up their context
# to hold the framework in 'Msf'
return if not param.context['Msf']
host = param.context['Msf'].db.find_or_create_host(:host => sock.peerhost, :state => Msf::HostState::Alive)
return if not host
host = param.context['Msf'].db.find_or_create_host(:host => sock.peerhost, :state => Msf::HostState::Alive)
return if not host
param.context['Msf'].db.report_service(:host => host, :proto => param.proto, :port => sock.peerport)
end
end
end
param.context['Msf'].db.report_service(:host => host, :proto => param.proto, :port => sock.peerport)
end
end
end
def initialize(framework, opts)
super
def initialize(framework, opts)
super
if(not framework.db.active)
raise PluginLoadError.new("The database backend has not been initialized")
end
framework.plugins.each { |plugin|
if (plugin.class == Msf::Plugin::DB_Tracer)
raise PluginLoadError.new("This plugin should not be loaded more than once")
end
}
if(not framework.db.active)
raise PluginLoadError.new("The database backend has not been initialized")
end
framework.plugins.each { |plugin|
if (plugin.class == Msf::Plugin::DB_Tracer)
raise PluginLoadError.new("This plugin should not be loaded more than once")
end
}
@eh = DBTracerEventHandler.new
Rex::Socket::Comm::Local.register_event_handler(@eh)
end
@eh = DBTracerEventHandler.new
Rex::Socket::Comm::Local.register_event_handler(@eh)
end
def cleanup
Rex::Socket::Comm::Local.deregister_event_handler(@eh)
end
def cleanup
Rex::Socket::Comm::Local.deregister_event_handler(@eh)
end
def name
"db_tracker"
end
def name
"db_tracker"
end
def desc
"Monitors socket calls and updates the database backend"
end
def desc
"Monitors socket calls and updates the database backend"
end
end
end
+56 -56
View File
@@ -12,74 +12,74 @@ module Msf
###
class Plugin::Editor < Msf::Plugin
###
#
# This class implements a single edit command.
#
###
class EditorCommandDispatcher
include Msf::Ui::Console::ModuleCommandDispatcher
###
#
# This class implements a single edit command.
#
###
class EditorCommandDispatcher
include Msf::Ui::Console::ModuleCommandDispatcher
#
# The dispatcher's name.
#
def name
"Editor"
end
#
# The dispatcher's name.
#
def name
"Editor"
end
#
# Returns the hash of commands supported by this dispatcher.
#
def commands
# Don't update super here since we don't want the commands from
# super, just the methods
{
"edit" => "A handy editor commmand"
}
end
#
# Returns the hash of commands supported by this dispatcher.
#
def commands
# Don't update super here since we don't want the commands from
# super, just the methods
{
"edit" => "A handy editor commmand"
}
end
#
# This method handles the edit command.
#
def cmd_edit(*args)
print_line("Launching editor...")
#
# This method handles the edit command.
#
def cmd_edit(*args)
print_line("Launching editor...")
e = Rex::Compat.getenv("EDITOR") || "vi"
e = Rex::Compat.getenv("EDITOR") || "vi"
if (not mod) or (not (path = mod.file_path))
print_line("Error: No active module selected")
return nil
end
if (not mod) or (not (path = mod.file_path))
print_line("Error: No active module selected")
return nil
end
ret = system(e, path)
if not ret
print_line("Failed to execute your editor (#{e})")
return
end
ret = system(e, path)
if not ret
print_line("Failed to execute your editor (#{e})")
return
end
reload
ret
end
end
reload
ret
end
end
def initialize(framework, opts)
super
def initialize(framework, opts)
super
# console dispatcher commands.
add_console_dispatcher(EditorCommandDispatcher)
end
# console dispatcher commands.
add_console_dispatcher(EditorCommandDispatcher)
end
def cleanup
remove_console_dispatcher('Editor')
end
def cleanup
remove_console_dispatcher('Editor')
end
def name
"editor"
end
def name
"editor"
end
def desc
"Simple Editor Plugin"
end
def desc
"Simple Editor Plugin"
end
protected
end
+26 -26
View File
@@ -6,34 +6,34 @@
module Msf
class Plugin::EventTester < Msf::Plugin
class Subscriber
def respond_to?(name)
# Why yes, I can do that.
true
end
def method_missing(name, *args)
$stdout.puts("Event fired: #{name}(#{args.join(", ")})")
end
end
class Subscriber
def respond_to?(name)
# Why yes, I can do that.
true
end
def method_missing(name, *args)
$stdout.puts("Event fired: #{name}(#{args.join(", ")})")
end
end
def name; "event_tester"; end
def name; "event_tester"; end
def initialize(framework, opts)
super
@subscriber = Subscriber.new
framework.events.add_exploit_subscriber(@subscriber)
framework.events.add_session_subscriber(@subscriber)
framework.events.add_general_subscriber(@subscriber)
framework.events.add_db_subscriber(@subscriber)
framework.events.add_ui_subscriber(@subscriber)
end
def cleanup
framework.events.remove_exploit_subscriber(@subscriber)
framework.events.remove_session_subscriber(@subscriber)
framework.events.remove_general_subscriber(@subscriber)
framework.events.remove_db_subscriber(@subscriber)
framework.events.remove_ui_subscriber(@subscriber)
end
def initialize(framework, opts)
super
@subscriber = Subscriber.new
framework.events.add_exploit_subscriber(@subscriber)
framework.events.add_session_subscriber(@subscriber)
framework.events.add_general_subscriber(@subscriber)
framework.events.add_db_subscriber(@subscriber)
framework.events.add_ui_subscriber(@subscriber)
end
def cleanup
framework.events.remove_exploit_subscriber(@subscriber)
framework.events.remove_session_subscriber(@subscriber)
framework.events.remove_general_subscriber(@subscriber)
framework.events.remove_db_subscriber(@subscriber)
framework.events.remove_ui_subscriber(@subscriber)
end
end
end
+73 -73
View File
@@ -12,95 +12,95 @@ module Msf
###
class Plugin::FFAutoRegen < Msf::Plugin
###
#
# This class implements a single edit command.
#
###
class FFAutoRegenCommandDispatcher
include Msf::Ui::Console::CommandDispatcher
###
#
# This class implements a single edit command.
#
###
class FFAutoRegenCommandDispatcher
include Msf::Ui::Console::CommandDispatcher
#
# The dispatcher's name.
#
def name
"FFAutoRegen"
end
#
# The dispatcher's name.
#
def name
"FFAutoRegen"
end
#
# Returns the hash of commands supported by this dispatcher.
#
def commands
{
"ffautoregen" => "Automatically regenerate the document when the exploti source changes"
}
end
#
# Returns the hash of commands supported by this dispatcher.
#
def commands
{
"ffautoregen" => "Automatically regenerate the document when the exploti source changes"
}
end
#
# This method handles the command.
#
def cmd_ffautoregen(*args)
if (not active_module) or (not (path = active_module.file_path))
print_line("Error: No active module selected")
return nil
end
#
# This method handles the command.
#
def cmd_ffautoregen(*args)
if (not active_module) or (not (path = active_module.file_path))
print_line("Error: No active module selected")
return nil
end
last = mt = File.stat(path).mtime
last = mt = File.stat(path).mtime
loop {
sleep(1)
mt = File.stat(path).mtime
loop {
sleep(1)
mt = File.stat(path).mtime
if (mt != last)
last = mt
if (mt != last)
last = mt
omod = active_module
nmod = framework.modules.reload_module(active_module)
if not nmod
print_line("Error: Failed to reload module, trying again on next change...")
next
end
omod = active_module
nmod = framework.modules.reload_module(active_module)
if not nmod
print_line("Error: Failed to reload module, trying again on next change...")
next
end
active_module = nmod
active_module = nmod
jobify = false
payload = nmod.datastore['PAYLOAD']
encoder = nmod.datastore['ENCODER']
target = nmod.datastore['TARGET']
nop = nmod.datastore['NOP']
jobify = false
payload = nmod.datastore['PAYLOAD']
encoder = nmod.datastore['ENCODER']
target = nmod.datastore['TARGET']
nop = nmod.datastore['NOP']
nmod.exploit_simple(
'Encoder' => encoder,
'Payload' => payload,
'Target' => target,
'Nop' => nop,
nmod.exploit_simple(
'Encoder' => encoder,
'Payload' => payload,
'Target' => target,
'Nop' => nop,
# 'OptionStr' => opt_str,
'LocalInput' => driver.input,
'LocalOutput' => driver.output,
'RunAsJob' => jobify)
end
}
end
end
'LocalInput' => driver.input,
'LocalOutput' => driver.output,
'RunAsJob' => jobify)
end
}
end
end
def initialize(framework, opts)
super
def initialize(framework, opts)
super
# console dispatcher commands.
add_console_dispatcher(FFAutoRegenCommandDispatcher)
end
# console dispatcher commands.
add_console_dispatcher(FFAutoRegenCommandDispatcher)
end
def cleanup
remove_console_dispatcher('FFAutoRegen')
end
def cleanup
remove_console_dispatcher('FFAutoRegen')
end
def name
"ffautoregen"
end
def name
"ffautoregen"
end
def desc
"FileFormat AutoRegen Plugin"
end
def desc
"FileFormat AutoRegen Plugin"
end
protected
end
+75 -75
View File
@@ -15,44 +15,44 @@ module Msf
class Plugin::IPSFilter < Msf::Plugin
###
#
# This class implements a socket communication logger
#
###
class IPSSocketEventHandler
include Rex::Socket::Comm::Events
###
#
# This class implements a socket communication logger
#
###
class IPSSocketEventHandler
include Rex::Socket::Comm::Events
def on_before_socket_create(comm, param)
end
def on_before_socket_create(comm, param)
end
def on_socket_created(comm, sock, param)
# Sockets created by the exploit have MsfExploit set and MsfPayload not set
if (param.context['MsfExploit'] and (! param.context['MsfPayload'] ))
sock.extend(IPSFilter::SocketTracer)
sock.context = param.context
end
end
end
def on_socket_created(comm, sock, param)
# Sockets created by the exploit have MsfExploit set and MsfPayload not set
if (param.context['MsfExploit'] and (! param.context['MsfPayload'] ))
sock.extend(IPSFilter::SocketTracer)
sock.context = param.context
end
end
end
def initialize(framework, opts)
super
@ips_eh = IPSSocketEventHandler.new
Rex::Socket::Comm::Local.register_event_handler(@ips_eh)
end
def initialize(framework, opts)
super
@ips_eh = IPSSocketEventHandler.new
Rex::Socket::Comm::Local.register_event_handler(@ips_eh)
end
def cleanup
Rex::Socket::Comm::Local.deregister_event_handler(@ips_eh)
end
def cleanup
Rex::Socket::Comm::Local.deregister_event_handler(@ips_eh)
end
def name
"ips_filter"
end
def name
"ips_filter"
end
def desc
"Scans all outgoing data to see if it matches a known IPS signature"
end
def desc
"Scans all outgoing data to see if it matches a known IPS signature"
end
protected
end
@@ -63,57 +63,57 @@ end
module IPSFilter
module SocketTracer
attr_accessor :context
attr_accessor :context
# Hook the write method
def write(buf, opts = {})
if (ips_match(buf))
print_error "Outbound write blocked due to possible signature match"
return 0
end
super(buf, opts)
end
# Hook the write method
def write(buf, opts = {})
if (ips_match(buf))
print_error "Outbound write blocked due to possible signature match"
return 0
end
super(buf, opts)
end
# Hook the read method
def read(length = nil, opts = {})
r = super(length, opts)
if (ips_match(r))
print_error "Incoming read may match a known signature"
end
return r
end
# Hook the read method
def read(length = nil, opts = {})
r = super(length, opts)
if (ips_match(r))
print_error "Incoming read may match a known signature"
end
return r
end
def close(*args)
super(*args)
end
def close(*args)
super(*args)
end
def ips_match(data)
lp = localport
rp = peerport
def ips_match(data)
lp = localport
rp = peerport
SIGS.each do |s|
begin
r = Regexp.new(s[1])
if (data.match(r))
print_error "Matched IPS signature #{s[0]}"
return true
end
rescue ::Exception => e
print_error "Compiled error: #{s[1]}"
end
end
SIGS.each do |s|
begin
r = Regexp.new(s[1])
if (data.match(r))
print_error "Matched IPS signature #{s[0]}"
return true
end
rescue ::Exception => e
print_error "Compiled error: #{s[1]}"
end
end
return false
end
return false
end
# Extend this as needed :-)
SIGS =
[
['DCOM.C', ".*\\\x5c\x00\\\x5c\x00\x46\x00\x58\x00\x4e\x00\x42\x00\x46\x00\x58\x00\x46\x00\x58\x00.*\xcc\xe0\xfd\x7f.*"],
['BLASTER', ".*\\\x5c\x00\\\x5c\x00\x46\x00\x58\x00\x4e\x00\x42\x00\x46\x00\x58\x00\x46\x00\x58\x00.*\xcc\xe0\xfd\x7f.*"],
['REMACT', ".*\xb8\x4a\x9f\x4d\x1c\\}\xcf\x11\x86\x1e\x00\x20\xaf\x6e.*"],
['x86 NOP SLED', "\x90\x90"],
]
# Extend this as needed :-)
SIGS =
[
['DCOM.C', ".*\\\x5c\x00\\\x5c\x00\x46\x00\x58\x00\x4e\x00\x42\x00\x46\x00\x58\x00\x46\x00\x58\x00.*\xcc\xe0\xfd\x7f.*"],
['BLASTER', ".*\\\x5c\x00\\\x5c\x00\x46\x00\x58\x00\x4e\x00\x42\x00\x46\x00\x58\x00\x46\x00\x58\x00.*\xcc\xe0\xfd\x7f.*"],
['REMACT', ".*\xb8\x4a\x9f\x4d\x1c\\}\xcf\x11\x86\x1e\x00\x20\xaf\x6e.*"],
['x86 NOP SLED', "\x90\x90"],
]
end
end
+562 -562
View File
@@ -10,568 +10,568 @@ require 'yaml'
module Msf
class Plugin::Lab < Msf::Plugin
class LabCommandDispatcher
include Msf::Ui::Console::CommandDispatcher
attr_accessor :controller
def initialize(driver)
super(driver)
@controller = nil
#
# Require the lab gem, but fail nicely if it's not there.
#
begin
require 'lab'
rescue LoadError
raise "WARNING: Lab gem not found, Please 'gem install lab'"
end
end
#
# Returns the hash of commands supported by this dispatcher.
#
def commands
{
"lab_help" => "lab_help <lab command> - Show that command's description.",
"lab_show" => "lab_show - show all vms in the lab.",
"lab_search" => "lab_search - search local vms in the lab.",
"lab_search_tags" => "lab_search_tag - search local vms in the lab.",
#"lab_search_remote" => "lab_search_remote - search remote vms in the lab.",
"lab_show_running" => "lab_show_running - show running vms.",
"lab_load" => "lab_load [file] - load a lab definition from disk.",
"lab_save" => "lab_save [filename] - persist a lab definition in a file.",
"lab_load_running" => "lab_load_running [type] [user] [host] - use the running vms to create a lab.",
"lab_load_config" => "lab_load_config [type] [user] [host] - use the vms in the config to create a lab.",
"lab_load_dir" => "lab_load_dir [type] [directory] - create a lab from a specified directory.",
"lab_clear" => "lab_clear - clear the running lab.",
"lab_start" => "lab_start [vmid+|all] start the specified vm.",
"lab_reset" => "lab_reset [vmid+|all] reset the specified vm.",
"lab_suspend" => "lab_suspend [vmid+|all] suspend the specified vm.",
"lab_stop" => "lab_stop [vmid+|all] stop the specified vm.",
"lab_revert" => "lab_revert [vmid+|all] [snapshot] revert the specified vm.",
"lab_snapshot" => "lab_snapshot [vmid+|all] [snapshot] snapshot all targets for this exploit.",
"lab_upload" => "lab_upload [vmid] [local_path] [remote_path] upload a file.",
"lab_run_command" => "lab_run_command [vmid+|all] [command] run a command on all targets.",
"lab_browse_to" => "lab_browse_to [vmid+|all] [uri] use the default browser to browse to a uri."
}
end
def name
"Lab"
end
##
## Regular Lab Commands
##
def cmd_lab_load(*args)
return lab_usage unless args.count == 1
res = args[0]
good_res = nil
if (File.file? res and File.readable? res)
# then the provided argument is an absolute path and is gtg.
good_res = res
elsif
# let's check to see if it's in the data/lab dir (like when tab completed)
[
::Msf::Config.data_directory + File::SEPARATOR + "lab",
# there isn't a user_data_directory, but could use:
#::Msf::Config.user_plugins_directory + File::SEPARATOR + "lab"
].each do |dir|
res_path = dir + File::SEPARATOR + res
if (File.file?(res_path) and File.readable?(res_path))
good_res = res_path
break
end
end
end
if good_res
@controller.from_file(good_res)
else
print_error("#{res} is not a valid lab definition file (.yml)")
end
end
#
# Tab completion for the lab_load command
#
def cmd_lab_load_tabs(str, words)
tabs = []
#return tabs if words.length > 1
if ( str and str =~ /^#{Regexp.escape(File::SEPARATOR)}/ )
# then you are probably specifying a full path so let's just use normal file completion
return tab_complete_filenames(str,words)
elsif (not words[1] or not words[1].match(/^\//))
# then let's start tab completion in the data/lab directory
begin
[
::Msf::Config.data_directory + File::SEPARATOR + "lab",
# there isn't a user_data_directory, but could use:
#::Msf::Config.user_plugins_directory + File::SEPARATOR + "lab"
].each do |dir|
next if not ::File.exist? dir
tabs += ::Dir.new(dir).find_all { |e|
path = dir + File::SEPARATOR + e
::File.file?(path) and File.readable?(path)
}
end
rescue Exception
end
else
tabs += tab_complete_filenames(str,words)
end
return tabs
end
def cmd_lab_load_dir(*args)
return lab_usage unless args.count == 2
@controller.build_from_dir(args[0],args[1],true)
end
def cmd_lab_clear(*args)
@controller.clear!
end
def cmd_lab_save(*args)
return lab_usage if args.empty?
@controller.to_file(args[0])
end
def cmd_lab_load_running(*args)
return lab_usage if args.empty?
if args[0] =~ /^remote_/
return lab_usage unless args.count == 3
## Expect a username & password
@controller.build_from_running(args[0], args[1], args[2])
else
return lab_usage unless args.count == 1
@controller.build_from_running(args[0])
end
end
def cmd_lab_load_config(*args)
return lab_usage if args.empty?
if args[0] =~ /^remote_/
return lab_usage unless args.count == 3
## Expect a username & password
@controller.build_from_config(args[0], args[1], args[2])
else
return lab_usage unless args.count == 1
@controller.build_from_config(args[0])
end
end
def cmd_lab_load_dir(*args)
return lab_usage unless args.count == 2
@controller.build_from_dir(args[0],args[1],true)
end
def cmd_lab_clear(*args)
@controller.clear!
end
def cmd_lab_save(*args)
return lab_usage if args.empty?
@controller.to_file(args[0])
end
##
## Commands for dealing with a currently-loaded lab
##
def cmd_lab_show(*args)
if args.empty?
hlp_print_lab
else
args.each do |name|
if @controller.includes_hostname? name
print_line @controller[name].to_yaml
else
print_error "Unknown vm '#{name}'"
end
end
end
end
def cmd_lab_show_running(*args)
hlp_print_lab_running
end
def cmd_lab_search(*args)
if args.empty?
hlp_print_lab
else
args.each do |arg|
print_line "Searching for vms with hostname matching #{arg}"
@controller.each do |vm|
print_line "checking to see #{vm.hostname} matches #{arg}"
print_line "#{vm.hostname} matched #{arg}" if vm.hostname =~ Regexp.new(arg)
end
end
end
end
def cmd_lab_search_tags(*args)
if args.empty?
hlp_print_lab
else
args.each do |arg|
print_line "Searching for vms with tags matching #{arg}"
@controller.each do |vm|
print_line "checking to see #{vm.hostname} is tagged #{arg}"
print_line "#{vm.hostname} tagged #{arg}" if vm.tagged?(arg)
end
end
end
end
def cmd_lab_start(*args)
return lab_usage if args.empty?
if args[0] == "all"
@controller.each do |vm|
print_line "Starting lab vm #{vm.hostname}."
if !vm.running?
vm.start
else
print_line "Lab vm #{vm.hostname} already running."
end
end
else
args.each do |arg|
if @controller.includes_hostname? arg
vm = @controller.find_by_hostname(arg)
if !vm.running?
print_line "Starting lab vm #{vm.hostname}."
vm.start
else
print_line "Lab vm #{vm.hostname} already running."
end
end
end
end
end
def cmd_lab_stop(*args)
return lab_usage if args.empty?
if args[0] == "all"
@controller.each do |vm|
print_line "Stopping lab vm #{vm.hostname}."
if vm.running?
vm.stop
else
print_line "Lab vm #{vm.hostname} not running."
end
end
else
args.each do |arg|
if @controller.includes_hostname? arg
vm = @controller.find_by_hostname(arg)
if vm.running?
print_line "Stopping lab vm #{vm.hostname}."
vm.stop
else
print_line "Lab vm #{vm.hostname} not running."
end
end
end
end
end
def cmd_lab_suspend(*args)
return lab_usage if args.empty?
if args[0] == "all"
@controller.each{ |vm| vm.suspend }
else
args.each do |arg|
if @controller.includes_hostname? arg
if @controller.find_by_hostname(arg).running?
print_line "Suspending lab vm #{arg}."
@controller.find_by_hostname(arg).suspend
end
end
end
end
end
def cmd_lab_reset(*args)
return lab_usage if args.empty?
if args[0] == "all"
print_line "Resetting all lab vms."
@controller.each{ |vm| vm.reset }
else
args.each do |arg|
if @controller.includes_hostname? arg
if @controller.find_by_hostname(arg).running?
print_line "Resetting lab vm #{arg}."
@controller.find_by_hostname(arg).reset
end
end
end
end
end
def cmd_lab_snapshot(*args)
return lab_usage if args.count < 2
snapshot = args[args.count-1]
if args[0] == "all"
print_line "Snapshotting all lab vms to snapshot: #{snapshot}."
@controller.each{ |vm| vm.create_snapshot(snapshot) }
else
args[0..-2].each do |name_arg|
next unless @controller.includes_hostname? name_arg
print_line "Snapshotting #{name_arg} to snapshot: #{snapshot}."
@controller[name_arg].create_snapshot(snapshot)
end
end
end
def cmd_lab_revert(*args)
return lab_usage if args.count < 2
snapshot = args[args.count-1]
if args[0] == "all"
print_line "Reverting all lab vms to snapshot: #{snapshot}."
@controller.each{ |vm| vm.revert_snapshot(snapshot) }
else
args[0..-2].each do |name_arg|
next unless @controller.includes_hostname? name_arg
print_line "Reverting #{name_arg} to snapshot: #{snapshot}."
@controller[name_arg].revert_snapshot(snapshot)
end
end
end
def cmd_lab_run_command(*args)
return lab_usage if args.empty?
command = args[args.count-1]
if args[0] == "all"
print_line "Running command #{command} on all vms."
@controller.each do |vm|
if vm.running?
print_line "#{vm.hostname} running command: #{command}."
vm.run_command(command)
end
end
else
args[0..-2].each do |name_arg|
next unless @controller.includes_hostname? name_arg
if @controller[name_arg].running?
print_line "#{name_arg} running command: #{command}."
@controller[name_arg].run_command(command)
end
end
end
end
#
# Command: lab_upload [vmids] [from] [to]
#
# Description: Uploads a file to the guest(s)
#
# Quirks: Pass "all" as a vmid to have it operate on all vms.
#
def cmd_lab_upload(*args)
return lab_usage if args.empty?
return lab_usage if args.count < 3
local_path = args[args.count-2]
vm_path = args[args.count-1]
if args[0] == "all"
@controller.each do |vm|
if vm.running?
print_line "Copying from #{local_path} to #{vm_path} on #{vm.hostname}"
vm.copy_to_guest(local_path, vm_path)
end
end
else
args[0..-2].each do |vmid_arg|
next unless @controller.includes_hostname? vmid_arg
if @controller[vmid_arg].running?
print_line "Copying from #{local_path} to #{vm_path} on #{vmid_arg}"
@controller[vmid_arg].copy_to_guest(local_path, vm_path)
end
end
end
end
def cmd_lab_browse_to(*args)
return lab_usage if args.empty?
uri = args[args.count-1]
if args[0] == "all"
print_line "Opening: #{uri} on all vms."
@controller.each do |vm|
if vm.running?
print_line "#{vm.hostname} opening to uri: #{uri}."
vm.open_uri(uri)
end
end
else
args[0..-2].each do |name_arg|
next unless @controller.includes_hostname? name_arg
if @controller[name_arg].running?
print_line "#{name_arg} opening to uri: #{uri}."
@controller[name_arg].open_uri(uri)
end
end
end
end
##
## Commands for help
##
def longest_cmd_size
commands.keys.map {|x| x.size}.sort.last
end
# No extended help yet, but this is where more detailed documentation
# on particular commands would live. Key is command, (not cmd_command),
# value is the documentation.
def extended_help
{
"lab_fake_cmd" => "This is a fake command. It's got its own special docs." +
(" " * longest_cmd_size) + "It might be long so so deal with formatting somehow."
}
end
# Map for usages
def lab_usage
caller[0][/`cmd_(.*)'/]
cmd = $1
if extended_help[cmd] || commands[cmd]
cmd_lab_help cmd
else # Should never really get here...
print_error "Unknown command. Try 'help'"
end
end
def cmd_lab_help(*args)
if args.empty?
commands.each_pair {|k,v| print_line "%-#{longest_cmd_size}s - %s" % [k,v] }
else
args.each do |c|
if extended_help[c] || commands[c]
print_line "%-#{longest_cmd_size}s - %s" % [c,extended_help[c] || commands[c]]
else
print_error "Unknown command '#{c}'"
end
end
end
print_line
print_line "In order to use this plugin, you'll want to configure a .yml lab file"
print_line "You can find an example in data/lab/test_targets.yml"
print_line
end
private
def hlp_print_lab
indent = ' '
tbl = Rex::Ui::Text::Table.new(
'Header' => 'Available Lab VMs',
'Indent' => indent.length,
'Columns' => [ 'Hostname', 'Driver', 'Type' ]
)
@controller.each do |vm|
tbl << [ vm.hostname,
vm.driver.class,
vm.type]
end
print_line tbl.to_s
end
def hlp_print_lab_running
indent = ' '
tbl = Rex::Ui::Text::Table.new(
'Header' => 'Running Lab VMs',
'Indent' => indent.length,
'Columns' => [ 'Hostname', 'Driver', 'Type', 'Power?' ]
)
@controller.each do |vm|
if vm.running?
tbl << [ vm.hostname,
vm.driver.class,
vm.type,
vm.running?]
end
end
print_line tbl.to_s
end
end
#
# The constructor is called when an instance of the plugin is created. The
# framework instance that the plugin is being associated with is passed in
# the framework parameter. Plugins should call the parent constructor when
# inheriting from Msf::Plugin to ensure that the framework attribute on
# their instance gets set.
#
attr_accessor :controller
def initialize(framework, opts)
super
## Register the commands above
console_dispatcher = add_console_dispatcher(LabCommandDispatcher)
@controller = ::Lab::Controllers::VmController.new
## Share the vms
console_dispatcher.controller = @controller
end
#
# The cleanup routine for plugins gives them a chance to undo any actions
# they may have done to the framework. For instance, if a console
# dispatcher was added, then it should be removed in the cleanup routine.
#
def cleanup
# If we had previously registered a console dispatcher with the console,
# deregister it now.
remove_console_dispatcher('Lab')
end
#
# This method returns a short, friendly name for the plugin.
#
def name
"lab"
end
#
# This method returns a brief description of the plugin. It should be no
# more than 60 characters, but there are no hard limits.
#
def desc
"Adds the ability to manage VMs"
end
class LabCommandDispatcher
include Msf::Ui::Console::CommandDispatcher
attr_accessor :controller
def initialize(driver)
super(driver)
@controller = nil
#
# Require the lab gem, but fail nicely if it's not there.
#
begin
require 'lab'
rescue LoadError
raise "WARNING: Lab gem not found, Please 'gem install lab'"
end
end
#
# Returns the hash of commands supported by this dispatcher.
#
def commands
{
"lab_help" => "lab_help <lab command> - Show that command's description.",
"lab_show" => "lab_show - show all vms in the lab.",
"lab_search" => "lab_search - search local vms in the lab.",
"lab_search_tags" => "lab_search_tag - search local vms in the lab.",
#"lab_search_remote" => "lab_search_remote - search remote vms in the lab.",
"lab_show_running" => "lab_show_running - show running vms.",
"lab_load" => "lab_load [file] - load a lab definition from disk.",
"lab_save" => "lab_save [filename] - persist a lab definition in a file.",
"lab_load_running" => "lab_load_running [type] [user] [host] - use the running vms to create a lab.",
"lab_load_config" => "lab_load_config [type] [user] [host] - use the vms in the config to create a lab.",
"lab_load_dir" => "lab_load_dir [type] [directory] - create a lab from a specified directory.",
"lab_clear" => "lab_clear - clear the running lab.",
"lab_start" => "lab_start [vmid+|all] start the specified vm.",
"lab_reset" => "lab_reset [vmid+|all] reset the specified vm.",
"lab_suspend" => "lab_suspend [vmid+|all] suspend the specified vm.",
"lab_stop" => "lab_stop [vmid+|all] stop the specified vm.",
"lab_revert" => "lab_revert [vmid+|all] [snapshot] revert the specified vm.",
"lab_snapshot" => "lab_snapshot [vmid+|all] [snapshot] snapshot all targets for this exploit.",
"lab_upload" => "lab_upload [vmid] [local_path] [remote_path] upload a file.",
"lab_run_command" => "lab_run_command [vmid+|all] [command] run a command on all targets.",
"lab_browse_to" => "lab_browse_to [vmid+|all] [uri] use the default browser to browse to a uri."
}
end
def name
"Lab"
end
##
## Regular Lab Commands
##
def cmd_lab_load(*args)
return lab_usage unless args.count == 1
res = args[0]
good_res = nil
if (File.file? res and File.readable? res)
# then the provided argument is an absolute path and is gtg.
good_res = res
elsif
# let's check to see if it's in the data/lab dir (like when tab completed)
[
::Msf::Config.data_directory + File::SEPARATOR + "lab",
# there isn't a user_data_directory, but could use:
#::Msf::Config.user_plugins_directory + File::SEPARATOR + "lab"
].each do |dir|
res_path = dir + File::SEPARATOR + res
if (File.file?(res_path) and File.readable?(res_path))
good_res = res_path
break
end
end
end
if good_res
@controller.from_file(good_res)
else
print_error("#{res} is not a valid lab definition file (.yml)")
end
end
#
# Tab completion for the lab_load command
#
def cmd_lab_load_tabs(str, words)
tabs = []
#return tabs if words.length > 1
if ( str and str =~ /^#{Regexp.escape(File::SEPARATOR)}/ )
# then you are probably specifying a full path so let's just use normal file completion
return tab_complete_filenames(str,words)
elsif (not words[1] or not words[1].match(/^\//))
# then let's start tab completion in the data/lab directory
begin
[
::Msf::Config.data_directory + File::SEPARATOR + "lab",
# there isn't a user_data_directory, but could use:
#::Msf::Config.user_plugins_directory + File::SEPARATOR + "lab"
].each do |dir|
next if not ::File.exist? dir
tabs += ::Dir.new(dir).find_all { |e|
path = dir + File::SEPARATOR + e
::File.file?(path) and File.readable?(path)
}
end
rescue Exception
end
else
tabs += tab_complete_filenames(str,words)
end
return tabs
end
def cmd_lab_load_dir(*args)
return lab_usage unless args.count == 2
@controller.build_from_dir(args[0],args[1],true)
end
def cmd_lab_clear(*args)
@controller.clear!
end
def cmd_lab_save(*args)
return lab_usage if args.empty?
@controller.to_file(args[0])
end
def cmd_lab_load_running(*args)
return lab_usage if args.empty?
if args[0] =~ /^remote_/
return lab_usage unless args.count == 3
## Expect a username & password
@controller.build_from_running(args[0], args[1], args[2])
else
return lab_usage unless args.count == 1
@controller.build_from_running(args[0])
end
end
def cmd_lab_load_config(*args)
return lab_usage if args.empty?
if args[0] =~ /^remote_/
return lab_usage unless args.count == 3
## Expect a username & password
@controller.build_from_config(args[0], args[1], args[2])
else
return lab_usage unless args.count == 1
@controller.build_from_config(args[0])
end
end
def cmd_lab_load_dir(*args)
return lab_usage unless args.count == 2
@controller.build_from_dir(args[0],args[1],true)
end
def cmd_lab_clear(*args)
@controller.clear!
end
def cmd_lab_save(*args)
return lab_usage if args.empty?
@controller.to_file(args[0])
end
##
## Commands for dealing with a currently-loaded lab
##
def cmd_lab_show(*args)
if args.empty?
hlp_print_lab
else
args.each do |name|
if @controller.includes_hostname? name
print_line @controller[name].to_yaml
else
print_error "Unknown vm '#{name}'"
end
end
end
end
def cmd_lab_show_running(*args)
hlp_print_lab_running
end
def cmd_lab_search(*args)
if args.empty?
hlp_print_lab
else
args.each do |arg|
print_line "Searching for vms with hostname matching #{arg}"
@controller.each do |vm|
print_line "checking to see #{vm.hostname} matches #{arg}"
print_line "#{vm.hostname} matched #{arg}" if vm.hostname =~ Regexp.new(arg)
end
end
end
end
def cmd_lab_search_tags(*args)
if args.empty?
hlp_print_lab
else
args.each do |arg|
print_line "Searching for vms with tags matching #{arg}"
@controller.each do |vm|
print_line "checking to see #{vm.hostname} is tagged #{arg}"
print_line "#{vm.hostname} tagged #{arg}" if vm.tagged?(arg)
end
end
end
end
def cmd_lab_start(*args)
return lab_usage if args.empty?
if args[0] == "all"
@controller.each do |vm|
print_line "Starting lab vm #{vm.hostname}."
if !vm.running?
vm.start
else
print_line "Lab vm #{vm.hostname} already running."
end
end
else
args.each do |arg|
if @controller.includes_hostname? arg
vm = @controller.find_by_hostname(arg)
if !vm.running?
print_line "Starting lab vm #{vm.hostname}."
vm.start
else
print_line "Lab vm #{vm.hostname} already running."
end
end
end
end
end
def cmd_lab_stop(*args)
return lab_usage if args.empty?
if args[0] == "all"
@controller.each do |vm|
print_line "Stopping lab vm #{vm.hostname}."
if vm.running?
vm.stop
else
print_line "Lab vm #{vm.hostname} not running."
end
end
else
args.each do |arg|
if @controller.includes_hostname? arg
vm = @controller.find_by_hostname(arg)
if vm.running?
print_line "Stopping lab vm #{vm.hostname}."
vm.stop
else
print_line "Lab vm #{vm.hostname} not running."
end
end
end
end
end
def cmd_lab_suspend(*args)
return lab_usage if args.empty?
if args[0] == "all"
@controller.each{ |vm| vm.suspend }
else
args.each do |arg|
if @controller.includes_hostname? arg
if @controller.find_by_hostname(arg).running?
print_line "Suspending lab vm #{arg}."
@controller.find_by_hostname(arg).suspend
end
end
end
end
end
def cmd_lab_reset(*args)
return lab_usage if args.empty?
if args[0] == "all"
print_line "Resetting all lab vms."
@controller.each{ |vm| vm.reset }
else
args.each do |arg|
if @controller.includes_hostname? arg
if @controller.find_by_hostname(arg).running?
print_line "Resetting lab vm #{arg}."
@controller.find_by_hostname(arg).reset
end
end
end
end
end
def cmd_lab_snapshot(*args)
return lab_usage if args.count < 2
snapshot = args[args.count-1]
if args[0] == "all"
print_line "Snapshotting all lab vms to snapshot: #{snapshot}."
@controller.each{ |vm| vm.create_snapshot(snapshot) }
else
args[0..-2].each do |name_arg|
next unless @controller.includes_hostname? name_arg
print_line "Snapshotting #{name_arg} to snapshot: #{snapshot}."
@controller[name_arg].create_snapshot(snapshot)
end
end
end
def cmd_lab_revert(*args)
return lab_usage if args.count < 2
snapshot = args[args.count-1]
if args[0] == "all"
print_line "Reverting all lab vms to snapshot: #{snapshot}."
@controller.each{ |vm| vm.revert_snapshot(snapshot) }
else
args[0..-2].each do |name_arg|
next unless @controller.includes_hostname? name_arg
print_line "Reverting #{name_arg} to snapshot: #{snapshot}."
@controller[name_arg].revert_snapshot(snapshot)
end
end
end
def cmd_lab_run_command(*args)
return lab_usage if args.empty?
command = args[args.count-1]
if args[0] == "all"
print_line "Running command #{command} on all vms."
@controller.each do |vm|
if vm.running?
print_line "#{vm.hostname} running command: #{command}."
vm.run_command(command)
end
end
else
args[0..-2].each do |name_arg|
next unless @controller.includes_hostname? name_arg
if @controller[name_arg].running?
print_line "#{name_arg} running command: #{command}."
@controller[name_arg].run_command(command)
end
end
end
end
#
# Command: lab_upload [vmids] [from] [to]
#
# Description: Uploads a file to the guest(s)
#
# Quirks: Pass "all" as a vmid to have it operate on all vms.
#
def cmd_lab_upload(*args)
return lab_usage if args.empty?
return lab_usage if args.count < 3
local_path = args[args.count-2]
vm_path = args[args.count-1]
if args[0] == "all"
@controller.each do |vm|
if vm.running?
print_line "Copying from #{local_path} to #{vm_path} on #{vm.hostname}"
vm.copy_to_guest(local_path, vm_path)
end
end
else
args[0..-2].each do |vmid_arg|
next unless @controller.includes_hostname? vmid_arg
if @controller[vmid_arg].running?
print_line "Copying from #{local_path} to #{vm_path} on #{vmid_arg}"
@controller[vmid_arg].copy_to_guest(local_path, vm_path)
end
end
end
end
def cmd_lab_browse_to(*args)
return lab_usage if args.empty?
uri = args[args.count-1]
if args[0] == "all"
print_line "Opening: #{uri} on all vms."
@controller.each do |vm|
if vm.running?
print_line "#{vm.hostname} opening to uri: #{uri}."
vm.open_uri(uri)
end
end
else
args[0..-2].each do |name_arg|
next unless @controller.includes_hostname? name_arg
if @controller[name_arg].running?
print_line "#{name_arg} opening to uri: #{uri}."
@controller[name_arg].open_uri(uri)
end
end
end
end
##
## Commands for help
##
def longest_cmd_size
commands.keys.map {|x| x.size}.sort.last
end
# No extended help yet, but this is where more detailed documentation
# on particular commands would live. Key is command, (not cmd_command),
# value is the documentation.
def extended_help
{
"lab_fake_cmd" => "This is a fake command. It's got its own special docs." +
(" " * longest_cmd_size) + "It might be long so so deal with formatting somehow."
}
end
# Map for usages
def lab_usage
caller[0][/`cmd_(.*)'/]
cmd = $1
if extended_help[cmd] || commands[cmd]
cmd_lab_help cmd
else # Should never really get here...
print_error "Unknown command. Try 'help'"
end
end
def cmd_lab_help(*args)
if args.empty?
commands.each_pair {|k,v| print_line "%-#{longest_cmd_size}s - %s" % [k,v] }
else
args.each do |c|
if extended_help[c] || commands[c]
print_line "%-#{longest_cmd_size}s - %s" % [c,extended_help[c] || commands[c]]
else
print_error "Unknown command '#{c}'"
end
end
end
print_line
print_line "In order to use this plugin, you'll want to configure a .yml lab file"
print_line "You can find an example in data/lab/test_targets.yml"
print_line
end
private
def hlp_print_lab
indent = ' '
tbl = Rex::Ui::Text::Table.new(
'Header' => 'Available Lab VMs',
'Indent' => indent.length,
'Columns' => [ 'Hostname', 'Driver', 'Type' ]
)
@controller.each do |vm|
tbl << [ vm.hostname,
vm.driver.class,
vm.type]
end
print_line tbl.to_s
end
def hlp_print_lab_running
indent = ' '
tbl = Rex::Ui::Text::Table.new(
'Header' => 'Running Lab VMs',
'Indent' => indent.length,
'Columns' => [ 'Hostname', 'Driver', 'Type', 'Power?' ]
)
@controller.each do |vm|
if vm.running?
tbl << [ vm.hostname,
vm.driver.class,
vm.type,
vm.running?]
end
end
print_line tbl.to_s
end
end
#
# The constructor is called when an instance of the plugin is created. The
# framework instance that the plugin is being associated with is passed in
# the framework parameter. Plugins should call the parent constructor when
# inheriting from Msf::Plugin to ensure that the framework attribute on
# their instance gets set.
#
attr_accessor :controller
def initialize(framework, opts)
super
## Register the commands above
console_dispatcher = add_console_dispatcher(LabCommandDispatcher)
@controller = ::Lab::Controllers::VmController.new
## Share the vms
console_dispatcher.controller = @controller
end
#
# The cleanup routine for plugins gives them a chance to undo any actions
# they may have done to the framework. For instance, if a console
# dispatcher was added, then it should be removed in the cleanup routine.
#
def cleanup
# If we had previously registered a console dispatcher with the console,
# deregister it now.
remove_console_dispatcher('Lab')
end
#
# This method returns a short, friendly name for the plugin.
#
def name
"lab"
end
#
# This method returns a brief description of the plugin. It should be no
# more than 60 characters, but there are no hard limits.
#
def desc
"Adds the ability to manage VMs"
end
end ## End Class
end ## End Module
+123 -123
View File
@@ -20,144 +20,144 @@ module Msf
###
class Plugin::Msfd < Msf::Plugin
#
# The default local hostname that the server listens on.
#
DefaultHost = "127.0.0.1"
#
# The default local hostname that the server listens on.
#
DefaultHost = "127.0.0.1"
#
# The default local port that the server listens on.
#
DefaultPort = 55554
#
# The default local port that the server listens on.
#
DefaultPort = 55554
#
# Initializes the msfd plugin. The following options are supported in the
# hash by this plugin:
#
# ServerHost
#
# The local hostname to listen on for connections. The default is
# 127.0.0.1.
#
# ServerPort
#
# The local port to listen on for connections. The default is 55554.
#
# SSL
#
# Use SSL
#
# RunInForeground
#
# Instructs the plugin to now execute the daemon in a worker thread and to
# instead allow the caller to manage executing the daemon through the
# ``run'' method.
#
# HostsAllowed
#
# List of hosts (in NBO) allowed to use msfd
#
# HostsDenied
#
# List of hosts (in NBO) not allowed to use msfd
#
def initialize(framework, opts)
super
#
# Initializes the msfd plugin. The following options are supported in the
# hash by this plugin:
#
# ServerHost
#
# The local hostname to listen on for connections. The default is
# 127.0.0.1.
#
# ServerPort
#
# The local port to listen on for connections. The default is 55554.
#
# SSL
#
# Use SSL
#
# RunInForeground
#
# Instructs the plugin to now execute the daemon in a worker thread and to
# instead allow the caller to manage executing the daemon through the
# ``run'' method.
#
# HostsAllowed
#
# List of hosts (in NBO) allowed to use msfd
#
# HostsDenied
#
# List of hosts (in NBO) not allowed to use msfd
#
def initialize(framework, opts)
super
# Start listening for connections.
self.server = Rex::Socket::TcpServer.create(
'LocalHost' => opts['ServerHost'] || DefaultHost,
'LocalPort' => opts['ServerPort'] || DefaultPort,
'SSL' => opts['SSL'])
# Start listening for connections.
self.server = Rex::Socket::TcpServer.create(
'LocalHost' => opts['ServerHost'] || DefaultHost,
'LocalPort' => opts['ServerPort'] || DefaultPort,
'SSL' => opts['SSL'])
# If the run in foreground flag is not specified, then go ahead and fire
# it off in a worker thread.
if (opts['RunInForeground'] != true)
Thread.new {
run(opts)
}
end
end
# If the run in foreground flag is not specified, then go ahead and fire
# it off in a worker thread.
if (opts['RunInForeground'] != true)
Thread.new {
run(opts)
}
end
end
#
# Returns 'msfd'
#
def name
"msfd"
end
#
# Returns 'msfd'
#
def name
"msfd"
end
#
# Returns the msfd plugin description.
#
def desc
"Provides a console interface to users over a listening TCP port."
end
#
# Returns the msfd plugin description.
#
def desc
"Provides a console interface to users over a listening TCP port."
end
#
# Runs the msfd plugin by blocking on new connections and then spawning
# threads to handle the console interface for each client.
#
def run(opts={})
while true
client = server.accept
#
# Runs the msfd plugin by blocking on new connections and then spawning
# threads to handle the console interface for each client.
#
def run(opts={})
while true
client = server.accept
addr = Rex::Socket.resolv_nbo(client.peerhost)
addr = Rex::Socket.resolv_nbo(client.peerhost)
if opts['HostsAllowed'] and
not opts['HostsAllowed'].find { |x| x == addr }
client.close
next
end
if opts['HostsAllowed'] and
not opts['HostsAllowed'].find { |x| x == addr }
client.close
next
end
if opts['HostsDenied'] and
opts['HostsDenied'].find { |x| x == addr }
client.close
next
end
msg = "Msfd: New connection from #{client.peerhost}"
ilog(msg, 'core')
print_status(msg)
if opts['HostsDenied'] and
opts['HostsDenied'].find { |x| x == addr }
client.close
next
end
msg = "Msfd: New connection from #{client.peerhost}"
ilog(msg, 'core')
print_status(msg)
# Spawn a thread for the client connection
Thread.new(client) { |cli|
begin
Msf::Ui::Console::Driver.new(
Msf::Ui::Console::Driver::DefaultPrompt,
Msf::Ui::Console::Driver::DefaultPromptChar,
'Framework' => framework,
'LocalInput' => Rex::Ui::Text::Input::Socket.new(cli),
'LocalOutput' => Rex::Ui::Text::Output::Socket.new(cli),
'AllowCommandPassthru' => false).run
rescue
elog("Msfd: Client error: #{$!}\n\n#{$@.join("\n")}", 'core')
ensure
msg = "Msfd: Closing client connection with #{cli.peerhost}"
ilog(msg, 'core')
print_status(msg)
begin
cli.shutdown
cli.close
rescue IOError
end
end
}
end
end
# Spawn a thread for the client connection
Thread.new(client) { |cli|
begin
Msf::Ui::Console::Driver.new(
Msf::Ui::Console::Driver::DefaultPrompt,
Msf::Ui::Console::Driver::DefaultPromptChar,
'Framework' => framework,
'LocalInput' => Rex::Ui::Text::Input::Socket.new(cli),
'LocalOutput' => Rex::Ui::Text::Output::Socket.new(cli),
'AllowCommandPassthru' => false).run
rescue
elog("Msfd: Client error: #{$!}\n\n#{$@.join("\n")}", 'core')
ensure
msg = "Msfd: Closing client connection with #{cli.peerhost}"
ilog(msg, 'core')
print_status(msg)
begin
cli.shutdown
cli.close
rescue IOError
end
end
}
end
end
#
# Closes the listener service.
#
def cleanup
ilog("Msfd: Shutting down server", 'core')
self.server.close
end
#
# Closes the listener service.
#
def cleanup
ilog("Msfd: Shutting down server", 'core')
self.server.close
end
protected
#
# The listening socket instance.
#
attr_accessor :server
#
# The listening socket instance.
#
attr_accessor :server
end
+82 -82
View File
@@ -23,103 +23,103 @@ module Msf
###
class Plugin::MSGRPC < Msf::Plugin
#
# The default local hostname that the server listens on.
#
DefaultHost = "127.0.0.1"
#
# The default local hostname that the server listens on.
#
DefaultHost = "127.0.0.1"
#
# The default local port that the server listens on.
#
DefaultPort = 55552
#
# The default local port that the server listens on.
#
DefaultPort = 55552
#
# ServerPort
#
# The local port to listen on for connections. The default is 55553
#
def initialize(framework, opts)
super
#
# ServerPort
#
# The local port to listen on for connections. The default is 55553
#
def initialize(framework, opts)
super
host = opts['ServerHost'] || DefaultHost
port = opts['ServerPort'] || DefaultPort
ssl = (opts['SSL'] and opts['SSL'].to_s =~ /^[ty]/i) ? true : false
cert = opts['SSLCert']
host = opts['ServerHost'] || DefaultHost
port = opts['ServerPort'] || DefaultPort
ssl = (opts['SSL'] and opts['SSL'].to_s =~ /^[ty]/i) ? true : false
cert = opts['SSLCert']
user = opts['User'] || "msf"
pass = opts['Pass'] || ::Rex::Text.rand_text_alphanumeric(8)
uri = opts['URI'] || "/api"
user = opts['User'] || "msf"
pass = opts['Pass'] || ::Rex::Text.rand_text_alphanumeric(8)
uri = opts['URI'] || "/api"
print_status("MSGRPC Service: #{host}:#{port} #{ssl ? " (SSL)" : ""}")
print_status("MSGRPC Username: #{user}")
print_status("MSGRPC Password: #{pass}")
print_status("MSGRPC Service: #{host}:#{port} #{ssl ? " (SSL)" : ""}")
print_status("MSGRPC Username: #{user}")
print_status("MSGRPC Password: #{pass}")
self.server = ::Msf::RPC::Service.new(framework, {
:host => host,
:port => port,
:ssl => ssl,
:cert => cert,
:uri => uri,
:tokens => { }
})
self.server = ::Msf::RPC::Service.new(framework, {
:host => host,
:port => port,
:ssl => ssl,
:cert => cert,
:uri => uri,
:tokens => { }
})
self.server.add_user(user, pass)
self.server.add_user(user, pass)
# If the run in foreground flag is not specified, then go ahead and fire
# it off in a worker thread.
if (opts['RunInForeground'] != true)
# Store a handle to the thread so we can kill it during
# cleanup when we get unloaded.
self.thread = Thread.new { run }
framework.threads.register(self.thread, "MetasploitRPCServer", true)
end
end
# If the run in foreground flag is not specified, then go ahead and fire
# it off in a worker thread.
if (opts['RunInForeground'] != true)
# Store a handle to the thread so we can kill it during
# cleanup when we get unloaded.
self.thread = Thread.new { run }
framework.threads.register(self.thread, "MetasploitRPCServer", true)
end
end
#
# Returns 'msgrpc'
#
def name
"msgrpc"
end
#
# Returns 'msgrpc'
#
def name
"msgrpc"
end
#
# Returns the plugin description.
#
def desc
"Provides a MessagePack interface over HTTP"
end
#
# Returns the plugin description.
#
def desc
"Provides a MessagePack interface over HTTP"
end
#
# The meat of the plugin, sets up handlers for requests
#
def run
# Start the actual service
self.server.start
#
# The meat of the plugin, sets up handlers for requests
#
def run
# Start the actual service
self.server.start
# Register
framework.threads.register(Thread.current, "MetasploitRPCServer", true)
# Register
framework.threads.register(Thread.current, "MetasploitRPCServer", true)
# Wait for the service to complete
self.server.wait
end
# Wait for the service to complete
self.server.wait
end
#
# Closes the listener service.
#
def cleanup
self.server.stop if self.server
self.thread.kill if self.thread
self.server = nil
super
end
#
# Closes the listener service.
#
def cleanup
self.server.stop if self.server
self.thread.kill if self.thread
self.server = nil
super
end
#
# The MSGRPC instance.
#
attr_accessor :server
attr_accessor :thread
attr_accessor :users
attr_accessor :tokens
#
# The MSGRPC instance.
#
attr_accessor :server
attr_accessor :thread
attr_accessor :users
attr_accessor :tokens
end
+1675 -1675
View File
@@ -5,1679 +5,1679 @@ require 'nessus/nessus-xmlrpc'
require 'rex/parser/nessus_xml'
module Msf
class Plugin::Nessus < Msf::Plugin
#creates the index of exploit details to make searching for exploits much faster.
def create_xindex
start = Time.now
print_status("Creating Exploit Search Index - (#{@xindex}) - this wont take long.")
count = 0
# use Msf::Config.get_config_root as the location.
File.open("#{@xindex}", "w+") do |f|
#need to add version line.
f.puts(Msf::Framework::RepoRevision)
framework.exploits.sort.each { |refname, mod|
stuff = ""
o = nil
begin
o = mod.new
rescue ::Exception
end
stuff << "#{refname}|#{o.name}|#{o.platform_to_s}|#{o.arch_to_s}"
next if not o
o.references.map do |x|
if !(x.ctx_id == "URL")
if (x.ctx_id == "MSB")
stuff << "|#{x.ctx_val}"
else
stuff << "|#{x.ctx_id}-#{x.ctx_val}"
end
end
end
stuff << "\n"
f.puts(stuff)
}
end
total = Time.now - start
print_status("It has taken : #{total} seconds to build the exploits search index")
end
def nessus_index
if File.exist?("#{@xindex}")
#check if it's version line matches current version.
File.open("#{@xindex}") {|f|
line = f.readline
line.chomp!
if line.to_i == Msf::Framework::RepoRevision
print_good("Exploit Index - (#{@xindex}) - is valid.")
else
create_xindex
end
}
else
create_xindex
end
end
class ConsoleCommandDispatcher
include Msf::Ui::Console::CommandDispatcher
def name
"Nessus"
end
def commands
{
"nessus_connect" => "Connect to a nessus server: nconnect username:password@hostname:port <ssl ok>.",
"nessus_admin" => "Checks if user is an admin.",
"nessus_help" => "Get help on all commands.",
"nessus_logout" => "Terminate the session.",
"nessus_server_status" => "Check the status of your Nessus Server.",
"nessus_server_feed" => "Nessus Feed Type.",
"nessus_server_prefs" => "Display Server Prefs.",
"nessus_report_list" => "List all Nessus reports.",
"nessus_report_get" => "Import a report from the nessus server in Nessus v2 format.",
"nessus_report_del" => "Delete a report.",
"nessus_report_vulns" => "Get list of vulns from a report.",
"nessus_report_hosts" => "Get list of hosts from a report.",
"nessus_report_host_ports" => "Get list of open ports from a host from a report.",
"nessus_report_host_detail" => "Detail from a report item on a host.",
"nessus_scan_status" => "List all currently running Nessus scans.",
"nessus_scan_new" => "Create new Nessus Scan.",
"nessus_scan_pause" => "Pause a Nessus Scan.",
"nessus_scan_pause_all" => "Pause all Nessus Scans.",
"nessus_scan_stop" => "Stop a Nessus Scan.",
"nessus_scan_stop_all" => "Stop all Nessus Scans.",
"nessus_scan_resume" => "Resume a Nessus Scan.",
"nessus_scan_resume_all" => "Resume all Nessus Scans.",
"nessus_user_list" => "Show Nessus Users.",
"nessus_user_add" => "Add a new Nessus User.",
"nessus_user_del" => "Delete a Nessus User.",
"nessus_user_passwd" => "Change Nessus Users Password.",
"nessus_plugin_family" => "List plugins in a family.",
"nessus_plugin_details" => "List details of a particular plugin.",
"nessus_plugin_list" => "Displays each plugin family and the number of plugins.",
"nessus_plugin_prefs" => "Display Plugin Prefs.",
"nessus_policy_list" => "List all polciies.",
"nessus_policy_del" => "Delete a policy.",
"nessus_index" => "Manually generates a search index for exploits.",
"nessus_template_list" => "List all the templates on the server.",
"nessus_db_scan" => "Create a scan of all ips in db_hosts.",
"nessus_save" => "Save username/passowrd/server/port details."
}
end
def cmd_nessus_index
Msf::Plugin::Nessus.nessus_index
end
def cmd_nessus_save(*args)
#if we are logged in, save session details to nessus.yaml
@nessus_yaml = "#{Msf::Config.get_config_root}/nessus.yaml"
if args[0] == "-h"
print_status("Usage: ")
print_status(" nessus_save")
return
end
if args[0]
print_status("Usage: ")
print_status(" nessus_save")
return
end
group = "default"
if ((@user and @user.length > 0) and (@host and @host.length > 0) and (@port and @port.length > 0 and @port.to_i > 0) and (@pass and @pass.length > 0))
config = Hash.new
config = {"#{group}" => {'username' => @user, 'password' => @pass, 'server' => @host, 'port' => @port}}
File.open("#{@nessus_yaml}", "w+") do |f|
f.puts YAML.dump(config)
end
print_good("#{@nessus_yaml} created.")
else
print_error("Missing username/password/server/port - relogin and then try again.")
return
end
end
def cmd_nessus_db_scan(*args)
if args[0] == "-h"
print_status("Usage: ")
print_status(" nessus_db_scan <policy id> <scan name>")
print_status(" Example:> nessus_db_scan 1 \"My Scan\"")
print_status()
print_status("Creates a scan based on all the hosts listed in db_hosts.")
print_status("use nessus_policy_list to list all available policies")
return
end
if ! nessus_verify_token
return
end
case args.length
when 2
pid = args[0].to_i
name = args[1]
else
print_status("Usage: ")
print_status(" nessus_db_scan <policy id> <scan name>")
print_status(" use nessus_policy_list to list all available policies")
return
end
if check_policy(pid)
print_error("That policy does not exist.")
return
end
tgts = ""
framework.db.hosts(framework.db.workspace).each do |host|
tgts << host.address
tgts << ","
end
tgts.chop!
print_status("Creating scan from policy number #{pid}, called \"#{name}\" and scanning all hosts in workspace")
scan = @n.scan_new(pid, name, tgts)
if scan
print_status("Scan started. uid is #{scan}")
end
end
def cmd_nessus_logout
@token = nil
print_status("Logged out")
system("rm #{@nessus_yaml}")
print_good("#{@nessus_yaml} removed.")
return
end
def cmd_nessus_help(*args)
tbl = Rex::Ui::Text::Table.new(
'Columns' => [
"Command",
"Help Text"
],
'SortIndex' => -1
)
tbl << [ "Generic Commands", "" ]
tbl << [ "-----------------", "-----------------"]
tbl << [ "nessus_connect", "Connect to a nessus server" ]
tbl << [ "nessus_save", "Save nessus login info between sessions" ]
tbl << [ "nessus_logout", "Logout from the nessus server" ]
tbl << [ "nessus_help", "Listing of available nessus commands" ]
tbl << [ "nessus_server_status", "Check the status of your Nessus Server" ]
tbl << [ "nessus_admin", "Checks if user is an admin" ]
tbl << [ "nessus_server_feed", "Nessus Feed Type" ]
tbl << [ "nessus_find_targets", "Try to find vulnerable targets from a report" ]
tbl << [ "nessus_server_prefs", "Display Server Prefs" ]
tbl << [ "", ""]
tbl << [ "Reports Commands", "" ]
tbl << [ "-----------------", "-----------------"]
tbl << [ "nessus_report_list", "List all Nessus reports" ]
tbl << [ "nessus_report_get", "Import a report from the nessus server in Nessus v2 format" ]
tbl << [ "nessus_report_vulns", "Get list of vulns from a report" ]
tbl << [ "nessus_report_hosts", "Get list of hosts from a report" ]
tbl << [ "nessus_report_host_ports", "Get list of open ports from a host from a report" ]
tbl << [ "nessus_report_host_detail", "Detail from a report item on a host" ]
tbl << [ "", ""]
tbl << [ "Scan Commands", "" ]
tbl << [ "-----------------", "-----------------"]
tbl << [ "nessus_scan_new", "Create new Nessus Scan" ]
tbl << [ "nessus_scan_status", "List all currently running Nessus scans" ]
tbl << [ "nessus_scan_pause", "Pause a Nessus Scan" ]
tbl << [ "nessus_scan_pause_all", "Pause all Nessus Scans" ]
tbl << [ "nessus_scan_stop", "Stop a Nessus Scan" ]
tbl << [ "nessus_scan_stop_all", "Stop all Nessus Scans" ]
tbl << [ "nessus_scan_resume", "Resume a Nessus Scan" ]
tbl << [ "nessus_scan_resume_all", "Resume all Nessus Scans" ]
tbl << [ "", ""]
tbl << [ "Plugin Commands", "" ]
tbl << [ "-----------------", "-----------------"]
tbl << [ "nessus_plugin_list", "Displays each plugin family and the number of plugins" ]
tbl << [ "nessus_plugin_family", "List plugins in a family" ]
tbl << [ "nessus_plugin_details", "List details of a particular plugin" ]
tbl << [ "", ""]
tbl << [ "User Commands", "" ]
tbl << [ "-----------------", "-----------------"]
tbl << [ "nessus_user_list", "Show Nessus Users" ]
tbl << [ "nessus_user_add", "Add a new Nessus User" ]
tbl << [ "nessus_user_del", "Delete a Nessus User" ]
tbl << [ "nessus_user_passwd", "Change Nessus Users Password" ]
tbl << [ "", ""]
tbl << [ "Policy Commands", "" ]
tbl << [ "-----------------", "-----------------"]
tbl << [ "nessus_policy_list", "List all polciies" ]
tbl << [ "nessus_policy_del", "Delete a policy" ]
print_status ""
print_line tbl.to_s
print_status ""
end
def cmd_nessus_server_feed(*args)
if args[0] == "-h"
print_status("Usage: ")
print_status(" nessus_server_feed")
print_status(" Example:> nessus_server_feed")
print_status()
print_status("Returns information about the feed type and server version.")
return
end
if nessus_verify_token
@feed, @version, @web_version = @n.feed
tbl = Rex::Ui::Text::Table.new(
'Columns' => [
'Feed',
'Nessus Version',
'Nessus Web Version'
])
tbl << [@feed, @version, @web_version]
print_good("Nessus Status")
print_good "\n"
print_line tbl.to_s
end
end
def nessus_verify_token
if @token.nil? or @token == ''
ncusage
return false
end
true
end
def nessus_verify_db
if ! (framework.db and framework.db.active)
print_error("No database has been configured, please use db_create/db_connect first")
return false
end
true
end
def ncusage
print_status("%redYou must do this before any other commands.%clr")
print_status("Usage: ")
print_status(" nessus_connect username:password@hostname:port <ssl ok>")
print_status(" Example:> nessus_connect msf:msf@192.168.1.10:8834 ok")
print_status(" OR")
print_status(" nessus_connect username@hostname:port <ssl ok>")
print_status(" Example:> nessus_connect msf@192.168.1.10:8834 ok")
print_status(" OR")
print_status(" nessus_connect hostname:port <ssl ok>")
print_status(" Example:> nessus_connect 192.168.1.10:8834 ok")
print_status(" OR")
print_status(" nessus_connect")
print_status(" Example:> nessus_connect")
print_status("This only works after you have saved creds with nessus_save")
return
end
def cmd_nessus_connect(*args)
# Check if config file exists and load it
@nessus_yaml = "#{Msf::Config.get_config_root}/nessus.yaml"
if ! args[0]
if File.exist?("#{@nessus_yaml}")
lconfig = YAML.load_file("#{@nessus_yaml}")
@user = lconfig['default']['username']
@pass = lconfig['default']['password']
@host = lconfig['default']['server']
@port = lconfig['default']['port']
nessus_login
return
else
ncusage
return
end
end
if args[0] == "-h"
print_status("%redYou must do this before any other commands.%clr")
print_status("Usage: ")
print_status(" nessus_connect username:password@hostname:port <ssl ok>")
print_status(" Example:> nessus_connect msf:msf@192.168.1.10:8834 ok")
print_status(" OR")
print_status(" nessus_connect username@hostname:port <ssl ok>")
print_status(" Example:> nessus_connect msf@192.168.1.10:8834 ok")
print_status(" OR")
print_status(" nessus_connect hostname:port <ssl ok>")
print_status(" Example:> nessus_connect 192.168.1.10:8834 ok")
print_status(" OR")
print_status(" nessus_connect")
print_status(" Example:> nessus_connect")
print_status("This only works after you have saved creds with nessus_save")
print_status()
print_status("%bldusername%clr and %bldpassword%clr are the ones you use to login to the nessus web front end")
print_status("%bldhostname%clr can be an ip address or a dns name of the web front end.")
print_status("%bldport%clr is the standard that the nessus web front end runs on : 8834. This is NOT 1241.")
print_status("The \"ok\" on the end is important. It is a way of letting you")
print_status("know that nessus used a self signed cert and the risk that presents.")
return
end
if ! @token == ''
print_error("You are already authenticated. Call nessus_logout before authing again")
return
end
if(args.length == 0 or args[0].empty?)
ncusage
return
end
@user = @pass = @host = @port = @sslv = nil
case args.length
when 1,2
if args[0].include? "@"
cred,targ = args[0].split('@', 2)
@user,@pass = cred.split(':', 2)
targ ||= '127.0.0.1:8834'
@host,@port = targ.split(':', 2)
@port ||= '8834'
@sslv = args[1]
else
@host,@port = args[0].split(':', 2)
@port ||= '8834'
@sslv = args[1]
end
when 3,4,5
ncusage
return
else
ncusage
return
end
if /\/\//.match(@host)
ncusage
return
end
if(@host != "localhost" and @host != "127.0.0.1" and @sslv != "ok")
print_error("Warning: SSL connections are not verified in this release, it is possible for an attacker")
print_error(" with the ability to man-in-the-middle the Nessus traffic to capture the Nessus")
print_error(" credentials. If you are running this on a trusted network, please pass in 'ok'")
print_error(" as an additional parameter to this command.")
return
end
if ! @user
print_error("Missing Username")
ncusage
return
end
if ! @pass
print_error("Missing Password")
ncusage
return
end
if ! ((@user and @user.length > 0) and (@host and @host.length > 0) and (@port and @port.length > 0 and @port.to_i > 0) and (@pass and @pass.length > 0))
ncusage
return
end
nessus_login
end
def nessus_login
if ! ((@user and @user.length > 0) and (@host and @host.length > 0) and (@port and @port.length > 0 and @port.to_i > 0) and (@pass and @pass.length > 0))
print_status("You need to connect to a server first.")
ncusage
return
end
@url = "https://#{@host}:#{@port}/"
print_status("Connecting to #{@url} as #{@user}")
@n=NessusXMLRPC::NessusXMLRPC.new(@url,@user,@pass)
@token=@n.login(@user,@pass)
if @n.logged_in
print_status("Authenticated")
else
print_error("Error connecting/logging to the server!")
return
end
end
def cmd_nessus_report_list(*args)
if args[0] == "-h"
print_status("Usage: ")
print_status(" nessus_report_list")
print_status(" Example:> nessus_report_list")
print_status()
print_status("Generates a list of all reports visable to your user.")
return
end
if ! nessus_verify_token
return
end
list=@n.report_list_hash
tbl = Rex::Ui::Text::Table.new(
'Columns' => [
'ID',
'Name',
'Status',
'Date'
])
list.each {|report|
t = Time.at(report['timestamp'].to_i)
tbl << [ report['id'], report['name'], report['status'], t.strftime("%H:%M %b %d %Y") ]
}
print_good("Nessus Report List")
print_good "\n"
print_line tbl.to_s + "\n"
print_status("You can:")
print_status(" Get a list of hosts from the report: nessus_report_hosts <report id>")
end
def check_scan(*args)
case args.length
when 1
rid = args[0]
else
print_error("No Report ID Supplied")
return
end
scans = @n.scan_list_hash
scans.each {|scan|
if scan['id'] == rid
return true
end
}
return false
end
def cmd_nessus_report_get(*args)
if args[0] == "-h"
print_status("Usage: ")
print_status(" nessus_report_get <report id>")
print_status(" Example:> nessus_report_get f0eabba3-4065-7d54-5763-f191e98eb0f7f9f33db7e75a06ca")
print_status()
print_status("This command pulls the provided report from the nessus server in the nessusv2 format")
print_status("and parses it the same way db_import_nessus does. After it is parsed it will be")
print_status("available to commands such as db_hosts, db_vulns, db_services and db_autopwn.")
print_status("Use: nessus_report_list to obtain a list of report id's")
return
end
if ! nessus_verify_token
return
end
if ! nessus_verify_db
return
end
if(args.length == 0 or args[0].empty? or args[0] == "-h")
print_status("Usage: ")
print_status(" nessus_report_get <report id> ")
print_status(" use nessus_report_list to list all available reports for importing")
return
end
rid = nil
case args.length
when 1
rid = args[0]
else
print_status("Usage: ")
print_status(" nessus_report_get <report id> ")
print_status(" use nessus_report_list to list all available reports for importing")
return
end
if check_scan(rid)
print_error("That scan is still running.")
return
end
content = nil
content=@n.report_file_download(rid)
if content.nil?
print_error("Failed, please reauthenticate")
return
end
print_status("importing " + rid)
framework.db.import({:data => content}) do |type,data|
case type
when :address
print_line("%bld%blu[*]%clr %bld#{data}%clr")
end
end
print_good("Done")
end
def cmd_nessus_scan_status(*args)
if args[0] == "-h"
print_status("Usage: ")
print_status(" nessus_scan_status")
print_status(" Example:> nessus_scan_status")
print_status()
print_status("Returns a list of information about currently running scans.")
return
end
if ! nessus_verify_token
return
end
list=@n.scan_list_hash
if list.empty?
print_status("No Scans Running.")
print_status("You can:")
print_status(" List of completed scans: nessus_report_list")
print_status(" Create a scan: nessus_scan_new <policy id> <scan name> <target(s)>")
return
end
tbl = Rex::Ui::Text::Table.new(
'Columns' => [
'Scan ID',
'Name',
'Owner',
'Started',
'Status',
'Current Hosts',
'Total Hosts'
])
list.each {|scan|
t = Time.at(scan['start'].to_i)
tbl << [ scan['id'], scan['name'], scan['owner'], t.strftime("%H:%M %b %d %Y"), scan['status'], scan['current'], scan['total'] ]
}
print_good("Running Scans")
print_good "\n"
print_line tbl.to_s
print_good "\n"
print_status("You can:")
print_good(" Import Nessus report to database : nessus_report_get <reportid>")
print_good(" Pause a nessus scan : nessus_scan_pause <scanid>")
end
def cmd_nessus_template_list(*args)
if args[0] == "-h"
print_status("Usage: ")
print_status(" nessus_template_list")
print_status(" Example:> nessus_template_list")
print_status()
print_status("Returns a list of information about the server templates..")
return
end
if ! nessus_verify_token
return
end
list=@n.template_list_hash
if list.empty?
print_status("No Templates Created.")
print_status("You can:")
print_status(" List of completed scans: nessus_report_list")
print_status(" Create a template: nessus_template_new <policy id> <scan name> <target(s)>")
return
end
tbl = Rex::Ui::Text::Table.new(
'Columns' => [
'Template ID',
'Policy ID',
'Name',
'Owner',
'Target'
])
list.each {|template|
tbl << [ template['name'], template['pid'], template['rname'], template['owner'], template['target'] ]
}
print_good("Templates")
print_good "\n"
print_line tbl.to_s + "\n"
print_good "\n"
print_status("You can:")
print_good(" Import Nessus report to database : nessus_report_get <reportid>")
end
def cmd_nessus_user_list(*args)
if args[0] == "-h"
print_status("Usage: ")
print_status(" nessus_user_list")
print_status(" Example:> nessus_user_list")
print_status()
print_status("Returns a list of the users on the Nessus server and their access level.")
return
end
if ! nessus_verify_token
return
end
if ! @n.is_admin
print_status("Your Nessus user is not an admin")
end
list=@n.users_list
print_good("There are #{list.length} users")
tbl = Rex::Ui::Text::Table.new(
'Columns' => [
'Name',
'Is Admin?',
'Last Login'
])
list.each {|user|
t = Time.at(user['lastlogin'].to_i)
tbl << [ user['name'], user['admin'], t.strftime("%H:%M %b %d %Y") ]
}
print_good("Nessus users")
print_good "\n"
print_line tbl.to_s
end
def cmd_nessus_server_status(*args)
if args[0] == "-h"
print_status("Usage: ")
print_status(" nessus_server_status")
print_status(" Example:> nessus_server_status")
print_status()
print_status("Returns some status items for the server..")
return
end
#Auth
if ! nessus_verify_token
return
end
#Check if we are an admin
if ! @n.is_admin
print_status("You need to be an admin for this.")
return
end
#Versions
cmd_nessus_server_feed
tbl = Rex::Ui::Text::Table.new(
'Columns' => [
'Users',
'Policies',
'Running Scans',
'Reports',
'Plugins'
])
#Count how many users the server has.
list=@n.users_list
users = list.length
#Count how many policies
list=@n.policy_list_hash
policies = list.length
#Count how many running scans
list=@n.scan_list_uids
scans = list.length
#Count how many reports are available
list=@n.report_list_hash
reports = list.length
#Count how many plugins
list=@n.plugins_list
total = Array.new
list.each {|plugin|
total.push(plugin['num'].to_i)
}
plugins = total.sum
tbl << [users, policies, scans, reports, plugins]
print_good "\n"
print_line tbl.to_s
end
def cmd_nessus_plugin_list(*args)
if args[0] == "-h"
print_status("Usage: ")
print_status(" nessus_plugin_list")
print_status(" Example:> nessus_plugin_list")
print_status()
print_status("Returns a list of the plugins on the server per family.")
return
end
if ! nessus_verify_token
return
end
tbl = Rex::Ui::Text::Table.new(
'Columns' => [
'Family Name',
'Total Plugins'
])
list=@n.plugins_list
total = Array.new
list.each {|plugin|
total.push(plugin['num'].to_i)
tbl << [ plugin['name'], plugin['num'] ]
}
plugins = total.sum
tbl << [ '', '']
tbl << [ 'Total Plugins', plugins ]
print_good("Plugins By Family")
print_good "\n"
print_line tbl.to_s
print_status("List plugins for a family : nessus_plugin_family <family name>")
end
def check_policy(*args)
case args.length
when 1
pid = args[0]
else
print_error("No Policy ID supplied.")
return
end
pol = @n.policy_list_hash
pol.each {|p|
if p['id'].to_i == pid
return false
end
}
return true
end
def cmd_nessus_scan_new(*args)
if args[0] == "-h"
print_status("Usage: ")
print_status(" nessus_scan_new <policy id> <scan name> <targets>")
print_status(" Example:> nessus_scan_new 1 \"My Scan\" 192.168.1.250")
print_status()
print_status("Creates a scan based on a policy id and targets.")
print_status("use nessus_policy_list to list all available policies")
return
end
if ! nessus_verify_token
return
end
case args.length
when 3
pid = args[0].to_i
name = args[1]
tgts = args[2]
else
print_status("Usage: ")
print_status(" nessus_scan_new <policy id> <scan name> <targets>")
print_status(" use nessus_policy_list to list all available policies")
return
end
if check_policy(pid)
print_error("That policy does not exist.")
return
end
print_status("Creating scan from policy number #{pid}, called \"#{name}\" and scanning #{tgts}")
scan = @n.scan_new(pid, name, tgts)
if scan
print_status("Scan started. uid is #{scan}")
end
end
def cmd_nessus_scan_pause(*args)
if args[0] == "-h"
print_status("Usage: ")
print_status(" nessus_scan_pause <scan id>")
print_status(" Example:> nessus_scan_pause f0eabba3-4065-7d54-5763-f191e98eb0f7f9f33db7e75a06ca")
print_status()
print_status("Pauses a running scan")
print_status("use nessus_scan_status to list all available scans")
return
end
if ! nessus_verify_token
return
end
case args.length
when 1
sid = args[0]
else
print_status("Usage: ")
print_status(" nessus_scan_pause <scan id>")
print_status(" use nessus_scan_status to list all available scans")
return
end
pause = @n.scan_pause(sid)
print_status("#{sid} has been paused")
end
def cmd_nessus_scan_resume(*args)
if args[0] == "-h"
print_status("Usage: ")
print_status(" nessus_scan_resume <scan id>")
print_status(" Example:> nessus_scan_resume f0eabba3-4065-7d54-5763-f191e98eb0f7f9f33db7e75a06ca")
print_status()
print_status("resumes a running scan")
print_status("use nessus_scan_status to list all available scans")
return
end
if ! nessus_verify_token
return
end
case args.length
when 1
sid = args[0]
else
print_status("Usage: ")
print_status(" nessus_scan_resume <scan id>")
print_status(" use nessus_scan_status to list all available scans")
return
end
resume = @n.scan_resume(sid)
print_status("#{sid} has been resumed")
end
def cmd_nessus_report_hosts(*args)
if args[0] == "-h"
print_status("Usage: ")
print_status(" nessus_report_hosts <report id>")
print_status(" Example:> nessus_report_hosts f0eabba3-4065-7d54-5763-f191e98eb0f7f9f33db7e75a06ca")
print_status()
print_status("Returns all the hosts associated with a scan and details about their vulnerabilities")
print_status("use nessus_report_list to list all available scans")
return
end
if ! nessus_verify_token
return
end
case args.length
when 1
rid = args[0]
else
print_status("Usage: ")
print_status(" nessus_report_hosts <report id>")
print_status(" use nessus_report_list to list all available reports")
return
end
tbl = Rex::Ui::Text::Table.new(
'Columns' => [
'Hostname',
'Severity',
'Sev 0',
'Sev 1',
'Sev 2',
'Sev 3',
'Current Progress',
'Total Progress'
])
hosts=@n.report_hosts(rid)
hosts.each {|host|
tbl << [ host['hostname'], host['severity'], host['sev0'], host['sev1'], host['sev2'], host['sev3'], host['current'], host['total'] ]
}
print_good("Report Info")
print_good "\n"
print_line tbl.to_s
print_status("You can:")
print_status(" Get information from a particular host: nessus_report_host_ports <hostname> <report id>")
end
def cmd_nessus_report_vulns(*args)
if args[0] == "-h"
print_status("Usage: ")
print_status(" nessus_report_vulns <report id>")
print_status(" Example:> nessus_report_vulns f0eabba3-4065-7d54-5763-f191e98eb0f7f9f33db7e75a06ca")
print_status()
print_status("Returns all the vulns associated with a scan and details about hosts and their vulnerabilities")
print_status("use nessus_report_list to list all available scans")
return
end
if ! nessus_verify_token
return
end
case args.length
when 1
rid = args[0]
else
print_status("Usage: ")
print_status(" nessus_report_vulns <report id>")
print_status(" use nessus_report_vulns to list all available reports")
return
end
tbl = Rex::Ui::Text::Table.new(
'Columns' => [
'Hostname',
'Port',
'Proto',
'Sev',
'PluginID',
'Plugin Name'
])
print_status("Grabbing all vulns for report #{rid}")
hosts=@n.report_hosts(rid)
hosts.each do |host|
ports=@n.report_host_ports(rid, host['hostname'])
ports.each do |port|
details=@n.report_host_port_details(rid, host['hostname'], port['portnum'], port['protocol'])
details.each do |detail|
tbl << [host['hostname'],
port['portnum'],
port['protocol'],
detail['severity'],
detail['pluginID'],
detail['pluginName']
]
end
end
end
print_good("Report Info")
print_line
print_line tbl.to_s
print_status("You can:")
print_status(" Get information from a particular host: nessus_report_host_ports <hostname> <report id>")
end
def cmd_nessus_report_host_ports(*args)
if args[0] == "-h"
print_status("Usage: ")
print_status(" nessus_report_host_ports <hostname> <report id>")
print_status(" Example:> nessus_report_host_ports 192.168.1.250 f0eabba3-4065-7d54-5763-f191e98eb0f7f9f33db7e75a06ca")
print_status()
print_status("Returns all the ports associated with a host and details about their vulnerabilities")
print_status("use nessus_report_hosts to list all available hosts for a report")
end
if ! nessus_verify_token
return
end
case args.length
when 2
host = args[0]
rid = args[1]
else
print_status("Usage: ")
print_status(" nessus_report_host_ports <hostname> <report id>")
print_status(" use nessus_report_list to list all available reports")
return
end
tbl = Rex::Ui::Text::Table.new(
'Columns' => [
'Port',
'Protocol',
'Severity',
'Service Name',
'Sev 0',
'Sev 1',
'Sev 2',
'Sev 3'
])
ports=@n.report_host_ports(rid, host)
ports.each {|port|
tbl << [ port['portnum'], port['protocol'], port['severity'], port['svcname'], port['sev0'], port['sev1'], port['sev2'], port['sev3'] ]
}
print_good("Host Info")
print_good "\n"
print_line tbl.to_s
print_status("You can:")
print_status(" Get detailed scan infromation about a specfic port: nessus_report_host_detail <hostname> <port> <protocol> <report id>")
end
def cmd_nessus_report_host_detail(*args)
if args[0] == "-h"
print_status("Usage: ")
print_status(" nessus_report_host_detail <hostname> <port> <protocol> <report id>")
print_status(" Example:> nessus_report_host_ports 192.168.1.250 445 tcp f0eabba3-4065-7d54-5763-f191e98eb0f7f9f33db7e75a06ca")
print_status()
print_status("Returns all the vulns associated with a port for a specific host")
print_status("use nessus_report_host_ports to list all available ports for a host")
return
end
if ! nessus_verify_token
return
end
case args.length
when 4
host = args[0]
port = args[1]
prot = args[2]
rid = args[3]
else
print_status("Usage: ")
print_status(" nessus_report_host_detail <hostname> <port> <protocol> <report id>")
print_status(" use nessus_report_host_ports to list all available ports")
return
end
tbl = Rex::Ui::Text::Table.new(
'Columns' => [
'Port',
'Severity',
'PluginID',
'Plugin Name',
'CVSS2',
'Exploit?',
'CVE',
'Risk Factor',
'CVSS Vector'
])
details=@n.report_host_port_details(rid, host, port, prot)
details.each {|detail|
tbl << [
detail['port'],
detail['severity'],
detail['pluginID'],
detail['pluginName'],
detail['cvss_base_score'] || 'none',
detail['exploit_available'] || '.',
detail['cve'] || '.',
detail['risk_factor'] || '.',
detail['cvss_vector'] || '.'
]
}
print_good("Port Info")
print_good "\n"
print_line tbl.to_s
end
def cmd_nessus_scan_pause_all(*args)
if args[0] == "-h"
print_status("Usage: ")
print_status(" nessus_scan_pause_all")
print_status(" Example:> nessus_scan_pause_all")
print_status()
print_status("Pauses all currently running scans")
print_status("use nessus_scan_list to list all running scans")
return
end
if ! nessus_verify_token
return
end
pause = @n.scan_pause_all
print_status("All scans have been paused")
end
def cmd_nessus_scan_stop(*args)
if args[0] == "-h"
print_status("Usage: ")
print_status(" nessus_scan_stop <scan id>")
print_status(" Example:> nessus_scan_stop f0eabba3-4065-7d54-5763-f191e98eb0f7f9f33db7e75a06ca")
print_status()
print_status("Stops a currently running scans")
print_status("use nessus_scan_list to list all running scans")
return
end
if ! nessus_verify_token
return
end
case args.length
when 1
sid = args[0]
else
print_status("Usage: ")
print_status(" nessus_scan_stop <scan id>")
print_status(" use nessus_scan_status to list all available scans")
return
end
pause = @n.scan_stop(sid)
print_status("#{sid} has been stopped")
end
def cmd_nessus_scan_stop_all(*args)
if args[0] == "-h"
print_status("Usage: ")
print_status(" nessus_scan_stop_all")
print_status(" Example:> nessus_scan_stop_all")
print_status()
print_status("stops all currently running scans")
print_status("use nessus_scan_list to list all running scans")
return
end
if ! nessus_verify_token
return
end
pause = @n.scan_stop_all
print_status("All scans have been stopped")
end
def cmd_nessus_scan_resume_all(*args)
if args[0] == "-h"
print_status("Usage: ")
print_status(" nessus_scan_resume_all")
print_status(" Example:> nessus_scan_resume_all")
print_status()
print_status("resumes all currently running scans")
print_status("use nessus_scan_list to list all running scans")
return
end
if ! nessus_verify_token
return
end
pause = @n.scan_resume_all
print_status("All scans have been resumed")
end
def cmd_nessus_user_add(*args)
if args[0] == "-h"
print_status("Usage: ")
print_status(" nessus_user_add <username> <password>")
print_status(" Example:> nessus_user_add msf msf")
print_status()
print_status("Only adds non admin users. Must be an admin to add users.")
print_status("use nessus_user_list to list all users")
return
end
if ! nessus_verify_token
return
end
if ! @n.is_admin
print_error("Your Nessus user is not an admin")
return
end
case args.length
when 2
user = args[0]
pass = args[1]
else
print_status("Usage: ")
print_status(" nessus_user_add <username> <password>")
print_status(" Only adds non admin users")
return
end
u = @n.users_list
u.each { |stuff|
if stuff['name'] == user
print_error("That user exists")
return
end
}
add = @n.user_add(user,pass)
status = add.root.elements['status'].text if add
if status == "OK"
print_good("#{user} has been added")
else
print_error("#{user} was not added")
end
end
def cmd_nessus_user_del(*args)
if args[0] == "-h"
print_status("Usage: ")
print_status(" nessus_user_del <username>")
print_status(" Example:> nessus_user_del msf")
print_status()
print_status("Only dels non admin users. Must be an admin to del users.")
print_status("use nessus_user_list to list all users")
return
end
if ! nessus_verify_token
return
end
if ! @n.is_admin
print_error("Your Nessus user is not an admin")
return
end
case args.length
when 1
user = args[0]
else
print_status("Usage: ")
print_status(" nessus_user_del <username>")
print_status(" Only dels non admin users")
return
end
del = @n.user_del(user)
status = del.root.elements['status'].text
if status == "OK"
print_good("#{user} has been deleted")
else
print_error("#{user} was not deleted")
end
end
def cmd_nessus_user_passwd(*args)
if args[0] == "-h"
print_status("Usage: ")
print_status(" nessus_user_passwd <username> <password>")
print_status(" Example:> nessus_user_passwd msf newpassword")
print_status()
print_status("Changes the password of a user. Must be an admin to change passwords.")
print_status("use nessus_user_list to list all users")
return
end
if ! nessus_verify_token
return
end
if ! @n.is_admin
print_error("Your Nessus user is not an admin")
return
end
case args.length
when 2
user = args[0]
pass = args[1]
else
print_status("Usage: ")
print_status(" nessus_user_passwd <username> <password>")
print_status(" User list from nessus_user_list")
return
end
pass = @n.user_pass(user,pass)
status = pass.root.elements['status'].text
if status == "OK"
print_good("#{user}'s password has been changed")
else
print_error("#{user}'s password has not been changed")
end
end
def cmd_nessus_admin(*args)
if args[0] == "-h"
print_status("Usage: ")
print_status(" nessus_admin")
print_status(" Example:> nessus_admin")
print_status()
print_status("Checks to see if the current user is an admin")
print_status("use nessus_user_list to list all users")
return
end
if ! nessus_verify_token
return
end
if ! @n.is_admin
print_error("Your Nessus user is not an admin")
else
print_good("Your Nessus user is an admin")
end
end
def cmd_nessus_plugin_family(*args)
if args[0] == "-h"
print_status("Usage: ")
print_status(" nessus_plugin_family <plugin family name>")
print_status(" Example:> nessus_plugin_family \"Windows : Microsoft Bulletins\" ")
print_status()
print_status("Returns a list of all plugins in that family.")
print_status("use nessus_plugin_list to list all plugins")
return
end
if ! nessus_verify_token
return
end
case args.length
when 1
fam = args[0]
else
print_status("Usage: ")
print_status(" nessus_plugin_family <plugin family name>")
print_status(" list all plugins from a Family from nessus_plugin_list")
return
end
tbl = Rex::Ui::Text::Table.new(
'Columns' => [
'Plugin ID',
'Plugin Name',
'Plugin File Name'
])
family = @n.plugin_family(fam)
family.each {|plugin|
tbl << [ plugin['id'], plugin['name'], plugin['filename'] ]
}
print_good("#{fam} Info")
print_good "\n"
print_line tbl.to_s
end
def cmd_nessus_policy_list(*args)
if args[0] == "-h"
print_status("Usage: ")
print_status(" nessus_policy_list")
print_status(" Example:> nessus_policy_list")
print_status()
print_status("Lists all policies on the server")
return
end
if ! nessus_verify_token
return
end
tbl = Rex::Ui::Text::Table.new(
'Columns' => [
'ID',
'Name',
'Comments'
])
list=@n.policy_list_hash
list.each {|policy|
tbl << [ policy['id'], policy['name'], policy['comments'] ]
}
print_good("Nessus Policy List")
print_good "\n"
print_line tbl.to_s
end
def cmd_nessus_policy_del(*args)
if args[0] == "-h"
print_status("Usage: ")
print_status(" nessus_policy_del <policy ID>")
print_status(" Example:> nessus_policy_del 1")
print_status()
print_status("Must be an admin to del policies.")
print_status("use nessus_policy_list to list all policies")
return
end
if ! nessus_verify_token
return
end
if ! @n.is_admin
print_error("Your Nessus user is not an admin")
return
end
case args.length
when 1
pid = args[0]
else
print_status("Usage: ")
print_status(" nessus_policy_del <policy ID>")
print_status(" nessus_policy_list to find the id.")
return
end
del = @n.policy_del(pid)
status = del.root.elements['status'].text
if status == "OK"
print_good("Policy number #{pid} has been deleted")
else
print_error("Policy number #{pid} was not deleted")
end
end
def cmd_nessus_plugin_details(*args)
if args[0] == "-h"
print_status("Usage: ")
print_status(" nessus_plugin_details <plugin file name>")
print_status(" Example:> nessus_plugin_details ping_host.nasl ")
print_status()
print_status("Returns details on a particular plugin.")
print_status("use nessus_plugin_list to list all plugins")
return
end
if ! nessus_verify_token
return
end
case args.length
when 1
pname = args[0]
else
print_status("Usage: ")
print_status(" nessus_policy_del <plugin file name>")
print_status(" nessus_plugin_list and then nessus_plugin_family to find the plugin file name.")
return
end
tbl = Rex::Ui::Text::Table.new(
'Columns' => [
'',
''
])
entry = @n.plugin_detail(pname)
print_good("Plugin Details for #{entry['name']}")
tbl << [ "Plugin ID", entry['id'] ]
tbl << [ "Plugin Family", entry['family'] ]
tbl << [ "CVSS Base Score", entry['cvss_base_score'] ]
tbl << [ "CVSS Vector", entry['cvss_vector'] ]
tbl << [ "CVSS Temporal Score", entry['cvss_temporal_score'] ]
tbl << [ "CVSS Temporal Vector", entry['cvss_temporal_vector'] ]
tbl << [ "Risk Factor", entry['risk_factor'] ]
tbl << [ "Exploit Available", entry['exploit_available'] ]
tbl << [ "Exploitability Ease", entry['exploit_ease'] ]
tbl << [ "Synopsis", entry['synopsis'] ]
tbl << [ "Description", entry['description'] ]
tbl << [ "Solution", entry['solution'] ]
tbl << [ "Plugin Pub Date", entry['plugin_publication_date'] ]
tbl << [ "Plugin Modification Date", entry['plugin_modification_date'] ]
print_good "\n"
print_line tbl.to_s
end
def cmd_nessus_report_del(*args)
if args[0] == "-h"
print_status("Usage: ")
print_status(" nessus_report_del <reportname>")
print_status(" Example:> nessus_report_del f0eabba3-4065-7d54-5763-f191e98eb0f7f9f33db7e75a06ca")
print_status()
print_status("Must be an admin to del reports.")
print_status("use nessus_report_list to list all reports")
return
end
if ! nessus_verify_token
return
end
if ! @n.is_admin
print_error("Your Nessus user is not an admin")
return
end
case args.length
when 1
rid = args[0]
else
print_status("Usage: ")
print_status(" nessus_report_del <report ID>")
print_status(" nessus_report_list to find the id.")
return
end
del = @n.report_del(rid)
status = del.root.elements['status'].text
if status == "OK"
print_good("Report #{rid} has been deleted")
else
print_error("Report #{rid} was not deleted")
end
end
def cmd_nessus_server_prefs(*args)
if args[0] == "-h"
print_status("Usage: ")
print_status(" nessus_server_prefs")
print_status(" Example:> nessus_server_prefs")
print_status()
print_status("Returns a long list of server prefs.")
return
end
if ! nessus_verify_token
return
end
if ! @n.is_admin
print_error("Your Nessus user is not an admin")
return
end
tbl = Rex::Ui::Text::Table.new(
'Columns' => [
'Name',
'Value'
])
prefs = @n.server_prefs
prefs.each {|pref|
tbl << [ pref['name'], pref['value'] ]
}
print_good("Nessus Server Pref List")
print_good "\n"
print_line tbl.to_s + "\n"
end
def cmd_nessus_plugin_prefs(*args)
if args[0] == "-h"
print_status("Usage: ")
print_status(" nessus_plugin_prefs")
print_status(" Example:> nessus_plugin_prefs")
print_status()
print_status("Returns a long list of plugin prefs.")
return
end
if ! nessus_verify_token
return
end
if ! @n.is_admin
print_error("Your Nessus user is not an admin")
return
end
tbl = Rex::Ui::Text::Table.new(
'Columns' => [
'Name',
'Value',
'Type'
])
prefs = @n.plugin_prefs
prefs.each {|pref|
tbl << [ pref['prefname'], pref['prefvalues'], pref['preftype'] ]
}
print_good("Nessus Plugins Pref List")
print_good "\n"
print_line tbl.to_s
end
end
def initialize(framework, opts)
super
add_console_dispatcher(ConsoleCommandDispatcher)
@nbver = "1.1" # Nessus Plugin Version. Increments each time we commit to msf
@xindex = "#{Msf::Config.get_config_root}/nessus_index" # location of the exploit index file used to speed up searching for valid exploits.
@nessus_yaml = "#{Msf::Config.get_config_root}/nessus.yaml" #location of the nessus.yml containing saved nessus creds
print_status("Nessus Bridge for Metasploit #{@nbver}")
print_good("Type %bldnessus_help%clr for a command listing")
#nessus_index
end
def cleanup
remove_console_dispatcher('Nessus')
end
def name
"nessus"
end
def desc
"Nessus Bridge for Metasploit #{@nbver}"
end
protected
end
class Plugin::Nessus < Msf::Plugin
#creates the index of exploit details to make searching for exploits much faster.
def create_xindex
start = Time.now
print_status("Creating Exploit Search Index - (#{@xindex}) - this wont take long.")
count = 0
# use Msf::Config.get_config_root as the location.
File.open("#{@xindex}", "w+") do |f|
#need to add version line.
f.puts(Msf::Framework::RepoRevision)
framework.exploits.sort.each { |refname, mod|
stuff = ""
o = nil
begin
o = mod.new
rescue ::Exception
end
stuff << "#{refname}|#{o.name}|#{o.platform_to_s}|#{o.arch_to_s}"
next if not o
o.references.map do |x|
if !(x.ctx_id == "URL")
if (x.ctx_id == "MSB")
stuff << "|#{x.ctx_val}"
else
stuff << "|#{x.ctx_id}-#{x.ctx_val}"
end
end
end
stuff << "\n"
f.puts(stuff)
}
end
total = Time.now - start
print_status("It has taken : #{total} seconds to build the exploits search index")
end
def nessus_index
if File.exist?("#{@xindex}")
#check if it's version line matches current version.
File.open("#{@xindex}") {|f|
line = f.readline
line.chomp!
if line.to_i == Msf::Framework::RepoRevision
print_good("Exploit Index - (#{@xindex}) - is valid.")
else
create_xindex
end
}
else
create_xindex
end
end
class ConsoleCommandDispatcher
include Msf::Ui::Console::CommandDispatcher
def name
"Nessus"
end
def commands
{
"nessus_connect" => "Connect to a nessus server: nconnect username:password@hostname:port <ssl ok>.",
"nessus_admin" => "Checks if user is an admin.",
"nessus_help" => "Get help on all commands.",
"nessus_logout" => "Terminate the session.",
"nessus_server_status" => "Check the status of your Nessus Server.",
"nessus_server_feed" => "Nessus Feed Type.",
"nessus_server_prefs" => "Display Server Prefs.",
"nessus_report_list" => "List all Nessus reports.",
"nessus_report_get" => "Import a report from the nessus server in Nessus v2 format.",
"nessus_report_del" => "Delete a report.",
"nessus_report_vulns" => "Get list of vulns from a report.",
"nessus_report_hosts" => "Get list of hosts from a report.",
"nessus_report_host_ports" => "Get list of open ports from a host from a report.",
"nessus_report_host_detail" => "Detail from a report item on a host.",
"nessus_scan_status" => "List all currently running Nessus scans.",
"nessus_scan_new" => "Create new Nessus Scan.",
"nessus_scan_pause" => "Pause a Nessus Scan.",
"nessus_scan_pause_all" => "Pause all Nessus Scans.",
"nessus_scan_stop" => "Stop a Nessus Scan.",
"nessus_scan_stop_all" => "Stop all Nessus Scans.",
"nessus_scan_resume" => "Resume a Nessus Scan.",
"nessus_scan_resume_all" => "Resume all Nessus Scans.",
"nessus_user_list" => "Show Nessus Users.",
"nessus_user_add" => "Add a new Nessus User.",
"nessus_user_del" => "Delete a Nessus User.",
"nessus_user_passwd" => "Change Nessus Users Password.",
"nessus_plugin_family" => "List plugins in a family.",
"nessus_plugin_details" => "List details of a particular plugin.",
"nessus_plugin_list" => "Displays each plugin family and the number of plugins.",
"nessus_plugin_prefs" => "Display Plugin Prefs.",
"nessus_policy_list" => "List all polciies.",
"nessus_policy_del" => "Delete a policy.",
"nessus_index" => "Manually generates a search index for exploits.",
"nessus_template_list" => "List all the templates on the server.",
"nessus_db_scan" => "Create a scan of all ips in db_hosts.",
"nessus_save" => "Save username/passowrd/server/port details."
}
end
def cmd_nessus_index
Msf::Plugin::Nessus.nessus_index
end
def cmd_nessus_save(*args)
#if we are logged in, save session details to nessus.yaml
@nessus_yaml = "#{Msf::Config.get_config_root}/nessus.yaml"
if args[0] == "-h"
print_status("Usage: ")
print_status(" nessus_save")
return
end
if args[0]
print_status("Usage: ")
print_status(" nessus_save")
return
end
group = "default"
if ((@user and @user.length > 0) and (@host and @host.length > 0) and (@port and @port.length > 0 and @port.to_i > 0) and (@pass and @pass.length > 0))
config = Hash.new
config = {"#{group}" => {'username' => @user, 'password' => @pass, 'server' => @host, 'port' => @port}}
File.open("#{@nessus_yaml}", "w+") do |f|
f.puts YAML.dump(config)
end
print_good("#{@nessus_yaml} created.")
else
print_error("Missing username/password/server/port - relogin and then try again.")
return
end
end
def cmd_nessus_db_scan(*args)
if args[0] == "-h"
print_status("Usage: ")
print_status(" nessus_db_scan <policy id> <scan name>")
print_status(" Example:> nessus_db_scan 1 \"My Scan\"")
print_status()
print_status("Creates a scan based on all the hosts listed in db_hosts.")
print_status("use nessus_policy_list to list all available policies")
return
end
if ! nessus_verify_token
return
end
case args.length
when 2
pid = args[0].to_i
name = args[1]
else
print_status("Usage: ")
print_status(" nessus_db_scan <policy id> <scan name>")
print_status(" use nessus_policy_list to list all available policies")
return
end
if check_policy(pid)
print_error("That policy does not exist.")
return
end
tgts = ""
framework.db.hosts(framework.db.workspace).each do |host|
tgts << host.address
tgts << ","
end
tgts.chop!
print_status("Creating scan from policy number #{pid}, called \"#{name}\" and scanning all hosts in workspace")
scan = @n.scan_new(pid, name, tgts)
if scan
print_status("Scan started. uid is #{scan}")
end
end
def cmd_nessus_logout
@token = nil
print_status("Logged out")
system("rm #{@nessus_yaml}")
print_good("#{@nessus_yaml} removed.")
return
end
def cmd_nessus_help(*args)
tbl = Rex::Ui::Text::Table.new(
'Columns' => [
"Command",
"Help Text"
],
'SortIndex' => -1
)
tbl << [ "Generic Commands", "" ]
tbl << [ "-----------------", "-----------------"]
tbl << [ "nessus_connect", "Connect to a nessus server" ]
tbl << [ "nessus_save", "Save nessus login info between sessions" ]
tbl << [ "nessus_logout", "Logout from the nessus server" ]
tbl << [ "nessus_help", "Listing of available nessus commands" ]
tbl << [ "nessus_server_status", "Check the status of your Nessus Server" ]
tbl << [ "nessus_admin", "Checks if user is an admin" ]
tbl << [ "nessus_server_feed", "Nessus Feed Type" ]
tbl << [ "nessus_find_targets", "Try to find vulnerable targets from a report" ]
tbl << [ "nessus_server_prefs", "Display Server Prefs" ]
tbl << [ "", ""]
tbl << [ "Reports Commands", "" ]
tbl << [ "-----------------", "-----------------"]
tbl << [ "nessus_report_list", "List all Nessus reports" ]
tbl << [ "nessus_report_get", "Import a report from the nessus server in Nessus v2 format" ]
tbl << [ "nessus_report_vulns", "Get list of vulns from a report" ]
tbl << [ "nessus_report_hosts", "Get list of hosts from a report" ]
tbl << [ "nessus_report_host_ports", "Get list of open ports from a host from a report" ]
tbl << [ "nessus_report_host_detail", "Detail from a report item on a host" ]
tbl << [ "", ""]
tbl << [ "Scan Commands", "" ]
tbl << [ "-----------------", "-----------------"]
tbl << [ "nessus_scan_new", "Create new Nessus Scan" ]
tbl << [ "nessus_scan_status", "List all currently running Nessus scans" ]
tbl << [ "nessus_scan_pause", "Pause a Nessus Scan" ]
tbl << [ "nessus_scan_pause_all", "Pause all Nessus Scans" ]
tbl << [ "nessus_scan_stop", "Stop a Nessus Scan" ]
tbl << [ "nessus_scan_stop_all", "Stop all Nessus Scans" ]
tbl << [ "nessus_scan_resume", "Resume a Nessus Scan" ]
tbl << [ "nessus_scan_resume_all", "Resume all Nessus Scans" ]
tbl << [ "", ""]
tbl << [ "Plugin Commands", "" ]
tbl << [ "-----------------", "-----------------"]
tbl << [ "nessus_plugin_list", "Displays each plugin family and the number of plugins" ]
tbl << [ "nessus_plugin_family", "List plugins in a family" ]
tbl << [ "nessus_plugin_details", "List details of a particular plugin" ]
tbl << [ "", ""]
tbl << [ "User Commands", "" ]
tbl << [ "-----------------", "-----------------"]
tbl << [ "nessus_user_list", "Show Nessus Users" ]
tbl << [ "nessus_user_add", "Add a new Nessus User" ]
tbl << [ "nessus_user_del", "Delete a Nessus User" ]
tbl << [ "nessus_user_passwd", "Change Nessus Users Password" ]
tbl << [ "", ""]
tbl << [ "Policy Commands", "" ]
tbl << [ "-----------------", "-----------------"]
tbl << [ "nessus_policy_list", "List all polciies" ]
tbl << [ "nessus_policy_del", "Delete a policy" ]
print_status ""
print_line tbl.to_s
print_status ""
end
def cmd_nessus_server_feed(*args)
if args[0] == "-h"
print_status("Usage: ")
print_status(" nessus_server_feed")
print_status(" Example:> nessus_server_feed")
print_status()
print_status("Returns information about the feed type and server version.")
return
end
if nessus_verify_token
@feed, @version, @web_version = @n.feed
tbl = Rex::Ui::Text::Table.new(
'Columns' => [
'Feed',
'Nessus Version',
'Nessus Web Version'
])
tbl << [@feed, @version, @web_version]
print_good("Nessus Status")
print_good "\n"
print_line tbl.to_s
end
end
def nessus_verify_token
if @token.nil? or @token == ''
ncusage
return false
end
true
end
def nessus_verify_db
if ! (framework.db and framework.db.active)
print_error("No database has been configured, please use db_create/db_connect first")
return false
end
true
end
def ncusage
print_status("%redYou must do this before any other commands.%clr")
print_status("Usage: ")
print_status(" nessus_connect username:password@hostname:port <ssl ok>")
print_status(" Example:> nessus_connect msf:msf@192.168.1.10:8834 ok")
print_status(" OR")
print_status(" nessus_connect username@hostname:port <ssl ok>")
print_status(" Example:> nessus_connect msf@192.168.1.10:8834 ok")
print_status(" OR")
print_status(" nessus_connect hostname:port <ssl ok>")
print_status(" Example:> nessus_connect 192.168.1.10:8834 ok")
print_status(" OR")
print_status(" nessus_connect")
print_status(" Example:> nessus_connect")
print_status("This only works after you have saved creds with nessus_save")
return
end
def cmd_nessus_connect(*args)
# Check if config file exists and load it
@nessus_yaml = "#{Msf::Config.get_config_root}/nessus.yaml"
if ! args[0]
if File.exist?("#{@nessus_yaml}")
lconfig = YAML.load_file("#{@nessus_yaml}")
@user = lconfig['default']['username']
@pass = lconfig['default']['password']
@host = lconfig['default']['server']
@port = lconfig['default']['port']
nessus_login
return
else
ncusage
return
end
end
if args[0] == "-h"
print_status("%redYou must do this before any other commands.%clr")
print_status("Usage: ")
print_status(" nessus_connect username:password@hostname:port <ssl ok>")
print_status(" Example:> nessus_connect msf:msf@192.168.1.10:8834 ok")
print_status(" OR")
print_status(" nessus_connect username@hostname:port <ssl ok>")
print_status(" Example:> nessus_connect msf@192.168.1.10:8834 ok")
print_status(" OR")
print_status(" nessus_connect hostname:port <ssl ok>")
print_status(" Example:> nessus_connect 192.168.1.10:8834 ok")
print_status(" OR")
print_status(" nessus_connect")
print_status(" Example:> nessus_connect")
print_status("This only works after you have saved creds with nessus_save")
print_status()
print_status("%bldusername%clr and %bldpassword%clr are the ones you use to login to the nessus web front end")
print_status("%bldhostname%clr can be an ip address or a dns name of the web front end.")
print_status("%bldport%clr is the standard that the nessus web front end runs on : 8834. This is NOT 1241.")
print_status("The \"ok\" on the end is important. It is a way of letting you")
print_status("know that nessus used a self signed cert and the risk that presents.")
return
end
if ! @token == ''
print_error("You are already authenticated. Call nessus_logout before authing again")
return
end
if(args.length == 0 or args[0].empty?)
ncusage
return
end
@user = @pass = @host = @port = @sslv = nil
case args.length
when 1,2
if args[0].include? "@"
cred,targ = args[0].split('@', 2)
@user,@pass = cred.split(':', 2)
targ ||= '127.0.0.1:8834'
@host,@port = targ.split(':', 2)
@port ||= '8834'
@sslv = args[1]
else
@host,@port = args[0].split(':', 2)
@port ||= '8834'
@sslv = args[1]
end
when 3,4,5
ncusage
return
else
ncusage
return
end
if /\/\//.match(@host)
ncusage
return
end
if(@host != "localhost" and @host != "127.0.0.1" and @sslv != "ok")
print_error("Warning: SSL connections are not verified in this release, it is possible for an attacker")
print_error(" with the ability to man-in-the-middle the Nessus traffic to capture the Nessus")
print_error(" credentials. If you are running this on a trusted network, please pass in 'ok'")
print_error(" as an additional parameter to this command.")
return
end
if ! @user
print_error("Missing Username")
ncusage
return
end
if ! @pass
print_error("Missing Password")
ncusage
return
end
if ! ((@user and @user.length > 0) and (@host and @host.length > 0) and (@port and @port.length > 0 and @port.to_i > 0) and (@pass and @pass.length > 0))
ncusage
return
end
nessus_login
end
def nessus_login
if ! ((@user and @user.length > 0) and (@host and @host.length > 0) and (@port and @port.length > 0 and @port.to_i > 0) and (@pass and @pass.length > 0))
print_status("You need to connect to a server first.")
ncusage
return
end
@url = "https://#{@host}:#{@port}/"
print_status("Connecting to #{@url} as #{@user}")
@n=NessusXMLRPC::NessusXMLRPC.new(@url,@user,@pass)
@token=@n.login(@user,@pass)
if @n.logged_in
print_status("Authenticated")
else
print_error("Error connecting/logging to the server!")
return
end
end
def cmd_nessus_report_list(*args)
if args[0] == "-h"
print_status("Usage: ")
print_status(" nessus_report_list")
print_status(" Example:> nessus_report_list")
print_status()
print_status("Generates a list of all reports visable to your user.")
return
end
if ! nessus_verify_token
return
end
list=@n.report_list_hash
tbl = Rex::Ui::Text::Table.new(
'Columns' => [
'ID',
'Name',
'Status',
'Date'
])
list.each {|report|
t = Time.at(report['timestamp'].to_i)
tbl << [ report['id'], report['name'], report['status'], t.strftime("%H:%M %b %d %Y") ]
}
print_good("Nessus Report List")
print_good "\n"
print_line tbl.to_s + "\n"
print_status("You can:")
print_status(" Get a list of hosts from the report: nessus_report_hosts <report id>")
end
def check_scan(*args)
case args.length
when 1
rid = args[0]
else
print_error("No Report ID Supplied")
return
end
scans = @n.scan_list_hash
scans.each {|scan|
if scan['id'] == rid
return true
end
}
return false
end
def cmd_nessus_report_get(*args)
if args[0] == "-h"
print_status("Usage: ")
print_status(" nessus_report_get <report id>")
print_status(" Example:> nessus_report_get f0eabba3-4065-7d54-5763-f191e98eb0f7f9f33db7e75a06ca")
print_status()
print_status("This command pulls the provided report from the nessus server in the nessusv2 format")
print_status("and parses it the same way db_import_nessus does. After it is parsed it will be")
print_status("available to commands such as db_hosts, db_vulns, db_services and db_autopwn.")
print_status("Use: nessus_report_list to obtain a list of report id's")
return
end
if ! nessus_verify_token
return
end
if ! nessus_verify_db
return
end
if(args.length == 0 or args[0].empty? or args[0] == "-h")
print_status("Usage: ")
print_status(" nessus_report_get <report id> ")
print_status(" use nessus_report_list to list all available reports for importing")
return
end
rid = nil
case args.length
when 1
rid = args[0]
else
print_status("Usage: ")
print_status(" nessus_report_get <report id> ")
print_status(" use nessus_report_list to list all available reports for importing")
return
end
if check_scan(rid)
print_error("That scan is still running.")
return
end
content = nil
content=@n.report_file_download(rid)
if content.nil?
print_error("Failed, please reauthenticate")
return
end
print_status("importing " + rid)
framework.db.import({:data => content}) do |type,data|
case type
when :address
print_line("%bld%blu[*]%clr %bld#{data}%clr")
end
end
print_good("Done")
end
def cmd_nessus_scan_status(*args)
if args[0] == "-h"
print_status("Usage: ")
print_status(" nessus_scan_status")
print_status(" Example:> nessus_scan_status")
print_status()
print_status("Returns a list of information about currently running scans.")
return
end
if ! nessus_verify_token
return
end
list=@n.scan_list_hash
if list.empty?
print_status("No Scans Running.")
print_status("You can:")
print_status(" List of completed scans: nessus_report_list")
print_status(" Create a scan: nessus_scan_new <policy id> <scan name> <target(s)>")
return
end
tbl = Rex::Ui::Text::Table.new(
'Columns' => [
'Scan ID',
'Name',
'Owner',
'Started',
'Status',
'Current Hosts',
'Total Hosts'
])
list.each {|scan|
t = Time.at(scan['start'].to_i)
tbl << [ scan['id'], scan['name'], scan['owner'], t.strftime("%H:%M %b %d %Y"), scan['status'], scan['current'], scan['total'] ]
}
print_good("Running Scans")
print_good "\n"
print_line tbl.to_s
print_good "\n"
print_status("You can:")
print_good(" Import Nessus report to database : nessus_report_get <reportid>")
print_good(" Pause a nessus scan : nessus_scan_pause <scanid>")
end
def cmd_nessus_template_list(*args)
if args[0] == "-h"
print_status("Usage: ")
print_status(" nessus_template_list")
print_status(" Example:> nessus_template_list")
print_status()
print_status("Returns a list of information about the server templates..")
return
end
if ! nessus_verify_token
return
end
list=@n.template_list_hash
if list.empty?
print_status("No Templates Created.")
print_status("You can:")
print_status(" List of completed scans: nessus_report_list")
print_status(" Create a template: nessus_template_new <policy id> <scan name> <target(s)>")
return
end
tbl = Rex::Ui::Text::Table.new(
'Columns' => [
'Template ID',
'Policy ID',
'Name',
'Owner',
'Target'
])
list.each {|template|
tbl << [ template['name'], template['pid'], template['rname'], template['owner'], template['target'] ]
}
print_good("Templates")
print_good "\n"
print_line tbl.to_s + "\n"
print_good "\n"
print_status("You can:")
print_good(" Import Nessus report to database : nessus_report_get <reportid>")
end
def cmd_nessus_user_list(*args)
if args[0] == "-h"
print_status("Usage: ")
print_status(" nessus_user_list")
print_status(" Example:> nessus_user_list")
print_status()
print_status("Returns a list of the users on the Nessus server and their access level.")
return
end
if ! nessus_verify_token
return
end
if ! @n.is_admin
print_status("Your Nessus user is not an admin")
end
list=@n.users_list
print_good("There are #{list.length} users")
tbl = Rex::Ui::Text::Table.new(
'Columns' => [
'Name',
'Is Admin?',
'Last Login'
])
list.each {|user|
t = Time.at(user['lastlogin'].to_i)
tbl << [ user['name'], user['admin'], t.strftime("%H:%M %b %d %Y") ]
}
print_good("Nessus users")
print_good "\n"
print_line tbl.to_s
end
def cmd_nessus_server_status(*args)
if args[0] == "-h"
print_status("Usage: ")
print_status(" nessus_server_status")
print_status(" Example:> nessus_server_status")
print_status()
print_status("Returns some status items for the server..")
return
end
#Auth
if ! nessus_verify_token
return
end
#Check if we are an admin
if ! @n.is_admin
print_status("You need to be an admin for this.")
return
end
#Versions
cmd_nessus_server_feed
tbl = Rex::Ui::Text::Table.new(
'Columns' => [
'Users',
'Policies',
'Running Scans',
'Reports',
'Plugins'
])
#Count how many users the server has.
list=@n.users_list
users = list.length
#Count how many policies
list=@n.policy_list_hash
policies = list.length
#Count how many running scans
list=@n.scan_list_uids
scans = list.length
#Count how many reports are available
list=@n.report_list_hash
reports = list.length
#Count how many plugins
list=@n.plugins_list
total = Array.new
list.each {|plugin|
total.push(plugin['num'].to_i)
}
plugins = total.sum
tbl << [users, policies, scans, reports, plugins]
print_good "\n"
print_line tbl.to_s
end
def cmd_nessus_plugin_list(*args)
if args[0] == "-h"
print_status("Usage: ")
print_status(" nessus_plugin_list")
print_status(" Example:> nessus_plugin_list")
print_status()
print_status("Returns a list of the plugins on the server per family.")
return
end
if ! nessus_verify_token
return
end
tbl = Rex::Ui::Text::Table.new(
'Columns' => [
'Family Name',
'Total Plugins'
])
list=@n.plugins_list
total = Array.new
list.each {|plugin|
total.push(plugin['num'].to_i)
tbl << [ plugin['name'], plugin['num'] ]
}
plugins = total.sum
tbl << [ '', '']
tbl << [ 'Total Plugins', plugins ]
print_good("Plugins By Family")
print_good "\n"
print_line tbl.to_s
print_status("List plugins for a family : nessus_plugin_family <family name>")
end
def check_policy(*args)
case args.length
when 1
pid = args[0]
else
print_error("No Policy ID supplied.")
return
end
pol = @n.policy_list_hash
pol.each {|p|
if p['id'].to_i == pid
return false
end
}
return true
end
def cmd_nessus_scan_new(*args)
if args[0] == "-h"
print_status("Usage: ")
print_status(" nessus_scan_new <policy id> <scan name> <targets>")
print_status(" Example:> nessus_scan_new 1 \"My Scan\" 192.168.1.250")
print_status()
print_status("Creates a scan based on a policy id and targets.")
print_status("use nessus_policy_list to list all available policies")
return
end
if ! nessus_verify_token
return
end
case args.length
when 3
pid = args[0].to_i
name = args[1]
tgts = args[2]
else
print_status("Usage: ")
print_status(" nessus_scan_new <policy id> <scan name> <targets>")
print_status(" use nessus_policy_list to list all available policies")
return
end
if check_policy(pid)
print_error("That policy does not exist.")
return
end
print_status("Creating scan from policy number #{pid}, called \"#{name}\" and scanning #{tgts}")
scan = @n.scan_new(pid, name, tgts)
if scan
print_status("Scan started. uid is #{scan}")
end
end
def cmd_nessus_scan_pause(*args)
if args[0] == "-h"
print_status("Usage: ")
print_status(" nessus_scan_pause <scan id>")
print_status(" Example:> nessus_scan_pause f0eabba3-4065-7d54-5763-f191e98eb0f7f9f33db7e75a06ca")
print_status()
print_status("Pauses a running scan")
print_status("use nessus_scan_status to list all available scans")
return
end
if ! nessus_verify_token
return
end
case args.length
when 1
sid = args[0]
else
print_status("Usage: ")
print_status(" nessus_scan_pause <scan id>")
print_status(" use nessus_scan_status to list all available scans")
return
end
pause = @n.scan_pause(sid)
print_status("#{sid} has been paused")
end
def cmd_nessus_scan_resume(*args)
if args[0] == "-h"
print_status("Usage: ")
print_status(" nessus_scan_resume <scan id>")
print_status(" Example:> nessus_scan_resume f0eabba3-4065-7d54-5763-f191e98eb0f7f9f33db7e75a06ca")
print_status()
print_status("resumes a running scan")
print_status("use nessus_scan_status to list all available scans")
return
end
if ! nessus_verify_token
return
end
case args.length
when 1
sid = args[0]
else
print_status("Usage: ")
print_status(" nessus_scan_resume <scan id>")
print_status(" use nessus_scan_status to list all available scans")
return
end
resume = @n.scan_resume(sid)
print_status("#{sid} has been resumed")
end
def cmd_nessus_report_hosts(*args)
if args[0] == "-h"
print_status("Usage: ")
print_status(" nessus_report_hosts <report id>")
print_status(" Example:> nessus_report_hosts f0eabba3-4065-7d54-5763-f191e98eb0f7f9f33db7e75a06ca")
print_status()
print_status("Returns all the hosts associated with a scan and details about their vulnerabilities")
print_status("use nessus_report_list to list all available scans")
return
end
if ! nessus_verify_token
return
end
case args.length
when 1
rid = args[0]
else
print_status("Usage: ")
print_status(" nessus_report_hosts <report id>")
print_status(" use nessus_report_list to list all available reports")
return
end
tbl = Rex::Ui::Text::Table.new(
'Columns' => [
'Hostname',
'Severity',
'Sev 0',
'Sev 1',
'Sev 2',
'Sev 3',
'Current Progress',
'Total Progress'
])
hosts=@n.report_hosts(rid)
hosts.each {|host|
tbl << [ host['hostname'], host['severity'], host['sev0'], host['sev1'], host['sev2'], host['sev3'], host['current'], host['total'] ]
}
print_good("Report Info")
print_good "\n"
print_line tbl.to_s
print_status("You can:")
print_status(" Get information from a particular host: nessus_report_host_ports <hostname> <report id>")
end
def cmd_nessus_report_vulns(*args)
if args[0] == "-h"
print_status("Usage: ")
print_status(" nessus_report_vulns <report id>")
print_status(" Example:> nessus_report_vulns f0eabba3-4065-7d54-5763-f191e98eb0f7f9f33db7e75a06ca")
print_status()
print_status("Returns all the vulns associated with a scan and details about hosts and their vulnerabilities")
print_status("use nessus_report_list to list all available scans")
return
end
if ! nessus_verify_token
return
end
case args.length
when 1
rid = args[0]
else
print_status("Usage: ")
print_status(" nessus_report_vulns <report id>")
print_status(" use nessus_report_vulns to list all available reports")
return
end
tbl = Rex::Ui::Text::Table.new(
'Columns' => [
'Hostname',
'Port',
'Proto',
'Sev',
'PluginID',
'Plugin Name'
])
print_status("Grabbing all vulns for report #{rid}")
hosts=@n.report_hosts(rid)
hosts.each do |host|
ports=@n.report_host_ports(rid, host['hostname'])
ports.each do |port|
details=@n.report_host_port_details(rid, host['hostname'], port['portnum'], port['protocol'])
details.each do |detail|
tbl << [host['hostname'],
port['portnum'],
port['protocol'],
detail['severity'],
detail['pluginID'],
detail['pluginName']
]
end
end
end
print_good("Report Info")
print_line
print_line tbl.to_s
print_status("You can:")
print_status(" Get information from a particular host: nessus_report_host_ports <hostname> <report id>")
end
def cmd_nessus_report_host_ports(*args)
if args[0] == "-h"
print_status("Usage: ")
print_status(" nessus_report_host_ports <hostname> <report id>")
print_status(" Example:> nessus_report_host_ports 192.168.1.250 f0eabba3-4065-7d54-5763-f191e98eb0f7f9f33db7e75a06ca")
print_status()
print_status("Returns all the ports associated with a host and details about their vulnerabilities")
print_status("use nessus_report_hosts to list all available hosts for a report")
end
if ! nessus_verify_token
return
end
case args.length
when 2
host = args[0]
rid = args[1]
else
print_status("Usage: ")
print_status(" nessus_report_host_ports <hostname> <report id>")
print_status(" use nessus_report_list to list all available reports")
return
end
tbl = Rex::Ui::Text::Table.new(
'Columns' => [
'Port',
'Protocol',
'Severity',
'Service Name',
'Sev 0',
'Sev 1',
'Sev 2',
'Sev 3'
])
ports=@n.report_host_ports(rid, host)
ports.each {|port|
tbl << [ port['portnum'], port['protocol'], port['severity'], port['svcname'], port['sev0'], port['sev1'], port['sev2'], port['sev3'] ]
}
print_good("Host Info")
print_good "\n"
print_line tbl.to_s
print_status("You can:")
print_status(" Get detailed scan infromation about a specfic port: nessus_report_host_detail <hostname> <port> <protocol> <report id>")
end
def cmd_nessus_report_host_detail(*args)
if args[0] == "-h"
print_status("Usage: ")
print_status(" nessus_report_host_detail <hostname> <port> <protocol> <report id>")
print_status(" Example:> nessus_report_host_ports 192.168.1.250 445 tcp f0eabba3-4065-7d54-5763-f191e98eb0f7f9f33db7e75a06ca")
print_status()
print_status("Returns all the vulns associated with a port for a specific host")
print_status("use nessus_report_host_ports to list all available ports for a host")
return
end
if ! nessus_verify_token
return
end
case args.length
when 4
host = args[0]
port = args[1]
prot = args[2]
rid = args[3]
else
print_status("Usage: ")
print_status(" nessus_report_host_detail <hostname> <port> <protocol> <report id>")
print_status(" use nessus_report_host_ports to list all available ports")
return
end
tbl = Rex::Ui::Text::Table.new(
'Columns' => [
'Port',
'Severity',
'PluginID',
'Plugin Name',
'CVSS2',
'Exploit?',
'CVE',
'Risk Factor',
'CVSS Vector'
])
details=@n.report_host_port_details(rid, host, port, prot)
details.each {|detail|
tbl << [
detail['port'],
detail['severity'],
detail['pluginID'],
detail['pluginName'],
detail['cvss_base_score'] || 'none',
detail['exploit_available'] || '.',
detail['cve'] || '.',
detail['risk_factor'] || '.',
detail['cvss_vector'] || '.'
]
}
print_good("Port Info")
print_good "\n"
print_line tbl.to_s
end
def cmd_nessus_scan_pause_all(*args)
if args[0] == "-h"
print_status("Usage: ")
print_status(" nessus_scan_pause_all")
print_status(" Example:> nessus_scan_pause_all")
print_status()
print_status("Pauses all currently running scans")
print_status("use nessus_scan_list to list all running scans")
return
end
if ! nessus_verify_token
return
end
pause = @n.scan_pause_all
print_status("All scans have been paused")
end
def cmd_nessus_scan_stop(*args)
if args[0] == "-h"
print_status("Usage: ")
print_status(" nessus_scan_stop <scan id>")
print_status(" Example:> nessus_scan_stop f0eabba3-4065-7d54-5763-f191e98eb0f7f9f33db7e75a06ca")
print_status()
print_status("Stops a currently running scans")
print_status("use nessus_scan_list to list all running scans")
return
end
if ! nessus_verify_token
return
end
case args.length
when 1
sid = args[0]
else
print_status("Usage: ")
print_status(" nessus_scan_stop <scan id>")
print_status(" use nessus_scan_status to list all available scans")
return
end
pause = @n.scan_stop(sid)
print_status("#{sid} has been stopped")
end
def cmd_nessus_scan_stop_all(*args)
if args[0] == "-h"
print_status("Usage: ")
print_status(" nessus_scan_stop_all")
print_status(" Example:> nessus_scan_stop_all")
print_status()
print_status("stops all currently running scans")
print_status("use nessus_scan_list to list all running scans")
return
end
if ! nessus_verify_token
return
end
pause = @n.scan_stop_all
print_status("All scans have been stopped")
end
def cmd_nessus_scan_resume_all(*args)
if args[0] == "-h"
print_status("Usage: ")
print_status(" nessus_scan_resume_all")
print_status(" Example:> nessus_scan_resume_all")
print_status()
print_status("resumes all currently running scans")
print_status("use nessus_scan_list to list all running scans")
return
end
if ! nessus_verify_token
return
end
pause = @n.scan_resume_all
print_status("All scans have been resumed")
end
def cmd_nessus_user_add(*args)
if args[0] == "-h"
print_status("Usage: ")
print_status(" nessus_user_add <username> <password>")
print_status(" Example:> nessus_user_add msf msf")
print_status()
print_status("Only adds non admin users. Must be an admin to add users.")
print_status("use nessus_user_list to list all users")
return
end
if ! nessus_verify_token
return
end
if ! @n.is_admin
print_error("Your Nessus user is not an admin")
return
end
case args.length
when 2
user = args[0]
pass = args[1]
else
print_status("Usage: ")
print_status(" nessus_user_add <username> <password>")
print_status(" Only adds non admin users")
return
end
u = @n.users_list
u.each { |stuff|
if stuff['name'] == user
print_error("That user exists")
return
end
}
add = @n.user_add(user,pass)
status = add.root.elements['status'].text if add
if status == "OK"
print_good("#{user} has been added")
else
print_error("#{user} was not added")
end
end
def cmd_nessus_user_del(*args)
if args[0] == "-h"
print_status("Usage: ")
print_status(" nessus_user_del <username>")
print_status(" Example:> nessus_user_del msf")
print_status()
print_status("Only dels non admin users. Must be an admin to del users.")
print_status("use nessus_user_list to list all users")
return
end
if ! nessus_verify_token
return
end
if ! @n.is_admin
print_error("Your Nessus user is not an admin")
return
end
case args.length
when 1
user = args[0]
else
print_status("Usage: ")
print_status(" nessus_user_del <username>")
print_status(" Only dels non admin users")
return
end
del = @n.user_del(user)
status = del.root.elements['status'].text
if status == "OK"
print_good("#{user} has been deleted")
else
print_error("#{user} was not deleted")
end
end
def cmd_nessus_user_passwd(*args)
if args[0] == "-h"
print_status("Usage: ")
print_status(" nessus_user_passwd <username> <password>")
print_status(" Example:> nessus_user_passwd msf newpassword")
print_status()
print_status("Changes the password of a user. Must be an admin to change passwords.")
print_status("use nessus_user_list to list all users")
return
end
if ! nessus_verify_token
return
end
if ! @n.is_admin
print_error("Your Nessus user is not an admin")
return
end
case args.length
when 2
user = args[0]
pass = args[1]
else
print_status("Usage: ")
print_status(" nessus_user_passwd <username> <password>")
print_status(" User list from nessus_user_list")
return
end
pass = @n.user_pass(user,pass)
status = pass.root.elements['status'].text
if status == "OK"
print_good("#{user}'s password has been changed")
else
print_error("#{user}'s password has not been changed")
end
end
def cmd_nessus_admin(*args)
if args[0] == "-h"
print_status("Usage: ")
print_status(" nessus_admin")
print_status(" Example:> nessus_admin")
print_status()
print_status("Checks to see if the current user is an admin")
print_status("use nessus_user_list to list all users")
return
end
if ! nessus_verify_token
return
end
if ! @n.is_admin
print_error("Your Nessus user is not an admin")
else
print_good("Your Nessus user is an admin")
end
end
def cmd_nessus_plugin_family(*args)
if args[0] == "-h"
print_status("Usage: ")
print_status(" nessus_plugin_family <plugin family name>")
print_status(" Example:> nessus_plugin_family \"Windows : Microsoft Bulletins\" ")
print_status()
print_status("Returns a list of all plugins in that family.")
print_status("use nessus_plugin_list to list all plugins")
return
end
if ! nessus_verify_token
return
end
case args.length
when 1
fam = args[0]
else
print_status("Usage: ")
print_status(" nessus_plugin_family <plugin family name>")
print_status(" list all plugins from a Family from nessus_plugin_list")
return
end
tbl = Rex::Ui::Text::Table.new(
'Columns' => [
'Plugin ID',
'Plugin Name',
'Plugin File Name'
])
family = @n.plugin_family(fam)
family.each {|plugin|
tbl << [ plugin['id'], plugin['name'], plugin['filename'] ]
}
print_good("#{fam} Info")
print_good "\n"
print_line tbl.to_s
end
def cmd_nessus_policy_list(*args)
if args[0] == "-h"
print_status("Usage: ")
print_status(" nessus_policy_list")
print_status(" Example:> nessus_policy_list")
print_status()
print_status("Lists all policies on the server")
return
end
if ! nessus_verify_token
return
end
tbl = Rex::Ui::Text::Table.new(
'Columns' => [
'ID',
'Name',
'Comments'
])
list=@n.policy_list_hash
list.each {|policy|
tbl << [ policy['id'], policy['name'], policy['comments'] ]
}
print_good("Nessus Policy List")
print_good "\n"
print_line tbl.to_s
end
def cmd_nessus_policy_del(*args)
if args[0] == "-h"
print_status("Usage: ")
print_status(" nessus_policy_del <policy ID>")
print_status(" Example:> nessus_policy_del 1")
print_status()
print_status("Must be an admin to del policies.")
print_status("use nessus_policy_list to list all policies")
return
end
if ! nessus_verify_token
return
end
if ! @n.is_admin
print_error("Your Nessus user is not an admin")
return
end
case args.length
when 1
pid = args[0]
else
print_status("Usage: ")
print_status(" nessus_policy_del <policy ID>")
print_status(" nessus_policy_list to find the id.")
return
end
del = @n.policy_del(pid)
status = del.root.elements['status'].text
if status == "OK"
print_good("Policy number #{pid} has been deleted")
else
print_error("Policy number #{pid} was not deleted")
end
end
def cmd_nessus_plugin_details(*args)
if args[0] == "-h"
print_status("Usage: ")
print_status(" nessus_plugin_details <plugin file name>")
print_status(" Example:> nessus_plugin_details ping_host.nasl ")
print_status()
print_status("Returns details on a particular plugin.")
print_status("use nessus_plugin_list to list all plugins")
return
end
if ! nessus_verify_token
return
end
case args.length
when 1
pname = args[0]
else
print_status("Usage: ")
print_status(" nessus_policy_del <plugin file name>")
print_status(" nessus_plugin_list and then nessus_plugin_family to find the plugin file name.")
return
end
tbl = Rex::Ui::Text::Table.new(
'Columns' => [
'',
''
])
entry = @n.plugin_detail(pname)
print_good("Plugin Details for #{entry['name']}")
tbl << [ "Plugin ID", entry['id'] ]
tbl << [ "Plugin Family", entry['family'] ]
tbl << [ "CVSS Base Score", entry['cvss_base_score'] ]
tbl << [ "CVSS Vector", entry['cvss_vector'] ]
tbl << [ "CVSS Temporal Score", entry['cvss_temporal_score'] ]
tbl << [ "CVSS Temporal Vector", entry['cvss_temporal_vector'] ]
tbl << [ "Risk Factor", entry['risk_factor'] ]
tbl << [ "Exploit Available", entry['exploit_available'] ]
tbl << [ "Exploitability Ease", entry['exploit_ease'] ]
tbl << [ "Synopsis", entry['synopsis'] ]
tbl << [ "Description", entry['description'] ]
tbl << [ "Solution", entry['solution'] ]
tbl << [ "Plugin Pub Date", entry['plugin_publication_date'] ]
tbl << [ "Plugin Modification Date", entry['plugin_modification_date'] ]
print_good "\n"
print_line tbl.to_s
end
def cmd_nessus_report_del(*args)
if args[0] == "-h"
print_status("Usage: ")
print_status(" nessus_report_del <reportname>")
print_status(" Example:> nessus_report_del f0eabba3-4065-7d54-5763-f191e98eb0f7f9f33db7e75a06ca")
print_status()
print_status("Must be an admin to del reports.")
print_status("use nessus_report_list to list all reports")
return
end
if ! nessus_verify_token
return
end
if ! @n.is_admin
print_error("Your Nessus user is not an admin")
return
end
case args.length
when 1
rid = args[0]
else
print_status("Usage: ")
print_status(" nessus_report_del <report ID>")
print_status(" nessus_report_list to find the id.")
return
end
del = @n.report_del(rid)
status = del.root.elements['status'].text
if status == "OK"
print_good("Report #{rid} has been deleted")
else
print_error("Report #{rid} was not deleted")
end
end
def cmd_nessus_server_prefs(*args)
if args[0] == "-h"
print_status("Usage: ")
print_status(" nessus_server_prefs")
print_status(" Example:> nessus_server_prefs")
print_status()
print_status("Returns a long list of server prefs.")
return
end
if ! nessus_verify_token
return
end
if ! @n.is_admin
print_error("Your Nessus user is not an admin")
return
end
tbl = Rex::Ui::Text::Table.new(
'Columns' => [
'Name',
'Value'
])
prefs = @n.server_prefs
prefs.each {|pref|
tbl << [ pref['name'], pref['value'] ]
}
print_good("Nessus Server Pref List")
print_good "\n"
print_line tbl.to_s + "\n"
end
def cmd_nessus_plugin_prefs(*args)
if args[0] == "-h"
print_status("Usage: ")
print_status(" nessus_plugin_prefs")
print_status(" Example:> nessus_plugin_prefs")
print_status()
print_status("Returns a long list of plugin prefs.")
return
end
if ! nessus_verify_token
return
end
if ! @n.is_admin
print_error("Your Nessus user is not an admin")
return
end
tbl = Rex::Ui::Text::Table.new(
'Columns' => [
'Name',
'Value',
'Type'
])
prefs = @n.plugin_prefs
prefs.each {|pref|
tbl << [ pref['prefname'], pref['prefvalues'], pref['preftype'] ]
}
print_good("Nessus Plugins Pref List")
print_good "\n"
print_line tbl.to_s
end
end
def initialize(framework, opts)
super
add_console_dispatcher(ConsoleCommandDispatcher)
@nbver = "1.1" # Nessus Plugin Version. Increments each time we commit to msf
@xindex = "#{Msf::Config.get_config_root}/nessus_index" # location of the exploit index file used to speed up searching for valid exploits.
@nessus_yaml = "#{Msf::Config.get_config_root}/nessus.yaml" #location of the nessus.yml containing saved nessus creds
print_status("Nessus Bridge for Metasploit #{@nbver}")
print_good("Type %bldnessus_help%clr for a command listing")
#nessus_index
end
def cleanup
remove_console_dispatcher('Nessus')
end
def name
"nessus"
end
def desc
"Nessus Bridge for Metasploit #{@nbver}"
end
protected
end
end
+654 -654
View File
@@ -10,661 +10,661 @@
require 'rapid7/nexpose'
module Msf
Nexpose_yaml = "#{Msf::Config.get_config_root}/nexpose.yaml" #location of the nexpose.yml containing saved nexpose creds
Nexpose_yaml = "#{Msf::Config.get_config_root}/nexpose.yaml" #location of the nexpose.yml containing saved nexpose creds
class Plugin::Nexpose < Msf::Plugin
class NexposeCommandDispatcher
include Msf::Ui::Console::CommandDispatcher
def name
"Nexpose"
end
def commands
{
'nexpose_connect' => "Connect to a running Nexpose instance ( user:pass@host[:port] )",
'nexpose_save' => "Save credentials to a Nexpose instance",
'nexpose_activity' => "Display any active scan jobs on the Nexpose instance",
'nexpose_scan' => "Launch a Nexpose scan against a specific IP range and import the results",
'nexpose_discover' => "Launch a scan but only perform host and minimal service discovery",
'nexpose_exhaustive' => "Launch a scan covering all TCP ports and all authorized safe checks",
'nexpose_dos' => "Launch a scan that includes checks that can crash services and devices (caution)",
'nexpose_disconnect' => "Disconnect from an active Nexpose instance",
'nexpose_sites' => "List all defined sites",
'nexpose_site_devices' => "List all discovered devices within a site",
'nexpose_site_import' => "Import data from the specified site ID",
'nexpose_report_templates' => "List all available report templates",
'nexpose_command' => "Execute a console command on the Nexpose instance",
'nexpose_sysinfo' => "Display detailed system information about the Nexpose instance",
# TODO:
# nexpose_stop_scan
}
end
def nexpose_verify_db
if ! (framework.db and framework.db.usable and framework.db.active)
print_error("No database has been configured, please use db_create/db_connect first")
return false
end
true
end
def nexpose_verify
return false if not nexpose_verify_db
if ! @nsc
print_error("No active Nexpose instance has been configured, please use 'nexpose_connect'")
return false
end
true
end
def cmd_nexpose_save(*args)
#if we are logged in, save session details to nexpose.yaml
if args[0] == "-h"
print_status("Usage: ")
print_status(" nexpose_save")
return
end
if args[0]
print_status("Usage: ")
print_status(" nexpose_save")
return
end
group = "default"
if ((@user and @user.length > 0) and (@host and @host.length > 0) and (@port and @port.length > 0 and @port.to_i > 0) and (@pass and @pass.length > 0))
config = {"#{group}" => {'username' => @user, 'password' => @pass, 'server' => @host, 'port' => @port}}
::File.open("#{Nexpose_yaml}", "wb") { |f| f.puts YAML.dump(config) }
print_good("#{Nexpose_yaml} created.")
else
print_error("Missing username/password/server/port - relogin and then try again.")
return
end
end
def cmd_nexpose_connect(*args)
return if not nexpose_verify_db
if ! args[0]
if ::File.readable?("#{Nexpose_yaml}")
lconfig = YAML.load_file("#{Nexpose_yaml}")
@user = lconfig['default']['username']
@pass = lconfig['default']['password']
@host = lconfig['default']['server']
@port = lconfig['default']['port']
@sslv = "ok" # TODO: Not super-thrilled about bypassing the SSL warning...
nexpose_login
return
end
end
if(args.length == 0 or args[0].empty? or args[0] == "-h")
print_status("Usage: ")
print_status(" nexpose_connect username:password@host[:port] <ssl-confirm>")
print_status(" -OR- ")
print_status(" nexpose_connect username password host port <ssl-confirm>")
return
end
@user = @pass = @host = @port = @sslv = nil
case args.length
when 1,2
cred,targ = args[0].split('@', 2)
@user,@pass = cred.split(':', 2)
targ ||= '127.0.0.1:3780'
@host,@port = targ.split(':', 2)
port ||= '3780'
@sslv = args[1]
when 4,5
@user,@pass,@host,@port,@sslv = args
else
print_status("Usage: ")
print_status(" nexpose_connect username:password@host[:port] <ssl-confirm>")
print_status(" -OR- ")
print_status(" nexpose_connect username password host port <ssl-confirm>")
return
end
nexpose_login
end
def nexpose_login
if ! ((@user and @user.length > 0) and (@host and @host.length > 0) and (@port and @port.length > 0 and @port.to_i > 0) and (@pass and @pass.length > 0))
print_status("Usage: ")
print_status(" nexpose_connect username:password@host[:port] <ssl-confirm>")
print_status(" -OR- ")
print_status(" nexpose_connect username password host port <ssl-confirm>")
return
end
if(@host != "localhost" and @host != "127.0.0.1" and @sslv != "ok")
print_error("Warning: SSL connections are not verified in this release, it is possible for an attacker")
print_error(" with the ability to man-in-the-middle the Nexpose traffic to capture the Nexpose")
print_error(" credentials. If you are running this on a trusted network, please pass in 'ok'")
print_error(" as an additional parameter to this command.")
return
end
# Wrap this so a duplicate session doesnt prevent a new login
begin
cmd_nexpose_disconnect
rescue ::Interrupt
raise $!
rescue ::Exception
end
begin
print_status("Connecting to Nexpose instance at #{@host}:#{@port} with username #{@user}...")
nsc = ::Nexpose::Connection.new(@host, @user, @pass, @port)
nsc.login
rescue ::Nexpose::APIError => e
print_error("Connection failed: #{e.reason}")
return
end
@nsc = nsc
nexpose_compatibility_check
nsc
end
def cmd_nexpose_activity(*args)
return if not nexpose_verify
scans = @nsc.scan_activity || []
case scans.length
when 0
print_status("There are currently no active scan jobs on this Nexpose instance")
when 1
print_status("There is 1 active scan job on this Nexpose instance")
else
print_status("There are currently #{scans.length} active scan jobs on this Nexpose instance")
end
scans.each do |scan|
print_status(" Scan ##{scan[:scan_id]} is running on Engine ##{scan[:engine_id]} against site ##{scan[:site_id]} since #{scan[:start_time].to_s}")
end
end
def cmd_nexpose_sites(*args)
return if not nexpose_verify
sites = @nsc.site_listing || []
case sites.length
when 0
print_status("There are currently no active sites on this Nexpose instance")
end
sites.each do |site|
print_status(" Site ##{site[:site_id]} '#{site[:name]}' Risk Factor: #{site[:risk_factor]} Risk Score: #{site[:risk_score]}")
end
end
def cmd_nexpose_site_devices(*args)
return if not nexpose_verify
site_id = args.shift
if not site_id
print_error("No site ID was specified")
return
end
devices = @nsc.site_device_listing(site_id) || []
case devices.length
when 0
print_status("There are currently no devices within this site")
end
devices.each do |device|
print_status(" Host: #{device[:address]} ID: #{device[:device_id]} Risk Factor: #{device[:risk_factor]} Risk Score: #{device[:risk_score]}")
end
end
def cmd_nexpose_report_templates(*args)
return if not nexpose_verify
res = @nsc.report_template_listing || []
res.each do |report|
print_status(" Template: #{report[:template_id]} Name: '#{report[:name]}' Description: #{report[:description]}")
end
end
def cmd_nexpose_command(*args)
return if not nexpose_verify
if args.length == 0
print_error("No command was specified")
return
end
res = @nsc.console_command(args.join(" ")) || ""
print_status("Command Output")
print_line(res)
print_line("")
end
def cmd_nexpose_sysinfo(*args)
return if not nexpose_verify
res = @nsc.system_information
print_status("System Information")
res.each_pair do |k,v|
print_status(" #{k}: #{v}")
end
end
def nexpose_compatibility_check
res = @nsc.console_command("ver")
if res !~ /^(NSC|Console) Version ID:\s*4[89]0\s*$/m
print_error("")
print_error("Warning: This version of Nexpose has not been tested with Metasploit!")
print_error("")
end
end
def cmd_nexpose_site_import(*args)
site_id = args.shift
if not site_id
print_error("No site ID was specified")
return
end
msfid = Time.now.to_i
report_formats = ["raw-xml-v2", "ns-xml"]
report_format = report_formats.shift
report = Nexpose::ReportConfig.new(@nsc)
report.set_name("Metasploit Export #{msfid}")
report.set_template_id("pentest-audit")
report.addFilter("SiteFilter", site_id)
report.set_generate_after_scan(0)
report.set_storeOnServer(1)
begin
report.set_format(report_format)
report.saveReport()
rescue ::Exception => e
report_format = report_formats.shift
if report_format
retry
end
raise e
end
print_status("Generating the export data file...")
url = nil
while(! url)
url = @nsc.report_last(report.config_id)
select(nil, nil, nil, 1.0)
end
print_status("Downloading the export data...")
data = @nsc.download(url)
# Delete the temporary report ID
@nsc.report_config_delete(report.config_id)
print_status("Importing Nexpose data...")
process_nexpose_data(report_format, data)
end
def cmd_nexpose_discover(*args)
args << "-h" if args.length == 0
args << "-t"
args << "aggressive-discovery"
cmd_nexpose_scan(*args)
end
def cmd_nexpose_exhaustive(*args)
args << "-h" if args.length == 0
args << "-t"
args << "exhaustive-audit"
cmd_nexpose_scan(*args)
end
def cmd_nexpose_dos(*args)
args << "-h" if args.length == 0
args << "-t"
args << "dos-audit"
cmd_nexpose_scan(*args)
end
def cmd_nexpose_scan(*args)
opts = Rex::Parser::Arguments.new(
"-h" => [ false, "This help menu"],
"-t" => [ true, "The scan template to use (default:pentest-audit options:full-audit,exhaustive-audit,discovery,aggressive-discovery,dos-audit)"],
"-c" => [ true, "Specify credentials to use against these targets (format is type:user:pass"],
"-n" => [ true, "The maximum number of IPs to scan at a time (default is 32)"],
"-s" => [ true, "The directory to store the raw XML files from the Nexpose instance (optional)"],
"-P" => [ false, "Leave the scan data on the server when it completes (this counts against the maximum licensed IPs)"],
"-v" => [ false, "Display diagnostic information about the scanning process"],
"-d" => [ false, "Scan hosts based on the contents of the existing database"],
"-I" => [ true, "Only scan systems with an address within the specified range"],
"-E" => [ true, "Exclude hosts in the specified range from the scan"]
)
opt_template = "pentest-audit"
opt_maxaddrs = 32
opt_monitor = false
opt_verbose = false
opt_savexml = nil
opt_preserve = false
opt_rescandb = false
opt_addrinc = nil
opt_addrexc = nil
opt_scanned = []
opt_credentials = []
opt_ranges = []
opts.parse(args) do |opt, idx, val|
case opt
when "-h"
print_line("Usage: nexpose_scan [options] <Target IP Ranges>")
print_line(opts.usage)
return
when "-t"
opt_template = val
when "-n"
opt_maxaddrs = val.to_i
when "-s"
opt_savexml = val
when "-c"
if (val =~ /^([^:]+):([^:]+):(.+)/)
type, user, pass = [ $1, $2, $3 ]
newcreds = Nexpose::AdminCredentials.new
newcreds.setCredentials(type, nil, nil, user, pass, nil)
opt_credentials << newcreds
else
print_error("Unrecognized Nexpose scan credentials: #{val}")
return
end
when "-v"
opt_verbose = true
when "-P"
opt_preserve = true
when "-d"
opt_rescandb = true
when '-I'
opt_addrinc = OptAddressRange.new('TEMPRANGE', [ true, '' ]).normalize(val)
when '-E'
opt_addrexc = OptAddressRange.new('TEMPRANGE', [ true, '' ]).normalize(val)
else
opt_ranges << val
end
end
return if not nexpose_verify
# Include all database hosts as scan targets if specified
if(opt_rescandb)
print_status("Loading scan targets from the active database...") if opt_verbose
framework.db.hosts.each do |host|
next if host.state != ::Msf::HostState::Alive
opt_ranges << host.address
end
end
possible_files = opt_ranges # don't allow DOS by circular reference
possible_files.each do |file|
if ::File.readable? file
print_status "Parsing ranges from #{file}"
range_list = ::File.open(file,"rb") {|f| f.read f.stat.size}
range_list.each_line { |subrange| opt_ranges << subrange}
opt_ranges.delete(file)
end
end
opt_ranges = opt_ranges.join(' ')
if(opt_ranges.strip.empty?)
print_line("Usage: nexpose_scan [options] <Target IP Ranges>")
print_line(opts.usage)
return
end
if(opt_verbose)
print_status("Creating a new scan using template #{opt_template} and #{opt_maxaddrs} concurrent IPs against #{opt_ranges}")
end
range_inp = ::Msf::OptAddressRange.new('TEMPRANGE', [ true, '' ]).normalize(opt_ranges)
range = ::Rex::Socket::RangeWalker.new(range_inp)
include_range = opt_addrinc ? ::Rex::Socket::RangeWalker.new(opt_addrinc) : nil
exclude_range = opt_addrexc ? ::Rex::Socket::RangeWalker.new(opt_addrexc) : nil
completed = 0
total = range.num_ips
count = 0
print_status("Scanning #{total} addresses with template #{opt_template} in sets of #{opt_maxaddrs}")
while(completed < total)
count += 1
queue = []
while(ip = range.next_ip and queue.length < opt_maxaddrs)
if(exclude_range and exclude_range.include?(ip))
print_status(" >> Skipping host #{ip} due to exclusion") if opt_verbose
next
end
if(include_range and ! include_range.include?(ip))
print_status(" >> Skipping host #{ip} due to inclusion filter") if opt_verbose
next
end
opt_scanned << ip
queue << ip
end
break if queue.empty?
print_status("Scanning #{queue[0]}-#{queue[-1]}...") if opt_verbose
msfid = Time.now.to_i
# Create a temporary site
site = Nexpose::Site.new(@nsc)
site.setSiteConfig("Metasploit-#{msfid}", "Autocreated by the Metasploit Framework")
queue.each do |ip|
site.site_config.addHost(Nexpose::IPRange.new(ip))
end
site.site_config._set_scanConfig(Nexpose::ScanConfig.new(-1, "tmp", opt_template))
opt_credentials.each do |c|
site.site_config.addCredentials(c)
end
site.saveSite()
print_status(" >> Created temporary site ##{site.site_id}") if opt_verbose
report_formats = ["raw-xml-v2", "ns-xml"]
report_format = report_formats.shift
report = Nexpose::ReportConfig.new(@nsc)
report.set_name("Metasploit Export #{msfid}")
report.set_template_id(opt_template)
report.addFilter("SiteFilter", site.site_id)
report.set_generate_after_scan(1)
report.set_storeOnServer(1)
begin
report.set_format(report_format)
report.saveReport()
rescue ::Exception => e
report_format = report_formats.shift
if report_format
retry
end
raise e
end
print_status(" >> Created temporary report configuration ##{report.config_id}") if opt_verbose
# Run the scan
res = site.scanSite()
sid = res[:scan_id]
print_status(" >> Scan has been launched with ID ##{sid}") if opt_verbose
rep = true
begin
prev = nil
while(true)
info = @nsc.scan_statistics(sid)
break if info[:summary]['status'] != "running"
stat = "Found #{info[:nodes]['live']} devices and #{info[:nodes]['dead']} unresponsive"
if(stat != prev)
print_status(" >> #{stat}") if opt_verbose
end
prev = stat
select(nil, nil, nil, 5.0)
end
print_status(" >> Scan has been completed with ID ##{sid}") if opt_verbose
rescue ::Interrupt
rep = false
print_status(" >> Terminating scan ID ##{sid} due to console interupt") if opt_verbose
@nsc.scan_stop(sid)
break
end
# Wait for the automatic report generation to complete
if(rep)
print_status(" >> Waiting on the report to generate...") if opt_verbose
url = nil
while(! url)
url = @nsc.report_last(report.config_id)
select(nil, nil, nil, 1.0)
end
print_status(" >> Downloading the report data from Nexpose...") if opt_verbose
data = @nsc.download(url)
if(opt_savexml)
::FileUtils.mkdir_p(opt_savexml)
path = ::File.join(opt_savexml, "nexpose-#{msfid}-#{count}.xml")
print_status(" >> Saving scan data into #{path}") if opt_verbose
::File.open(path, "wb") { |fd| fd.write(data) }
end
process_nexpose_data(report_format, data)
end
if ! opt_preserve
print_status(" >> Deleting the temporary site and report...") if opt_verbose
@nsc.site_delete(site.site_id)
end
end
print_status("Completed the scan of #{total} addresses")
end
def cmd_nexpose_disconnect(*args)
@nsc.logout if @nsc
@nsc = nil
end
def process_nexpose_data(fmt, data)
case fmt
when 'raw-xml-v2'
framework.db.import({:data => data})
when 'ns-xml'
framework.db.import({:data => data})
else
print_error("Unsupported Nexpose data format: #{fmt}")
end
end
#
# Nexpose vuln lookup
#
def nexpose_vuln_lookup(doc, vid, refs, host, serv=nil)
doc.elements.each("/NexposeReport/VulnerabilityDefinitions/vulnerability[@id = '#{vid}']]") do |vulndef|
title = vulndef.attributes['title']
pciSeverity = vulndef.attributes['pciSeverity']
cvss_score = vulndef.attributes['cvssScore']
cvss_vector = vulndef.attributes['cvssVector']
vulndef.elements['references'].elements.each('reference') do |ref|
if ref.attributes['source'] == 'BID'
refs[ 'BID-' + ref.text ] = true
elsif ref.attributes['source'] == 'CVE'
# ref.text is CVE-$ID
refs[ ref.text ] = true
elsif ref.attributes['source'] == 'MS'
refs[ 'MSB-MS-' + ref.text ] = true
end
end
refs[ 'NEXPOSE-' + vid.downcase ] = true
vuln = framework.db.find_or_create_vuln(
:host => host,
:service => serv,
:name => 'NEXPOSE-' + vid.downcase,
:data => title)
rids = []
refs.keys.each do |r|
rids << framework.db.find_or_create_ref(:name => r)
end
vuln.refs << (rids - vuln.refs)
end
end
end
#
# Plugin initialization
#
def initialize(framework, opts)
super
add_console_dispatcher(NexposeCommandDispatcher)
banner = ["0a205f5f5f5f202020202020202020202020205f20202020205f205f5f5f5f5f2020205f2020205f20202020205f5f20205f5f2020202020202020202020202020202020202020200a7c20205f205c205f5f205f205f205f5f20285f29205f5f7c207c5f5f5f20207c207c205c207c207c205f5f5f5c205c2f202f5f205f5f2020205f5f5f20205f5f5f20205f5f5f200a7c207c5f29202f205f60207c20275f205c7c207c2f205f60207c20202f202f20207c20205c7c207c2f205f205c5c20202f7c20275f205c202f205f205c2f205f5f7c2f205f205c0a7c20205f203c20285f7c207c207c5f29207c207c20285f7c207c202f202f2020207c207c5c20207c20205f5f2f2f20205c7c207c5f29207c20285f29205c5f5f205c20205f5f2f0a7c5f7c205c5f5c5f5f2c5f7c202e5f5f2f7c5f7c5c5f5f2c5f7c2f5f2f202020207c5f7c205c5f7c5c5f5f5f2f5f2f5c5f5c202e5f5f2f205c5f5f5f2f7c5f5f5f2f5c5f5f5f7c0a20202020202020202020207c5f7c20202020202020202020202020202020202020202020202020202020202020202020207c5f7c202020202020202020202020202020202020200a0a0a"].pack("H*")
# Do not use this UTF-8 encoded high-ascii art for non-UTF-8 or windows consoles
lang = Rex::Compat.getenv("LANG")
if (lang and lang =~ /UTF-8/)
# Cygwin/Windows should not be reporting UTF-8 either...
# (! (Rex::Compat.is_windows or Rex::Compat.is_cygwin))
banner = ["202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020200a20e29684e29684e29684202020e29684e29684202020202020202020202020e29684e29684e296842020e29684e29684e2968420202020202020202020202020202020202020202020202020202020202020202020202020202020200a20e29688e29688e29688202020e29688e2968820202020202020202020202020e29688e2968820e29684e29688e296882020202020202020202020202020202020202020202020202020202020202020202020202020202020200a20e29688e29688e29680e296882020e29688e29688202020e29684e29688e29688e29688e29688e296842020202020e29688e29688e29688e2968820202020e29688e29688e29684e29688e29688e29688e2968420202020e29684e29688e29688e29688e29688e29684202020e29684e29684e29688e29688e29688e29688e29688e29684202020e29684e29688e29688e29688e29688e2968420200a20e29688e2968820e29688e2968820e29688e296882020e29688e29688e29684e29684e29684e29684e29688e296882020202020e29688e296882020202020e29688e29688e296802020e29680e29688e296882020e29688e29688e296802020e29680e29688e296882020e29688e29688e29684e29684e29684e2968420e296802020e29688e29688e29684e29684e29684e29684e29688e29688200a20e29688e296882020e29688e29684e29688e296882020e29688e29688e29680e29680e29680e29680e29680e2968020202020e29688e29688e29688e2968820202020e29688e2968820202020e29688e296882020e29688e2968820202020e29688e29688202020e29680e29680e29680e29680e29688e29688e296842020e29688e29688e29680e29680e29680e29680e29680e29680200a20e29688e29688202020e29688e29688e296882020e29680e29688e29688e29684e29684e29684e29684e29688202020e29688e296882020e29688e29688202020e29688e29688e29688e29684e29684e29688e29688e296802020e29680e29688e29688e29684e29684e29688e29688e296802020e29688e29684e29684e29684e29684e29684e29688e296882020e29680e29688e29688e29684e29684e29684e29684e29688200a20e29680e29680202020e29680e29680e2968020202020e29680e29680e29680e29680e29680202020e29680e29680e296802020e29680e29680e296802020e29688e2968820e29680e29680e29680202020202020e29680e29680e29680e296802020202020e29680e29680e29680e29680e29680e296802020202020e29680e29680e29680e29680e2968020200a20202020202020202020202020202020202020202020202020202020202020e29688e29688202020202020202020202020202020202020202020202020202020202020202020202020200a202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020200a"].pack("H*")
end
print(banner)
print_status("Nexpose integration has been activated")
end
def cleanup
remove_console_dispatcher('Nexpose')
end
def name
"nexpose"
end
def desc
"Integrates with the Rapid7 Nexpose vulnerability management product"
end
class NexposeCommandDispatcher
include Msf::Ui::Console::CommandDispatcher
def name
"Nexpose"
end
def commands
{
'nexpose_connect' => "Connect to a running Nexpose instance ( user:pass@host[:port] )",
'nexpose_save' => "Save credentials to a Nexpose instance",
'nexpose_activity' => "Display any active scan jobs on the Nexpose instance",
'nexpose_scan' => "Launch a Nexpose scan against a specific IP range and import the results",
'nexpose_discover' => "Launch a scan but only perform host and minimal service discovery",
'nexpose_exhaustive' => "Launch a scan covering all TCP ports and all authorized safe checks",
'nexpose_dos' => "Launch a scan that includes checks that can crash services and devices (caution)",
'nexpose_disconnect' => "Disconnect from an active Nexpose instance",
'nexpose_sites' => "List all defined sites",
'nexpose_site_devices' => "List all discovered devices within a site",
'nexpose_site_import' => "Import data from the specified site ID",
'nexpose_report_templates' => "List all available report templates",
'nexpose_command' => "Execute a console command on the Nexpose instance",
'nexpose_sysinfo' => "Display detailed system information about the Nexpose instance",
# TODO:
# nexpose_stop_scan
}
end
def nexpose_verify_db
if ! (framework.db and framework.db.usable and framework.db.active)
print_error("No database has been configured, please use db_create/db_connect first")
return false
end
true
end
def nexpose_verify
return false if not nexpose_verify_db
if ! @nsc
print_error("No active Nexpose instance has been configured, please use 'nexpose_connect'")
return false
end
true
end
def cmd_nexpose_save(*args)
#if we are logged in, save session details to nexpose.yaml
if args[0] == "-h"
print_status("Usage: ")
print_status(" nexpose_save")
return
end
if args[0]
print_status("Usage: ")
print_status(" nexpose_save")
return
end
group = "default"
if ((@user and @user.length > 0) and (@host and @host.length > 0) and (@port and @port.length > 0 and @port.to_i > 0) and (@pass and @pass.length > 0))
config = {"#{group}" => {'username' => @user, 'password' => @pass, 'server' => @host, 'port' => @port}}
::File.open("#{Nexpose_yaml}", "wb") { |f| f.puts YAML.dump(config) }
print_good("#{Nexpose_yaml} created.")
else
print_error("Missing username/password/server/port - relogin and then try again.")
return
end
end
def cmd_nexpose_connect(*args)
return if not nexpose_verify_db
if ! args[0]
if ::File.readable?("#{Nexpose_yaml}")
lconfig = YAML.load_file("#{Nexpose_yaml}")
@user = lconfig['default']['username']
@pass = lconfig['default']['password']
@host = lconfig['default']['server']
@port = lconfig['default']['port']
@sslv = "ok" # TODO: Not super-thrilled about bypassing the SSL warning...
nexpose_login
return
end
end
if(args.length == 0 or args[0].empty? or args[0] == "-h")
print_status("Usage: ")
print_status(" nexpose_connect username:password@host[:port] <ssl-confirm>")
print_status(" -OR- ")
print_status(" nexpose_connect username password host port <ssl-confirm>")
return
end
@user = @pass = @host = @port = @sslv = nil
case args.length
when 1,2
cred,targ = args[0].split('@', 2)
@user,@pass = cred.split(':', 2)
targ ||= '127.0.0.1:3780'
@host,@port = targ.split(':', 2)
port ||= '3780'
@sslv = args[1]
when 4,5
@user,@pass,@host,@port,@sslv = args
else
print_status("Usage: ")
print_status(" nexpose_connect username:password@host[:port] <ssl-confirm>")
print_status(" -OR- ")
print_status(" nexpose_connect username password host port <ssl-confirm>")
return
end
nexpose_login
end
def nexpose_login
if ! ((@user and @user.length > 0) and (@host and @host.length > 0) and (@port and @port.length > 0 and @port.to_i > 0) and (@pass and @pass.length > 0))
print_status("Usage: ")
print_status(" nexpose_connect username:password@host[:port] <ssl-confirm>")
print_status(" -OR- ")
print_status(" nexpose_connect username password host port <ssl-confirm>")
return
end
if(@host != "localhost" and @host != "127.0.0.1" and @sslv != "ok")
print_error("Warning: SSL connections are not verified in this release, it is possible for an attacker")
print_error(" with the ability to man-in-the-middle the Nexpose traffic to capture the Nexpose")
print_error(" credentials. If you are running this on a trusted network, please pass in 'ok'")
print_error(" as an additional parameter to this command.")
return
end
# Wrap this so a duplicate session doesnt prevent a new login
begin
cmd_nexpose_disconnect
rescue ::Interrupt
raise $!
rescue ::Exception
end
begin
print_status("Connecting to Nexpose instance at #{@host}:#{@port} with username #{@user}...")
nsc = ::Nexpose::Connection.new(@host, @user, @pass, @port)
nsc.login
rescue ::Nexpose::APIError => e
print_error("Connection failed: #{e.reason}")
return
end
@nsc = nsc
nexpose_compatibility_check
nsc
end
def cmd_nexpose_activity(*args)
return if not nexpose_verify
scans = @nsc.scan_activity || []
case scans.length
when 0
print_status("There are currently no active scan jobs on this Nexpose instance")
when 1
print_status("There is 1 active scan job on this Nexpose instance")
else
print_status("There are currently #{scans.length} active scan jobs on this Nexpose instance")
end
scans.each do |scan|
print_status(" Scan ##{scan[:scan_id]} is running on Engine ##{scan[:engine_id]} against site ##{scan[:site_id]} since #{scan[:start_time].to_s}")
end
end
def cmd_nexpose_sites(*args)
return if not nexpose_verify
sites = @nsc.site_listing || []
case sites.length
when 0
print_status("There are currently no active sites on this Nexpose instance")
end
sites.each do |site|
print_status(" Site ##{site[:site_id]} '#{site[:name]}' Risk Factor: #{site[:risk_factor]} Risk Score: #{site[:risk_score]}")
end
end
def cmd_nexpose_site_devices(*args)
return if not nexpose_verify
site_id = args.shift
if not site_id
print_error("No site ID was specified")
return
end
devices = @nsc.site_device_listing(site_id) || []
case devices.length
when 0
print_status("There are currently no devices within this site")
end
devices.each do |device|
print_status(" Host: #{device[:address]} ID: #{device[:device_id]} Risk Factor: #{device[:risk_factor]} Risk Score: #{device[:risk_score]}")
end
end
def cmd_nexpose_report_templates(*args)
return if not nexpose_verify
res = @nsc.report_template_listing || []
res.each do |report|
print_status(" Template: #{report[:template_id]} Name: '#{report[:name]}' Description: #{report[:description]}")
end
end
def cmd_nexpose_command(*args)
return if not nexpose_verify
if args.length == 0
print_error("No command was specified")
return
end
res = @nsc.console_command(args.join(" ")) || ""
print_status("Command Output")
print_line(res)
print_line("")
end
def cmd_nexpose_sysinfo(*args)
return if not nexpose_verify
res = @nsc.system_information
print_status("System Information")
res.each_pair do |k,v|
print_status(" #{k}: #{v}")
end
end
def nexpose_compatibility_check
res = @nsc.console_command("ver")
if res !~ /^(NSC|Console) Version ID:\s*4[89]0\s*$/m
print_error("")
print_error("Warning: This version of Nexpose has not been tested with Metasploit!")
print_error("")
end
end
def cmd_nexpose_site_import(*args)
site_id = args.shift
if not site_id
print_error("No site ID was specified")
return
end
msfid = Time.now.to_i
report_formats = ["raw-xml-v2", "ns-xml"]
report_format = report_formats.shift
report = Nexpose::ReportConfig.new(@nsc)
report.set_name("Metasploit Export #{msfid}")
report.set_template_id("pentest-audit")
report.addFilter("SiteFilter", site_id)
report.set_generate_after_scan(0)
report.set_storeOnServer(1)
begin
report.set_format(report_format)
report.saveReport()
rescue ::Exception => e
report_format = report_formats.shift
if report_format
retry
end
raise e
end
print_status("Generating the export data file...")
url = nil
while(! url)
url = @nsc.report_last(report.config_id)
select(nil, nil, nil, 1.0)
end
print_status("Downloading the export data...")
data = @nsc.download(url)
# Delete the temporary report ID
@nsc.report_config_delete(report.config_id)
print_status("Importing Nexpose data...")
process_nexpose_data(report_format, data)
end
def cmd_nexpose_discover(*args)
args << "-h" if args.length == 0
args << "-t"
args << "aggressive-discovery"
cmd_nexpose_scan(*args)
end
def cmd_nexpose_exhaustive(*args)
args << "-h" if args.length == 0
args << "-t"
args << "exhaustive-audit"
cmd_nexpose_scan(*args)
end
def cmd_nexpose_dos(*args)
args << "-h" if args.length == 0
args << "-t"
args << "dos-audit"
cmd_nexpose_scan(*args)
end
def cmd_nexpose_scan(*args)
opts = Rex::Parser::Arguments.new(
"-h" => [ false, "This help menu"],
"-t" => [ true, "The scan template to use (default:pentest-audit options:full-audit,exhaustive-audit,discovery,aggressive-discovery,dos-audit)"],
"-c" => [ true, "Specify credentials to use against these targets (format is type:user:pass"],
"-n" => [ true, "The maximum number of IPs to scan at a time (default is 32)"],
"-s" => [ true, "The directory to store the raw XML files from the Nexpose instance (optional)"],
"-P" => [ false, "Leave the scan data on the server when it completes (this counts against the maximum licensed IPs)"],
"-v" => [ false, "Display diagnostic information about the scanning process"],
"-d" => [ false, "Scan hosts based on the contents of the existing database"],
"-I" => [ true, "Only scan systems with an address within the specified range"],
"-E" => [ true, "Exclude hosts in the specified range from the scan"]
)
opt_template = "pentest-audit"
opt_maxaddrs = 32
opt_monitor = false
opt_verbose = false
opt_savexml = nil
opt_preserve = false
opt_rescandb = false
opt_addrinc = nil
opt_addrexc = nil
opt_scanned = []
opt_credentials = []
opt_ranges = []
opts.parse(args) do |opt, idx, val|
case opt
when "-h"
print_line("Usage: nexpose_scan [options] <Target IP Ranges>")
print_line(opts.usage)
return
when "-t"
opt_template = val
when "-n"
opt_maxaddrs = val.to_i
when "-s"
opt_savexml = val
when "-c"
if (val =~ /^([^:]+):([^:]+):(.+)/)
type, user, pass = [ $1, $2, $3 ]
newcreds = Nexpose::AdminCredentials.new
newcreds.setCredentials(type, nil, nil, user, pass, nil)
opt_credentials << newcreds
else
print_error("Unrecognized Nexpose scan credentials: #{val}")
return
end
when "-v"
opt_verbose = true
when "-P"
opt_preserve = true
when "-d"
opt_rescandb = true
when '-I'
opt_addrinc = OptAddressRange.new('TEMPRANGE', [ true, '' ]).normalize(val)
when '-E'
opt_addrexc = OptAddressRange.new('TEMPRANGE', [ true, '' ]).normalize(val)
else
opt_ranges << val
end
end
return if not nexpose_verify
# Include all database hosts as scan targets if specified
if(opt_rescandb)
print_status("Loading scan targets from the active database...") if opt_verbose
framework.db.hosts.each do |host|
next if host.state != ::Msf::HostState::Alive
opt_ranges << host.address
end
end
possible_files = opt_ranges # don't allow DOS by circular reference
possible_files.each do |file|
if ::File.readable? file
print_status "Parsing ranges from #{file}"
range_list = ::File.open(file,"rb") {|f| f.read f.stat.size}
range_list.each_line { |subrange| opt_ranges << subrange}
opt_ranges.delete(file)
end
end
opt_ranges = opt_ranges.join(' ')
if(opt_ranges.strip.empty?)
print_line("Usage: nexpose_scan [options] <Target IP Ranges>")
print_line(opts.usage)
return
end
if(opt_verbose)
print_status("Creating a new scan using template #{opt_template} and #{opt_maxaddrs} concurrent IPs against #{opt_ranges}")
end
range_inp = ::Msf::OptAddressRange.new('TEMPRANGE', [ true, '' ]).normalize(opt_ranges)
range = ::Rex::Socket::RangeWalker.new(range_inp)
include_range = opt_addrinc ? ::Rex::Socket::RangeWalker.new(opt_addrinc) : nil
exclude_range = opt_addrexc ? ::Rex::Socket::RangeWalker.new(opt_addrexc) : nil
completed = 0
total = range.num_ips
count = 0
print_status("Scanning #{total} addresses with template #{opt_template} in sets of #{opt_maxaddrs}")
while(completed < total)
count += 1
queue = []
while(ip = range.next_ip and queue.length < opt_maxaddrs)
if(exclude_range and exclude_range.include?(ip))
print_status(" >> Skipping host #{ip} due to exclusion") if opt_verbose
next
end
if(include_range and ! include_range.include?(ip))
print_status(" >> Skipping host #{ip} due to inclusion filter") if opt_verbose
next
end
opt_scanned << ip
queue << ip
end
break if queue.empty?
print_status("Scanning #{queue[0]}-#{queue[-1]}...") if opt_verbose
msfid = Time.now.to_i
# Create a temporary site
site = Nexpose::Site.new(@nsc)
site.setSiteConfig("Metasploit-#{msfid}", "Autocreated by the Metasploit Framework")
queue.each do |ip|
site.site_config.addHost(Nexpose::IPRange.new(ip))
end
site.site_config._set_scanConfig(Nexpose::ScanConfig.new(-1, "tmp", opt_template))
opt_credentials.each do |c|
site.site_config.addCredentials(c)
end
site.saveSite()
print_status(" >> Created temporary site ##{site.site_id}") if opt_verbose
report_formats = ["raw-xml-v2", "ns-xml"]
report_format = report_formats.shift
report = Nexpose::ReportConfig.new(@nsc)
report.set_name("Metasploit Export #{msfid}")
report.set_template_id(opt_template)
report.addFilter("SiteFilter", site.site_id)
report.set_generate_after_scan(1)
report.set_storeOnServer(1)
begin
report.set_format(report_format)
report.saveReport()
rescue ::Exception => e
report_format = report_formats.shift
if report_format
retry
end
raise e
end
print_status(" >> Created temporary report configuration ##{report.config_id}") if opt_verbose
# Run the scan
res = site.scanSite()
sid = res[:scan_id]
print_status(" >> Scan has been launched with ID ##{sid}") if opt_verbose
rep = true
begin
prev = nil
while(true)
info = @nsc.scan_statistics(sid)
break if info[:summary]['status'] != "running"
stat = "Found #{info[:nodes]['live']} devices and #{info[:nodes]['dead']} unresponsive"
if(stat != prev)
print_status(" >> #{stat}") if opt_verbose
end
prev = stat
select(nil, nil, nil, 5.0)
end
print_status(" >> Scan has been completed with ID ##{sid}") if opt_verbose
rescue ::Interrupt
rep = false
print_status(" >> Terminating scan ID ##{sid} due to console interupt") if opt_verbose
@nsc.scan_stop(sid)
break
end
# Wait for the automatic report generation to complete
if(rep)
print_status(" >> Waiting on the report to generate...") if opt_verbose
url = nil
while(! url)
url = @nsc.report_last(report.config_id)
select(nil, nil, nil, 1.0)
end
print_status(" >> Downloading the report data from Nexpose...") if opt_verbose
data = @nsc.download(url)
if(opt_savexml)
::FileUtils.mkdir_p(opt_savexml)
path = ::File.join(opt_savexml, "nexpose-#{msfid}-#{count}.xml")
print_status(" >> Saving scan data into #{path}") if opt_verbose
::File.open(path, "wb") { |fd| fd.write(data) }
end
process_nexpose_data(report_format, data)
end
if ! opt_preserve
print_status(" >> Deleting the temporary site and report...") if opt_verbose
@nsc.site_delete(site.site_id)
end
end
print_status("Completed the scan of #{total} addresses")
end
def cmd_nexpose_disconnect(*args)
@nsc.logout if @nsc
@nsc = nil
end
def process_nexpose_data(fmt, data)
case fmt
when 'raw-xml-v2'
framework.db.import({:data => data})
when 'ns-xml'
framework.db.import({:data => data})
else
print_error("Unsupported Nexpose data format: #{fmt}")
end
end
#
# Nexpose vuln lookup
#
def nexpose_vuln_lookup(doc, vid, refs, host, serv=nil)
doc.elements.each("/NexposeReport/VulnerabilityDefinitions/vulnerability[@id = '#{vid}']]") do |vulndef|
title = vulndef.attributes['title']
pciSeverity = vulndef.attributes['pciSeverity']
cvss_score = vulndef.attributes['cvssScore']
cvss_vector = vulndef.attributes['cvssVector']
vulndef.elements['references'].elements.each('reference') do |ref|
if ref.attributes['source'] == 'BID'
refs[ 'BID-' + ref.text ] = true
elsif ref.attributes['source'] == 'CVE'
# ref.text is CVE-$ID
refs[ ref.text ] = true
elsif ref.attributes['source'] == 'MS'
refs[ 'MSB-MS-' + ref.text ] = true
end
end
refs[ 'NEXPOSE-' + vid.downcase ] = true
vuln = framework.db.find_or_create_vuln(
:host => host,
:service => serv,
:name => 'NEXPOSE-' + vid.downcase,
:data => title)
rids = []
refs.keys.each do |r|
rids << framework.db.find_or_create_ref(:name => r)
end
vuln.refs << (rids - vuln.refs)
end
end
end
#
# Plugin initialization
#
def initialize(framework, opts)
super
add_console_dispatcher(NexposeCommandDispatcher)
banner = ["0a205f5f5f5f202020202020202020202020205f20202020205f205f5f5f5f5f2020205f2020205f20202020205f5f20205f5f2020202020202020202020202020202020202020200a7c20205f205c205f5f205f205f205f5f20285f29205f5f7c207c5f5f5f20207c207c205c207c207c205f5f5f5c205c2f202f5f205f5f2020205f5f5f20205f5f5f20205f5f5f200a7c207c5f29202f205f60207c20275f205c7c207c2f205f60207c20202f202f20207c20205c7c207c2f205f205c5c20202f7c20275f205c202f205f205c2f205f5f7c2f205f205c0a7c20205f203c20285f7c207c207c5f29207c207c20285f7c207c202f202f2020207c207c5c20207c20205f5f2f2f20205c7c207c5f29207c20285f29205c5f5f205c20205f5f2f0a7c5f7c205c5f5c5f5f2c5f7c202e5f5f2f7c5f7c5c5f5f2c5f7c2f5f2f202020207c5f7c205c5f7c5c5f5f5f2f5f2f5c5f5c202e5f5f2f205c5f5f5f2f7c5f5f5f2f5c5f5f5f7c0a20202020202020202020207c5f7c20202020202020202020202020202020202020202020202020202020202020202020207c5f7c202020202020202020202020202020202020200a0a0a"].pack("H*")
# Do not use this UTF-8 encoded high-ascii art for non-UTF-8 or windows consoles
lang = Rex::Compat.getenv("LANG")
if (lang and lang =~ /UTF-8/)
# Cygwin/Windows should not be reporting UTF-8 either...
# (! (Rex::Compat.is_windows or Rex::Compat.is_cygwin))
banner = ["202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020200a20e29684e29684e29684202020e29684e29684202020202020202020202020e29684e29684e296842020e29684e29684e2968420202020202020202020202020202020202020202020202020202020202020202020202020202020200a20e29688e29688e29688202020e29688e2968820202020202020202020202020e29688e2968820e29684e29688e296882020202020202020202020202020202020202020202020202020202020202020202020202020202020200a20e29688e29688e29680e296882020e29688e29688202020e29684e29688e29688e29688e29688e296842020202020e29688e29688e29688e2968820202020e29688e29688e29684e29688e29688e29688e2968420202020e29684e29688e29688e29688e29688e29684202020e29684e29684e29688e29688e29688e29688e29688e29684202020e29684e29688e29688e29688e29688e2968420200a20e29688e2968820e29688e2968820e29688e296882020e29688e29688e29684e29684e29684e29684e29688e296882020202020e29688e296882020202020e29688e29688e296802020e29680e29688e296882020e29688e29688e296802020e29680e29688e296882020e29688e29688e29684e29684e29684e2968420e296802020e29688e29688e29684e29684e29684e29684e29688e29688200a20e29688e296882020e29688e29684e29688e296882020e29688e29688e29680e29680e29680e29680e29680e2968020202020e29688e29688e29688e2968820202020e29688e2968820202020e29688e296882020e29688e2968820202020e29688e29688202020e29680e29680e29680e29680e29688e29688e296842020e29688e29688e29680e29680e29680e29680e29680e29680200a20e29688e29688202020e29688e29688e296882020e29680e29688e29688e29684e29684e29684e29684e29688202020e29688e296882020e29688e29688202020e29688e29688e29688e29684e29684e29688e29688e296802020e29680e29688e29688e29684e29684e29688e29688e296802020e29688e29684e29684e29684e29684e29684e29688e296882020e29680e29688e29688e29684e29684e29684e29684e29688200a20e29680e29680202020e29680e29680e2968020202020e29680e29680e29680e29680e29680202020e29680e29680e296802020e29680e29680e296802020e29688e2968820e29680e29680e29680202020202020e29680e29680e29680e296802020202020e29680e29680e29680e29680e29680e296802020202020e29680e29680e29680e29680e2968020200a20202020202020202020202020202020202020202020202020202020202020e29688e29688202020202020202020202020202020202020202020202020202020202020202020202020200a202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020200a"].pack("H*")
end
print(banner)
print_status("Nexpose integration has been activated")
end
def cleanup
remove_console_dispatcher('Nexpose')
end
def name
"nexpose"
end
def desc
"Integrates with the Rapid7 Nexpose vulnerability management product"
end
end
end
+542 -542
View File
@@ -14,555 +14,555 @@ require 'openvas/openvas-omp'
module Msf
class Plugin::OpenVAS < Msf::Plugin
class OpenVASCommandDispatcher
include Msf::Ui::Console::CommandDispatcher
def name
"OpenVAS"
end
def commands
{
'openvas_help' => "Displays help",
'openvas_version' => "Display the version of the OpenVAS server",
'openvas_debug' => "Enable/Disable debugging",
'openvas_connect' => "Connect to an OpenVAS manager using OMP",
'openvas_disconnect' => "Disconnect from OpenVAS manager",
'openvas_task_create' => "Create a task (name, comment, target, config)",
'openvas_task_delete' => "Delete task by ID",
'openvas_task_list' => "Display list of tasks",
'openvas_task_start' => "Start task by ID",
'openvas_task_stop' => "Stop task by ID",
'openvas_task_pause' => "Pause task by ID",
'openvas_task_resume' => "Resume task by ID",
'openvas_task_resume_or_start' => "Resume task or start task by ID",
'openvas_target_create' => "Create target (name, hosts, comment)",
'openvas_target_delete' => "Delete target by ID",
'openvas_target_list' => "Display list of targets",
'openvas_config_list' => "Quickly display list of configs",
'openvas_format_list' => "Display list of available report formats",
'openvas_report_list' => "Display a list of available report formats",
'openvas_report_delete' => "Delete a report specified by ID",
'openvas_report_download' => "Save a report to disk",
'openvas_report_import' => "Import report specified by ID into framework",
}
end
def cmd_openvas_help()
print_status("openvas_help Display this help")
print_status("openvas_debug Enable/Disable debugging")
print_status("openvas_version Display the version of the OpenVAS server")
print_status
print_status("CONNECTION")
print_status("==========")
print_status("openvas_connect Connects to OpenVAS")
print_status("openvas_disconnect Disconnects from OpenVAS")
print_status
print_status("TARGETS")
print_status("=======")
print_status("openvas_target_create Create target")
print_status("openvas_target_delete Deletes target specified by ID")
print_status("openvas_target_list Lists targets")
print_status
print_status("TASKS")
print_status("=====")
print_status("openvas_task_create Create task")
print_status("openvas_task_delete Delete a task and all associated reports")
print_status("openvas_task_list Lists tasks")
print_status("openvas_task_start Starts task specified by ID")
print_status("openvas_task_stop Stops task specified by ID")
print_status("openvas_task_pause Pauses task specified by ID")
print_status("openvas_task_resume Resumes task specified by ID")
print_status("openvas_task_resume_or_start Resumes or starts task specified by ID")
print_status
print_status("CONFIGS")
print_status("=======")
print_status("openvas_config_list Lists scan configurations")
print_status
print_status("FORMATS")
print_status("=======")
print_status("openvas_format_list Lists available report formats")
print_status
print_status("REPORTS")
print_status("=======")
print_status("openvas_report_list Lists available reports")
print_status("openvas_report_delete Delete a report specified by ID")
print_status("openvas_report_import Imports an OpenVAS report specified by ID")
print_status("openvas_report_download Downloads an OpenVAS report specified by ID")
end
# Verify the database is connected and usable
def database?
if !(framework.db and framework.db.usable)
return false
else
return true
end
end
# Verify there is an active OpenVAS connection
def openvas?
if @ov
return true
else
print_error("No OpenVAS connection available. Please use openvas_connect.")
return false
end
end
# Verify correct number of arguments and verify -h was not given. Return
# true if correct number of arguments and help was not requested.
def args?(args, min=1, max=nil)
if not max then max = min end
if (args.length < min or args.length > max or args[0] == "-h")
return false
end
return true
end
#--------------------------
# Basic Functions
#--------------------------
def cmd_openvas_debug(*args)
return unless openvas?
if args?(args)
begin
resp = @ov.debug(args[0].to_i)
print_good(resp)
rescue OpenVASOMP::OMPError => e
print_error(e.to_s)
end
else
print_status("Usage:")
print_status("openvas_debug integer")
end
end
def cmd_openvas_version()
return unless openvas?
begin
ver = @ov.get_version
print_good("Using OMP version #{ver}")
rescue OpenVASOMP::OMPError => e
print_error(e.to_s)
end
end
#--------------------------
# Connection Functions
#--------------------------
def cmd_openvas_connect(*args)
# Is the database configured?
if not database?
print_error("No database has been configured.")
return
end
# Don't allow duplicate sessions
if @ov then
print_error("Session already open, please use openvas_disconnect first.")
return
end
# Make sure the correct number of arguments are present.
if args?(args, 4, 5)
user, pass, host, port, sslv = args
# SSL warning. User is required to confirm.
if(host != "localhost" and host != "127.0.0.1" and sslv != "ok")
print_error("Warning: SSL connections are not verified in this release, it is possible for an attacker")
print_error(" with the ability to man-in-the-middle the OpenVAS traffic to capture the OpenVAS")
print_error(" credentials. If you are running this on a trusted network, please pass in 'ok'")
print_error(" as an additional parameter to this command.")
return
end
begin
print_status("Connecting to OpenVAS instance at #{host}:#{port} with username #{user}...")
ov = OpenVASOMP::OpenVASOMP.new(user, pass, host, port)
rescue OpenVASOMP::OMPAuthError => e
print_error("Authentication failed: #{e.reason}")
return
rescue OpenVASOMP::OMPConnectionError => e
print_error("Connection failed: #{e.reason}")
return
end
print_good("OpenVAS connection successful")
@ov = ov
else
print_status("Usage:")
print_status("openvas_connect username password host port <ssl-confirm>")
end
end
# Disconnect from an OpenVAS manager
def cmd_openvas_disconnect()
return unless openvas?
@ov.logout
@ov = nil
end
#--------------------------
# Target Functions
#--------------------------
def cmd_openvas_target_create(*args)
return unless openvas?
if args?(args, 3)
begin
resp = @ov.target_create(args[0], args[1], args[2])
print_status(resp)
cmd_openvas_target_list
rescue OpenVASOMP::OMPError => e
print_error(e.to_s)
end
else
print_status("Usage: openvas_target_create <name> <hosts> <comment>")
end
end
def cmd_openvas_target_delete(*args)
return unless openvas?
if args?(args)
begin
resp = @ov.target_delete(args[0])
print_status(resp)
cmd_openvas_target_list
rescue OpenVASOMP::OMPError => e
print_error(e.to_s)
end
else
print_status("Usage: openvas_target_delete <target_id>")
end
end
def cmd_openvas_target_list(*args)
return unless openvas?
begin
tbl = Rex::Ui::Text::Table.new(
'Columns' => ["ID", "Name", "Hosts", "Max Hosts", "In Use", "Comment"])
id = 0
@ov.target_get_all().each do |target|
tbl << [ id, target["name"], target["hosts"], target["max_hosts"],
target["in_use"], target["comment"] ]
id += 1
end
print_good("OpenVAS list of targets")
print_line
print_line tbl.to_s
print_line
rescue OpenVASOMP::OMPError => e
print_error(e.to_s)
end
end
#--------------------------
# Task Functions
#--------------------------
def cmd_openvas_task_create(*args)
return unless openvas?
if args?(args, 4)
begin
resp = @ov.task_create(args[0], args[1], args[2], args[3])
print_status(resp)
cmd_openvas_task_list
rescue OpenVASOMP::OMPError => e
print_error(e.to_s)
end
else
print_status("Usage: openvas_task_create <name> <comment> <config_id> <target_id>")
end
end
def cmd_openvas_task_delete(*args)
return unless openvas?
if args?(args, 2)
# User is required to confirm before deleting task.
if(args[1] != "ok")
print_error("Warning: Deleting a task will also delete all reports associated with the ")
print_error("task, please pass in 'ok' as an additional parameter to this command.")
return
end
begin
resp = @ov.task_delete(args[0])
print_status(resp)
cmd_openvas_task_list
rescue OpenVASOMP::OMPError => e
print_error(e.to_s)
end
else
print_status("Usage: openvas_task_delete <id> ok")
print_error("This will delete the task and all associated reports.")
end
end
def cmd_openvas_task_list(*args)
return unless openvas?
begin
tbl = Rex::Ui::Text::Table.new(
'Columns' => ["ID", "Name", "Comment", "Status", "Progress"])
id = 0
@ov.task_get_all().each do |task|
tbl << [ id, task["name"], task["comment"], task["status"], task["progress"] ]
id += 1
end
print_good("OpenVAS list of tasks")
print_line
print_line tbl.to_s
print_line
rescue OpenVASOMP::OMPError => e
print_error(e.to_s)
end
end
def cmd_openvas_task_start(*args)
return unless openvas?
if args?(args)
begin
resp = @ov.task_start(args[0])
print_status(resp)
rescue OpenVASOMP::OMPError => e
print_error(e.to_s)
end
else
print_status("Usage: openvas_task_start <id>")
end
end
def cmd_openvas_task_stop(*args)
return unless openvas?
if args?(args)
begin
resp = @ov.task_stop(args[0])
print_status(resp)
rescue OpenVASOMP::OMPError => e
print_error(e.to_s)
end
else
print_status("Usage: openvas_task_stop <id>")
end
end
def cmd_openvas_task_pause(*args)
return unless openvas?
if args?(args)
begin
resp = @ov.task_pause(args[0])
print_status(resp)
rescue OpenVASOMP::OMPError => e
print_error(e.to_s)
end
else
print_status("Usage: openvas_task_pause <id>")
end
end
def cmd_openvas_task_resume(*args)
return unless openvas?
if args?(args)
begin
resp = @ov.task_resume_paused(args[0])
print_status(resp)
rescue OpenVASOMP::OMPError => e
print_error(e.to_s)
end
else
print_status("Usage: openvas_task_resume <id>")
end
end
def cmd_openvas_task_resume_or_start(*args)
return unless openvas?
if args?(args)
begin
resp = @ov.task_resume_or_start(args[0])
print_status(resp)
rescue OpenVASOMP::OMPError => e
print_error(e.to_s)
end
else
print_status("Usage: openvas_task_resume_or_start <id>")
end
end
#--------------------------
# Config Functions
#--------------------------
def cmd_openvas_config_list(*args)
return unless openvas?
begin
tbl = Rex::Ui::Text::Table.new(
'Columns' => [ "ID", "Name" ])
id = 0
@ov.configs.each do |config|
tbl << [ id, config["name"] ]
id += 1
end
print_good("OpenVAS list of configs")
print_line
print_line tbl.to_s
print_line
rescue OpenVASOMP::OMPError => e
print_error(e.to_s)
end
end
#--------------------------
# Format Functions
#--------------------------
def cmd_openvas_format_list(*args)
return unless openvas?
begin
tbl = Rex::Ui::Text::Table.new(
'Columns' => ["ID", "Name", "Extension", "Summary"])
id = 0
@ov.formats.each do |format|
tbl << [ id, format["name"], format["extension"], format["summary"] ]
id += 1
end
print_good("OpenVAS list of report formats")
print_line
print_line tbl.to_s
print_line
rescue OpenVASOMP::OMPError => e
print_error(e.to_s)
end
end
#--------------------------
# Report Functions
#--------------------------
def cmd_openvas_report_list(*args)
return unless openvas?
begin
tbl = Rex::Ui::Text::Table.new(
'Columns' => ["ID", "Task Name", "Start Time", "Stop Time"])
id = 0
@ov.report_get_all().each do |report|
tbl << [ id, report["task"], report["start_time"], report["stop_time"] ]
id += 1
end
print_good("OpenVAS list of reports")
print_line
print_line tbl.to_s
print_line
rescue OpenVASOMP::OMPError => e
print_error(e.to_s)
end
end
def cmd_openvas_report_delete(*args)
return unless openvas?
if args?(args)
begin
resp = @ov.report_delete(args[0])
print_status(resp)
cmd_openvas_report_list
rescue OpenVASOMP::OMPError => e
print_error(e.to_s)
end
else
print_status("Usage: openvas_report_delete <id>")
end
end
def cmd_openvas_report_download(*args)
return unless openvas?
if args?(args, 4)
begin
report = @ov.report_get_by_id(args[0], args[1])
::FileUtils.mkdir_p(args[2])
name = ::File.join(args[2], args[3])
print_status("Saving report to #{name}")
output = ::File.new(name, "w")
output.puts(report)
output.close
rescue OpenVASOMP::OMPError => e
print_error(e.to_s)
end
else
print_status("Usage: openvas_report_download <report_id> <format_id> <path> <report_name>")
end
end
def cmd_openvas_report_import(*args)
return unless openvas?
if args?(args, 2)
begin
report = @ov.report_get_by_id(args[0], args[1])
print_status("Importing report to database.")
framework.db.import({:data => report})
rescue OpenVASOMP::OMPError => e
print_error(e.to_s)
end
else
print_status("Usage: openvas_report_import <report_id> <format_id>")
print_status("Only the NBE and XML formats are supported for importing.")
end
end
end # End OpenVAS class
class OpenVASCommandDispatcher
include Msf::Ui::Console::CommandDispatcher
def name
"OpenVAS"
end
def commands
{
'openvas_help' => "Displays help",
'openvas_version' => "Display the version of the OpenVAS server",
'openvas_debug' => "Enable/Disable debugging",
'openvas_connect' => "Connect to an OpenVAS manager using OMP",
'openvas_disconnect' => "Disconnect from OpenVAS manager",
'openvas_task_create' => "Create a task (name, comment, target, config)",
'openvas_task_delete' => "Delete task by ID",
'openvas_task_list' => "Display list of tasks",
'openvas_task_start' => "Start task by ID",
'openvas_task_stop' => "Stop task by ID",
'openvas_task_pause' => "Pause task by ID",
'openvas_task_resume' => "Resume task by ID",
'openvas_task_resume_or_start' => "Resume task or start task by ID",
'openvas_target_create' => "Create target (name, hosts, comment)",
'openvas_target_delete' => "Delete target by ID",
'openvas_target_list' => "Display list of targets",
'openvas_config_list' => "Quickly display list of configs",
'openvas_format_list' => "Display list of available report formats",
'openvas_report_list' => "Display a list of available report formats",
'openvas_report_delete' => "Delete a report specified by ID",
'openvas_report_download' => "Save a report to disk",
'openvas_report_import' => "Import report specified by ID into framework",
}
end
def cmd_openvas_help()
print_status("openvas_help Display this help")
print_status("openvas_debug Enable/Disable debugging")
print_status("openvas_version Display the version of the OpenVAS server")
print_status
print_status("CONNECTION")
print_status("==========")
print_status("openvas_connect Connects to OpenVAS")
print_status("openvas_disconnect Disconnects from OpenVAS")
print_status
print_status("TARGETS")
print_status("=======")
print_status("openvas_target_create Create target")
print_status("openvas_target_delete Deletes target specified by ID")
print_status("openvas_target_list Lists targets")
print_status
print_status("TASKS")
print_status("=====")
print_status("openvas_task_create Create task")
print_status("openvas_task_delete Delete a task and all associated reports")
print_status("openvas_task_list Lists tasks")
print_status("openvas_task_start Starts task specified by ID")
print_status("openvas_task_stop Stops task specified by ID")
print_status("openvas_task_pause Pauses task specified by ID")
print_status("openvas_task_resume Resumes task specified by ID")
print_status("openvas_task_resume_or_start Resumes or starts task specified by ID")
print_status
print_status("CONFIGS")
print_status("=======")
print_status("openvas_config_list Lists scan configurations")
print_status
print_status("FORMATS")
print_status("=======")
print_status("openvas_format_list Lists available report formats")
print_status
print_status("REPORTS")
print_status("=======")
print_status("openvas_report_list Lists available reports")
print_status("openvas_report_delete Delete a report specified by ID")
print_status("openvas_report_import Imports an OpenVAS report specified by ID")
print_status("openvas_report_download Downloads an OpenVAS report specified by ID")
end
# Verify the database is connected and usable
def database?
if !(framework.db and framework.db.usable)
return false
else
return true
end
end
# Verify there is an active OpenVAS connection
def openvas?
if @ov
return true
else
print_error("No OpenVAS connection available. Please use openvas_connect.")
return false
end
end
# Verify correct number of arguments and verify -h was not given. Return
# true if correct number of arguments and help was not requested.
def args?(args, min=1, max=nil)
if not max then max = min end
if (args.length < min or args.length > max or args[0] == "-h")
return false
end
return true
end
#--------------------------
# Basic Functions
#--------------------------
def cmd_openvas_debug(*args)
return unless openvas?
if args?(args)
begin
resp = @ov.debug(args[0].to_i)
print_good(resp)
rescue OpenVASOMP::OMPError => e
print_error(e.to_s)
end
else
print_status("Usage:")
print_status("openvas_debug integer")
end
end
def cmd_openvas_version()
return unless openvas?
begin
ver = @ov.get_version
print_good("Using OMP version #{ver}")
rescue OpenVASOMP::OMPError => e
print_error(e.to_s)
end
end
#--------------------------
# Connection Functions
#--------------------------
def cmd_openvas_connect(*args)
# Is the database configured?
if not database?
print_error("No database has been configured.")
return
end
# Don't allow duplicate sessions
if @ov then
print_error("Session already open, please use openvas_disconnect first.")
return
end
# Make sure the correct number of arguments are present.
if args?(args, 4, 5)
user, pass, host, port, sslv = args
# SSL warning. User is required to confirm.
if(host != "localhost" and host != "127.0.0.1" and sslv != "ok")
print_error("Warning: SSL connections are not verified in this release, it is possible for an attacker")
print_error(" with the ability to man-in-the-middle the OpenVAS traffic to capture the OpenVAS")
print_error(" credentials. If you are running this on a trusted network, please pass in 'ok'")
print_error(" as an additional parameter to this command.")
return
end
begin
print_status("Connecting to OpenVAS instance at #{host}:#{port} with username #{user}...")
ov = OpenVASOMP::OpenVASOMP.new(user, pass, host, port)
rescue OpenVASOMP::OMPAuthError => e
print_error("Authentication failed: #{e.reason}")
return
rescue OpenVASOMP::OMPConnectionError => e
print_error("Connection failed: #{e.reason}")
return
end
print_good("OpenVAS connection successful")
@ov = ov
else
print_status("Usage:")
print_status("openvas_connect username password host port <ssl-confirm>")
end
end
# Disconnect from an OpenVAS manager
def cmd_openvas_disconnect()
return unless openvas?
@ov.logout
@ov = nil
end
#--------------------------
# Target Functions
#--------------------------
def cmd_openvas_target_create(*args)
return unless openvas?
if args?(args, 3)
begin
resp = @ov.target_create(args[0], args[1], args[2])
print_status(resp)
cmd_openvas_target_list
rescue OpenVASOMP::OMPError => e
print_error(e.to_s)
end
else
print_status("Usage: openvas_target_create <name> <hosts> <comment>")
end
end
def cmd_openvas_target_delete(*args)
return unless openvas?
if args?(args)
begin
resp = @ov.target_delete(args[0])
print_status(resp)
cmd_openvas_target_list
rescue OpenVASOMP::OMPError => e
print_error(e.to_s)
end
else
print_status("Usage: openvas_target_delete <target_id>")
end
end
def cmd_openvas_target_list(*args)
return unless openvas?
begin
tbl = Rex::Ui::Text::Table.new(
'Columns' => ["ID", "Name", "Hosts", "Max Hosts", "In Use", "Comment"])
id = 0
@ov.target_get_all().each do |target|
tbl << [ id, target["name"], target["hosts"], target["max_hosts"],
target["in_use"], target["comment"] ]
id += 1
end
print_good("OpenVAS list of targets")
print_line
print_line tbl.to_s
print_line
rescue OpenVASOMP::OMPError => e
print_error(e.to_s)
end
end
#--------------------------
# Task Functions
#--------------------------
def cmd_openvas_task_create(*args)
return unless openvas?
if args?(args, 4)
begin
resp = @ov.task_create(args[0], args[1], args[2], args[3])
print_status(resp)
cmd_openvas_task_list
rescue OpenVASOMP::OMPError => e
print_error(e.to_s)
end
else
print_status("Usage: openvas_task_create <name> <comment> <config_id> <target_id>")
end
end
def cmd_openvas_task_delete(*args)
return unless openvas?
if args?(args, 2)
# User is required to confirm before deleting task.
if(args[1] != "ok")
print_error("Warning: Deleting a task will also delete all reports associated with the ")
print_error("task, please pass in 'ok' as an additional parameter to this command.")
return
end
begin
resp = @ov.task_delete(args[0])
print_status(resp)
cmd_openvas_task_list
rescue OpenVASOMP::OMPError => e
print_error(e.to_s)
end
else
print_status("Usage: openvas_task_delete <id> ok")
print_error("This will delete the task and all associated reports.")
end
end
def cmd_openvas_task_list(*args)
return unless openvas?
begin
tbl = Rex::Ui::Text::Table.new(
'Columns' => ["ID", "Name", "Comment", "Status", "Progress"])
id = 0
@ov.task_get_all().each do |task|
tbl << [ id, task["name"], task["comment"], task["status"], task["progress"] ]
id += 1
end
print_good("OpenVAS list of tasks")
print_line
print_line tbl.to_s
print_line
rescue OpenVASOMP::OMPError => e
print_error(e.to_s)
end
end
def cmd_openvas_task_start(*args)
return unless openvas?
if args?(args)
begin
resp = @ov.task_start(args[0])
print_status(resp)
rescue OpenVASOMP::OMPError => e
print_error(e.to_s)
end
else
print_status("Usage: openvas_task_start <id>")
end
end
def cmd_openvas_task_stop(*args)
return unless openvas?
if args?(args)
begin
resp = @ov.task_stop(args[0])
print_status(resp)
rescue OpenVASOMP::OMPError => e
print_error(e.to_s)
end
else
print_status("Usage: openvas_task_stop <id>")
end
end
def cmd_openvas_task_pause(*args)
return unless openvas?
if args?(args)
begin
resp = @ov.task_pause(args[0])
print_status(resp)
rescue OpenVASOMP::OMPError => e
print_error(e.to_s)
end
else
print_status("Usage: openvas_task_pause <id>")
end
end
def cmd_openvas_task_resume(*args)
return unless openvas?
if args?(args)
begin
resp = @ov.task_resume_paused(args[0])
print_status(resp)
rescue OpenVASOMP::OMPError => e
print_error(e.to_s)
end
else
print_status("Usage: openvas_task_resume <id>")
end
end
def cmd_openvas_task_resume_or_start(*args)
return unless openvas?
if args?(args)
begin
resp = @ov.task_resume_or_start(args[0])
print_status(resp)
rescue OpenVASOMP::OMPError => e
print_error(e.to_s)
end
else
print_status("Usage: openvas_task_resume_or_start <id>")
end
end
#--------------------------
# Config Functions
#--------------------------
def cmd_openvas_config_list(*args)
return unless openvas?
begin
tbl = Rex::Ui::Text::Table.new(
'Columns' => [ "ID", "Name" ])
id = 0
@ov.configs.each do |config|
tbl << [ id, config["name"] ]
id += 1
end
print_good("OpenVAS list of configs")
print_line
print_line tbl.to_s
print_line
rescue OpenVASOMP::OMPError => e
print_error(e.to_s)
end
end
#--------------------------
# Format Functions
#--------------------------
def cmd_openvas_format_list(*args)
return unless openvas?
begin
tbl = Rex::Ui::Text::Table.new(
'Columns' => ["ID", "Name", "Extension", "Summary"])
id = 0
@ov.formats.each do |format|
tbl << [ id, format["name"], format["extension"], format["summary"] ]
id += 1
end
print_good("OpenVAS list of report formats")
print_line
print_line tbl.to_s
print_line
rescue OpenVASOMP::OMPError => e
print_error(e.to_s)
end
end
#--------------------------
# Report Functions
#--------------------------
def cmd_openvas_report_list(*args)
return unless openvas?
begin
tbl = Rex::Ui::Text::Table.new(
'Columns' => ["ID", "Task Name", "Start Time", "Stop Time"])
id = 0
@ov.report_get_all().each do |report|
tbl << [ id, report["task"], report["start_time"], report["stop_time"] ]
id += 1
end
print_good("OpenVAS list of reports")
print_line
print_line tbl.to_s
print_line
rescue OpenVASOMP::OMPError => e
print_error(e.to_s)
end
end
def cmd_openvas_report_delete(*args)
return unless openvas?
if args?(args)
begin
resp = @ov.report_delete(args[0])
print_status(resp)
cmd_openvas_report_list
rescue OpenVASOMP::OMPError => e
print_error(e.to_s)
end
else
print_status("Usage: openvas_report_delete <id>")
end
end
def cmd_openvas_report_download(*args)
return unless openvas?
if args?(args, 4)
begin
report = @ov.report_get_by_id(args[0], args[1])
::FileUtils.mkdir_p(args[2])
name = ::File.join(args[2], args[3])
print_status("Saving report to #{name}")
output = ::File.new(name, "w")
output.puts(report)
output.close
rescue OpenVASOMP::OMPError => e
print_error(e.to_s)
end
else
print_status("Usage: openvas_report_download <report_id> <format_id> <path> <report_name>")
end
end
def cmd_openvas_report_import(*args)
return unless openvas?
if args?(args, 2)
begin
report = @ov.report_get_by_id(args[0], args[1])
print_status("Importing report to database.")
framework.db.import({:data => report})
rescue OpenVASOMP::OMPError => e
print_error(e.to_s)
end
else
print_status("Usage: openvas_report_import <report_id> <format_id>")
print_status("Only the NBE and XML formats are supported for importing.")
end
end
end # End OpenVAS class
#------------------------------
# Plugin initialization
#------------------------------
def initialize(framework, opts)
super
add_console_dispatcher(OpenVASCommandDispatcher)
print_status("Welcome to OpenVAS integration by kost and averagesecurityguy.")
print_status
print_status("OpenVAS integration requires a database connection. Once the ")
print_status("database is ready, connect to the OpenVAS server using openvas_connect.")
print_status("For additional commands use openvas_help.")
print_status
@ov = nil
@formats = nil
end
def initialize(framework, opts)
super
add_console_dispatcher(OpenVASCommandDispatcher)
print_status("Welcome to OpenVAS integration by kost and averagesecurityguy.")
print_status
print_status("OpenVAS integration requires a database connection. Once the ")
print_status("database is ready, connect to the OpenVAS server using openvas_connect.")
print_status("For additional commands use openvas_help.")
print_status
@ov = nil
@formats = nil
end
def cleanup
remove_console_dispatcher('OpenVAS')
end
def cleanup
remove_console_dispatcher('OpenVAS')
end
def name
"OpenVAS"
end
def name
"OpenVAS"
end
def desc
"Integrates with the OpenVAS - open source vulnerability management"
end
def desc
"Integrates with the OpenVAS - open source vulnerability management"
end
end
end
+153 -153
View File
@@ -15,188 +15,188 @@ module Msf
class Plugin::PcapLog < Msf::Plugin
# Only little-endian is supported in this implementation.
PCAP_FILE_HEADER = "\xD4\xC3\xB2\xA1\x02\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00`\x00\x00\x00\x01\x00\x00\x00"
# Only little-endian is supported in this implementation.
PCAP_FILE_HEADER = "\xD4\xC3\xB2\xA1\x02\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00`\x00\x00\x00\x01\x00\x00\x00"
#
# Implements a pcap console command dispatcher.
#
class PcapLogDispatcher
#
# Implements a pcap console command dispatcher.
#
class PcapLogDispatcher
include Msf::Ui::Console::CommandDispatcher
include Msf::Ui::Console::CommandDispatcher
def name
"PcapLog"
end
def name
"PcapLog"
end
def commands
{
"pcap_filter" => "Set/Get a BPF-style packet filter",
"pcap_dir" => "Set/Get a directory to log pcaps to",
"pcap_prefix" => "Set/Get a filename prefix to log pcaps to",
"pcap_iface" => "Set/Get an interface to capture from",
"pcap_start" => "Start a capture",
"pcap_stop" => "Stop a running capture",
def commands
{
"pcap_filter" => "Set/Get a BPF-style packet filter",
"pcap_dir" => "Set/Get a directory to log pcaps to",
"pcap_prefix" => "Set/Get a filename prefix to log pcaps to",
"pcap_iface" => "Set/Get an interface to capture from",
"pcap_start" => "Start a capture",
"pcap_stop" => "Stop a running capture",
"pcap_show_config" => "Show the current PcapLog configuration"
}
end
"pcap_show_config" => "Show the current PcapLog configuration"
}
end
def cmd_pcap_filter(*args)
@filter = args.join(' ') || @filter
print_line "#{self.name} BPF filter: #{@filter}"
end
def cmd_pcap_filter(*args)
@filter = args.join(' ') || @filter
print_line "#{self.name} BPF filter: #{@filter}"
end
def cmd_pcap_prefix(*args)
@prefix = args[0] || @prefix || "msf3-session"
print_line "#{self.name} prefix: #{@prefix}"
end
def cmd_pcap_prefix(*args)
@prefix = args[0] || @prefix || "msf3-session"
print_line "#{self.name} prefix: #{@prefix}"
end
def cmd_pcap_dir(*args)
@dir = args[0] || @dir || "/tmp"
print_line "#{self.name} Directory: #{@dir}"
end
def cmd_pcap_dir(*args)
@dir = args[0] || @dir || "/tmp"
print_line "#{self.name} Directory: #{@dir}"
end
def cmd_pcap_iface(*args)
@iface = args[0] || @iface
print_line "#{self.name} Interface: #{@iface}"
end
def cmd_pcap_iface(*args)
@iface = args[0] || @iface
print_line "#{self.name} Interface: #{@iface}"
end
def cmd_pcap_start(*args)
def cmd_pcap_start(*args)
unless @pcaprub_loaded
print_error("Pcap module not available")
return false
end
unless @pcaprub_loaded
print_error("Pcap module not available")
return false
end
if @capture_thread && @capture_thread.alive?
print_error "Capture already started."
return false
end
if @capture_thread && @capture_thread.alive?
print_error "Capture already started."
return false
end
gen_fname
print_line "Starting packet capture from #{@iface} to #{@fname}"
okay,msg = validate_options
unless okay
print_error msg
return false
end
dev = (@iface || ::Pcap.lookupdev)
@capture_file.write(PCAP_FILE_HEADER)
@capture_file.flush
@pcap = ::Pcap.open_live(dev, 65535, true, 1)
@pcap.setfilter(@filter) if @filter
@capture_thread = Thread.new {
@pcap.each do |pkt|
@capture_file.write(convert_to_pcap(pkt))
@capture_file.flush
end
}
end
gen_fname
print_line "Starting packet capture from #{@iface} to #{@fname}"
okay,msg = validate_options
unless okay
print_error msg
return false
end
dev = (@iface || ::Pcap.lookupdev)
@capture_file.write(PCAP_FILE_HEADER)
@capture_file.flush
@pcap = ::Pcap.open_live(dev, 65535, true, 1)
@pcap.setfilter(@filter) if @filter
@capture_thread = Thread.new {
@pcap.each do |pkt|
@capture_file.write(convert_to_pcap(pkt))
@capture_file.flush
end
}
end
def cmd_pcap_stop(*args)
if @capture_thread && @capture_thread.alive?
print_line "Stopping packet capture from #{@iface} to #{@fname}"
print_line "Capture Stats: #{@pcap.stats.inspect}"
@pcap = nil
@capture_file.close if @capture_file.respond_to? :close
@capture_thread.kill
@capture_thread = nil
else
print_error "No capture running."
end
end
def cmd_pcap_stop(*args)
if @capture_thread && @capture_thread.alive?
print_line "Stopping packet capture from #{@iface} to #{@fname}"
print_line "Capture Stats: #{@pcap.stats.inspect}"
@pcap = nil
@capture_file.close if @capture_file.respond_to? :close
@capture_thread.kill
@capture_thread = nil
else
print_error "No capture running."
end
end
def convert_to_pcap(packet)
t = Time.now
sz = packet.size
[t.to_i, t.usec, sz, sz, packet].pack("V4A*")
end
def convert_to_pcap(packet)
t = Time.now
sz = packet.size
[t.to_i, t.usec, sz, sz, packet].pack("V4A*")
end
def gen_fname
t = Time.now
file_part = "%s_%04d-%02d-%02d_%02d-%02d-%02d.pcap" % [
@prefix, t.year, t.month, t.mday, t.hour, t.min, t.sec
]
@fname = File.join(@dir, file_part)
end
def gen_fname
t = Time.now
file_part = "%s_%04d-%02d-%02d_%02d-%02d-%02d.pcap" % [
@prefix, t.year, t.month, t.mday, t.hour, t.min, t.sec
]
@fname = File.join(@dir, file_part)
end
# Check for euid 0 and check for a valid place to write files
def validate_options
# Check for euid 0 and check for a valid place to write files
def validate_options
# Check for root.
unless Process.euid.zero?
msg = "You must run as root in order to capture packets."
return [false, msg]
end
# Check for root.
unless Process.euid.zero?
msg = "You must run as root in order to capture packets."
return [false, msg]
end
# Check directory suitability.
unless File.directory? @dir
msg = "Invalid pcap directory specified: '#{@dir}'"
return [false, msg]
end
# Check directory suitability.
unless File.directory? @dir
msg = "Invalid pcap directory specified: '#{@dir}'"
return [false, msg]
end
unless File.writable? @dir
msg = "No write permission to directory: '#{@dir}'"
return [false, msg]
end
unless File.writable? @dir
msg = "No write permission to directory: '#{@dir}'"
return [false, msg]
end
@capture_file = File.open(@fname, "ab")
unless File.writable? @fname
msg = "Cannot write to file: '#{@fname}'"
return [false, msg]
end
@capture_file = File.open(@fname, "ab")
unless File.writable? @fname
msg = "Cannot write to file: '#{@fname}'"
return [false, msg]
end
# If you got this far, you're golden.
msg = "We're good!"
return [true, msg]
end
# If you got this far, you're golden.
msg = "We're good!"
return [true, msg]
end
# Need to pretend to have a datastore for Exploit::Capture to
# function.
def datastore
{}
end
# Need to pretend to have a datastore for Exploit::Capture to
# function.
def datastore
{}
end
def initialize(*args)
super
@dir = File.join(Msf::Config.config_directory, 'logs')
@prefix = "msf3-session"
@filter = nil
@pcaprub_loaded = false
begin
require 'pcaprub'
@pcaprub_loaded = true
@iface = ::Pcap.lookupdev
rescue ::Exception => e
print_error "#{e.class}: #{e}"
@pcaprub_loaded = false
@pcaprub_error = e
end
end
def initialize(*args)
super
@dir = File.join(Msf::Config.config_directory, 'logs')
@prefix = "msf3-session"
@filter = nil
@pcaprub_loaded = false
begin
require 'pcaprub'
@pcaprub_loaded = true
@iface = ::Pcap.lookupdev
rescue ::Exception => e
print_error "#{e.class}: #{e}"
@pcaprub_loaded = false
@pcaprub_error = e
end
end
end
end
def initialize(framework, opts)
super
add_console_dispatcher(PcapLogDispatcher)
print_status "PcapLog plugin loaded."
end
def initialize(framework, opts)
super
add_console_dispatcher(PcapLogDispatcher)
print_status "PcapLog plugin loaded."
end
# Kill the background thread
def cleanup
@capture_thread.kill if @capture_thread && @capture_thread.alive?
@capture_file.close if @capture_file.respond_to? :close
remove_console_dispatcher('PcapLog')
end
# Kill the background thread
def cleanup
@capture_thread.kill if @capture_thread && @capture_thread.alive?
@capture_file.close if @capture_file.respond_to? :close
remove_console_dispatcher('PcapLog')
end
def name
"pcap_log"
end
def name
"pcap_log"
end
def desc
"Logs all socket operations to pcaps (in /tmp by default)"
end
def desc
"Logs all socket operations to pcaps (in /tmp by default)"
end
end
end
+66 -66
View File
@@ -15,81 +15,81 @@ module Msf
###
class Plugin::Sample < Msf::Plugin
###
#
# This class implements a sample console command dispatcher.
#
###
class ConsoleCommandDispatcher
include Msf::Ui::Console::CommandDispatcher
###
#
# This class implements a sample console command dispatcher.
#
###
class ConsoleCommandDispatcher
include Msf::Ui::Console::CommandDispatcher
#
# The dispatcher's name.
#
def name
"Sample"
end
#
# The dispatcher's name.
#
def name
"Sample"
end
#
# Returns the hash of commands supported by this dispatcher.
#
def commands
{
"sample" => "A sample command added by the sample plugin"
}
end
#
# Returns the hash of commands supported by this dispatcher.
#
def commands
{
"sample" => "A sample command added by the sample plugin"
}
end
#
# This method handles the sample command.
#
def cmd_sample(*args)
print_line("You passed: #{args.join(' ')}")
end
end
#
# This method handles the sample command.
#
def cmd_sample(*args)
print_line("You passed: #{args.join(' ')}")
end
end
#
# The constructor is called when an instance of the plugin is created. The
# framework instance that the plugin is being associated with is passed in
# the framework parameter. Plugins should call the parent constructor when
# inheriting from Msf::Plugin to ensure that the framework attribute on
# their instance gets set.
#
def initialize(framework, opts)
super
#
# The constructor is called when an instance of the plugin is created. The
# framework instance that the plugin is being associated with is passed in
# the framework parameter. Plugins should call the parent constructor when
# inheriting from Msf::Plugin to ensure that the framework attribute on
# their instance gets set.
#
def initialize(framework, opts)
super
# If this plugin is being loaded in the context of a console application
# that uses the framework's console user interface driver, register
# console dispatcher commands.
add_console_dispatcher(ConsoleCommandDispatcher)
# If this plugin is being loaded in the context of a console application
# that uses the framework's console user interface driver, register
# console dispatcher commands.
add_console_dispatcher(ConsoleCommandDispatcher)
print_status("Sample plugin loaded.")
end
print_status("Sample plugin loaded.")
end
#
# The cleanup routine for plugins gives them a chance to undo any actions
# they may have done to the framework. For instance, if a console
# dispatcher was added, then it should be removed in the cleanup routine.
#
def cleanup
# If we had previously registered a console dispatcher with the console,
# deregister it now.
remove_console_dispatcher('Sample')
end
#
# The cleanup routine for plugins gives them a chance to undo any actions
# they may have done to the framework. For instance, if a console
# dispatcher was added, then it should be removed in the cleanup routine.
#
def cleanup
# If we had previously registered a console dispatcher with the console,
# deregister it now.
remove_console_dispatcher('Sample')
end
#
# This method returns a short, friendly name for the plugin.
#
def name
"sample"
end
#
# This method returns a short, friendly name for the plugin.
#
def name
"sample"
end
#
# This method returns a brief description of the plugin. It should be no
# more than 60 characters, but there are no hard limits.
#
def desc
"Demonstrates using framework plugins"
end
#
# This method returns a brief description of the plugin. It should be no
# more than 60 characters, but there are no hard limits.
#
def desc
"Demonstrates using framework plugins"
end
protected
end
+28 -28
View File
@@ -15,43 +15,43 @@ module Msf
class Plugin::SessionTagger < Msf::Plugin
include Msf::SessionEvent
include Msf::SessionEvent
def on_session_open(session)
print_status("Hooked session #{session.sid} / #{session.session_host}")
def on_session_open(session)
print_status("Hooked session #{session.sid} / #{session.session_host}")
# XXX: Determine what type of session this is before writing to it
# XXX: Determine what type of session this is before writing to it
if (session.interactive?)
session.shell_write("MKDIR C:\\TaggedBy#{ENV['USER']}\n")
session.shell_write("mkdir /tmp/TaggedBy#{ENV['USER']}\n")
end
if (session.interactive?)
session.shell_write("MKDIR C:\\TaggedBy#{ENV['USER']}\n")
session.shell_write("mkdir /tmp/TaggedBy#{ENV['USER']}\n")
end
#
# Read output with session.shell_read()
#
end
#
# Read output with session.shell_read()
#
end
def on_session_close(session,reason='')
print_status("Hooked session #{session.sid} is shutting down")
end
def on_session_close(session,reason='')
print_status("Hooked session #{session.sid} is shutting down")
end
def initialize(framework, opts)
super
self.framework.events.add_session_subscriber(self)
end
def initialize(framework, opts)
super
self.framework.events.add_session_subscriber(self)
end
def cleanup
self.framework.events.remove_session_subscriber(self)
end
def cleanup
self.framework.events.remove_session_subscriber(self)
end
def name
"session_tagger"
end
def name
"session_tagger"
end
def desc
"Automatically interacts with new sessions"
end
def desc
"Automatically interacts with new sessions"
end
end
end
+68 -68
View File
@@ -13,55 +13,55 @@ module Msf
class Plugin::SocketLogger < Msf::Plugin
###
#
# This class implements a socket communication logger
#
###
class MySocketEventHandler
include Rex::Socket::Comm::Events
###
#
# This class implements a socket communication logger
#
###
class MySocketEventHandler
include Rex::Socket::Comm::Events
def initialize(path, prefix)
@path = path
@prefix = prefix
end
def initialize(path, prefix)
@path = path
@prefix = prefix
end
def on_before_socket_create(comm, param)
end
def on_before_socket_create(comm, param)
end
def on_socket_created(comm, sock, param)
# Sockets created by the exploit have MsfExploit set and MsfPayload not set
if (param.context['MsfExploit'] and (! param.context['MsfPayload'] ))
sock.extend(SocketLogger::SocketTracer)
sock.context = param.context
sock.params = param
sock.initlog(@path, @prefix)
def on_socket_created(comm, sock, param)
# Sockets created by the exploit have MsfExploit set and MsfPayload not set
if (param.context['MsfExploit'] and (! param.context['MsfPayload'] ))
sock.extend(SocketLogger::SocketTracer)
sock.context = param.context
sock.params = param
sock.initlog(@path, @prefix)
end
end
end
end
end
end
def initialize(framework, opts)
log_path = opts['path'] || "/tmp"
log_prefix = opts['prefix'] || "socket_"
def initialize(framework, opts)
log_path = opts['path'] || "/tmp"
log_prefix = opts['prefix'] || "socket_"
super
@eh = MySocketEventHandler.new(log_path, log_prefix)
Rex::Socket::Comm::Local.register_event_handler(@eh)
end
super
@eh = MySocketEventHandler.new(log_path, log_prefix)
Rex::Socket::Comm::Local.register_event_handler(@eh)
end
def cleanup
Rex::Socket::Comm::Local.deregister_event_handler(@eh)
end
def cleanup
Rex::Socket::Comm::Local.deregister_event_handler(@eh)
end
def name
"socket_logger"
end
def name
"socket_logger"
end
def desc
"Logs all socket operations to hex dumps in /tmp"
end
def desc
"Logs all socket operations to hex dumps in /tmp"
end
protected
end
@@ -72,41 +72,41 @@ end
module SocketLogger
module SocketTracer
@@last_id = 0
@@last_id = 0
attr_accessor :context, :params
attr_accessor :context, :params
# Hook the write method
def write(buf, opts = {})
@fd.puts "WRITE (#{buf.length} bytes)"
@fd.puts Rex::Text.to_hex_dump(buf)
super(buf, opts)
end
# Hook the write method
def write(buf, opts = {})
@fd.puts "WRITE (#{buf.length} bytes)"
@fd.puts Rex::Text.to_hex_dump(buf)
super(buf, opts)
end
# Hook the read method
def read(length = nil, opts = {})
r = super(length, opts)
# Hook the read method
def read(length = nil, opts = {})
r = super(length, opts)
@fd.puts "READ (#{r.length} bytes)"
@fd.puts Rex::Text.to_hex_dump(r)
return r
end
@fd.puts "READ (#{r.length} bytes)"
@fd.puts Rex::Text.to_hex_dump(r)
return r
end
def close(*args)
super(*args)
@fd.close
end
def close(*args)
super(*args)
@fd.close
end
def initlog(path, prefix)
@log_path = path
@log_prefix = prefix
@log_id = @@last_id
@@last_id += 1
@fd = File.open(File.join(@log_path, "#{@log_prefix}#{@log_id}.log"), "w")
@fd.puts "Socket created at #{Time.now}"
@fd.puts "Info: #{params.proto} #{params.localhost}:#{params.localport} -> #{params.peerhost}:#{params.peerport}"
@fd.puts ""
end
def initlog(path, prefix)
@log_path = path
@log_prefix = prefix
@log_id = @@last_id
@@last_id += 1
@fd = File.open(File.join(@log_path, "#{@log_prefix}#{@log_id}.log"), "w")
@fd.puts "Socket created at #{Time.now}"
@fd.puts "Info: #{params.proto} #{params.localhost}:#{params.localport} -> #{params.peerhost}:#{params.peerport}"
@fd.puts ""
end
end
end
+67 -67
View File
@@ -14,88 +14,88 @@ module Msf
class Plugin::EventSounds < Msf::Plugin
attr_accessor :theme, :base, :queue, :queue_thread
attr_accessor :theme, :base, :queue, :queue_thread
include Msf::SessionEvent
include Msf::SessionEvent
def play_sound(event)
self.queue.push(event)
end
def play_sound(event)
self.queue.push(event)
end
def on_session_open(session)
event = 'session_open_' + session.type
play_sound(event)
end
def on_session_open(session)
event = 'session_open_' + session.type
play_sound(event)
end
def on_session_close(session, reason='')
sid = session.sid.to_s
play_sound('session')
sid.unpack("C*").each do |c|
play_sound("num" + [c].pack("C"))
end
play_sound('closed')
end
def on_session_close(session, reason='')
sid = session.sid.to_s
play_sound('session')
sid.unpack("C*").each do |c|
play_sound("num" + [c].pack("C"))
end
play_sound('closed')
end
def on_plugin_load
play_sound('plugin_load')
end
def on_plugin_load
play_sound('plugin_load')
end
def on_plugin_unload
play_sound('plugin_unload')
end
def on_plugin_unload
play_sound('plugin_unload')
end
def start_sound_queue
self.queue_thread = Thread.new do
begin
while(true)
while(event = self.queue.shift)
path = ::File.join(self.base, self.theme, "#{event}.wav")
if(::File.exists?(path))
Rex::Compat.play_sound(path)
else
print_status("Warning: sound file not found: #{path}")
end
end
select(nil, nil, nil, 0.25)
end
rescue ::Exception => e
print_status("Sound plugin: fatal error #{e} #{e.backtrace}")
end
end
end
def start_sound_queue
self.queue_thread = Thread.new do
begin
while(true)
while(event = self.queue.shift)
path = ::File.join(self.base, self.theme, "#{event}.wav")
if(::File.exists?(path))
Rex::Compat.play_sound(path)
else
print_status("Warning: sound file not found: #{path}")
end
end
select(nil, nil, nil, 0.25)
end
rescue ::Exception => e
print_status("Sound plugin: fatal error #{e} #{e.backtrace}")
end
end
end
def stop_sound_queue
self.queue_thread.kill if self.queue_thread
self.queue_thread = nil
self.queue = []
end
def stop_sound_queue
self.queue_thread.kill if self.queue_thread
self.queue_thread = nil
self.queue = []
end
def initialize(framework, opts)
super
def initialize(framework, opts)
super
self.queue = []
self.theme = opts['theme'] || 'default'
self.base = File.join(Msf::Config.install_root, "data", "sounds")
self.framework.events.add_session_subscriber(self)
start_sound_queue
self.queue = []
self.theme = opts['theme'] || 'default'
self.base = File.join(Msf::Config.install_root, "data", "sounds")
self.framework.events.add_session_subscriber(self)
start_sound_queue
self.on_plugin_load
end
self.on_plugin_load
end
def cleanup
self.on_plugin_unload
self.framework.events.remove_session_subscriber(self)
stop_sound_queue
end
def cleanup
self.on_plugin_unload
self.framework.events.remove_session_subscriber(self)
stop_sound_queue
end
def name
"sounds"
end
def name
"sounds"
end
def desc
"Automatically plays a sound when various framework events occur"
end
def desc
"Automatically plays a sound when various framework events occur"
end
end
end
+99 -99
View File
@@ -15,121 +15,121 @@ module Msf
###
class Plugin::ThreadTest < Msf::Plugin
###
#
# This class implements a sample console command dispatcher.
#
###
class ConsoleCommandDispatcher
include Msf::Ui::Console::CommandDispatcher
###
#
# This class implements a sample console command dispatcher.
#
###
class ConsoleCommandDispatcher
include Msf::Ui::Console::CommandDispatcher
#
# The dispatcher's name.
#
def name
"ThreadTest"
end
#
# The dispatcher's name.
#
def name
"ThreadTest"
end
#
# Returns the hash of commands supported by this dispatcher.
#
def commands
{
"start_thread" => "Start a background thread that writes to the console",
"stop_thread" => "Stop a background thread",
"list_thread" => "List running threads"
}
end
#
# Returns the hash of commands supported by this dispatcher.
#
def commands
{
"start_thread" => "Start a background thread that writes to the console",
"stop_thread" => "Stop a background thread",
"list_thread" => "List running threads"
}
end
def cmd_start_thread(*args)
if (@mythread)
print_line("Test thread is already running")
return
end
def cmd_start_thread(*args)
if (@mythread)
print_line("Test thread is already running")
return
end
@mythread = ::Thread.new {
while(true)
print_line("--- test thread ---")
select(nil, nil, nil, 5)
end
}
print_line("Test thread created")
end
@mythread = ::Thread.new {
while(true)
print_line("--- test thread ---")
select(nil, nil, nil, 5)
end
}
print_line("Test thread created")
end
def cmd_stop_thread(*args)
if (! @mythread)
print_line("No test thread is running")
return
end
def cmd_stop_thread(*args)
if (! @mythread)
print_line("No test thread is running")
return
end
@mythread.kill
@mythread = nil
print_line("Test thread stopped")
end
@mythread.kill
@mythread = nil
print_line("Test thread stopped")
end
def cmd_list_thread(*args)
Thread.list.each do |t|
print_line(sprintf("Thread: 0x%.8x (%s/%d) (%s)", t.object_id, t.status, t.priority, t.tsource))
print_line("")
end
end
end
def cmd_list_thread(*args)
Thread.list.each do |t|
print_line(sprintf("Thread: 0x%.8x (%s/%d) (%s)", t.object_id, t.status, t.priority, t.tsource))
print_line("")
end
end
end
#
# The constructor is called when an instance of the plugin is created. The
# framework instance that the plugin is being associated with is passed in
# the framework parameter. Plugins should call the parent constructor when
# inheriting from Msf::Plugin to ensure that the framework attribute on
# their instance gets set.
#
def initialize(framework, opts)
super
#
# The constructor is called when an instance of the plugin is created. The
# framework instance that the plugin is being associated with is passed in
# the framework parameter. Plugins should call the parent constructor when
# inheriting from Msf::Plugin to ensure that the framework attribute on
# their instance gets set.
#
def initialize(framework, opts)
super
# If this plugin is being loaded in the context of a console application
# that uses the framework's console user interface driver, register
# console dispatcher commands.
add_console_dispatcher(ConsoleCommandDispatcher)
# If this plugin is being loaded in the context of a console application
# that uses the framework's console user interface driver, register
# console dispatcher commands.
add_console_dispatcher(ConsoleCommandDispatcher)
# Extend the thread to track the calling source
Thread.class_eval("
attr_accessor :tsource
# Extend the thread to track the calling source
Thread.class_eval("
attr_accessor :tsource
alias initialize_old initialize
alias initialize_old initialize
def initialize(&block)
self.tsource = caller(1)
initialize_old(&block)
end
")
def initialize(&block)
self.tsource = caller(1)
initialize_old(&block)
end
")
print_status("ThreadTest plugin loaded.")
end
print_status("ThreadTest plugin loaded.")
end
#
# The cleanup routine for plugins gives them a chance to undo any actions
# they may have done to the framework. For instance, if a console
# dispatcher was added, then it should be removed in the cleanup routine.
#
def cleanup
# If we had previously registered a console dispatcher with the console,
# deregister it now.
remove_console_dispatcher('ThreadTest')
end
#
# The cleanup routine for plugins gives them a chance to undo any actions
# they may have done to the framework. For instance, if a console
# dispatcher was added, then it should be removed in the cleanup routine.
#
def cleanup
# If we had previously registered a console dispatcher with the console,
# deregister it now.
remove_console_dispatcher('ThreadTest')
end
#
# This method returns a short, friendly name for the plugin.
#
def name
"threadtest"
end
#
# This method returns a short, friendly name for the plugin.
#
def name
"threadtest"
end
#
# This method returns a brief description of the plugin. It should be no
# more than 60 characters, but there are no hard limits.
#
def desc
"Thread testing plugin"
end
#
# This method returns a brief description of the plugin. It should be no
# more than 60 characters, but there are no hard limits.
#
def desc
"Thread testing plugin"
end
protected
end
+80 -80
View File
@@ -15,103 +15,103 @@ module Msf
class Plugin::TokenAdduser < Msf::Plugin
class TokenCommandDispatcher
include Msf::Ui::Console::CommandDispatcher
class TokenCommandDispatcher
include Msf::Ui::Console::CommandDispatcher
def name
"Token Adduser"
end
def name
"Token Adduser"
end
def commands
{
'token_adduser' => "Attempt to add an account using all connected meterpreter session tokens"
}
end
def commands
{
'token_adduser' => "Attempt to add an account using all connected meterpreter session tokens"
}
end
def cmd_token_adduser(*args)
def cmd_token_adduser(*args)
opts = Rex::Parser::Arguments.new(
"-h" => [ true, "Add account to host"],
)
opts = Rex::Parser::Arguments.new(
"-h" => [ true, "Add account to host"],
)
# This is ugly.
if (args.length == 0)
print_line("Usage: token_adduser [options] <username> <password>")
print_line(opts.usage)
return
end
opt_user_pass = []
username = nil
password = nil
host = nil
opts.parse(args) do |opt, idx, val|
case opt
when "-h"
host = val
# This is ugly.
if (args.length == 0)
print_line("Usage: token_adduser [options] <username> <password>")
print_line(opts.usage)
return
end
opt_user_pass = []
username = nil
password = nil
host = nil
opts.parse(args) do |opt, idx, val|
case opt
when "-h"
host = val
else
# Excuse my weak ruby skills. I'm sure there's a better way to get username and password
# from the args.
opt_user_pass << val
end
end
else
# Excuse my weak ruby skills. I'm sure there's a better way to get username and password
# from the args.
opt_user_pass << val
end
end
# Again, I'm sure there's a better way to do this.
username = opt_user_pass[0]
password = opt_user_pass[1]
# Again, I'm sure there's a better way to do this.
username = opt_user_pass[0]
password = opt_user_pass[1]
tokens_del = {}
tokens_imp = {}
tokens_del = {}
tokens_imp = {}
framework.sessions.each_key do |sid|
session = framework.sessions[sid]
next unless session.type == "meterpreter"
framework.sessions.each_key do |sid|
session = framework.sessions[sid]
next unless session.type == "meterpreter"
print_status(">> Opening session #{session.sid} / #{session.session_host}")
print_status(">> Opening session #{session.sid} / #{session.session_host}")
unless session.incognito
session.core.use("incognito")
end
unless session.incognito
session.core.use("incognito")
end
unless session.incognito
print_status("!! Failed to load incognito on #{session.sid} / #{session.session_host}")
next
end
#print "DEBUG #{username} #{password}\n"
res = session.incognito.incognito_add_user(host,username,password)
if(res)
print "#{res}\n"
unless session.incognito
print_status("!! Failed to load incognito on #{session.sid} / #{session.session_host}")
next
end
#print "DEBUG #{username} #{password}\n"
res = session.incognito.incognito_add_user(host,username,password)
if(res)
print "#{res}\n"
# Currently only stops on success if a user is trying to be added to a specific
# host. I can't think of a good reason to stop on success (or even make it an option)
# when trying to add a user to local sessions.
if (host)
if res =~ /\[\+\] Successfully|\[\-\] Password does not meet complexity requirements|\[\-\] User already exists/
break
end
end
end
end
end
end
# Currently only stops on success if a user is trying to be added to a specific
# host. I can't think of a good reason to stop on success (or even make it an option)
# when trying to add a user to local sessions.
if (host)
if res =~ /\[\+\] Successfully|\[\-\] Password does not meet complexity requirements|\[\-\] User already exists/
break
end
end
end
end
end
end
def initialize(framework, opts)
super
add_console_dispatcher(TokenCommandDispatcher)
end
def initialize(framework, opts)
super
add_console_dispatcher(TokenCommandDispatcher)
end
def cleanup
remove_console_dispatcher('Token Adduser')
end
def cleanup
remove_console_dispatcher('Token Adduser')
end
def name
"token_adduser"
end
def name
"token_adduser"
end
def desc
"Attempt to add an account using all connected meterpreter session tokens"
end
def desc
"Attempt to add an account using all connected meterpreter session tokens"
end
end
end
+110 -110
View File
@@ -7,147 +7,147 @@ module Msf
class Plugin::TokenHunter < Msf::Plugin
class TokenCommandDispatcher
include Msf::Ui::Console::CommandDispatcher
class TokenCommandDispatcher
include Msf::Ui::Console::CommandDispatcher
def name
"Token Hunter"
end
def name
"Token Hunter"
end
def commands
{
'token_hunt_user' => "Scan all connected meterpreter sessions for active tokens corresponding to one or more users"
}
end
def commands
{
'token_hunt_user' => "Scan all connected meterpreter sessions for active tokens corresponding to one or more users"
}
end
def cmd_token_hunt_user(*args)
def cmd_token_hunt_user(*args)
opts = Rex::Parser::Arguments.new(
"-h" => [ false, "This help menu"],
"-f" => [ true, "A file containing a list of users to search for (one per line)"]
)
opts = Rex::Parser::Arguments.new(
"-h" => [ false, "This help menu"],
"-f" => [ true, "A file containing a list of users to search for (one per line)"]
)
opt_userfile = nil
opt_users = []
opt_userfile = nil
opt_users = []
opts.parse(args) do |opt, idx, val|
case opt
when "-h"
print_line("Usage: token_hunt_user [options] <username> [username] .. [username]")
print_line(opts.usage)
return
when "-f"
opt_userfile = val
else
opt_users << val
end
end
opts.parse(args) do |opt, idx, val|
case opt
when "-h"
print_line("Usage: token_hunt_user [options] <username> [username] .. [username]")
print_line(opts.usage)
return
when "-f"
opt_userfile = val
else
opt_users << val
end
end
if(opt_userfile)
::File.open(opt_userfile, "rb") do |fd|
fd.each_line do |line|
line.strip!
next if line.empty?
next if line =~ /^#/
opt_users << line
end
end
end
if(opt_userfile)
::File.open(opt_userfile, "rb") do |fd|
fd.each_line do |line|
line.strip!
next if line.empty?
next if line =~ /^#/
opt_users << line
end
end
end
opt_users.uniq!
opt_users.uniq!
tokens_del = {}
tokens_imp = {}
tokens_del = {}
tokens_imp = {}
framework.sessions.each_key do |sid|
session = framework.sessions[sid]
next if session.type != "meterpreter"
framework.sessions.each_key do |sid|
session = framework.sessions[sid]
next if session.type != "meterpreter"
print_status(">> Scanning session #{session.sid} / #{session.session_host}")
print_status(">> Scanning session #{session.sid} / #{session.session_host}")
if(! session.incognito)
session.core.use("incognito")
end
if(! session.incognito)
session.core.use("incognito")
end
if(! session.incognito)
print_status("!! Failed to load incognito on #{session.sid} / #{session.session_host}")
next
end
if(! session.incognito)
print_status("!! Failed to load incognito on #{session.sid} / #{session.session_host}")
next
end
res = session.incognito.incognito_list_tokens(0)
if(res)
res["delegation"].split("\n").each do |user|
res = session.incognito.incognito_list_tokens(0)
if(res)
res["delegation"].split("\n").each do |user|
opt_users.each do |needle|
opt_users.each do |needle|
ndom,nusr = needle.split("\\")
if(not nusr)
nusr = ndom
ndom = nil
end
ndom,nusr = needle.split("\\")
if(not nusr)
nusr = ndom
ndom = nil
end
if(not user.nil? and ndom and user.strip.downcase == needle.strip.downcase)
print_status("FOUND: #{session.sid} - #{session.session_host} - #{user} (delegation)")
next
end
if(not user.nil? and ndom and user.strip.downcase == needle.strip.downcase)
print_status("FOUND: #{session.sid} - #{session.session_host} - #{user} (delegation)")
next
end
fdom,fusr = user.split("\\")
fdom,fusr = user.split("\\")
if (not fusr.nil? and ! ndom and fusr.strip.downcase == nusr.strip.downcase)
print_status("FOUND: #{session.sid} - #{session.session_host} - #{user} (delegation)")
end
end
if (not fusr.nil? and ! ndom and fusr.strip.downcase == nusr.strip.downcase)
print_status("FOUND: #{session.sid} - #{session.session_host} - #{user} (delegation)")
end
end
tokens_del[user] ||= []
tokens_del[user] << session.sid
end
tokens_del[user] ||= []
tokens_del[user] << session.sid
end
res["impersonation"].split("\n").each do |user|
res["impersonation"].split("\n").each do |user|
opt_users.each do |needle|
ndom,nusr = needle.split("\\")
if(not nusr)
nusr = ndom
ndom = nil
end
opt_users.each do |needle|
ndom,nusr = needle.split("\\")
if(not nusr)
nusr = ndom
ndom = nil
end
if(not user.nil? and ndom and user.strip.downcase == needle.strip.downcase)
print_status(">> Found #{session.sid} - #{session.session_host} - #{user} (impersonation)")
next
end
if(not user.nil? and ndom and user.strip.downcase == needle.strip.downcase)
print_status(">> Found #{session.sid} - #{session.session_host} - #{user} (impersonation)")
next
end
fdom,fusr = user.split("\\")
if (not fusr.nil? and ! ndom and fusr.strip.downcase == nusr.strip.downcase)
print_status(">> Found #{session.sid} - #{session.session_host} - #{user} (impersonation)")
end
end
fdom,fusr = user.split("\\")
if (not fusr.nil? and ! ndom and fusr.strip.downcase == nusr.strip.downcase)
print_status(">> Found #{session.sid} - #{session.session_host} - #{user} (impersonation)")
end
end
tokens_imp[user] ||= []
tokens_imp[user] << session.sid
end
end
end
end
end
tokens_imp[user] ||= []
tokens_imp[user] << session.sid
end
end
end
end
end
def initialize(framework, opts)
super
add_console_dispatcher(TokenCommandDispatcher)
end
def initialize(framework, opts)
super
add_console_dispatcher(TokenCommandDispatcher)
end
def cleanup
remove_console_dispatcher('Token Hunter')
end
def cleanup
remove_console_dispatcher('Token Hunter')
end
def name
"token_hunter"
end
def name
"token_hunter"
end
def desc
"Search all active meterpreter sessions for specific tokens"
end
def desc
"Search all active meterpreter sessions for specific tokens"
end
end
end
+2271 -2271
View File
@@ -13,2277 +13,2277 @@ require 'msf/core/rpc/v10/client'
module Msf
class Plugin::Wmap < Msf::Plugin
class WmapCommandDispatcher
attr_accessor :wmapmodules # Enabled Wmap modules
attr_accessor :targets # Targets
attr_accessor :lastsites # Temp location of previously obtained sites
attr_accessor :rpcarr # Array or rpc connections
attr_accessor :njobs # Max number of jobs
attr_accessor :nmaxdisplay # Flag to stop displaying the same mesg
attr_accessor :runlocal # Flag to run local modules only
attr_accessor :masstop # Flag to stop everything
attr_accessor :killwhenstop # Kill process when exiting
include Msf::Ui::Console::CommandDispatcher
def name
"wmap"
end
#
# The initial command set
#
def commands
{
"wmap_targets" => "Manage targets",
"wmap_sites" => "Manage sites",
"wmap_nodes" => "Manage nodes",
"wmap_run" => "Test targets",
"wmap_modules" => "Manage wmap modules",
"wmap_vulns" => "Display web vulns",
}
end
def cmd_wmap_vulns(*args)
args.push("-h") if args.length == 0
while (arg = args.shift)
case arg
when '-l'
view_vulns
return
when '-h'
print_status("Usage: wmap_vulns [options]")
print_line("\t-h Display this help text")
print_line("\t-l Display web vulns table")
print_line("")
return
else
print_error("Unknown flag.")
return
end
end
end
def cmd_wmap_modules(*args)
args.push("-h") if args.length == 0
while (arg = args.shift)
case arg
when '-l'
view_modules
return
when '-r'
load_wmap_modules(true)
return
when '-h'
print_status("Usage: wmap_modules [options]")
print_line("\t-h Display this help text")
print_line("\t-l List all wmap enabled modules")
print_line("\t-r Reload wmap modules")
print_line("")
return
else
print_error("Unknown flag.")
return
end
end
end
def cmd_wmap_targets(*args)
args.push("-h") if args.length == 0
while (arg = args.shift)
case arg
when '-c'
self.targets = Hash.new()
when '-l'
view_targets
return
when '-t'
process_urls(args.shift)
when '-d'
process_ids(args.shift)
when '-h'
print_status("Usage: wmap_targets [options]")
print_line("\t-h Display this help text")
print_line("\t-t [urls] Define target sites (vhost1,url[space]vhost2,url) ")
print_line("\t-d [ids] Define target sites (id1, id2, id3 ...)")
print_line("\t-c Clean target sites list")
print_line("\t-l List all target sites")
print_line("")
return
else
print_error("Unknown flag.")
return
end
end
end
def cmd_wmap_sites(*args)
args.push("-h") if args.length == 0
while (arg = args.shift)
case arg
when '-a'
s = add_web_site(args.shift)
if s
print_status("Site created.")
else
print_error("Unable to create site")
end
when '-d'
del_idx = args
if del_idx
delete_sites(del_idx.select {|d| d =~ /^[0-9]*$/}.map(&:to_i).uniq)
return
else
print_error("Provide index of site to delete")
end
when '-l'
view_sites
return
when '-s'
u = args.shift
l = args.shift
s = args.shift
if not u
return
end
if l == nil or l.empty?
l = 200
s = true
else
l = l.to_i
s = false
end
if u.include? 'http'
# Parameters are in url form
view_site_tree(u,l,s)
else
# Parameters are digits
if !self.lastsites or self.lastsites.length == 0
view_sites
print_status ("Web sites ids. referenced from previous table.")
end
target_whitelist = []
ids = u.to_s.split(/,/)
ids.each do |id|
next if id.to_s.strip.empty?
if id.to_i > self.lastsites.length
print_error("Skipping id #{id}...")
else
target_whitelist << self.lastsites[id.to_i]
#print_status("Loading #{self.lastsites[id.to_i]}.")
end
end
# Skip the DB entirely if no matches
return if target_whitelist.length == 0
if not self.targets
self.targets = Hash.new()
end
target_whitelist.each do |ent|
view_site_tree(ent,l,s)
end
end
return
when '-h'
print_status("Usage: wmap_sites [options]")
print_line("\t-h Display this help text")
print_line("\t-a [url] Add site (vhost,url)")
print_line("\t-d [ids] Delete sites (separate ids with space)")
print_line("\t-l List all available sites")
print_line("\t-s [id] Display site structure (vhost,url|ids) (level)")
print_line("")
return
else
print_error("Unknown flag.")
return
end
end
end
def cmd_wmap_nodes(*args)
if not self.rpcarr
self.rpcarr=Hash.new()
end
args.push("-h") if args.length == 0
while (arg = args.shift)
case arg
when '-a'
h = args.shift
r = args.shift
s = args.shift
u = args.shift
p = args.shift
res = rpc_add_node(h,r,s,u,p,false)
if res
print_status("Node created.")
else
print_error("Unable to create node")
end
when '-c'
idref = args.shift
if not idref
print_error("No id defined")
return
end
if idref.upcase == 'ALL'
print_status("All nodes removed")
self.rpcarr = Hash.new()
else
idx=0
self.rpcarr.each do |k,v|
if idx == idref.to_i
self.rpcarr.delete(k)
print_status("Node deleted #{k}")
end
idx += 1
end
end
when '-d'
host = args.shift
port = args.shift
user = args.shift
pass = args.shift
dbname = args.shift
res = rpc_db_nodes(host,port,user,pass,dbname)
if res
print_status("OK.")
else
print_error("Error")
end
when '-l'
rpc_list_nodes
return
when '-j'
rpc_view_jobs
return
when '-k'
node = args.shift
jid = args.shift
rpc_kill_node(node,jid)
return
when '-h'
print_status("Usage: wmap_nodes [options]")
print_line("\t-h Display this help text")
print_line("\t-c id Remove id node (Use ALL for ALL nodes")
print_line("\t-a host port ssl user pass Add node")
print_line("\t-d host port user pass db Force all nodes to connect to db")
print_line("\t-j View detailed jobs")
print_line("\t-k ALL|id ALL|job_id Kill jobs on node")
print_line("\t-l List all current nodes")
print_line("")
return
else
print_error("Unknown flag.")
return
end
end
end
def cmd_wmap_run(*args)
# Stop everything
self.masstop = false
self.killwhenstop = true
trap("INT") {
print_error("Stopping execution...")
self.masstop = true
if self.killwhenstop
rpc_kill_node('ALL','ALL')
end
}
# Max numbers of concurrent jobs per node
self.njobs = 25
self.nmaxdisplay = false
self.runlocal = false
# Formating
sizeline = 60
wmap_show = 2**0
wmap_expl = 2**1
# Exclude files can be modified by setting datastore['WMAP_EXCLUDE']
wmap_exclude_files = '.*\.(gif|jpg|png*)$'
run_wmap_ssl = true
run_wmap_server = true
run_wmap_dir_file = true
run_wmap_query = true
run_wmap_unique_query = true
run_wmap_generic = true
# If module supports datastore['VERBOSE']
moduleverbose = false
showprogress = false
if not self.rpcarr
self.rpcarr = Hash.new()
end
if not run_wmap_ssl
print_status("Loading of wmap ssl modules disabled.")
end
if not run_wmap_server
print_status("Loading of wmap server modules disabled.")
end
if not run_wmap_dir_file
print_status("Loading of wmap dir and file modules disabled.")
end
if not run_wmap_query
print_status("Loading of wmap query modules disabled.")
end
if not run_wmap_unique_query
print_status("Loading of wmap unique query modules disabled.")
end
if not run_wmap_generic
print_status("Loading of wmap generic modules disabled.")
end
stamp = Time.now.to_f
mode = 0
eprofile = []
using_p = false
using_m = false
usinginipath = false
mname = ''
inipathname = '/'
args.push("-h") if args.length == 0
while (arg = args.shift)
case arg
when '-t'
mode |= wmap_show
when '-e'
mode |= wmap_expl
profile = args.shift
if profile
print_status("Using profile #{profile}.")
begin
File.open(profile).each do |str|
if not str.include? '#'
# Not a comment
modname = str.strip
if not modname.empty?
eprofile << modname
end
end
using_p = true
end
rescue
print_error("Profile not found or invalid.")
return
end
else
print_status("Using ALL wmap enabled modules.")
end
when '-m'
mode |= wmap_expl
mname = args.shift
if mname
print_status("Using module #{mname}.")
end
using_m = true
when '-p'
mode |= wmap_expl
inipathname = args.shift
if inipathname
print_status("Using initial path #{inipathname}.")
end
usinginipath = true
when '-h'
print_status("Usage: wmap_run [options]")
print_line("\t-h Display this help text")
print_line("\t-t Show all enabled modules")
print_line("\t-m [regex] Launch only modules that name match provided regex.")
print_line("\t-p [regex] Only test path defined by regex.")
print_line("\t-e [/path/to/profile] Launch profile modules against all matched targets.")
print_line("\t (No profile file runs all enabled modules.)")
print_line("")
return
else
print_error("Unknown flag")
return
end
end
if (self.rpcarr.length == 0) and (mode & wmap_show == 0)
print_error("NO WMAP NODES DEFINED. Executing local modules")
self.runlocal = true
end
if self.targets == nil
print_error("Targets have not been selected.")
return
end
if self.targets.keys.length == 0
print_error("Targets have not been selected.")
return
end
execmod = true
if (mode & wmap_show != 0)
execmod = false
end
self.targets.each_with_index do |t, idx|
selected_host = t[1][:host]
selected_port = t[1][:port]
selected_ssl = t[1][:ssl]
selected_vhost = t[1][:vhost]
print_status ("Testing target:")
print_status ("\tSite: #{selected_vhost} (#{selected_host})")
print_status ("\tPort: #{selected_port} SSL: #{selected_ssl}")
print_line '='* sizeline
print_status("Testing started. #{(Time.now )}")
if not selected_ssl
run_wmap_ssl = false
#print_status ("Target is not SSL. SSL modules disabled.")
end
# wmap_dir, wmap_file
matches = Hash.new()
# wmap_server
matches1 = Hash.new()
# wmap_query
matches2 = Hash.new()
# wmap_ssl
matches3 = Hash.new()
# wmap_unique_query
matches5 = Hash.new()
# wmap_generic
matches10 = Hash.new()
# OPTIONS
opt_str = nil
jobify = false
# This will be clean later
load_wmap_modules(false)
self.wmapmodules.each do |w|
case w[2]
when :wmap_server
if run_wmap_server
matches1[w]=true
end
when :wmap_query
if run_wmap_query
matches2[w]=true
end
when :wmap_unique_query
if run_wmap_unique_query
matches5[w]=true
end
when :wmap_generic
if run_wmap_generic
matches10[w]=true
end
when :wmap_dir, :wmap_file
if run_wmap_dir_file
matches[w]=true
end
when :wmap_ssl
if run_wmap_ssl
matches3[w]=true
end
else
# Black Hole
end
end
# Execution order (orderid)
matches = sort_by_orderid(matches)
matches1 = sort_by_orderid(matches1)
matches2 = sort_by_orderid(matches2)
matches3 = sort_by_orderid(matches3)
matches5 = sort_by_orderid(matches5)
matches10 = sort_by_orderid(matches10)
#
# Handle modules that need to be run before all tests IF SERVER is SSL, once usually again the SSL web server.
# :wmap_ssl
#
print_status "\n=[ SSL testing ]="
print_line "=" * sizeline
if not selected_ssl
print_status ("Target is not SSL. SSL modules disabled.")
end
idx = 0
matches3.each_key do |xref|
if self.masstop
print_error("STOPPED.")
return
end
# Module not part of profile or not match
if ( using_p and eprofile.include? xref[0].split('/').last ) or (using_m and xref[0].to_s.match(mname)) or (not using_m and not using_p)
idx += 1
begin
# Module options hash
modopts = Hash.new()
#
# The code is just a proof-of-concept and will be expanded in the future
#
print_status "Module #{xref[0]}"
if (mode & wmap_expl != 0)
#
# For modules to have access to the global datastore
# i.e. set -g DOMAIN test.com
#
self.framework.datastore.each do |gkey,gval|
modopts[gkey]=gval
end
#
# Parameters passed in hash xref
#
modopts['RHOST'] = selected_host
modopts['RHOSTS'] = selected_host
modopts['RPORT'] = selected_port.to_s
modopts['SSL'] = selected_ssl
modopts['VHOST'] = selected_vhost.to_s
modopts['VERBOSE'] = moduleverbose
modopts['ShowProgress'] = showprogress
modopts['RunAsJob'] = jobify
begin
if execmod
rpcnode = rpc_round_exec(xref[0],xref[1], modopts, self.njobs)
end
rescue ::Exception
print_status(" >> Exception during launch from #{xref[0]}: #{$!}")
end
end
rescue ::Exception
print_status(" >> Exception from #{xref[0]}: #{$!}")
end
end
end
#
# Handle modules that need to be run before all tests, once usually again the web server.
# :wmap_server
#
print_status "\n=[ Web Server testing ]="
print_line "=" * sizeline
idx = 0
matches1.each_key do |xref|
if self.masstop
print_error("STOPPED.")
return
end
# Module not part of profile or not match
if ( using_p and eprofile.include? xref[0].split('/').last ) or (using_m and xref[0].to_s.match(mname)) or (not using_m and not using_p)
idx += 1
begin
# Module options hash
modopts = Hash.new()
#
# The code is just a proof-of-concept and will be expanded in the future
#
print_status "Module #{xref[0]}"
if (mode & wmap_expl != 0)
#
# For modules to have access to the global datastore
# i.e. set -g DOMAIN test.com
#
self.framework.datastore.each do |gkey,gval|
modopts[gkey]=gval
end
#
# Parameters passed in hash xref
#
modopts['RHOST'] = selected_host
modopts['RHOSTS'] = selected_host
modopts['RPORT'] = selected_port.to_s
modopts['SSL'] = selected_ssl
modopts['VHOST'] = selected_vhost.to_s
modopts['VERBOSE'] = moduleverbose
modopts['ShowProgress'] = showprogress
modopts['RunAsJob'] = jobify
begin
if execmod
rpcnode = rpc_round_exec(xref[0],xref[1], modopts, self.njobs)
end
rescue ::Exception
print_status(" >> Exception during launch from #{xref[0]}: #{$!}")
end
end
rescue ::Exception
print_status(" >> Exception from #{xref[0]}: #{$!}")
end
end
end
#
# Handle modules to be run at every path/file
# wmap_dir, wmap_file
#
print_status "\n=[ File/Dir testing ]="
print_line "=" * sizeline
idx = 0
matches.each_key do |xref|
if self.masstop
print_error("STOPPED.")
return
end
# Module not part of profile or not match
if ( using_p and eprofile.include? xref[0].split('/').last ) or (using_m and xref[0].to_s.match(mname)) or (not using_m and not using_p)
idx+=1
begin
# Module options hash
modopts = Hash.new()
#
# The code is just a proof-of-concept and will be expanded in the future
#
print_status "Module #{xref[0]}"
if (mode & wmap_expl != 0)
#
# For modules to have access to the global datastore
# i.e. set -g DOMAIN test.com
#
self.framework.datastore.each do |gkey,gval|
modopts[gkey]=gval
end
#
# Parameters passed in hash xref
#
modopts['RHOST'] = selected_host
modopts['RHOSTS'] = selected_host
modopts['RPORT'] = selected_port.to_s
modopts['SSL'] = selected_ssl
modopts['VHOST'] = selected_vhost.to_s
modopts['VERBOSE'] = moduleverbose
modopts['ShowProgress'] = showprogress
modopts['RunAsJob'] = jobify
#
# Run the plugins that only need to be
# launched once.
#
wtype = xref[2]
h = self.framework.db.workspace.hosts.find_by_address(selected_host)
s = h.services.find_by_port(selected_port)
w = s.web_sites.find_by_vhost(selected_vhost)
test_tree = load_tree(w)
test_tree.each do |node|
if self.masstop
print_error("STOPPED.")
return
end
p = node.current_path
testpath = Pathname.new(p)
strpath = testpath.cleanpath(false).to_s
#
# Fixing paths
#
if node.is_leaf? and not node.is_root?
#
# Later we can add here more checks to see if its a file
#
else
if node.is_root?
strpath = "/"
else
strpath = strpath.chomp + "/"
end
end
strpath = strpath.gsub("//", "/")
#print_status("Testing path: #{strpath}")
#
# Launch plugin depending module type.
# Module type depends on main input type.
# Code may be the same but it depend on final
# versions of plugins
#
case wtype
when :wmap_file
if node.is_leaf? and not node.is_root?
#
# Check if an exclusion regex has been defined
#
if self.framework.datastore['WMAP_EXCLUDE']
excludefilestr = self.framework.datastore['WMAP_EXCLUDE']
else
excludefilestr = wmap_exclude_files
end
if not strpath.match(excludefilestr)
if (not usinginipath) or (usinginipath and strpath.match(inipathname))
modopts['PATH'] = strpath
print_status("Path: #{strpath}")
begin
if execmod
rpcnode = rpc_round_exec(xref[0],xref[1], modopts, self.njobs)
end
rescue ::Exception
print_status(" >> Exception during launch from #{xref[0]}: #{$!}")
end
end
end
end
when :wmap_dir
if (node.is_leaf? and not strpath.include? ".") or node.is_root? or not node.is_leaf?
if (not usinginipath) or (usinginipath and strpath.match(inipathname))
modopts['PATH'] = strpath
print_status("Path: #{strpath}")
begin
if execmod
rpcnode = rpc_round_exec(xref[0],xref[1], modopts, njobs)
end
rescue ::Exception
print_status(" >> Exception during launch from #{xref[0]}: #{$!}")
end
end
end
end
end
end
rescue ::Exception
print_status(" >> Exception from #{xref[0]}: #{$!}")
end
end
end
#
# Run modules for each request to play with URI with UNIQUE query parameters.
# wmap_unique_query
#
print_status "\n=[ Unique Query testing ]="
print_line "=" * sizeline
idx = 0
matches5.each_key do |xref|
if self.masstop
print_error("STOPPED.")
return
end
# Module not part of profile or not match
if ( using_p and eprofile.include? xref[0].split('/').last ) or (using_m and xref[0].to_s.match(mname)) or (not using_m and not using_p)
idx += 1
begin
# Module options hash
modopts = Hash.new()
#
# The code is just a proof-of-concept and will be expanded in the future
#
print_status "Module #{xref[0]}"
if (mode & wmap_expl != 0)
#
# For modules to have access to the global datastore
# i.e. set -g DOMAIN test.com
#
self.framework.datastore.each do |gkey,gval|
modopts[gkey]=gval
end
#
# Parameters passed in hash xref
#
modopts['RHOST'] = selected_host
modopts['RHOSTS'] = selected_host
modopts['RPORT'] = selected_port.to_s
modopts['SSL'] = selected_ssl
modopts['VHOST'] = selected_vhost.to_s
modopts['VERBOSE'] = moduleverbose
modopts['ShowProgress'] = showprogress
modopts['RunAsJob'] = jobify
#
# Run the plugins for each request that have a distinct
# GET/POST URI QUERY string.
#
utest_query = Hash.new()
h = self.framework.db.workspace.hosts.find_by_address(selected_host)
s = h.services.find_by_port(selected_port)
w = s.web_sites.find_by_vhost(selected_vhost)
w.web_forms.each do |form|
if self.masstop
print_error("STOPPED.")
return
end
#
# Only test unique query strings by comparing signature to previous tested signatures 'path,p1,p2,pn'
#
datastr = ""
typestr = ""
temparr = []
#print_status "---------"
#print_status form.params
#print_status "+++++++++"
form.params.each do |p|
pn, pv, pt = p
if pn
if not pn.empty?
if not pv or pv.empty?
#TODO add value based on param name
pv = "aaa"
end
#temparr << pn.to_s + "=" + Rex::Text.uri_encode(pv.to_s)
temparr << pn.to_s + "=" + pv.to_s
end
else
print_error("Blank parameter name. Form #{form.path}")
end
end
datastr = temparr.join("&") if (temparr and not temparr.empty?)
if (utest_query.has_key?(signature(form.path,datastr)) == false)
modopts['METHOD'] = form.method.upcase
modopts['PATH'] = form.path
modopts['QUERY'] = form.query
if form.method.upcase == 'GET'
modopts['QUERY'] = datastr
modopts['DATA'] = ""
end
if form.method.upcase == 'POST'
modopts['DATA'] = datastr
end
modopts['TYPES'] = typestr
#
# TODO: Add headers, etc.
#
if (not usinginipath) or (usinginipath and form.path.match(inipathname))
print_status "Path #{form.path}"
#print_status("Unique PATH #{modopts['PATH']}")
#print_status("Unique GET #{modopts['QUERY']}")
#print_status("Unique POST #{modopts['DATA']}")
#print_status("MODOPTS: #{modopts}")
begin
if execmod
rpcnode = rpc_round_exec(xref[0],xref[1], modopts, self.njobs)
end
utest_query[signature(form.path,datastr)]=1
rescue ::Exception
print_status(" >> Exception during launch from #{xref[0]}: #{$!}")
end
end
else
#print_status("Already tested")
end
end
end
rescue ::Exception
print_status(" >> Exception from #{xref[0]}: #{$!}")
end
end
end
#
# Run modules for each request to play with URI query parameters.
# This approach will reduce the complexity of the Tree used before
# and will make this shotgun implementation much simple.
# wmap_query
#
print_status "\n=[ Query testing ]="
print_line "=" * sizeline
idx = 0
matches2.each_key do |xref|
if self.masstop
print_error("STOPPED.")
return
end
# Module not part of profile or not match
if not ( using_p and eprofile.include? xref[0].split('/').last ) or (using_m and xref[0].to_s.match(mname)) or (not using_m and not using_p)
idx += 1
begin
# Module options hash
modopts = Hash.new()
#
# The code is just a proof-of-concept and will be expanded in the future
#
print_status "Module #{xref[0]}"
if (mode & wmap_expl != 0)
#
# For modules to have access to the global datastore
# i.e. set -g DOMAIN test.com
#
self.framework.datastore.each do |gkey,gval|
modopts[gkey]=gval
end
#
# Parameters passed in hash xref
#
modopts['RHOST'] = selected_host
modopts['RHOSTS'] = selected_host
modopts['RPORT'] = selected_port.to_s
modopts['SSL'] = selected_ssl
modopts['VHOST'] = selected_vhost.to_s
modopts['VERBOSE'] = moduleverbose
modopts['ShowProgress'] = showprogress
modopts['RunAsJob'] = jobify
#
# Run the plugins for each request that have a distinct
# GET/POST URI QUERY string.
#
h = self.framework.db.workspace.hosts.find_by_address(selected_host)
s = h.services.find_by_port(selected_port)
w = s.web_sites.find_by_vhost(selected_vhost)
w.web_forms.each do |req|
if self.masstop
print_error("STOPPED.")
return
end
datastr = ""
typestr = ""
temparr = []
req.params.each do |p|
pn, pv, pt = p
if pn
if not pn.empty?
if not pv or pv.empty?
#TODO add value based on param name
pv = "aaa"
end
#temparr << pn.to_s + "=" + Rex::Text.uri_encode(pv.to_s)
temparr << pn.to_s + "=" + pv.to_s
end
else
print_error("Blank parameter name. Form #{req.path}")
end
end
datastr = temparr.join("&") if (temparr and not temparr.empty?)
modopts['METHOD'] = req.method.upcase
modopts['PATH'] = req.path
if req.method.upcase == 'GET'
modopts['QUERY'] = datastr
modopts['DATA'] = ""
end
modopts['DATA'] = datastr if req.method.upcase == 'POST'
modopts['TYPES'] = typestr
#
# TODO: Add method, headers, etc.
#
if (not usinginipath) or (usinginipath and req.path.match(inipathname))
print_status "Path #{req.path}"
#print_status("Query PATH #{modopts['PATH']}")
#print_status("Query GET #{modopts['QUERY']}")
#print_status("Query POST #{modopts['DATA']}")
#print_status("Query TYPES #{typestr}")
begin
if execmod
rpcnode = rpc_round_exec(xref[0],xref[1], modopts, self.njobs)
end
rescue ::Exception
print_status(" >> Exception during launch from #{xref[0]}: #{$!}")
end
end
end
end
rescue ::Exception
print_status(" >> Exception from #{xref[0]}: #{$!}")
end
end
end
#
# Handle modules that need to be after all tests, once.
# Good place to have modules that analize the test results and/or
# launch exploits.
# :wmap_generic
#
print_status "\n=[ General testing ]="
print_line "=" * sizeline
idx = 0
matches10.each_key do |xref|
if self.masstop
print_error("STOPPED.")
return
end
# Module not part of profile or not match
if not ( using_p and eprofile.include? xref[0].split('/').last ) or (using_m and xref[0].to_s.match(mname)) or (not using_m and not using_p)
idx += 1
begin
# Module options hash
modopts = Hash.new()
#
# The code is just a proof-of-concept and will be expanded in the future
#
print_status "Module #{xref[0]}"
if (mode & wmap_expl != 0)
#
# For modules to have access to the global datastore
# i.e. set -g DOMAIN test.com
#
self.framework.datastore.each do |gkey,gval|
modopts[gkey]=gval
end
#
# Parameters passed in hash xref
#
modopts['RHOST'] = selected_host
modopts['RHOSTS'] = selected_host
modopts['RPORT'] = selected_port.to_s
modopts['SSL'] = selected_ssl
modopts['VHOST'] = selected_vhost.to_s
modopts['VERBOSE'] = moduleverbose
modopts['ShowProgress'] = showprogress
modopts['RunAsJob'] = jobify
#
# Run the plugins that only need to be
# launched once.
#
begin
if execmod
rpcnode = rpc_round_exec(xref[0],xref[1], modopts, self.njobs)
end
rescue ::Exception
print_status(" >> Exception during launch from #{xref[0]}: #{$!}")
end
end
rescue ::Exception
print_status(" >> Exception from #{xref[0]}: #{$!}")
end
end
end
if (mode & wmap_expl != 0)
print_line "+" * sizeline
if not self.runlocal
if execmod
rpc_list_nodes()
print_status("Note: Use wmap_nodes -l to list node status for completion")
end
end
print_line("Launch completed in #{(Time.now.to_f - stamp)} seconds.")
print_line "+" * sizeline
end
print_status("Done.")
end
# EOM
end
def view_targets
if self.targets == nil or self.targets.keys.length == 0
print_status "No targets have been defined"
return
end
indent = ' '
tbl = Rex::Ui::Text::Table.new(
'Indent' => indent.length,
'Header' => 'Defined targets',
'Columns' =>
[
'Id',
'Vhost',
'Host',
'Port',
'SSL',
'Path',
])
self.targets.each_with_index { |t, idx|
tbl << [ idx.to_s, t[1][:vhost], t[1][:host], t[1][:port], t[1][:ssl], "\t"+t[1][:path].to_s ]
}
print_status tbl.to_s + "\n"
end
def delete_sites(wmap_index)
idx = 0
to_del = {}
# Rebuild the index from wmap_sites -l
self.framework.db.hosts.each do |bdhost|
bdhost.services.each do |serv|
serv.web_sites.each do |web|
# If the index of this site matches any deletion index,
# add to our hash, saving the index for later output
to_del[idx] = web if wmap_index.any? {|w| w.to_i == idx}
idx += 1
end
end
end
to_del.each do |widx,wsite|
if wsite.delete
print_status("Deleted #{wsite.vhost} on #{wsite.service.host.address} at index #{widx}")
else
print_error("Could note delete {wsite.vhost} on #{wsite.service.host.address} at index #{widx}")
end
end
end
def view_sites
# Clean temporary sites list
self.lastsites = []
indent = ' '
tbl = Rex::Ui::Text::Table.new(
'Indent' => indent.length,
'Header' => 'Available sites',
'Columns' =>
[
'Id',
'Host',
'Vhost',
'Port',
'Proto',
'# Pages',
'# Forms',
])
idx = 0
self.framework.db.hosts.each do |bdhost|
bdhost.services.each do |serv|
serv.web_sites.each do |web|
c = web.web_pages.count
f = web.web_forms.count
tbl << [ idx.to_s, bdhost.address, web.vhost, serv.port, serv.name, c.to_s, f.to_s ]
idx += 1
turl = web.vhost + "," + serv.name + "://" +bdhost.address.to_s + ":" + serv.port.to_s + "/"
self.lastsites << turl
end
end
end
print_status tbl.to_s + "\n"
end
# Reusing code from hdmoore
#
# Allow the URL to be supplied as VHOST,URL if a custom VHOST
# should be used. This allows for things like:
# localhost,http://192.168.0.2/admin/
def add_web_site(url)
vhost = nil
# Allow the URL to be supplied as VHOST,URL if a custom VHOST
# should be used. This allows for things like:
# localhost,http://192.168.0.2/admin/
if url !~ /^http/
vhost,url = url.split(",", 2)
if url.to_s.empty?
url = vhost
vhost = nil
end
end
# Prefix http:// when the URL has no specified parameter
if url !~ /^[a-z0-9A-Z]+:\/\//
url = "http://" + url
end
uri = URI.parse(url) rescue nil
if not uri
print_error("Could not understand URL: #{url}")
return
end
if uri.scheme !~ /^https?/
print_error("Only http and https URLs are accepted: #{url}")
return
end
ssl = false
if uri.scheme == 'https'
ssl = true
end
site = self.framework.db.report_web_site(:wait => true, :host => uri.host, :port => uri.port, :vhost => vhost, :ssl => ssl)
return site
end
# Code by hdm. Modified two lines by et
#
def process_urls(urlstr)
target_whitelist = []
urls = urlstr.to_s.split(/\s+/)
urls.each do |url|
next if url.to_s.strip.empty?
vhost = nil
# Allow the URL to be supplied as VHOST,URL if a custom VHOST
# should be used. This allows for things like:
# localhost,http://192.168.0.2/admin/
if url !~ /^http/
vhost,url = url.split(",", 2)
if url.to_s.empty?
url = vhost
vhost = nil
end
end
# Prefix http:// when the URL has no specified parameter
if url !~ /^[a-z0-9A-Z]+:\/\//
url = "http://" + url
end
uri = URI.parse(url) rescue nil
if not uri
print_error("Could not understand URL: #{url}")
next
end
if uri.scheme !~ /^https?/
print_error("Only http and https URLs are accepted: #{url}")
next
end
target_whitelist << [vhost || uri.host, uri]
end
# Skip the DB entirely if no matches
return if target_whitelist.length == 0
if not self.targets
# First time targets are defined
self.targets = Hash.new()
end
target_whitelist.each do |ent|
vhost,target = ent
host = self.framework.db.workspace.hosts.find_by_address(target.host)
if not host
print_error("No matching host for #{target.host}")
next
end
serv = host.services.find_by_port_and_proto(target.port, 'tcp')
if not serv
print_error("No matching service for #{target.host}:#{target.port}")
next
end
#print_status "aaa"
#print_status framework.db.workspace.name
sites = serv.web_sites.where('vhost = ? and service_id = ?', vhost, serv.id)
sites.each do |site|
# Initial defaul path
inipath = target.path
if target.path.empty?
inipath = '/'
end
#site.web_forms.find_all_by_path(target.path).each do |form|
ckey = [ site.vhost, host.address, serv.port, inipath].join("|")
if not self.targets[ckey]
self.targets[ckey] = WebTarget.new
self.targets[ckey].merge!({
:vhost => site.vhost,
:host => host.address,
:port => serv.port,
:ssl => (serv.name == "https"),
:path => inipath
})
#self.targets[ckey][inipath] = []
else
print_status("Target already set in targets list.")
end
# Store the form object in the hash for this path
#self.targets[ckey][inipath] << inipath
#end
end
end
end
# Code by hdm. Modified two lines by et
# lastsites contains a temporary array with vhost,url strings so the id can be
# referenced in the array and prevent new sites added in the db to corrupt previous id list.
def process_ids(idsstr)
if !self.lastsites or self.lastsites.length == 0
view_sites
print_status ("Web sites ids. referenced from previous table.")
end
target_whitelist = []
ids = idsstr.to_s.split(/,/)
ids.each do |id|
next if id.to_s.strip.empty?
if id.to_i > self.lastsites.length
print_error("Skipping id #{id}...")
else
target_whitelist << self.lastsites[id.to_i]
print_status("Loading #{self.lastsites[id.to_i]}.")
end
end
# Skip the DB entirely if no matches
return if target_whitelist.length == 0
if not self.targets
self.targets = Hash.new()
end
target_whitelist.each do |ent|
process_urls(ent)
end
end
def view_site_tree(urlstr, md, ld)
if not urlstr
return
end
site_whitelist = []
urls = urlstr.to_s.split(/\s+/)
urls.each do |url|
next if url.to_s.strip.empty?
vhost = nil
# Allow the URL to be supplied as VHOST,URL if a custom VHOST
# should be used. This allows for things like:
# localhost,http://192.168.0.2/admin/
if url !~ /^http/
vhost,url = url.split(",", 2)
if url.to_s.empty?
url = vhost
vhost = nil
end
end
# Prefix http:// when the URL has no specified parameter
if url !~ /^[a-z0-9A-Z]+:\/\//
url = "http://" + url
end
uri = URI.parse(url) rescue nil
if not uri
print_error("Could not understand URL: #{url}")
next
end
if uri.scheme !~ /^https?/
print_error("Only http and https URLs are accepted: #{url}")
next
end
site_whitelist << [vhost || uri.host, uri]
end
# Skip the DB entirely if no matches
return if site_whitelist.length == 0
vsites = Hash.new()
site_whitelist.each do |ent|
vhost,target = ent
host = self.framework.db.workspace.hosts.find_by_address(target.host)
if not host
print_error("No matching host for #{target.host}")
next
end
serv = host.services.find_by_port_and_proto(target.port, 'tcp')
if not serv
print_error("No matching service for #{target.host}:#{target.port}")
next
end
sites = serv.web_sites.where('vhost = ? and service_id = ?', vhost, serv.id)
sites.each do |site|
t = load_tree(site)
print_tree(t,target.host,md,ld)
print_line("\n")
end
end
end
#
# Load website structure into a tree
#
def load_tree(s)
pathchr = '/'
wtree = Tree.new(s.vhost)
# Load site pages
s.web_pages.find(:all, :order => 'path').each do |req|
tarray = req.path.to_s.split(pathchr)
tarray.delete("")
tpath = Pathname.new(pathchr)
tarray.each do |df|
wtree.add_at_path(tpath.to_s,df)
tpath = tpath + Pathname.new(df.to_s)
end
end
# Load site forms
s.web_forms.each do |req|
tarray = req.path.to_s.split(pathchr)
tarray.delete("")
tpath = Pathname.new(pathchr)
tarray.each do |df|
wtree.add_at_path(tpath.to_s,df)
tpath = tpath + Pathname.new(df.to_s)
end
end
return wtree
end
#
# Print Tree structure. Still ugly
#
def print_tree(tree, ip, maxlevel, limitlevel)
initab = " " * 4
indent = 6
if tree != nil and tree.depth <= maxlevel
print initab + (" " * indent * tree.depth)
if tree.depth > 0
print "|"+("-" * (indent-1))+"/"
end
if tree.depth >= 0
if tree.depth == 0
print "[#{tree.name}] (#{ip})\n"+initab+(" " * indent)+"\n"
else
c = tree.children.count
if c > 0
print tree.name + " (" + c.to_s+")\n"
else
print tree.name + "\n"
end
end
end
tree.children.each_pair do |name,child|
print_tree(child,ip,maxlevel,limitlevel)
end
end
end
def signature(fpath,fquery)
hsig = Hash.new()
hsig = queryparse(fquery)
#
# Signature of the form ',p1,p2,pn' then to be appended to path: path,p1,p2,pn
#
sigstr = fpath + "," + hsig.map{|p| p[0].to_s}.join(",")
end
def queryparse(query)
params = Hash.new()
query.split(/[&;]/n).each do |pairs|
key, value = pairs.split('=',2)
if params.has_key?(key)
#Error
else
params[key] = value
end
end
params
end
def rpc_add_node(host,port,ssl,user,pass,bypass_exist)
if not self.rpcarr
self.rpcarr = Hash.new()
end
begin
istr = "#{host}|#{port}|#{ssl}|#{user}|#{pass}"
if self.rpcarr.has_key?(istr) and not bypass_exist and self.rpcarr[istr] != nil
print_error("Connection already exists #{istr}")
else
begin
temprpc = ::Msf::RPC::Client.new(
:host => host,
:port => port,
:ssl => ssl
)
rescue
print_error "Unable to connect"
#raise ConnectionError
return
end
res = temprpc.login( user , pass)
if not res
print_error("Unable to authenticate to #{host}:#{port}.")
return
else
res = temprpc.call('core.version')
end
print_status("Connected to #{host}:#{port} [#{res['version']}].")
self.rpcarr[istr] = temprpc
end
rescue
print_error("Unable to connect")
end
end
def local_module_exec(mod,mtype, opts, nmaxjobs)
jobify = false
modinst = framework.modules.create(mod)
if(not modinst)
print_error("Unknown module")
return
end
sess = nil
case mtype
when 'auxiliary'
Msf::Simple::Auxiliary.run_simple(modinst, {
'Action' => opts['ACTION'],
'LocalOutput' => driver.output,
'RunAsJob' => jobify,
'Options' => opts
})
when 'exploit'
if not opts['PAYLOAD']
opts['PAYLOAD'] = WmapCommandDispatcher::Exploit.choose_payload(modinst, opts['TARGET'])
end
sess = Msf::Simple::Exploit.exploit_simple(modinst, {
'Payload' => opts['PAYLOAD'],
'Target' => opts['TARGET'],
'LocalOutput' => driver.output,
'RunAsJob' => jobify,
'Options' => opts
})
else
print_error("Wrong mtype.")
end
if sess
if (jobify == false and sess.interactive?)
print_line
driver.run_single("sessions -q -i #{sess.sid}")
else
print_status("Session #{sess.sid} created in the background.")
end
end
end
def rpc_round_exec(mod,mtype, opts, nmaxjobs)
res = nil
idx = 0
if active_rpc_nodes == 0
if not self.runlocal
print_error("All active nodes not working or removed")
return
end
res = true
else
rpc_reconnect_nodes()
end
if self.masstop
return
end
while not res
if active_rpc_nodes == 0
print_error("All active nodes not working or removed")
return
end
#find the node with less jobs load.
minjobs = nmaxjobs
minconn = nil
nid = 0
self.rpcarr.each do |k,rpccon|
if not rpccon
print_error("Skipping inactive node #{nid} #{k}")
else
begin
currentjobs = rpccon.call('job.list').length
if currentjobs < minjobs
minconn = rpccon
minjobs = currentjobs
end
if currentjobs == nmaxjobs
if self.nmaxdisplay == false
print_error("Node #{nid} reached max number of jobs #{nmaxjobs}")
print_error("Waiting for available node/slot...")
self.nmaxdisplay = true
end
end
#print_status("Node #{nid} #currentjobs #{currentjobs} #min #{minjobs}")
rescue
print_error("Unable to connect. Node #{tarr[0]}:#{tarr[1]}")
self.rpcarr[k]=nil
if active_rpc_nodes == 0
print_error("All active nodes ,not working or removed")
return
else
print_error("Sending job to next node")
next
end
end
end
nid += 1
end
if minjobs < nmaxjobs
res=minconn.call('module.execute', mtype, mod, opts)
self.nmaxdisplay = false
#print_status(">>>#{res} #{mod}")
if res
if res.has_key?("job_id")
return
else
print_error("Unable to execute module in node #{k} #{res}")
end
end
else
#print_status("Max number of jobs #{nmaxjobs} reached in node #{k}")
end
idx += 1
end
if self.runlocal and not self.masstop
local_module_exec(mod,mtype, opts, nmaxjobs)
end
end
def rpc_db_nodes(host,port,user,pass,name)
rpc_reconnect_nodes()
if active_rpc_nodes == 0
print_error("No active nodes at this time")
return
end
self.rpcarr.each do |k,v|
if v
res = v.call('db.driver',{:driver => 'postgresql'})
res = v.call('db.connect',{:database => name, :host => host, :port => port, :username => user, :password => pass})
res = v.call('db.status')
if res['db'] == name
print_status("db_connect #{res} #{host}:#{port} OK")
else
print_error("Error db_connect #{res} #{host}:#{port}")
end
else
print_error("No connection to node #{k}")
end
end
end
def rpc_reconnect_nodes()
begin
# Sucky 5 mins token timeout.
idx = nil
self.rpcarr.each do |k,rpccon|
if rpccon
idx = k
begin
currentjobs = rpccon.call('job.list').length
rescue
tarr = k.split("|")
rflag = false
res = rpccon.login(tarr[3],tarr[4])
if res
rflag = true
print_error("Reauth to node #{tarr[0]}:#{tarr[1]}")
break
else
raise ConnectionError
end
end
end
end
rescue
print_error("ERROR CONNECTING TO NODE. Disabling #{idx} use wmap_nodes -a to reconnect")
self.rpcarr[idx] = nil
if active_rpc_nodes == 0
print_error("No active nodes")
self.masstop = true
else
#blah
end
end
end
def rpc_kill_node(i,j)
if not i
print_error("Nodes not defined")
return
end
if not j
print_error("Node jobs defined")
return
end
rpc_reconnect_nodes()
if active_rpc_nodes == 0
print_error("No active nodes at this time")
return
end
idx=0
self.rpcarr.each do |k,rpccon|
if idx == i.to_i or i.upcase == 'ALL'
#begin
if not rpccon
print_error("No connection to node #{idx}")
else
n = rpccon.call('job.list')
n.each do |id,name|
if j==id.to_s or j.upcase == 'ALL'
rpccon.call('job.stop',id)
print_status("Node #{idx} Killed job id #{id} #{name}")
end
end
end
#rescue
# print_error("No connection")
#end
end
idx += 1
end
end
def rpc_view_jobs()
indent = ' '
rpc_reconnect_nodes()
if active_rpc_nodes == 0
print_error("No active nodes at this time")
return
end
idx=0
self.rpcarr.each do |k,rpccon|
if not rpccon
print_status("[Node ##{idx}: #{k} DISABLED/NO CONNECTION]")
else
arrk = k.split('|')
print_status("[Node ##{idx}: #{arrk[0]} Port:#{arrk[1]} SSL:#{arrk[2]} User:#{arrk[3]}]")
begin
n = rpccon.call('job.list')
tbl = Rex::Ui::Text::Table.new(
'Indent' => indent.length,
'Header' => 'Jobs',
'Columns' =>
[
'Id',
'Job name',
'Target',
'PATH',
])
n.each do |id,name|
jinfo = rpccon.call('job.info',id)
dstore = jinfo['datastore']
tbl << [ id.to_s, name,dstore['VHOST']+":"+dstore['RPORT'],dstore['PATH']]
end
print_status tbl.to_s + "\n"
rescue
print_status("[Node ##{idx} #{k} DISABLED/NO CONNECTION]")
end
end
idx += 1
end
end
# Modified from http://stackoverflow.com/questions/946738/detect-key-press-non-blocking-w-o-getc-gets-in-ruby
def quit?
begin
while c = driver.input.read_nonblock(1)
print_status("Quited")
return true if c == 'Q'
end
false
rescue Errno::EINTR
false
rescue Errno::EAGAIN
false
rescue EOFError
true
end
end
def rpc_mon_nodes()
# Pretty monitor
color = self.opts["ConsoleDriver"].output.supports_color? rescue false
colors = [
'%grn',
'%blu',
'%yel',
'%whi'
]
#begin
loop do
rpc_reconnect_nodes()
idx = 0
self.rpcarr.each do |k,rpccon|
arrk = k.split('|')
v = "NOCONN"
n = 1
c = '%red'
if not rpccon
v = "NOCONN"
n = 1
c = '%red'
else
begin
v = ""
c = '%blu'
rescue
v = "ERROR"
c = '%red'
end
begin
n = rpccon.call('job.list').length
c = '%blu'
rescue
n = 1
v = "NOCONN"
c = '%red'
end
end
#begin
if not @stdio
@stdio = Rex::Ui::Text::Output::Stdio.new
end
if color == true
@stdio.auto_color
else
@stdio.disable_color
end
msg = "[#{idx}] #{"%bld#{c}||%clr"*n} #{n} #{v}\n"
@stdio.print_raw(@stdio.substitute_colors(msg))
#rescue
#blah
#end
sleep(2)
idx += 1
end
end
#rescue
# print_status("End.")
#end
end
def rpc_list_nodes()
indent = ' '
tbl = Rex::Ui::Text::Table.new(
'Indent' => indent.length,
'Header' => 'Nodes',
'Columns' =>
[
'Id',
'Host',
'Port',
'SSL',
'User',
'Pass',
'Status',
'#jobs',
])
idx=0
rpc_reconnect_nodes()
self.rpcarr.each do |k,rpccon|
arrk = k.split('|')
if not rpccon
v = "NOCONN"
n = ""
else
begin
v = rpccon.call('core.version')['version']
rescue
v = "ERROR"
end
begin
n = rpccon.call('job.list').length
rescue
n = ""
end
end
tbl << [ idx.to_s, arrk[0], arrk[1], arrk[2], arrk[3], arrk[4], v, n]
idx += 1
end
print_status tbl.to_s + "\n"
end
def active_rpc_nodes
if self.rpcarr.length == 0
return 0
else
idx = 0
self.rpcarr.each do |k,conn|
if conn
idx += 1
end
end
return idx
end
end
def view_modules
indent = ' '
wmaptype = [:wmap_ssl,
:wmap_server,
:wmap_dir,
:wmap_file,
:wmap_unique_query,
:wmap_query,
:wmap_generic
]
if not self.wmapmodules
load_wmap_modules(true)
end
wmaptype.each do |modt|
tbl = Rex::Ui::Text::Table.new(
'Indent' => indent.length,
'Header' => modt.to_s,
'Columns' =>
[
'Name',
'OrderID',
])
idx = 0
self.wmapmodules.each do |w|
oid = w[3]
if w[3] == 0xFFFFFF
oid = ":last"
end
if w[2] == modt
tbl << [w[0],oid]
idx += 1
end
end
print_status tbl.to_s + "\n"
end
end
# Yes sorting hashes dont make sense but actually it does when you are enumerating one. And
# sort_by of a hash returns an array so this is the reason for this ugly piece of code
def sort_by_orderid(m)
temphash=Hash.new()
temparr=[]
temparr = m.sort_by do |xref,v|
xref[3]
end
temparr.each do |b|
temphash[b[0]] = b[1]
end
temphash
end
# Load all wmap modules
def load_wmap_modules(reload)
if reload or not self.wmapmodules
print_status("Loading wmap modules...")
self.wmapmodules=[]
idx = 0
[ [ framework.auxiliary, 'auxiliary' ], [framework.exploits, 'exploit' ] ].each do |mtype|
# Scan all exploit modules for matching references
mtype[0].each_module do |n,m|
e = m.new
# Only include wmap_enabled plugins
if e.respond_to?("wmap_enabled")
penabled = e.wmap_enabled
if penabled
self.wmapmodules << [mtype[1]+'/'+n,mtype[1],e.wmap_type,e.orderid]
idx += 1
end
end
end
end
print_status("#{idx} wmap enabled modules loaded.")
end
end
def view_vulns
framework.db.hosts.each do |host|
host.services.each do |serv|
serv.web_sites.each do |site|
site.web_vulns.each do |wv|
print_status("+ [#{host.address}] (#{site.vhost}): #{wv.category} #{wv.path}")
print_status("\t#{wv.name} #{wv.description}")
print_status("\t#{wv.method} #{wv.proof}")
end
end
end
end
end
end
class WebTarget < ::Hash
def to_url
proto = self[:ssl] ? "https" : "http"
"#{proto}://#{self[:host]}:#{self[:port]}#{self[:path]}"
end
end
def initialize(framework, opts)
super
color = self.opts["ConsoleDriver"].output.supports_color? rescue false
wmapversion = '1.5.1'
wmapbanner = "%red\n.-.-.-..-.-.-..---..---.%clr\n"
wmapbanner += "%red| | | || | | || | || |-'%clr\n"
wmapbanner += "%red`-----'`-'-'-'`-^-'`-'%clr\n"
wmapbanner += "[WMAP #{wmapversion}] === et [ ] metasploit.com 2012\n"
if not @stdio
@stdio = Rex::Ui::Text::Output::Stdio.new
end
if color == true
@stdio.auto_color
else
@stdio.disable_color
end
@stdio.print_raw(@stdio.substitute_colors(wmapbanner))
add_console_dispatcher(WmapCommandDispatcher)
#print_status("#{wmapbanner}")
end
def cleanup
remove_console_dispatcher('wmap')
end
def name
"wmap"
end
def desc
"Web assessment plugin"
end
class WmapCommandDispatcher
attr_accessor :wmapmodules # Enabled Wmap modules
attr_accessor :targets # Targets
attr_accessor :lastsites # Temp location of previously obtained sites
attr_accessor :rpcarr # Array or rpc connections
attr_accessor :njobs # Max number of jobs
attr_accessor :nmaxdisplay # Flag to stop displaying the same mesg
attr_accessor :runlocal # Flag to run local modules only
attr_accessor :masstop # Flag to stop everything
attr_accessor :killwhenstop # Kill process when exiting
include Msf::Ui::Console::CommandDispatcher
def name
"wmap"
end
#
# The initial command set
#
def commands
{
"wmap_targets" => "Manage targets",
"wmap_sites" => "Manage sites",
"wmap_nodes" => "Manage nodes",
"wmap_run" => "Test targets",
"wmap_modules" => "Manage wmap modules",
"wmap_vulns" => "Display web vulns",
}
end
def cmd_wmap_vulns(*args)
args.push("-h") if args.length == 0
while (arg = args.shift)
case arg
when '-l'
view_vulns
return
when '-h'
print_status("Usage: wmap_vulns [options]")
print_line("\t-h Display this help text")
print_line("\t-l Display web vulns table")
print_line("")
return
else
print_error("Unknown flag.")
return
end
end
end
def cmd_wmap_modules(*args)
args.push("-h") if args.length == 0
while (arg = args.shift)
case arg
when '-l'
view_modules
return
when '-r'
load_wmap_modules(true)
return
when '-h'
print_status("Usage: wmap_modules [options]")
print_line("\t-h Display this help text")
print_line("\t-l List all wmap enabled modules")
print_line("\t-r Reload wmap modules")
print_line("")
return
else
print_error("Unknown flag.")
return
end
end
end
def cmd_wmap_targets(*args)
args.push("-h") if args.length == 0
while (arg = args.shift)
case arg
when '-c'
self.targets = Hash.new()
when '-l'
view_targets
return
when '-t'
process_urls(args.shift)
when '-d'
process_ids(args.shift)
when '-h'
print_status("Usage: wmap_targets [options]")
print_line("\t-h Display this help text")
print_line("\t-t [urls] Define target sites (vhost1,url[space]vhost2,url) ")
print_line("\t-d [ids] Define target sites (id1, id2, id3 ...)")
print_line("\t-c Clean target sites list")
print_line("\t-l List all target sites")
print_line("")
return
else
print_error("Unknown flag.")
return
end
end
end
def cmd_wmap_sites(*args)
args.push("-h") if args.length == 0
while (arg = args.shift)
case arg
when '-a'
s = add_web_site(args.shift)
if s
print_status("Site created.")
else
print_error("Unable to create site")
end
when '-d'
del_idx = args
if del_idx
delete_sites(del_idx.select {|d| d =~ /^[0-9]*$/}.map(&:to_i).uniq)
return
else
print_error("Provide index of site to delete")
end
when '-l'
view_sites
return
when '-s'
u = args.shift
l = args.shift
s = args.shift
if not u
return
end
if l == nil or l.empty?
l = 200
s = true
else
l = l.to_i
s = false
end
if u.include? 'http'
# Parameters are in url form
view_site_tree(u,l,s)
else
# Parameters are digits
if !self.lastsites or self.lastsites.length == 0
view_sites
print_status ("Web sites ids. referenced from previous table.")
end
target_whitelist = []
ids = u.to_s.split(/,/)
ids.each do |id|
next if id.to_s.strip.empty?
if id.to_i > self.lastsites.length
print_error("Skipping id #{id}...")
else
target_whitelist << self.lastsites[id.to_i]
#print_status("Loading #{self.lastsites[id.to_i]}.")
end
end
# Skip the DB entirely if no matches
return if target_whitelist.length == 0
if not self.targets
self.targets = Hash.new()
end
target_whitelist.each do |ent|
view_site_tree(ent,l,s)
end
end
return
when '-h'
print_status("Usage: wmap_sites [options]")
print_line("\t-h Display this help text")
print_line("\t-a [url] Add site (vhost,url)")
print_line("\t-d [ids] Delete sites (separate ids with space)")
print_line("\t-l List all available sites")
print_line("\t-s [id] Display site structure (vhost,url|ids) (level)")
print_line("")
return
else
print_error("Unknown flag.")
return
end
end
end
def cmd_wmap_nodes(*args)
if not self.rpcarr
self.rpcarr=Hash.new()
end
args.push("-h") if args.length == 0
while (arg = args.shift)
case arg
when '-a'
h = args.shift
r = args.shift
s = args.shift
u = args.shift
p = args.shift
res = rpc_add_node(h,r,s,u,p,false)
if res
print_status("Node created.")
else
print_error("Unable to create node")
end
when '-c'
idref = args.shift
if not idref
print_error("No id defined")
return
end
if idref.upcase == 'ALL'
print_status("All nodes removed")
self.rpcarr = Hash.new()
else
idx=0
self.rpcarr.each do |k,v|
if idx == idref.to_i
self.rpcarr.delete(k)
print_status("Node deleted #{k}")
end
idx += 1
end
end
when '-d'
host = args.shift
port = args.shift
user = args.shift
pass = args.shift
dbname = args.shift
res = rpc_db_nodes(host,port,user,pass,dbname)
if res
print_status("OK.")
else
print_error("Error")
end
when '-l'
rpc_list_nodes
return
when '-j'
rpc_view_jobs
return
when '-k'
node = args.shift
jid = args.shift
rpc_kill_node(node,jid)
return
when '-h'
print_status("Usage: wmap_nodes [options]")
print_line("\t-h Display this help text")
print_line("\t-c id Remove id node (Use ALL for ALL nodes")
print_line("\t-a host port ssl user pass Add node")
print_line("\t-d host port user pass db Force all nodes to connect to db")
print_line("\t-j View detailed jobs")
print_line("\t-k ALL|id ALL|job_id Kill jobs on node")
print_line("\t-l List all current nodes")
print_line("")
return
else
print_error("Unknown flag.")
return
end
end
end
def cmd_wmap_run(*args)
# Stop everything
self.masstop = false
self.killwhenstop = true
trap("INT") {
print_error("Stopping execution...")
self.masstop = true
if self.killwhenstop
rpc_kill_node('ALL','ALL')
end
}
# Max numbers of concurrent jobs per node
self.njobs = 25
self.nmaxdisplay = false
self.runlocal = false
# Formating
sizeline = 60
wmap_show = 2**0
wmap_expl = 2**1
# Exclude files can be modified by setting datastore['WMAP_EXCLUDE']
wmap_exclude_files = '.*\.(gif|jpg|png*)$'
run_wmap_ssl = true
run_wmap_server = true
run_wmap_dir_file = true
run_wmap_query = true
run_wmap_unique_query = true
run_wmap_generic = true
# If module supports datastore['VERBOSE']
moduleverbose = false
showprogress = false
if not self.rpcarr
self.rpcarr = Hash.new()
end
if not run_wmap_ssl
print_status("Loading of wmap ssl modules disabled.")
end
if not run_wmap_server
print_status("Loading of wmap server modules disabled.")
end
if not run_wmap_dir_file
print_status("Loading of wmap dir and file modules disabled.")
end
if not run_wmap_query
print_status("Loading of wmap query modules disabled.")
end
if not run_wmap_unique_query
print_status("Loading of wmap unique query modules disabled.")
end
if not run_wmap_generic
print_status("Loading of wmap generic modules disabled.")
end
stamp = Time.now.to_f
mode = 0
eprofile = []
using_p = false
using_m = false
usinginipath = false
mname = ''
inipathname = '/'
args.push("-h") if args.length == 0
while (arg = args.shift)
case arg
when '-t'
mode |= wmap_show
when '-e'
mode |= wmap_expl
profile = args.shift
if profile
print_status("Using profile #{profile}.")
begin
File.open(profile).each do |str|
if not str.include? '#'
# Not a comment
modname = str.strip
if not modname.empty?
eprofile << modname
end
end
using_p = true
end
rescue
print_error("Profile not found or invalid.")
return
end
else
print_status("Using ALL wmap enabled modules.")
end
when '-m'
mode |= wmap_expl
mname = args.shift
if mname
print_status("Using module #{mname}.")
end
using_m = true
when '-p'
mode |= wmap_expl
inipathname = args.shift
if inipathname
print_status("Using initial path #{inipathname}.")
end
usinginipath = true
when '-h'
print_status("Usage: wmap_run [options]")
print_line("\t-h Display this help text")
print_line("\t-t Show all enabled modules")
print_line("\t-m [regex] Launch only modules that name match provided regex.")
print_line("\t-p [regex] Only test path defined by regex.")
print_line("\t-e [/path/to/profile] Launch profile modules against all matched targets.")
print_line("\t (No profile file runs all enabled modules.)")
print_line("")
return
else
print_error("Unknown flag")
return
end
end
if (self.rpcarr.length == 0) and (mode & wmap_show == 0)
print_error("NO WMAP NODES DEFINED. Executing local modules")
self.runlocal = true
end
if self.targets == nil
print_error("Targets have not been selected.")
return
end
if self.targets.keys.length == 0
print_error("Targets have not been selected.")
return
end
execmod = true
if (mode & wmap_show != 0)
execmod = false
end
self.targets.each_with_index do |t, idx|
selected_host = t[1][:host]
selected_port = t[1][:port]
selected_ssl = t[1][:ssl]
selected_vhost = t[1][:vhost]
print_status ("Testing target:")
print_status ("\tSite: #{selected_vhost} (#{selected_host})")
print_status ("\tPort: #{selected_port} SSL: #{selected_ssl}")
print_line '='* sizeline
print_status("Testing started. #{(Time.now )}")
if not selected_ssl
run_wmap_ssl = false
#print_status ("Target is not SSL. SSL modules disabled.")
end
# wmap_dir, wmap_file
matches = Hash.new()
# wmap_server
matches1 = Hash.new()
# wmap_query
matches2 = Hash.new()
# wmap_ssl
matches3 = Hash.new()
# wmap_unique_query
matches5 = Hash.new()
# wmap_generic
matches10 = Hash.new()
# OPTIONS
opt_str = nil
jobify = false
# This will be clean later
load_wmap_modules(false)
self.wmapmodules.each do |w|
case w[2]
when :wmap_server
if run_wmap_server
matches1[w]=true
end
when :wmap_query
if run_wmap_query
matches2[w]=true
end
when :wmap_unique_query
if run_wmap_unique_query
matches5[w]=true
end
when :wmap_generic
if run_wmap_generic
matches10[w]=true
end
when :wmap_dir, :wmap_file
if run_wmap_dir_file
matches[w]=true
end
when :wmap_ssl
if run_wmap_ssl
matches3[w]=true
end
else
# Black Hole
end
end
# Execution order (orderid)
matches = sort_by_orderid(matches)
matches1 = sort_by_orderid(matches1)
matches2 = sort_by_orderid(matches2)
matches3 = sort_by_orderid(matches3)
matches5 = sort_by_orderid(matches5)
matches10 = sort_by_orderid(matches10)
#
# Handle modules that need to be run before all tests IF SERVER is SSL, once usually again the SSL web server.
# :wmap_ssl
#
print_status "\n=[ SSL testing ]="
print_line "=" * sizeline
if not selected_ssl
print_status ("Target is not SSL. SSL modules disabled.")
end
idx = 0
matches3.each_key do |xref|
if self.masstop
print_error("STOPPED.")
return
end
# Module not part of profile or not match
if ( using_p and eprofile.include? xref[0].split('/').last ) or (using_m and xref[0].to_s.match(mname)) or (not using_m and not using_p)
idx += 1
begin
# Module options hash
modopts = Hash.new()
#
# The code is just a proof-of-concept and will be expanded in the future
#
print_status "Module #{xref[0]}"
if (mode & wmap_expl != 0)
#
# For modules to have access to the global datastore
# i.e. set -g DOMAIN test.com
#
self.framework.datastore.each do |gkey,gval|
modopts[gkey]=gval
end
#
# Parameters passed in hash xref
#
modopts['RHOST'] = selected_host
modopts['RHOSTS'] = selected_host
modopts['RPORT'] = selected_port.to_s
modopts['SSL'] = selected_ssl
modopts['VHOST'] = selected_vhost.to_s
modopts['VERBOSE'] = moduleverbose
modopts['ShowProgress'] = showprogress
modopts['RunAsJob'] = jobify
begin
if execmod
rpcnode = rpc_round_exec(xref[0],xref[1], modopts, self.njobs)
end
rescue ::Exception
print_status(" >> Exception during launch from #{xref[0]}: #{$!}")
end
end
rescue ::Exception
print_status(" >> Exception from #{xref[0]}: #{$!}")
end
end
end
#
# Handle modules that need to be run before all tests, once usually again the web server.
# :wmap_server
#
print_status "\n=[ Web Server testing ]="
print_line "=" * sizeline
idx = 0
matches1.each_key do |xref|
if self.masstop
print_error("STOPPED.")
return
end
# Module not part of profile or not match
if ( using_p and eprofile.include? xref[0].split('/').last ) or (using_m and xref[0].to_s.match(mname)) or (not using_m and not using_p)
idx += 1
begin
# Module options hash
modopts = Hash.new()
#
# The code is just a proof-of-concept and will be expanded in the future
#
print_status "Module #{xref[0]}"
if (mode & wmap_expl != 0)
#
# For modules to have access to the global datastore
# i.e. set -g DOMAIN test.com
#
self.framework.datastore.each do |gkey,gval|
modopts[gkey]=gval
end
#
# Parameters passed in hash xref
#
modopts['RHOST'] = selected_host
modopts['RHOSTS'] = selected_host
modopts['RPORT'] = selected_port.to_s
modopts['SSL'] = selected_ssl
modopts['VHOST'] = selected_vhost.to_s
modopts['VERBOSE'] = moduleverbose
modopts['ShowProgress'] = showprogress
modopts['RunAsJob'] = jobify
begin
if execmod
rpcnode = rpc_round_exec(xref[0],xref[1], modopts, self.njobs)
end
rescue ::Exception
print_status(" >> Exception during launch from #{xref[0]}: #{$!}")
end
end
rescue ::Exception
print_status(" >> Exception from #{xref[0]}: #{$!}")
end
end
end
#
# Handle modules to be run at every path/file
# wmap_dir, wmap_file
#
print_status "\n=[ File/Dir testing ]="
print_line "=" * sizeline
idx = 0
matches.each_key do |xref|
if self.masstop
print_error("STOPPED.")
return
end
# Module not part of profile or not match
if ( using_p and eprofile.include? xref[0].split('/').last ) or (using_m and xref[0].to_s.match(mname)) or (not using_m and not using_p)
idx+=1
begin
# Module options hash
modopts = Hash.new()
#
# The code is just a proof-of-concept and will be expanded in the future
#
print_status "Module #{xref[0]}"
if (mode & wmap_expl != 0)
#
# For modules to have access to the global datastore
# i.e. set -g DOMAIN test.com
#
self.framework.datastore.each do |gkey,gval|
modopts[gkey]=gval
end
#
# Parameters passed in hash xref
#
modopts['RHOST'] = selected_host
modopts['RHOSTS'] = selected_host
modopts['RPORT'] = selected_port.to_s
modopts['SSL'] = selected_ssl
modopts['VHOST'] = selected_vhost.to_s
modopts['VERBOSE'] = moduleverbose
modopts['ShowProgress'] = showprogress
modopts['RunAsJob'] = jobify
#
# Run the plugins that only need to be
# launched once.
#
wtype = xref[2]
h = self.framework.db.workspace.hosts.find_by_address(selected_host)
s = h.services.find_by_port(selected_port)
w = s.web_sites.find_by_vhost(selected_vhost)
test_tree = load_tree(w)
test_tree.each do |node|
if self.masstop
print_error("STOPPED.")
return
end
p = node.current_path
testpath = Pathname.new(p)
strpath = testpath.cleanpath(false).to_s
#
# Fixing paths
#
if node.is_leaf? and not node.is_root?
#
# Later we can add here more checks to see if its a file
#
else
if node.is_root?
strpath = "/"
else
strpath = strpath.chomp + "/"
end
end
strpath = strpath.gsub("//", "/")
#print_status("Testing path: #{strpath}")
#
# Launch plugin depending module type.
# Module type depends on main input type.
# Code may be the same but it depend on final
# versions of plugins
#
case wtype
when :wmap_file
if node.is_leaf? and not node.is_root?
#
# Check if an exclusion regex has been defined
#
if self.framework.datastore['WMAP_EXCLUDE']
excludefilestr = self.framework.datastore['WMAP_EXCLUDE']
else
excludefilestr = wmap_exclude_files
end
if not strpath.match(excludefilestr)
if (not usinginipath) or (usinginipath and strpath.match(inipathname))
modopts['PATH'] = strpath
print_status("Path: #{strpath}")
begin
if execmod
rpcnode = rpc_round_exec(xref[0],xref[1], modopts, self.njobs)
end
rescue ::Exception
print_status(" >> Exception during launch from #{xref[0]}: #{$!}")
end
end
end
end
when :wmap_dir
if (node.is_leaf? and not strpath.include? ".") or node.is_root? or not node.is_leaf?
if (not usinginipath) or (usinginipath and strpath.match(inipathname))
modopts['PATH'] = strpath
print_status("Path: #{strpath}")
begin
if execmod
rpcnode = rpc_round_exec(xref[0],xref[1], modopts, njobs)
end
rescue ::Exception
print_status(" >> Exception during launch from #{xref[0]}: #{$!}")
end
end
end
end
end
end
rescue ::Exception
print_status(" >> Exception from #{xref[0]}: #{$!}")
end
end
end
#
# Run modules for each request to play with URI with UNIQUE query parameters.
# wmap_unique_query
#
print_status "\n=[ Unique Query testing ]="
print_line "=" * sizeline
idx = 0
matches5.each_key do |xref|
if self.masstop
print_error("STOPPED.")
return
end
# Module not part of profile or not match
if ( using_p and eprofile.include? xref[0].split('/').last ) or (using_m and xref[0].to_s.match(mname)) or (not using_m and not using_p)
idx += 1
begin
# Module options hash
modopts = Hash.new()
#
# The code is just a proof-of-concept and will be expanded in the future
#
print_status "Module #{xref[0]}"
if (mode & wmap_expl != 0)
#
# For modules to have access to the global datastore
# i.e. set -g DOMAIN test.com
#
self.framework.datastore.each do |gkey,gval|
modopts[gkey]=gval
end
#
# Parameters passed in hash xref
#
modopts['RHOST'] = selected_host
modopts['RHOSTS'] = selected_host
modopts['RPORT'] = selected_port.to_s
modopts['SSL'] = selected_ssl
modopts['VHOST'] = selected_vhost.to_s
modopts['VERBOSE'] = moduleverbose
modopts['ShowProgress'] = showprogress
modopts['RunAsJob'] = jobify
#
# Run the plugins for each request that have a distinct
# GET/POST URI QUERY string.
#
utest_query = Hash.new()
h = self.framework.db.workspace.hosts.find_by_address(selected_host)
s = h.services.find_by_port(selected_port)
w = s.web_sites.find_by_vhost(selected_vhost)
w.web_forms.each do |form|
if self.masstop
print_error("STOPPED.")
return
end
#
# Only test unique query strings by comparing signature to previous tested signatures 'path,p1,p2,pn'
#
datastr = ""
typestr = ""
temparr = []
#print_status "---------"
#print_status form.params
#print_status "+++++++++"
form.params.each do |p|
pn, pv, pt = p
if pn
if not pn.empty?
if not pv or pv.empty?
#TODO add value based on param name
pv = "aaa"
end
#temparr << pn.to_s + "=" + Rex::Text.uri_encode(pv.to_s)
temparr << pn.to_s + "=" + pv.to_s
end
else
print_error("Blank parameter name. Form #{form.path}")
end
end
datastr = temparr.join("&") if (temparr and not temparr.empty?)
if (utest_query.has_key?(signature(form.path,datastr)) == false)
modopts['METHOD'] = form.method.upcase
modopts['PATH'] = form.path
modopts['QUERY'] = form.query
if form.method.upcase == 'GET'
modopts['QUERY'] = datastr
modopts['DATA'] = ""
end
if form.method.upcase == 'POST'
modopts['DATA'] = datastr
end
modopts['TYPES'] = typestr
#
# TODO: Add headers, etc.
#
if (not usinginipath) or (usinginipath and form.path.match(inipathname))
print_status "Path #{form.path}"
#print_status("Unique PATH #{modopts['PATH']}")
#print_status("Unique GET #{modopts['QUERY']}")
#print_status("Unique POST #{modopts['DATA']}")
#print_status("MODOPTS: #{modopts}")
begin
if execmod
rpcnode = rpc_round_exec(xref[0],xref[1], modopts, self.njobs)
end
utest_query[signature(form.path,datastr)]=1
rescue ::Exception
print_status(" >> Exception during launch from #{xref[0]}: #{$!}")
end
end
else
#print_status("Already tested")
end
end
end
rescue ::Exception
print_status(" >> Exception from #{xref[0]}: #{$!}")
end
end
end
#
# Run modules for each request to play with URI query parameters.
# This approach will reduce the complexity of the Tree used before
# and will make this shotgun implementation much simple.
# wmap_query
#
print_status "\n=[ Query testing ]="
print_line "=" * sizeline
idx = 0
matches2.each_key do |xref|
if self.masstop
print_error("STOPPED.")
return
end
# Module not part of profile or not match
if not ( using_p and eprofile.include? xref[0].split('/').last ) or (using_m and xref[0].to_s.match(mname)) or (not using_m and not using_p)
idx += 1
begin
# Module options hash
modopts = Hash.new()
#
# The code is just a proof-of-concept and will be expanded in the future
#
print_status "Module #{xref[0]}"
if (mode & wmap_expl != 0)
#
# For modules to have access to the global datastore
# i.e. set -g DOMAIN test.com
#
self.framework.datastore.each do |gkey,gval|
modopts[gkey]=gval
end
#
# Parameters passed in hash xref
#
modopts['RHOST'] = selected_host
modopts['RHOSTS'] = selected_host
modopts['RPORT'] = selected_port.to_s
modopts['SSL'] = selected_ssl
modopts['VHOST'] = selected_vhost.to_s
modopts['VERBOSE'] = moduleverbose
modopts['ShowProgress'] = showprogress
modopts['RunAsJob'] = jobify
#
# Run the plugins for each request that have a distinct
# GET/POST URI QUERY string.
#
h = self.framework.db.workspace.hosts.find_by_address(selected_host)
s = h.services.find_by_port(selected_port)
w = s.web_sites.find_by_vhost(selected_vhost)
w.web_forms.each do |req|
if self.masstop
print_error("STOPPED.")
return
end
datastr = ""
typestr = ""
temparr = []
req.params.each do |p|
pn, pv, pt = p
if pn
if not pn.empty?
if not pv or pv.empty?
#TODO add value based on param name
pv = "aaa"
end
#temparr << pn.to_s + "=" + Rex::Text.uri_encode(pv.to_s)
temparr << pn.to_s + "=" + pv.to_s
end
else
print_error("Blank parameter name. Form #{req.path}")
end
end
datastr = temparr.join("&") if (temparr and not temparr.empty?)
modopts['METHOD'] = req.method.upcase
modopts['PATH'] = req.path
if req.method.upcase == 'GET'
modopts['QUERY'] = datastr
modopts['DATA'] = ""
end
modopts['DATA'] = datastr if req.method.upcase == 'POST'
modopts['TYPES'] = typestr
#
# TODO: Add method, headers, etc.
#
if (not usinginipath) or (usinginipath and req.path.match(inipathname))
print_status "Path #{req.path}"
#print_status("Query PATH #{modopts['PATH']}")
#print_status("Query GET #{modopts['QUERY']}")
#print_status("Query POST #{modopts['DATA']}")
#print_status("Query TYPES #{typestr}")
begin
if execmod
rpcnode = rpc_round_exec(xref[0],xref[1], modopts, self.njobs)
end
rescue ::Exception
print_status(" >> Exception during launch from #{xref[0]}: #{$!}")
end
end
end
end
rescue ::Exception
print_status(" >> Exception from #{xref[0]}: #{$!}")
end
end
end
#
# Handle modules that need to be after all tests, once.
# Good place to have modules that analize the test results and/or
# launch exploits.
# :wmap_generic
#
print_status "\n=[ General testing ]="
print_line "=" * sizeline
idx = 0
matches10.each_key do |xref|
if self.masstop
print_error("STOPPED.")
return
end
# Module not part of profile or not match
if not ( using_p and eprofile.include? xref[0].split('/').last ) or (using_m and xref[0].to_s.match(mname)) or (not using_m and not using_p)
idx += 1
begin
# Module options hash
modopts = Hash.new()
#
# The code is just a proof-of-concept and will be expanded in the future
#
print_status "Module #{xref[0]}"
if (mode & wmap_expl != 0)
#
# For modules to have access to the global datastore
# i.e. set -g DOMAIN test.com
#
self.framework.datastore.each do |gkey,gval|
modopts[gkey]=gval
end
#
# Parameters passed in hash xref
#
modopts['RHOST'] = selected_host
modopts['RHOSTS'] = selected_host
modopts['RPORT'] = selected_port.to_s
modopts['SSL'] = selected_ssl
modopts['VHOST'] = selected_vhost.to_s
modopts['VERBOSE'] = moduleverbose
modopts['ShowProgress'] = showprogress
modopts['RunAsJob'] = jobify
#
# Run the plugins that only need to be
# launched once.
#
begin
if execmod
rpcnode = rpc_round_exec(xref[0],xref[1], modopts, self.njobs)
end
rescue ::Exception
print_status(" >> Exception during launch from #{xref[0]}: #{$!}")
end
end
rescue ::Exception
print_status(" >> Exception from #{xref[0]}: #{$!}")
end
end
end
if (mode & wmap_expl != 0)
print_line "+" * sizeline
if not self.runlocal
if execmod
rpc_list_nodes()
print_status("Note: Use wmap_nodes -l to list node status for completion")
end
end
print_line("Launch completed in #{(Time.now.to_f - stamp)} seconds.")
print_line "+" * sizeline
end
print_status("Done.")
end
# EOM
end
def view_targets
if self.targets == nil or self.targets.keys.length == 0
print_status "No targets have been defined"
return
end
indent = ' '
tbl = Rex::Ui::Text::Table.new(
'Indent' => indent.length,
'Header' => 'Defined targets',
'Columns' =>
[
'Id',
'Vhost',
'Host',
'Port',
'SSL',
'Path',
])
self.targets.each_with_index { |t, idx|
tbl << [ idx.to_s, t[1][:vhost], t[1][:host], t[1][:port], t[1][:ssl], "\t"+t[1][:path].to_s ]
}
print_status tbl.to_s + "\n"
end
def delete_sites(wmap_index)
idx = 0
to_del = {}
# Rebuild the index from wmap_sites -l
self.framework.db.hosts.each do |bdhost|
bdhost.services.each do |serv|
serv.web_sites.each do |web|
# If the index of this site matches any deletion index,
# add to our hash, saving the index for later output
to_del[idx] = web if wmap_index.any? {|w| w.to_i == idx}
idx += 1
end
end
end
to_del.each do |widx,wsite|
if wsite.delete
print_status("Deleted #{wsite.vhost} on #{wsite.service.host.address} at index #{widx}")
else
print_error("Could note delete {wsite.vhost} on #{wsite.service.host.address} at index #{widx}")
end
end
end
def view_sites
# Clean temporary sites list
self.lastsites = []
indent = ' '
tbl = Rex::Ui::Text::Table.new(
'Indent' => indent.length,
'Header' => 'Available sites',
'Columns' =>
[
'Id',
'Host',
'Vhost',
'Port',
'Proto',
'# Pages',
'# Forms',
])
idx = 0
self.framework.db.hosts.each do |bdhost|
bdhost.services.each do |serv|
serv.web_sites.each do |web|
c = web.web_pages.count
f = web.web_forms.count
tbl << [ idx.to_s, bdhost.address, web.vhost, serv.port, serv.name, c.to_s, f.to_s ]
idx += 1
turl = web.vhost + "," + serv.name + "://" +bdhost.address.to_s + ":" + serv.port.to_s + "/"
self.lastsites << turl
end
end
end
print_status tbl.to_s + "\n"
end
# Reusing code from hdmoore
#
# Allow the URL to be supplied as VHOST,URL if a custom VHOST
# should be used. This allows for things like:
# localhost,http://192.168.0.2/admin/
def add_web_site(url)
vhost = nil
# Allow the URL to be supplied as VHOST,URL if a custom VHOST
# should be used. This allows for things like:
# localhost,http://192.168.0.2/admin/
if url !~ /^http/
vhost,url = url.split(",", 2)
if url.to_s.empty?
url = vhost
vhost = nil
end
end
# Prefix http:// when the URL has no specified parameter
if url !~ /^[a-z0-9A-Z]+:\/\//
url = "http://" + url
end
uri = URI.parse(url) rescue nil
if not uri
print_error("Could not understand URL: #{url}")
return
end
if uri.scheme !~ /^https?/
print_error("Only http and https URLs are accepted: #{url}")
return
end
ssl = false
if uri.scheme == 'https'
ssl = true
end
site = self.framework.db.report_web_site(:wait => true, :host => uri.host, :port => uri.port, :vhost => vhost, :ssl => ssl)
return site
end
# Code by hdm. Modified two lines by et
#
def process_urls(urlstr)
target_whitelist = []
urls = urlstr.to_s.split(/\s+/)
urls.each do |url|
next if url.to_s.strip.empty?
vhost = nil
# Allow the URL to be supplied as VHOST,URL if a custom VHOST
# should be used. This allows for things like:
# localhost,http://192.168.0.2/admin/
if url !~ /^http/
vhost,url = url.split(",", 2)
if url.to_s.empty?
url = vhost
vhost = nil
end
end
# Prefix http:// when the URL has no specified parameter
if url !~ /^[a-z0-9A-Z]+:\/\//
url = "http://" + url
end
uri = URI.parse(url) rescue nil
if not uri
print_error("Could not understand URL: #{url}")
next
end
if uri.scheme !~ /^https?/
print_error("Only http and https URLs are accepted: #{url}")
next
end
target_whitelist << [vhost || uri.host, uri]
end
# Skip the DB entirely if no matches
return if target_whitelist.length == 0
if not self.targets
# First time targets are defined
self.targets = Hash.new()
end
target_whitelist.each do |ent|
vhost,target = ent
host = self.framework.db.workspace.hosts.find_by_address(target.host)
if not host
print_error("No matching host for #{target.host}")
next
end
serv = host.services.find_by_port_and_proto(target.port, 'tcp')
if not serv
print_error("No matching service for #{target.host}:#{target.port}")
next
end
#print_status "aaa"
#print_status framework.db.workspace.name
sites = serv.web_sites.where('vhost = ? and service_id = ?', vhost, serv.id)
sites.each do |site|
# Initial defaul path
inipath = target.path
if target.path.empty?
inipath = '/'
end
#site.web_forms.find_all_by_path(target.path).each do |form|
ckey = [ site.vhost, host.address, serv.port, inipath].join("|")
if not self.targets[ckey]
self.targets[ckey] = WebTarget.new
self.targets[ckey].merge!({
:vhost => site.vhost,
:host => host.address,
:port => serv.port,
:ssl => (serv.name == "https"),
:path => inipath
})
#self.targets[ckey][inipath] = []
else
print_status("Target already set in targets list.")
end
# Store the form object in the hash for this path
#self.targets[ckey][inipath] << inipath
#end
end
end
end
# Code by hdm. Modified two lines by et
# lastsites contains a temporary array with vhost,url strings so the id can be
# referenced in the array and prevent new sites added in the db to corrupt previous id list.
def process_ids(idsstr)
if !self.lastsites or self.lastsites.length == 0
view_sites
print_status ("Web sites ids. referenced from previous table.")
end
target_whitelist = []
ids = idsstr.to_s.split(/,/)
ids.each do |id|
next if id.to_s.strip.empty?
if id.to_i > self.lastsites.length
print_error("Skipping id #{id}...")
else
target_whitelist << self.lastsites[id.to_i]
print_status("Loading #{self.lastsites[id.to_i]}.")
end
end
# Skip the DB entirely if no matches
return if target_whitelist.length == 0
if not self.targets
self.targets = Hash.new()
end
target_whitelist.each do |ent|
process_urls(ent)
end
end
def view_site_tree(urlstr, md, ld)
if not urlstr
return
end
site_whitelist = []
urls = urlstr.to_s.split(/\s+/)
urls.each do |url|
next if url.to_s.strip.empty?
vhost = nil
# Allow the URL to be supplied as VHOST,URL if a custom VHOST
# should be used. This allows for things like:
# localhost,http://192.168.0.2/admin/
if url !~ /^http/
vhost,url = url.split(",", 2)
if url.to_s.empty?
url = vhost
vhost = nil
end
end
# Prefix http:// when the URL has no specified parameter
if url !~ /^[a-z0-9A-Z]+:\/\//
url = "http://" + url
end
uri = URI.parse(url) rescue nil
if not uri
print_error("Could not understand URL: #{url}")
next
end
if uri.scheme !~ /^https?/
print_error("Only http and https URLs are accepted: #{url}")
next
end
site_whitelist << [vhost || uri.host, uri]
end
# Skip the DB entirely if no matches
return if site_whitelist.length == 0
vsites = Hash.new()
site_whitelist.each do |ent|
vhost,target = ent
host = self.framework.db.workspace.hosts.find_by_address(target.host)
if not host
print_error("No matching host for #{target.host}")
next
end
serv = host.services.find_by_port_and_proto(target.port, 'tcp')
if not serv
print_error("No matching service for #{target.host}:#{target.port}")
next
end
sites = serv.web_sites.where('vhost = ? and service_id = ?', vhost, serv.id)
sites.each do |site|
t = load_tree(site)
print_tree(t,target.host,md,ld)
print_line("\n")
end
end
end
#
# Load website structure into a tree
#
def load_tree(s)
pathchr = '/'
wtree = Tree.new(s.vhost)
# Load site pages
s.web_pages.find(:all, :order => 'path').each do |req|
tarray = req.path.to_s.split(pathchr)
tarray.delete("")
tpath = Pathname.new(pathchr)
tarray.each do |df|
wtree.add_at_path(tpath.to_s,df)
tpath = tpath + Pathname.new(df.to_s)
end
end
# Load site forms
s.web_forms.each do |req|
tarray = req.path.to_s.split(pathchr)
tarray.delete("")
tpath = Pathname.new(pathchr)
tarray.each do |df|
wtree.add_at_path(tpath.to_s,df)
tpath = tpath + Pathname.new(df.to_s)
end
end
return wtree
end
#
# Print Tree structure. Still ugly
#
def print_tree(tree, ip, maxlevel, limitlevel)
initab = " " * 4
indent = 6
if tree != nil and tree.depth <= maxlevel
print initab + (" " * indent * tree.depth)
if tree.depth > 0
print "|"+("-" * (indent-1))+"/"
end
if tree.depth >= 0
if tree.depth == 0
print "[#{tree.name}] (#{ip})\n"+initab+(" " * indent)+"\n"
else
c = tree.children.count
if c > 0
print tree.name + " (" + c.to_s+")\n"
else
print tree.name + "\n"
end
end
end
tree.children.each_pair do |name,child|
print_tree(child,ip,maxlevel,limitlevel)
end
end
end
def signature(fpath,fquery)
hsig = Hash.new()
hsig = queryparse(fquery)
#
# Signature of the form ',p1,p2,pn' then to be appended to path: path,p1,p2,pn
#
sigstr = fpath + "," + hsig.map{|p| p[0].to_s}.join(",")
end
def queryparse(query)
params = Hash.new()
query.split(/[&;]/n).each do |pairs|
key, value = pairs.split('=',2)
if params.has_key?(key)
#Error
else
params[key] = value
end
end
params
end
def rpc_add_node(host,port,ssl,user,pass,bypass_exist)
if not self.rpcarr
self.rpcarr = Hash.new()
end
begin
istr = "#{host}|#{port}|#{ssl}|#{user}|#{pass}"
if self.rpcarr.has_key?(istr) and not bypass_exist and self.rpcarr[istr] != nil
print_error("Connection already exists #{istr}")
else
begin
temprpc = ::Msf::RPC::Client.new(
:host => host,
:port => port,
:ssl => ssl
)
rescue
print_error "Unable to connect"
#raise ConnectionError
return
end
res = temprpc.login( user , pass)
if not res
print_error("Unable to authenticate to #{host}:#{port}.")
return
else
res = temprpc.call('core.version')
end
print_status("Connected to #{host}:#{port} [#{res['version']}].")
self.rpcarr[istr] = temprpc
end
rescue
print_error("Unable to connect")
end
end
def local_module_exec(mod,mtype, opts, nmaxjobs)
jobify = false
modinst = framework.modules.create(mod)
if(not modinst)
print_error("Unknown module")
return
end
sess = nil
case mtype
when 'auxiliary'
Msf::Simple::Auxiliary.run_simple(modinst, {
'Action' => opts['ACTION'],
'LocalOutput' => driver.output,
'RunAsJob' => jobify,
'Options' => opts
})
when 'exploit'
if not opts['PAYLOAD']
opts['PAYLOAD'] = WmapCommandDispatcher::Exploit.choose_payload(modinst, opts['TARGET'])
end
sess = Msf::Simple::Exploit.exploit_simple(modinst, {
'Payload' => opts['PAYLOAD'],
'Target' => opts['TARGET'],
'LocalOutput' => driver.output,
'RunAsJob' => jobify,
'Options' => opts
})
else
print_error("Wrong mtype.")
end
if sess
if (jobify == false and sess.interactive?)
print_line
driver.run_single("sessions -q -i #{sess.sid}")
else
print_status("Session #{sess.sid} created in the background.")
end
end
end
def rpc_round_exec(mod,mtype, opts, nmaxjobs)
res = nil
idx = 0
if active_rpc_nodes == 0
if not self.runlocal
print_error("All active nodes not working or removed")
return
end
res = true
else
rpc_reconnect_nodes()
end
if self.masstop
return
end
while not res
if active_rpc_nodes == 0
print_error("All active nodes not working or removed")
return
end
#find the node with less jobs load.
minjobs = nmaxjobs
minconn = nil
nid = 0
self.rpcarr.each do |k,rpccon|
if not rpccon
print_error("Skipping inactive node #{nid} #{k}")
else
begin
currentjobs = rpccon.call('job.list').length
if currentjobs < minjobs
minconn = rpccon
minjobs = currentjobs
end
if currentjobs == nmaxjobs
if self.nmaxdisplay == false
print_error("Node #{nid} reached max number of jobs #{nmaxjobs}")
print_error("Waiting for available node/slot...")
self.nmaxdisplay = true
end
end
#print_status("Node #{nid} #currentjobs #{currentjobs} #min #{minjobs}")
rescue
print_error("Unable to connect. Node #{tarr[0]}:#{tarr[1]}")
self.rpcarr[k]=nil
if active_rpc_nodes == 0
print_error("All active nodes ,not working or removed")
return
else
print_error("Sending job to next node")
next
end
end
end
nid += 1
end
if minjobs < nmaxjobs
res=minconn.call('module.execute', mtype, mod, opts)
self.nmaxdisplay = false
#print_status(">>>#{res} #{mod}")
if res
if res.has_key?("job_id")
return
else
print_error("Unable to execute module in node #{k} #{res}")
end
end
else
#print_status("Max number of jobs #{nmaxjobs} reached in node #{k}")
end
idx += 1
end
if self.runlocal and not self.masstop
local_module_exec(mod,mtype, opts, nmaxjobs)
end
end
def rpc_db_nodes(host,port,user,pass,name)
rpc_reconnect_nodes()
if active_rpc_nodes == 0
print_error("No active nodes at this time")
return
end
self.rpcarr.each do |k,v|
if v
res = v.call('db.driver',{:driver => 'postgresql'})
res = v.call('db.connect',{:database => name, :host => host, :port => port, :username => user, :password => pass})
res = v.call('db.status')
if res['db'] == name
print_status("db_connect #{res} #{host}:#{port} OK")
else
print_error("Error db_connect #{res} #{host}:#{port}")
end
else
print_error("No connection to node #{k}")
end
end
end
def rpc_reconnect_nodes()
begin
# Sucky 5 mins token timeout.
idx = nil
self.rpcarr.each do |k,rpccon|
if rpccon
idx = k
begin
currentjobs = rpccon.call('job.list').length
rescue
tarr = k.split("|")
rflag = false
res = rpccon.login(tarr[3],tarr[4])
if res
rflag = true
print_error("Reauth to node #{tarr[0]}:#{tarr[1]}")
break
else
raise ConnectionError
end
end
end
end
rescue
print_error("ERROR CONNECTING TO NODE. Disabling #{idx} use wmap_nodes -a to reconnect")
self.rpcarr[idx] = nil
if active_rpc_nodes == 0
print_error("No active nodes")
self.masstop = true
else
#blah
end
end
end
def rpc_kill_node(i,j)
if not i
print_error("Nodes not defined")
return
end
if not j
print_error("Node jobs defined")
return
end
rpc_reconnect_nodes()
if active_rpc_nodes == 0
print_error("No active nodes at this time")
return
end
idx=0
self.rpcarr.each do |k,rpccon|
if idx == i.to_i or i.upcase == 'ALL'
#begin
if not rpccon
print_error("No connection to node #{idx}")
else
n = rpccon.call('job.list')
n.each do |id,name|
if j==id.to_s or j.upcase == 'ALL'
rpccon.call('job.stop',id)
print_status("Node #{idx} Killed job id #{id} #{name}")
end
end
end
#rescue
# print_error("No connection")
#end
end
idx += 1
end
end
def rpc_view_jobs()
indent = ' '
rpc_reconnect_nodes()
if active_rpc_nodes == 0
print_error("No active nodes at this time")
return
end
idx=0
self.rpcarr.each do |k,rpccon|
if not rpccon
print_status("[Node ##{idx}: #{k} DISABLED/NO CONNECTION]")
else
arrk = k.split('|')
print_status("[Node ##{idx}: #{arrk[0]} Port:#{arrk[1]} SSL:#{arrk[2]} User:#{arrk[3]}]")
begin
n = rpccon.call('job.list')
tbl = Rex::Ui::Text::Table.new(
'Indent' => indent.length,
'Header' => 'Jobs',
'Columns' =>
[
'Id',
'Job name',
'Target',
'PATH',
])
n.each do |id,name|
jinfo = rpccon.call('job.info',id)
dstore = jinfo['datastore']
tbl << [ id.to_s, name,dstore['VHOST']+":"+dstore['RPORT'],dstore['PATH']]
end
print_status tbl.to_s + "\n"
rescue
print_status("[Node ##{idx} #{k} DISABLED/NO CONNECTION]")
end
end
idx += 1
end
end
# Modified from http://stackoverflow.com/questions/946738/detect-key-press-non-blocking-w-o-getc-gets-in-ruby
def quit?
begin
while c = driver.input.read_nonblock(1)
print_status("Quited")
return true if c == 'Q'
end
false
rescue Errno::EINTR
false
rescue Errno::EAGAIN
false
rescue EOFError
true
end
end
def rpc_mon_nodes()
# Pretty monitor
color = self.opts["ConsoleDriver"].output.supports_color? rescue false
colors = [
'%grn',
'%blu',
'%yel',
'%whi'
]
#begin
loop do
rpc_reconnect_nodes()
idx = 0
self.rpcarr.each do |k,rpccon|
arrk = k.split('|')
v = "NOCONN"
n = 1
c = '%red'
if not rpccon
v = "NOCONN"
n = 1
c = '%red'
else
begin
v = ""
c = '%blu'
rescue
v = "ERROR"
c = '%red'
end
begin
n = rpccon.call('job.list').length
c = '%blu'
rescue
n = 1
v = "NOCONN"
c = '%red'
end
end
#begin
if not @stdio
@stdio = Rex::Ui::Text::Output::Stdio.new
end
if color == true
@stdio.auto_color
else
@stdio.disable_color
end
msg = "[#{idx}] #{"%bld#{c}||%clr"*n} #{n} #{v}\n"
@stdio.print_raw(@stdio.substitute_colors(msg))
#rescue
#blah
#end
sleep(2)
idx += 1
end
end
#rescue
# print_status("End.")
#end
end
def rpc_list_nodes()
indent = ' '
tbl = Rex::Ui::Text::Table.new(
'Indent' => indent.length,
'Header' => 'Nodes',
'Columns' =>
[
'Id',
'Host',
'Port',
'SSL',
'User',
'Pass',
'Status',
'#jobs',
])
idx=0
rpc_reconnect_nodes()
self.rpcarr.each do |k,rpccon|
arrk = k.split('|')
if not rpccon
v = "NOCONN"
n = ""
else
begin
v = rpccon.call('core.version')['version']
rescue
v = "ERROR"
end
begin
n = rpccon.call('job.list').length
rescue
n = ""
end
end
tbl << [ idx.to_s, arrk[0], arrk[1], arrk[2], arrk[3], arrk[4], v, n]
idx += 1
end
print_status tbl.to_s + "\n"
end
def active_rpc_nodes
if self.rpcarr.length == 0
return 0
else
idx = 0
self.rpcarr.each do |k,conn|
if conn
idx += 1
end
end
return idx
end
end
def view_modules
indent = ' '
wmaptype = [:wmap_ssl,
:wmap_server,
:wmap_dir,
:wmap_file,
:wmap_unique_query,
:wmap_query,
:wmap_generic
]
if not self.wmapmodules
load_wmap_modules(true)
end
wmaptype.each do |modt|
tbl = Rex::Ui::Text::Table.new(
'Indent' => indent.length,
'Header' => modt.to_s,
'Columns' =>
[
'Name',
'OrderID',
])
idx = 0
self.wmapmodules.each do |w|
oid = w[3]
if w[3] == 0xFFFFFF
oid = ":last"
end
if w[2] == modt
tbl << [w[0],oid]
idx += 1
end
end
print_status tbl.to_s + "\n"
end
end
# Yes sorting hashes dont make sense but actually it does when you are enumerating one. And
# sort_by of a hash returns an array so this is the reason for this ugly piece of code
def sort_by_orderid(m)
temphash=Hash.new()
temparr=[]
temparr = m.sort_by do |xref,v|
xref[3]
end
temparr.each do |b|
temphash[b[0]] = b[1]
end
temphash
end
# Load all wmap modules
def load_wmap_modules(reload)
if reload or not self.wmapmodules
print_status("Loading wmap modules...")
self.wmapmodules=[]
idx = 0
[ [ framework.auxiliary, 'auxiliary' ], [framework.exploits, 'exploit' ] ].each do |mtype|
# Scan all exploit modules for matching references
mtype[0].each_module do |n,m|
e = m.new
# Only include wmap_enabled plugins
if e.respond_to?("wmap_enabled")
penabled = e.wmap_enabled
if penabled
self.wmapmodules << [mtype[1]+'/'+n,mtype[1],e.wmap_type,e.orderid]
idx += 1
end
end
end
end
print_status("#{idx} wmap enabled modules loaded.")
end
end
def view_vulns
framework.db.hosts.each do |host|
host.services.each do |serv|
serv.web_sites.each do |site|
site.web_vulns.each do |wv|
print_status("+ [#{host.address}] (#{site.vhost}): #{wv.category} #{wv.path}")
print_status("\t#{wv.name} #{wv.description}")
print_status("\t#{wv.method} #{wv.proof}")
end
end
end
end
end
end
class WebTarget < ::Hash
def to_url
proto = self[:ssl] ? "https" : "http"
"#{proto}://#{self[:host]}:#{self[:port]}#{self[:path]}"
end
end
def initialize(framework, opts)
super
color = self.opts["ConsoleDriver"].output.supports_color? rescue false
wmapversion = '1.5.1'
wmapbanner = "%red\n.-.-.-..-.-.-..---..---.%clr\n"
wmapbanner += "%red| | | || | | || | || |-'%clr\n"
wmapbanner += "%red`-----'`-'-'-'`-^-'`-'%clr\n"
wmapbanner += "[WMAP #{wmapversion}] === et [ ] metasploit.com 2012\n"
if not @stdio
@stdio = Rex::Ui::Text::Output::Stdio.new
end
if color == true
@stdio.auto_color
else
@stdio.disable_color
end
@stdio.print_raw(@stdio.substitute_colors(wmapbanner))
add_console_dispatcher(WmapCommandDispatcher)
#print_status("#{wmapbanner}")
end
def cleanup
remove_console_dispatcher('wmap')
end
def name
"wmap"
end
def desc
"Web assessment plugin"
end
protected
end