Files
metasploit-gs/spec/support/shared/examples/msf/db_manager/session.rb
T
Jeffrey Martin 07cbe426e2 Rails 5, all models inherit from ApplicationRecord
ApplicationRecord is a new superclass for all app models, analogous to app controllers subclassing ApplicationController instead of ActionController::Base. This gives apps a single spot to configure app-wide model behavior.
https://edgeguides.rubyonrails.org/upgrading_ruby_on_rails.html#active-record-models-now-inherit-from-applicationrecord-by-default

Deprecated Relation#uniq use Relation#distinct instead.
https://edgeguides.rubyonrails.org/5_0_release_notes.html#active-record-deprecations
2020-07-31 11:56:49 -05:00

984 lines
33 KiB
Ruby

RSpec.shared_examples_for 'Msf::DBManager::Session' do
it { is_expected.to respond_to :get_session }
if ENV['REMOTE_DB']
before {skip("Awaiting sessions port")}
end
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
FactoryBot.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
FactoryBot.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
FactoryBot.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',
}
}
)
FactoryBot.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
FactoryBot.create(:automatic_exploitation_match_set, user: session_workspace.owner,workspace:session_workspace)
end
let(:run) do
FactoryBot.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
context 'with session responds to arch' do
let(:arch) do
FactoryBot.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
FactoryBot.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.session_type) }
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
context 'with session responds to arch' do
let(:arch) do
FactoryBot.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
FactoryBot.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.session_type) }
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
FactoryBot.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
FactoryBot.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(ApplicationRecord.connection_pool).not_to receive(:with_connection)
report_session
end
end
end
end