# # Specs # require 'spec_helper' # # Project # require 'metasploit/framework/database' require 'msf/core' describe Msf::DBManager do include_context 'Msf::DBManager' subject do db_manager end it_should_behave_like 'Msf::DBManager::Migration' it_should_behave_like 'Msf::DBManager::ImportMsfXml' context 'CONSTANTS' do context 'ADAPTER' do subject(:adapter) { described_class::ADAPTER } it { is_expected.to eq('postgresql') } end end it { is_expected.to respond_to :active } context '#add_rails_engine_migration_paths' do def add_rails_engine_migration_paths db_manager.add_rails_engine_migration_paths end it 'should not add duplicate paths to ActiveRecord::Migrator.migrations_paths' do add_rails_engine_migration_paths expect { add_rails_engine_migration_paths }.to_not change { ActiveRecord::Migrator.migrations_paths.length } ActiveRecord::Migrator.migrations_paths.uniq.should == ActiveRecord::Migrator.migrations_paths end end it { is_expected.to respond_to :add_workspace } it { is_expected.to respond_to :after_establish_connection } it { is_expected.to respond_to :check } it { is_expected.to respond_to :connect } it { is_expected.to respond_to :connection_established? } it { is_expected.to respond_to :create_db } it { is_expected.to respond_to :create_request } it { is_expected.to respond_to :create_target } it { is_expected.to respond_to :creds } it { is_expected.to respond_to :default_workspace } it { is_expected.to respond_to :dehex } it { is_expected.to respond_to :del_host } it { is_expected.to respond_to :del_service } it { is_expected.to respond_to :delete_all_targets } it { is_expected.to respond_to :disconnect } it { is_expected.to respond_to :driver } it { is_expected.to respond_to :drivers } it { is_expected.to respond_to :drivers= } it { is_expected.to respond_to :each_cred } it { is_expected.to respond_to :each_distinct_target } it { is_expected.to respond_to :each_exploited_host } it { is_expected.to respond_to :each_host } it { is_expected.to respond_to :each_loot } it { is_expected.to respond_to :each_note } it { is_expected.to respond_to :each_request } it { is_expected.to respond_to :each_request_target } it { is_expected.to respond_to :each_request_target_with_body } it { is_expected.to respond_to :each_request_target_with_headers } it { is_expected.to respond_to :each_request_target_with_path } it { is_expected.to respond_to :each_request_target_with_query } it { is_expected.to respond_to :each_service } it { is_expected.to respond_to :each_target } it { is_expected.to respond_to :each_vuln } it { is_expected.to respond_to :emit } it { is_expected.to respond_to :error } it { is_expected.to respond_to :events } it { is_expected.to respond_to :exploited_hosts } it { is_expected.to respond_to :find_or_create_client } it { is_expected.to respond_to :find_or_create_cred } it { is_expected.to respond_to :find_or_create_host } it { is_expected.to respond_to :find_or_create_loot } it { is_expected.to respond_to :find_or_create_note } it { is_expected.to respond_to :find_or_create_ref } it { is_expected.to respond_to :find_or_create_report } it { is_expected.to respond_to :find_or_create_service } it { is_expected.to respond_to :find_or_create_task } it { is_expected.to respond_to :find_or_create_vuln } it { is_expected.to respond_to :find_qualys_asset_ports } it { is_expected.to respond_to :find_qualys_asset_vuln_refs } it { is_expected.to respond_to :find_qualys_asset_vulns } it { is_expected.to respond_to :find_vuln_by_details } it { is_expected.to respond_to :find_vuln_by_refs } it { is_expected.to respond_to :find_workspace } it { is_expected.to respond_to :get_client } it { is_expected.to respond_to :get_host } it { is_expected.to respond_to :get_ref } it { is_expected.to respond_to :get_service } it { is_expected.to respond_to :get_session } it { is_expected.to respond_to :get_target } it { is_expected.to respond_to :get_vuln } it { is_expected.to respond_to :has_host? } it { is_expected.to respond_to :has_ref? } it { is_expected.to respond_to :has_vuln? } it { is_expected.to respond_to :hosts } it { is_expected.to respond_to :import } it { is_expected.to respond_to :import_acunetix_noko_stream } it { is_expected.to respond_to :import_acunetix_xml } it { is_expected.to respond_to :import_amap_log } it { is_expected.to respond_to :import_amap_log_file } it { is_expected.to respond_to :import_amap_mlog } it { is_expected.to respond_to :import_appscan_noko_stream } it { is_expected.to respond_to :import_appscan_xml } it { is_expected.to respond_to :import_burp_session_noko_stream } it { is_expected.to respond_to :import_burp_session_xml } it { is_expected.to respond_to :import_ci_noko_stream } it { is_expected.to respond_to :import_ci_xml } it { is_expected.to respond_to :import_file } it { is_expected.to respond_to :import_filetype_detect } it { is_expected.to respond_to :import_foundstone_noko_stream } it { is_expected.to respond_to :import_foundstone_xml } it { is_expected.to respond_to :import_fusionvm_xml } it { is_expected.to respond_to :import_ip360_aspl_xml } it { is_expected.to respond_to :import_ip360_xml_file } it { is_expected.to respond_to :import_ip360_xml_v3 } it { is_expected.to respond_to :import_ip_list } it { is_expected.to respond_to :import_ip_list_file } it { is_expected.to respond_to :import_libpcap } it { is_expected.to respond_to :import_libpcap_file } it { is_expected.to respond_to :import_mbsa_noko_stream } it { is_expected.to respond_to :import_mbsa_xml } it { is_expected.to respond_to :import_msf_collateral } it { is_expected.to respond_to :import_msf_cred_dump } it { is_expected.to respond_to :import_msf_cred_dump_zip } it { is_expected.to respond_to :import_msf_file } it { is_expected.to respond_to :import_msf_pwdump } it { is_expected.to respond_to :import_msf_zip } it { is_expected.to respond_to :import_nessus_nbe } it { is_expected.to respond_to :import_nessus_nbe_file } it { is_expected.to respond_to :import_nessus_xml } it { is_expected.to respond_to :import_nessus_xml_file } it { is_expected.to respond_to :import_nessus_xml_v2 } it { is_expected.to respond_to :import_netsparker_xml } it { is_expected.to respond_to :import_netsparker_xml_file } it { is_expected.to respond_to :import_nexpose_noko_stream } it { is_expected.to respond_to :import_nexpose_raw_noko_stream } it { is_expected.to respond_to :import_nexpose_rawxml } it { is_expected.to respond_to :import_nexpose_rawxml_file } it { is_expected.to respond_to :import_nexpose_simplexml } it { is_expected.to respond_to :import_nexpose_simplexml_file } it { is_expected.to respond_to :import_nikto_xml } it { is_expected.to respond_to :import_nmap_noko_stream } it { is_expected.to respond_to :import_nmap_xml } it { is_expected.to respond_to :import_nmap_xml_file } it { is_expected.to respond_to :import_openvas_new_xml } it { is_expected.to respond_to :import_openvas_new_xml_file } it { is_expected.to respond_to :import_openvas_xml } it { is_expected.to respond_to :import_outpost24_noko_stream } it { is_expected.to respond_to :import_outpost24_xml } it { is_expected.to respond_to :import_qualys_asset_xml } it { is_expected.to respond_to :import_qualys_scan_xml } it { is_expected.to respond_to :import_qualys_scan_xml_file } it { is_expected.to respond_to :import_report } it { is_expected.to respond_to :import_retina_xml } it { is_expected.to respond_to :import_retina_xml_file } it { is_expected.to respond_to :import_spiceworks_csv } it { is_expected.to respond_to :import_wapiti_xml } it { is_expected.to respond_to :import_wapiti_xml_file } it { is_expected.to respond_to :initialize_adapter } it { is_expected.to respond_to :initialize_database_support } it { is_expected.to respond_to :initialize_sink } it { is_expected.to respond_to :inspect_single_packet } it { is_expected.to respond_to :inspect_single_packet_http } it { is_expected.to respond_to :ipv46_validator } it { is_expected.to respond_to :ipv4_validator } it { is_expected.to respond_to :ipv6_validator } it { is_expected.to respond_to :loots } it { is_expected.to respond_to :modules_cached } it { is_expected.to respond_to :modules_cached } it { is_expected.to respond_to :modules_cached= } it { is_expected.to respond_to :modules_cached= } it { is_expected.to respond_to :msf_import_timestamps } it { is_expected.to respond_to :netsparker_method_map } it { is_expected.to respond_to :netsparker_params_map } it { is_expected.to respond_to :netsparker_pname_map } it { is_expected.to respond_to :netsparker_vulnerability_map } it { is_expected.to respond_to :nexpose_host_from_rawxml } it { is_expected.to respond_to :nexpose_refs_to_struct } it { is_expected.to respond_to :nils_for_nulls } it { is_expected.to respond_to :nmap_msf_service_map } it { is_expected.to respond_to :normalize_host } it { is_expected.to respond_to :notes } context '#purge_all_module_details' do def purge_all_module_details db_manager.purge_all_module_details end let(:migrated) do false end let(:module_detail_count) do 2 end let!(:module_details) do FactoryGirl.create_list( :mdm_module_detail, module_detail_count ) end before(:each) do db_manager.stub(:migrated => migrated) end context 'with migrated' do let(:migrated) do true end let(:modules_caching) do false end before(:each) do db_manager.stub(:modules_caching => modules_caching) end context 'with modules_caching' do let(:modules_caching) do true end it 'should not destroy Mdm::Module::Details' do expect { purge_all_module_details }.to_not change(Mdm::Module::Detail, :count) end end context 'without modules_caching' do it 'should destroy all Mdm::Module::Details' do expect { purge_all_module_details }.to change(Mdm::Module::Detail, :count).by(-module_detail_count) end end end context 'without migrated' do it 'should not destroy Mdm::Module::Details' do expect { purge_all_module_details }.to_not change(Mdm::Module::Detail, :count) end end end it { is_expected.to respond_to :queue } context '#remove_module_details' do def remove_module_details db_manager.remove_module_details(mtype, refname) end let(:migrated) do false end let(:mtype) do FactoryGirl.generate :mdm_module_detail_mtype end let(:refname) do FactoryGirl.generate :mdm_module_detail_refname end let!(:module_detail) do FactoryGirl.create( :mdm_module_detail ) end before(:each) do db_manager.stub(:migrated => migrated) end context 'with migrated' do let(:migrated) do true end let!(:module_detail) do FactoryGirl.create(:mdm_module_detail) end context 'with matching Mdm::Module::Detail' do let(:mtype) do module_detail.mtype end let(:refname) do module_detail.refname end it 'should destroy Mdm::Module::Detail' do expect { remove_module_details }.to change(Mdm::Module::Detail, :count).by(-1) end end context 'without matching Mdm::Module::Detail' do it 'should not destroy Mdm::Module::Detail' do expect { remove_module_details }.to_not change(Mdm::Module::Detail, :count) end end end context 'without migrated' do it 'should not destroy Mdm::Module::Detail' do expect { remove_module_details }.to_not change(Mdm::Module::Detail, :count) end end end it { is_expected.to respond_to :report_artifact } it { is_expected.to respond_to :report_auth } it { is_expected.to respond_to :report_auth_info } it { is_expected.to respond_to :report_client } it { is_expected.to respond_to :report_cred } it { is_expected.to respond_to :report_event } it { is_expected.to respond_to :report_exploit } it { is_expected.to respond_to :report_exploit_attempt } it { is_expected.to respond_to :report_exploit_failure } it { is_expected.to respond_to :report_exploit_success } it { is_expected.to respond_to :report_host } it { is_expected.to respond_to :report_host_details } it { is_expected.to respond_to :report_host_tag } it { is_expected.to respond_to :report_import_note } it { is_expected.to respond_to :report_loot } it { is_expected.to respond_to :report_note } it { is_expected.to respond_to :report_report } it { is_expected.to respond_to :report_service } context '#report_session' do let(:options) do {} end subject(:report_session) do db_manager.report_session(options) end context 'with active' do let(:active) do true end context 'with :session' do before(:each) do options[:session] = session end context 'with Msf::Session' do let(:exploit_datastore) do Msf::ModuleDataStore.new(module_instance).tap do |datastore| datastore['ParentModule'] = parent_module_fullname remote_port = rand(2 ** 16 - 1) datastore['RPORT'] = remote_port end end let(:host) do FactoryGirl.create(:mdm_host, :workspace => session_workspace) end let(:module_instance) do name = 'multi/handler' double( 'Msf::Module', :fullname => "exploit/#{name}", :framework => framework, :name => name ) end let(:options_workspace) do FactoryGirl.create(:mdm_workspace) end let(:parent_module_fullname) do "exploit/#{parent_module_name}" end let(:parent_module_name) do 'windows/smb/ms08_067_netapi' end let(:parent_path) do Metasploit::Framework.root.join('modules').to_path end let(:session) do session_class.new.tap do |session| session.exploit_datastore = exploit_datastore session.info = 'Info' session.platform = 'Platform' session.session_host = host.address session.sid = rand(100) session.type = 'Session Type' session.via_exploit = 'exploit/multi/handler' session.via_payload = 'payload/single/windows/metsvc_bind_tcp' session.workspace = session_workspace.name end end let(:session_class) do Class.new do include Msf::Session attr_accessor :datastore attr_accessor :platform attr_accessor :type attr_accessor :via_exploit attr_accessor :via_payload end end let(:session_workspace) do FactoryGirl.create(:mdm_workspace) end before(:each) do reference_name = 'multi/handler' path = File.join(parent_path, 'exploits', reference_name) # fake cache data for exploit/multi/handler so it can be loaded framework.modules.send( :module_info_by_path=, { path => { :parent_path => parent_path, :reference_name => reference_name, :type => 'exploit', } } ) FactoryGirl.create( :mdm_module_detail, :fullname => parent_module_fullname, :name => parent_module_name ) end context 'with :workspace' do before(:each) do options[:workspace] = options_workspace end it 'should not find workspace from session' do db_manager.should_not_receive(:find_workspace) report_session end end context 'without :workspace' do it 'should find workspace from session' do db_manager.should_receive(:find_workspace).with(session.workspace).and_call_original report_session end it 'should pass session.workspace to #find_or_create_host' do db_manager.should_receive(:find_or_create_host).with( hash_including( :workspace => session_workspace ) ).and_return(host) report_session end end context 'with workspace from either :workspace or session' do it 'should pass normalized host from session as :host to #find_or_create_host' do normalized_host = double('Normalized Host') db_manager.stub(:normalize_host).with(session).and_return(normalized_host) # stub report_vuln so its use of find_or_create_host and normalize_host doesn't interfere. db_manager.stub(:report_vuln) db_manager.should_receive(:find_or_create_host).with( hash_including( :host => normalized_host ) ).and_return(host) report_session end context 'with session responds to arch' do let(:arch) do FactoryGirl.generate :mdm_host_arch end before(:each) do session.stub(:arch => arch) end it 'should pass :arch to #find_or_create_host' do db_manager.should_receive(:find_or_create_host).with( hash_including( :arch => arch ) ).and_call_original report_session end end context 'without session responds to arch' do it 'should not pass :arch to #find_or_create_host' do db_manager.should_receive(:find_or_create_host).with( hash_excluding( :arch ) ).and_call_original report_session end end it 'should create an Mdm::Session' do expect { report_session }.to change(Mdm::Session, :count).by(1) end it { should be_an Mdm::Session } it 'should set session.db_record to created Mdm::Session' do mdm_session = report_session session.db_record.should == mdm_session end context 'with session.via_exploit' do it 'should create session.via_exploit module' do framework.modules.should_receive(:create).with(session.via_exploit).and_call_original report_session end it 'should create Mdm::Vuln' do expect { report_session }.to change(Mdm::Vuln, :count).by(1) end context 'created Mdm::Vuln' do let(:mdm_session) do Mdm::Session.last end let(:rport) do nil end before(:each) do Timecop.freeze session.exploit_datastore['RPORT'] = rport report_session end after(:each) do Timecop.return end subject(:vuln) do Mdm::Vuln.last end it { expect(subject.host).to eq(Mdm::Host.last) } it { expect(subject.refs).to eq([]) } it { expect(subject.exploited_at).to be_within(1.second).of(Time.now.utc) } context "with session.via_exploit 'exploit/multi/handler'" do context "with session.exploit_datastore['ParentModule']" do it { expect(subject.info).to eq("Exploited by #{parent_module_fullname} to create Session #{mdm_session.id}") } it { expect(subject.name).to eq(parent_module_name) } end end context "without session.via_exploit 'exploit/multi/handler'" do let(:reference_name) do 'windows/smb/ms08_067_netapi' end before(:each) do path = File.join( parent_path, 'exploits', "#{reference_name}.rb" ) type = 'exploit' # fake cache data for ParentModule so it can be loaded framework.modules.send( :module_info_by_path=, { path => { :parent_path => parent_path, :reference_name => reference_name, :type => type, } } ) session.via_exploit = "#{type}/#{reference_name}" end it { expect(subject.info).to eq("Exploited by #{session.via_exploit} to create Session #{mdm_session.id}") } it { expect(subject.name).to eq(reference_name) } end context 'with RPORT' do let(:rport) do # use service.port instead of having service use rport so # that service is forced to exist before call to # report_service, which happens right after using rport in # outer context's before(:each) service.port end let(:service) do FactoryGirl.create( :mdm_service, :host => host ) end it { expect(subject.service).to eq(service) } end context 'without RPORT' do it { expect(subject.service).to be_nil } end end context 'created Mdm::ExploitAttempt' do let(:rport) do nil end before(:each) do Timecop.freeze session.exploit_datastore['RPORT'] = rport report_session end after(:each) do Timecop.return end subject(:exploit_attempt) do Mdm::ExploitAttempt.last end it { expect(subject.attempted_at).to be_within(1.second).of(Time.now.utc) } # @todo https://www.pivotaltracker.com/story/show/48362615 it { expect(subject.session_id).to eq(Mdm::Session.last.id) } it { expect(subject.exploited).to be_truthy } # @todo https://www.pivotaltracker.com/story/show/48362615 it { expect(subject.vuln_id).to eq(Mdm::Vuln.last.id) } context "with session.via_exploit 'exploit/multi/handler'" do context "with session.datastore['ParentModule']" do it { expect(subject.module).to eq(parent_module_fullname) } end end context "without session.via_exploit 'exploit/multi/handler'" do before(:each) do session.via_exploit = parent_module_fullname end it { expect(subject.module).to eq(session.via_exploit) } end end end context 'returned Mdm::Session' do before(:each) do Timecop.freeze end after(:each) do Timecop.return end subject(:mdm_session) do report_session end # # Ensure session has attributes present so its on mdm_session are # not just comparing nils. # it 'should have session.info present' do session.info.should be_present end it 'should have session.sid present' do session.sid.should be_present end it 'should have session.platform present' do session.platform.should be_present end it 'should have session.type present' do session.type.should be_present end it 'should have session.via_exploit present' do session.via_exploit.should be_present end it 'should have session.via_payload present' do session.via_exploit.should be_present end it { expect(subject.datastore).to eq(session.exploit_datastore.to_h) } it { expect(subject.desc).to eq(session.info) } it { expect(subject.host_id).to eq(Mdm::Host.last.id) } it { expect(subject.last_seen).to be_within(1.second).of(Time.now.utc) } it { expect(subject.local_id).to eq(session.sid) } it { expect(subject.opened_at).to be_within(1.second).of(Time.now.utc) } it { expect(subject.platform).to eq(session.platform) } it { expect(subject.routes).to eq([]) } it { expect(subject.stype).to eq(session.type) } it { expect(subject.via_payload).to eq(session.via_payload) } context "with session.via_exploit 'exploit/multi/handler'" do it "should have session.via_exploit of 'exploit/multi/handler'" do session.via_exploit.should == 'exploit/multi/handler' end context "with session.exploit_datastore['ParentModule']" do it "should have session.exploit_datastore['ParentModule']" do session.exploit_datastore['ParentModule'].should_not be_nil end it { expect(subject.via_exploit).to eq(parent_module_fullname) } end end context "without session.via_exploit 'exploit/multi/handler'" do before(:each) do reference_name = 'windows/smb/ms08_067_netapi' path = File.join( parent_path, 'exploits', "#{reference_name}.rb" ) type = 'exploit' # fake cache data for ParentModule so it can be loaded framework.modules.send( :module_info_by_path=, { path => { :parent_path => parent_path, :reference_name => reference_name, :type => type, } } ) session.via_exploit = "#{type}/#{reference_name}" end it "should not have session.via_exploit of 'exploit/multi/handler'" do session.via_exploit.should_not == 'exploit/multi/handler' end it { expect(subject.via_exploit).to eq(session.via_exploit) } end end end end context 'without Msf::Session' do let(:session) do double('Not a Msf::Session') end it 'should raise ArgumentError' do expect { report_session }.to raise_error(ArgumentError, "Invalid :session, expected Msf::Session") end end end context 'without :session' do context 'with :host' do before(:each) do options[:host] = host end context 'with Mdm::Host' do let(:host) do FactoryGirl.create(:mdm_host) end context 'created Mdm::Session' do let(:closed_at) do nil end let(:close_reason) do 'Closed because...' end let(:description) do 'Session Description' end let(:exploit_full_name) do 'exploit/windows/smb/ms08_067_netapi' end let(:last_seen) do nil end let(:opened_at) do Time.now.utc - 5.minutes end let(:payload_full_name) do 'payload/singles/windows/metsvc_reverse_tcp' end let(:platform) do 'Host Platform' end let(:routes) do nil end let(:session_type) do 'Session Type' end before(:each) do options[:closed_at] = closed_at options[:close_reason] = close_reason options[:desc] = description options[:last_seen] = last_seen options[:opened_at] = opened_at options[:platform] = platform options[:routes] = routes options[:stype] = session_type options[:via_payload] = payload_full_name options[:via_exploit] = exploit_full_name end subject(:mdm_session) do report_session end it { expect(subject.close_reason).to eq(close_reason) } it { expect(subject.desc).to eq(description) } it { expect(subject.host).to eq(host) } it { expect(subject.platform).to eq(platform) } it { expect(subject.stype).to eq(session_type) } it { expect(subject.via_exploit).to eq(exploit_full_name) } it { expect(subject.via_payload).to eq(payload_full_name) } context 'with :last_seen' do let(:last_seen) do opened_at end it { expect(subject.last_seen).to eq(last_seen) } end context 'with :closed_at' do let(:closed_at) do opened_at + 1.minute end it { expect(subject.closed_at).to eq(closed_at) } end context 'without :closed_at' do it { expect(subject.closed_at).to be_nil } end context 'without :last_seen' do context 'with :closed_at' do let(:closed_at) do opened_at + 1.minute end it { expect(subject.last_seen).to eq(closed_at) } end context 'without :closed_at' do it { expect(subject.last_seen).to be_nil } end end context 'with :routes' do let(:routes) do FactoryGirl.build_list( :mdm_route, 1, :session => nil ) end it { expect(subject.routes).to eq(routes) } end context 'without :routes' do it { expect(subject.routes).to eq([]) } end end end context 'without Mdm::Host' do let(:host) do '192.168.0.1' end it 'should raise ArgumentError' do expect { report_session }.to raise_error(ArgumentError, "Invalid :host, expected Host object") end end end context 'without :host' do it 'should raise ArgumentError' do expect { report_session }.to raise_error(ArgumentError) end end end end context 'without active' do let(:active) do false end it { should be_nil } it 'should not create a connection' do ActiveRecord::Base.connection_pool.should_not_receive(:with_connection) report_session end end end it { is_expected.to respond_to :report_session_event } it { is_expected.to respond_to :report_session_route } it { is_expected.to respond_to :report_session_route_remove } it { is_expected.to respond_to :report_task } it { is_expected.to respond_to :report_vuln } it { is_expected.to respond_to :report_vuln_attempt } it { is_expected.to respond_to :report_vuln_details } it { is_expected.to respond_to :report_web_form } it { is_expected.to respond_to :report_web_page } it { is_expected.to respond_to :report_web_site } it { is_expected.to respond_to :report_web_vuln } it { is_expected.to respond_to :reports } it { is_expected.to respond_to :request_distinct_targets } it { is_expected.to respond_to :request_sql } it { is_expected.to respond_to :requests } it { is_expected.to respond_to :rexmlify } it { is_expected.to respond_to :rfc3330_reserved } context '#search_modules' do subject(:search_modules) do db_manager.search_modules(search_string) end let(:module_details) do search_modules.to_a end context 'with app keyword' do let(:search_string) do "app:#{app}" end before(:each) do Mdm::Module::Detail::STANCES.each do |stance| FactoryGirl.create(:mdm_module_detail, :stance => stance) end end context 'with client' do let(:app) do 'client' end it "should match Mdm::Module::Detail#stance 'passive'" do module_details.count.should > 0 module_details.all? { |module_detail| module_detail.stance == 'passive' }.should be_truthy end end context 'with server' do let(:app) do 'server' end it "should match Mdm::Module::Detail#stance 'aggressive'" do module_details.count.should > 0 module_details.all? { |module_detail| module_detail.stance == 'aggressive' }.should be_truthy end end end context 'with author keyword' do let(:search_string) do # us inspect so strings with spaces are quoted correctly "author:#{author}" end let!(:module_authors) do FactoryGirl.create_list(:mdm_module_author, 2) end let(:target_module_author) do module_authors.first end context 'with Mdm::Module::Author#email' do let(:author) do target_module_author.email end it 'should match Mdm::Module::Author#email' do module_details.count.should > 0 module_details.all? { |module_detail| module_detail.authors.any? { |module_author| module_author.email == target_module_author.email } }.should be_truthy end end context 'with Mdm::Module::Author#name' do let(:author) do # use inspect to quote space in name target_module_author.name.inspect end it 'should match Mdm::Module::Author#name' do module_details.count.should > 0 module_details.all? { |module_detail| module_detail.authors.any? { |module_author| module_author.name == target_module_author.name } }.should be_truthy end end end it_should_behave_like 'Msf::DBManager#search_modules Mdm::Module::Ref#name keyword', :bid it_should_behave_like 'Msf::DBManager#search_modules Mdm::Module::Ref#name keyword', :cve it_should_behave_like 'Msf::DBManager#search_modules Mdm::Module::Ref#name keyword', :edb context 'with name keyword' do let(:search_string) do "name:#{name}" end let!(:existing_module_details) do FactoryGirl.create_list(:mdm_module_detail, 2) end let(:target_module_detail) do existing_module_details.first end context 'with Mdm::Module::Detail#fullname' do let(:name) do target_module_detail.fullname end it 'should match Mdm::Module::Detail#fullname' do module_details.count.should > 0 module_details.all? { |module_detail| module_detail.fullname == target_module_detail.fullname }.should be_truthy end end context 'with Mdm::Module::Detail#name' do let(:name) do # use inspect so spaces are inside quotes target_module_detail.name.inspect end it 'should match Mdm::Module::Detail#name' do module_details.count.should > 0 module_details.all? { |module_detail| module_detail.name == target_module_detail.name }.should be_truthy end end end it_should_behave_like 'Msf::DBManager#search_modules Mdm::Module::Platform#name or Mdm::Module::Target#name keyword', :os it_should_behave_like 'Msf::DBManager#search_modules Mdm::Module::Ref#name keyword', :osvdb it_should_behave_like 'Msf::DBManager#search_modules Mdm::Module::Platform#name or Mdm::Module::Target#name keyword', :platform context 'with ref keyword' do let(:ref) do FactoryGirl.generate :mdm_module_ref_name end let(:search_string) do # use inspect to quote spaces in string "ref:#{ref.inspect}" end let!(:module_ref) do FactoryGirl.create(:mdm_module_ref) end context 'with Mdm::Module::Ref#name' do let(:ref) do module_ref.name end it 'should match Mdm::Module::Ref#name' do module_details.count.should > 0 module_details.all? { |module_detail| module_detail.refs.any? { |module_ref| module_ref.name == ref } }.should be_truthy end end context 'without Mdm::Module::Ref#name' do it 'should not match Mdm::Module::Ref#name' do module_details.count.should == 0 end end end context 'with type keyword' do let(:type) do FactoryGirl.generate :mdm_module_detail_mtype end let(:search_string) do "type:#{type}" end let(:target_module_detail) do all_module_details.first end let!(:all_module_details) do FactoryGirl.create_list(:mdm_module_detail, 2) end context 'with Mdm::Module::Ref#name' do let(:type) do target_module_detail.mtype end it 'should match Mdm::Module::Detail#mtype' do module_details.count.should > 0 module_details.all? { |module_detail| module_detail.mtype == type }.should be_truthy end end context 'without Mdm::Module::Detail#mtype' do it 'should not match Mdm::Module::Detail#mtype' do module_details.count.should == 0 end end end context 'without keyword' do context 'with Mdm::Module::Action#name' do let(:search_string) do module_action.name end let!(:module_action) do FactoryGirl.create(:mdm_module_action) end it 'should match Mdm::Module::Action#name' do module_details.count.should > 0 module_details.all? { |module_detail| module_detail.actions.any? { |module_action| module_action.name == search_string } }.should be_truthy end end context 'with Mdm::Module::Arch#name' do let(:search_string) do module_arch.name end let!(:module_arch) do FactoryGirl.create(:mdm_module_arch) end it 'should match Mdm::Module::Arch#name' do module_details.count.should > 0 module_details.all? { |module_detail| module_detail.archs.any? { |module_arch| module_arch.name == search_string } }.should be_truthy end end context 'with Mdm::Module::Author#name' do let(:search_string) do module_author.name end let!(:module_author) do FactoryGirl.create(:mdm_module_author) end it 'should match Mdm::Module::Author#name' do module_details.count.should > 0 module_details.all? { |module_detail| module_detail.authors.any? { |module_author| module_author.name == search_string } }.should be_truthy end end context 'with Mdm::Module::Detail' do let(:target_module_detail) do all_module_details.first end let!(:all_module_details) do FactoryGirl.create_list(:mdm_module_detail, 3) end context 'with #description' do let(:search_string) do # use inspect to quote spaces in string target_module_detail.description.inspect end it 'should match Mdm::Module::Detail#description' do module_details.count.should == 1 module_details.all? { |module_detail| module_detail.description == target_module_detail.description }.should be_truthy end end context 'with #fullname' do let(:search_string) do target_module_detail.fullname end it 'should match Mdm::Module::Detail#fullname' do module_details.count.should == 1 module_details.all? { |module_detail| module_detail.fullname == search_string }.should be_truthy end end context 'with #name' do let(:search_string) do # use inspect to quote spaces in string target_module_detail.name.inspect end it 'should match Mdm::Module::Detail#name' do module_details.count.should == 1 module_details.all? { |module_detail| module_detail.name == target_module_detail.name }.should be_truthy end end end context 'with Mdm::Module::Platform#name' do let(:search_string) do module_platform.name end let!(:module_platform) do FactoryGirl.create(:mdm_module_platform) end it 'should match Mdm::Module::Platform#name' do module_details.count.should > 0 module_details.all? { |module_detail| module_detail.platforms.any? { |module_platform| module_platform.name == search_string } }.should be_truthy end end context 'with Mdm::Module::Ref#name' do let(:search_string) do module_ref.name end let!(:module_ref) do FactoryGirl.create(:mdm_module_ref) end it 'should match Mdm::Module::Ref#name' do module_details.count.should > 0 module_details.all? { |module_detail| module_detail.refs.any? { |module_ref| module_ref.name == search_string } }.should be_truthy end end context 'with Mdm::Module::Target#name' do let(:search_string) do module_target.name end let!(:module_target) do FactoryGirl.create(:mdm_module_target) end it 'should match Mdm::Module::Target#name' do module_details.count.should > 0 module_details.all? { |module_detail| module_detail.targets.any? { |module_target| module_target.name == search_string } }.should be_truthy end end end end it { is_expected.to respond_to :selected_host } it { is_expected.to respond_to :selected_id } it { is_expected.to respond_to :selected_port } it { is_expected.to respond_to :selected_ssl } it { is_expected.to respond_to :selected_wmap_target } it { is_expected.to respond_to :service_name_map } it { is_expected.to respond_to :services } it { is_expected.to respond_to :sink } it { is_expected.to respond_to :sql_query } it { is_expected.to respond_to :sync } it { is_expected.to respond_to :target_requests } it { is_expected.to respond_to :targets } it { is_expected.to respond_to :tasks } it { is_expected.to respond_to :unserialize_object } context '#update_all_module_details' do def update_all_module_details db_manager.update_all_module_details end let(:migrated) do false end before(:each) do db_manager.stub(:migrated => migrated) end context 'with migrated' do let(:migrated) do true end let(:modules_caching) do true end before(:each) do db_manager.stub(:modules_caching => modules_caching) end context 'with modules_caching' do it 'should not update module details' do db_manager.should_not_receive(:update_module_details) update_all_module_details end end context 'without modules_caching' do let(:modules_caching) do false end it 'should set framework.cache_thread to current thread and then nil' do framework.should_receive(:cache_thread=).with(Thread.current).ordered framework.should_receive(:cache_thread=).with(nil).ordered update_all_module_details end it 'should set modules_cached to false and then true' do db_manager.should_receive(:modules_cached=).with(false).ordered db_manager.should_receive(:modules_cached=).with(true).ordered update_all_module_details end it 'should set modules_caching to true and then false' do db_manager.should_receive(:modules_caching=).with(true).ordered db_manager.should_receive(:modules_caching=).with(false).ordered update_all_module_details end context 'with Mdm::Module::Details' do let(:module_pathname) do parent_pathname.join( 'exploits', "#{reference_name}.rb" ) end let(:modification_time) do module_pathname.mtime end let(:parent_pathname) do Metasploit::Framework.root.join('modules') end let(:reference_name) do 'windows/smb/ms08_067_netapi' end let(:type) do 'exploit' end let!(:module_detail) do # needs to reference a real module so that it can be loaded FactoryGirl.create( :mdm_module_detail, :file => module_pathname.to_path, :mtime => modification_time, :mtype => type, :ready => ready, :refname => reference_name ) end context '#ready' do context 'false' do let(:ready) do false end it_should_behave_like 'Msf::DBManager#update_all_module_details refresh' end context 'true' do let(:ready) do true end context 'with existing Mdm::Module::Detail#file' do context 'with same Mdm::Module::Detail#mtime and File.mtime' do it 'should not update module details' do db_manager.should_not_receive(:update_module_details) update_all_module_details end end context 'without same Mdm::Module::Detail#mtime and File.mtime' do let(:modification_time) do # +1 as rand can return 0 and the time must be different for # this context. super() - (rand(1.day) + 1) end it_should_behave_like 'Msf::DBManager#update_all_module_details refresh' end end # Emulates a module being removed or renamed context 'without existing Mdm::Module::Detail#file' do # have to compute modification manually since the # `module_pathname` refers to a non-existent file and # `module_pathname.mtime` would error. let(:modification_time) do Time.now.utc - 1.day end let(:module_pathname) do parent_pathname.join('exploits', 'deleted.rb') end it 'should not update module details' do db_manager.should_not_receive(:update_module_details) update_all_module_details end end end end end end end context 'without migrated' do it 'should not update module details' do db_manager.should_not_receive(:update_module_details) update_all_module_details end end end it { is_expected.to respond_to :update_host_via_sysinfo } context '#update_module_details' do def update_module_details db_manager.update_module_details(module_instance) end let(:loader) do loader = framework.modules.send(:loaders).find { |loader| loader.loadable?(parent_path) } # Override load_error so that rspec will print it instead of going to framework log def loader.load_error(module_path, error) raise error end loader end let(:migrated) do false end let(:module_instance) do # make sure the module is loaded into the module_set loaded = loader.load_module(parent_path, module_type, module_reference_name) unless loaded module_path = loader.module_path(parent_path, type, module_reference_name) fail "#{description} failed to load: #{module_path}" end module_set.create(module_reference_name) end let(:module_set) do framework.modules.module_set(module_type) end let(:module_type) do 'exploit' end let(:module_reference_name) do 'windows/smb/ms08_067_netapi' end let(:parent_path) do parent_pathname.to_path end let(:parent_pathname) do Metasploit::Framework.root.join('modules') end let(:type_directory) do 'exploits' end before(:each) do db_manager.stub(:migrated => migrated) end context 'with migrated' do let(:migrated) do true end it 'should call module_to_details_hash to get Mdm::Module::Detail attributes and association attributes' do db_manager.should_receive(:module_to_details_hash).and_call_original update_module_details end it 'should create an Mdm::Module::Detail' do expect { update_module_details }.to change(Mdm::Module::Detail, :count).by(1) end context 'module_to_details_hash' do let(:module_to_details_hash) do { :mtype => module_type, :privileged => privileged, :rank => rank, :refname => module_reference_name, :stance => stance } end let(:privileged) do FactoryGirl.generate :mdm_module_detail_privileged end let(:rank) do FactoryGirl.generate :mdm_module_detail_rank end let(:stance) do FactoryGirl.generate :mdm_module_detail_stance end before(:each) do db_manager.stub( :module_to_details_hash ).with( module_instance ).and_return( module_to_details_hash ) end context 'Mdm::Module::Detail' do subject(:module_detail) do Mdm::Module::Detail.last end before(:each) do update_module_details end it { expect(subject.mtype).to eq(module_type) } it { expect(subject.privileged).to eq(privileged) } it { expect(subject.rank).to eq(rank) } it { expect(subject.ready).to be_truthy } it { expect(subject.refname).to eq(module_reference_name) } it { expect(subject.stance).to eq(stance) } end context 'with :bits' do let(:bits) do [] end before(:each) do module_to_details_hash[:bits] = bits end context 'with :action' do let(:name) do FactoryGirl.generate :mdm_module_action_name end let(:bits) do super() << [ :action, { :name => name } ] end it 'should create an Mdm::Module::Action' do expect { update_module_details }.to change(Mdm::Module::Action, :count).by(1) end context 'Mdm::Module::Action' do subject(:module_action) do module_detail.actions.last end let(:module_detail) do Mdm::Module::Detail.last end before(:each) do update_module_details end it { expect(subject.name).to eq(name) } end end context 'with :arch' do let(:name) do FactoryGirl.generate :mdm_module_arch_name end let(:bits) do super() << [ :arch, { :name => name } ] end it 'should create an Mdm::Module::Arch' do expect { update_module_details }.to change(Mdm::Module::Arch, :count).by(1) end context 'Mdm::Module::Arch' do subject(:module_arch) do module_detail.archs.last end let(:module_detail) do Mdm::Module::Detail.last end before(:each) do update_module_details end it { expect(subject.name).to eq(name) } end end context 'with :author' do let(:email) do FactoryGirl.generate :mdm_module_author_email end let(:name) do FactoryGirl.generate :mdm_module_author_name end let(:bits) do super() << [ :author, { :email => email, :name => name } ] end it 'should create an Mdm::Module::Author' do expect { update_module_details }.to change(Mdm::Module::Author, :count).by(1) end context 'Mdm::Module::Author' do subject(:module_author) do module_detail.authors.last end let(:module_detail) do Mdm::Module::Detail.last end before(:each) do update_module_details end it { expect(subject.name).to eq(name) } it { expect(subject.email).to eq(email) } end end context 'with :platform' do let(:bits) do super() << [ :platform, { :name => name } ] end let(:name) do FactoryGirl.generate :mdm_module_platform_name end it 'should create an Mdm::Module::Platform' do expect { update_module_details }.to change(Mdm::Module::Platform, :count).by(1) end context 'Mdm::Module::Platform' do subject(:module_platform) do module_detail.platforms.last end let(:module_detail) do Mdm::Module::Detail.last end before(:each) do update_module_details end it { expect(subject.name).to eq(name) } end end context 'with :ref' do let(:bits) do super() << [ :ref, { :name => name } ] end let(:name) do FactoryGirl.generate :mdm_module_ref_name end it 'should create an Mdm::Module::Ref' do expect { update_module_details }.to change(Mdm::Module::Ref, :count).by(1) end context 'Mdm::Module::Ref' do subject(:module_ref) do module_detail.refs.last end let(:module_detail) do Mdm::Module::Detail.last end before(:each) do update_module_details end it { expect(subject.name).to eq(name) } end end context 'with :target' do let(:bits) do super() << [ :target, { :index => index, :name => name } ] end let(:index) do FactoryGirl.generate :mdm_module_target_index end let(:name) do FactoryGirl.generate :mdm_module_target_name end it 'should create an Mdm::Module::Target' do expect { update_module_details }.to change(Mdm::Module::Target, :count).by(1) end context 'Mdm::Module::Target' do subject(:module_target) do module_detail.targets.last end let(:module_detail) do Mdm::Module::Detail.last end before(:each) do update_module_details end it { expect(subject.index).to eq(index) } it { expect(subject.name).to eq(name) } end end end end it_should_behave_like 'Msf::DBManager#update_module_details with module', :reference_name => 'admin/2wire/xslt_password_reset', :type => 'auxiliary' it_should_behave_like 'Msf::DBManager#update_module_details with module', :reference_name => 'generic/none', :type => 'encoder' it_should_behave_like 'Msf::DBManager#update_module_details with module', :reference_name => 'windows/smb/ms08_067_netapi', :type => 'exploit' it_should_behave_like 'Msf::DBManager#update_module_details with module', :reference_name => 'x64/simple', :type => 'nop' # @todo determine how to load a single payload to test payload type outside of msfconsole it_should_behave_like 'Msf::DBManager#update_module_details with module', :reference_name => 'windows/escalate/screen_unlock', :type => 'post' end context 'without migrated' do it 'should not create an Mdm::Module::Detail' do expect { update_module_details }.to_not change(Mdm::Module::Detail, :count) end end end it { is_expected.to respond_to :update_vuln_details } it { is_expected.to respond_to :usable } it { is_expected.to respond_to :usable= } it { is_expected.to respond_to :validate_import_file } it { is_expected.to respond_to :validate_ips } it { is_expected.to respond_to :vulns } it { is_expected.to respond_to :warn_about_rubies } it { is_expected.to respond_to :workspace } it { is_expected.to respond_to :workspace= } it { is_expected.to respond_to :workspaces } end