Files
metasploit-gs/spec/support/shared/examples/msf/db_manager/session.rb
T
David Maloney c6656e4031 example_group and hook_scope conversions
not strictly required, these conversions keep us
up to date with latest rspec conventions and best practices
which will prevent use from having to convert them when they become
deprecated later
2015-12-31 16:56:13 -06:00

1008 lines
34 KiB
Ruby

RSpec.shared_examples_for 'Msf::DBManager::Session' do
it { is_expected.to respond_to :get_session }
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(:example) do
options[:session] = session
end
context 'with Msf::Session' do
include_context 'Metasploit::Framework::Spec::Constants cleaner'
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'
d = double(
'Msf::Exploit',
user_data: user_data,
fullname: "exploit/#{name}",
framework: framework,
name: name
)
d
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 = module_instance
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 :arch
attr_accessor :exploit
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(:example) 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 a run_id in user_data' do
before(:example) do
allow(db_manager).to receive(:create_match_for_vuln).and_return(nil)
end
let(:match_set) do
FactoryGirl.create(:automatic_exploitation_match_set, user: session_workspace.owner,workspace:session_workspace)
end
let(:run) do
FactoryGirl.create(:automatic_exploitation_run, workspace: session_workspace, match_set_id: match_set.id)
end
let(:user_data) do
{
run_id: run.id
}
end
context 'with :workspace' do
before(:example) do
options[:workspace] = options_workspace
end
it 'should not find workspace from session' do
expect(db_manager).not_to receive(:find_workspace)
expect { report_session }.to change(Mdm::Vuln, :count).by(1)
end
end
context 'without :workspace' do
it 'should find workspace from session' do
expect(db_manager).to receive(:find_workspace).with(session.workspace).and_call_original
report_session
end
it 'should pass session.workspace to #find_or_create_host' do
expect(db_manager).to receive(:find_or_create_host).with(
hash_including(
:workspace => session_workspace
)
).and_return(host)
expect { report_session }.to change(Mdm::Vuln, :count).by(1)
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')
expect(db_manager).to receive(: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.
expect(db_manager).to receive(:report_vuln)
expect(db_manager).to 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(:example) do
allow(session).to receive(:arch).and_return(arch)
end
it 'should pass :arch to #find_or_create_host' do
expect(db_manager).to receive(:find_or_create_host).with(
hash_including(
:arch => arch
)
).and_call_original
expect { report_session }.to change(Mdm::Vuln, :count).by(1)
end
end
context 'without session responds to arch' do
it 'should not pass :arch to #find_or_create_host' do
expect(db_manager).to receive(:find_or_create_host).with(
hash_excluding(
:arch
)
).and_call_original
expect { report_session }.to change(Mdm::Vuln, :count).by(1)
end
end
it 'should create an Mdm::Session' do
expect {
report_session
}.to change(Mdm::Session, :count).by(1)
end
it { is_expected.to be_an Mdm::Session }
it 'should set session.db_record to created Mdm::Session' do
mdm_session = report_session
expect(session.db_record).to eq mdm_session
end
context 'with session.via_exploit' do
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(:example) do
Timecop.freeze
session.exploit_datastore['RPORT'] = rport
report_session
end
after(:example) 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(:example) 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(:example) do
Timecop.freeze
session.exploit_datastore['RPORT'] = rport
report_session
end
after(:example) 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(:example) 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(:example) do
Timecop.freeze
end
after(:example) 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
expect(session.info).to be_present
end
it 'should have session.sid present' do
expect(session.sid).to be_present
end
it 'should have session.platform present' do
expect(session.platform).to be_present
end
it 'should have session.type present' do
expect(session.type).to be_present
end
it 'should have session.via_exploit present' do
expect(session.via_exploit).to be_present
end
it 'should have session.via_payload present' do
expect(session.via_exploit).to 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
expect(session.via_exploit).to eq 'exploit/multi/handler'
end
context "with session.exploit_datastore['ParentModule']" do
it "should have session.exploit_datastore['ParentModule']" do
expect(session.exploit_datastore['ParentModule']).not_to 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(:example) 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
expect(session.via_exploit).not_to eq 'exploit/multi/handler'
end
it { expect(subject.via_exploit).to eq(session.via_exploit) }
end
end
end
end
context 'without user_data' do
let(:user_data) { nil }
context 'with :workspace' do
before(:example) do
options[:workspace] = options_workspace
end
it 'should not find workspace from session' do
expect(db_manager).not_to receive(:find_workspace)
expect { report_session }.to change(Mdm::Vuln, :count).by(1)
end
end
context 'without :workspace' do
it 'should find workspace from session' do
expect(db_manager).to receive(:find_workspace).with(session.workspace).and_call_original
report_session
end
it 'should pass session.workspace to #find_or_create_host' do
expect(db_manager).to receive(:find_or_create_host).with(
hash_including(
:workspace => session_workspace
)
).and_return(host)
expect { report_session }.to change(Mdm::Vuln, :count).by(1)
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')
allow(db_manager).to receive(: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.
allow(db_manager).to receive(:report_vuln)
expect(db_manager).to 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(:example) do
allow(session).to receive(:arch).and_return(arch)
end
it 'should pass :arch to #find_or_create_host' do
expect(db_manager).to receive(:find_or_create_host).with(
hash_including(
:arch => arch
)
).and_call_original
expect { report_session }.to change(Mdm::Vuln, :count).by(1)
end
end
context 'without session responds to arch' do
it 'should not pass :arch to #find_or_create_host' do
expect(db_manager).to receive(:find_or_create_host).with(
hash_excluding(
:arch
)
).and_call_original
expect { report_session }.to change(Mdm::Vuln, :count).by(1)
end
end
it 'should create an Mdm::Session' do
expect {
report_session
}.to change(Mdm::Session, :count).by(1)
end
it { is_expected.to be_an Mdm::Session }
it 'should set session.db_record to created Mdm::Session' do
mdm_session = report_session
expect(session.db_record).to eq mdm_session
end
context 'with session.via_exploit' do
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(:example) do
Timecop.freeze
session.exploit_datastore['RPORT'] = rport
report_session
end
after(:example) 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(:example) 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(:example) do
Timecop.freeze
session.exploit_datastore['RPORT'] = rport
report_session
end
after(:example) 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(:example) 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(:example) do
Timecop.freeze
end
after(:example) 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
expect(session.info).to be_present
end
it 'should have session.sid present' do
expect(session.sid).to be_present
end
it 'should have session.platform present' do
expect(session.platform).to be_present
end
it 'should have session.type present' do
expect(session.type).to be_present
end
it 'should have session.via_exploit present' do
expect(session.via_exploit).to be_present
end
it 'should have session.via_payload present' do
expect(session.via_exploit).to 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
expect(session.via_exploit).to eq 'exploit/multi/handler'
end
context "with session.exploit_datastore['ParentModule']" do
it "should have session.exploit_datastore['ParentModule']" do
expect(session.exploit_datastore['ParentModule']).not_to 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(:example) 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
expect(session.via_exploit).not_to eq 'exploit/multi/handler'
end
it { expect(subject.via_exploit).to eq(session.via_exploit) }
end
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(:example) 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(:example) 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_a).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 { is_expected.to be_nil }
it 'should not create a connection' do
expect(ActiveRecord::Base.connection_pool).not_to receive(:with_connection)
report_session
end
end
end
end