Redo BES rspec
parent
6eb25743e3
commit
ed69e5f902
|
@ -1,53 +1,84 @@
|
|||
require 'spec_helper'
|
||||
#require 'spec_helper'
|
||||
require 'msf/core'
|
||||
|
||||
describe Msf::Exploit::Remote::BrowserExploitServer do
|
||||
|
||||
# When unpacked, this gives us:
|
||||
# {
|
||||
# "BAP.1433806920.Client.blLGFIlwYrxfvcY" =>
|
||||
# {
|
||||
# "source" => "script",
|
||||
# "os_name" => "Windows 8.1",
|
||||
# "os_vendor" => "undefined",
|
||||
# "os_device" => "undefined",
|
||||
# "ua_name" => "Firefox",
|
||||
# "ua_ver" => "35.0",
|
||||
# "arch" => "x86",
|
||||
# "java" => "1.7",
|
||||
# "silverlight" => "false",
|
||||
# "flash" => "14.0",
|
||||
# "vuln_test" => "true",
|
||||
# "proxy" => false,
|
||||
# "language" => "en-US,en;q=0.5",
|
||||
# "tried" => true,
|
||||
# "activex" => [{"clsid"=>"{D27CDB6E-AE6D-11cf-96B8-444553540000}", "method"=>"LoadMovie"}]
|
||||
# }}
|
||||
let(:first_packed_profile) do
|
||||
"\x81\xD9%BAP.1433806920.Client.blLGFIlwYrxfvcY\x8F\xA6source\xA6script\xA7os_name\xABWindows 8.1\xA9os_vendor\xA9undefined\xA9os_device\xA9undefined\xA7ua_name\xA7Firefox\xA6ua_ver\xA435.0\xA4arch\xA3x86\xA4java\xA31.7\xABsilverlight\xA5false\xA5flash\xA414.0\xA9vuln_test\xA4true\xA5proxy\xC2\xA8language\xC4\x0Een-US,en;q=0.5\xA5tried\xC3\xA7activex\x91\x82\xA5clsid\xD9&{D27CDB6E-AE6D-11cf-96B8-444553540000}\xA6method\xA9LoadMovie"
|
||||
end
|
||||
|
||||
let(:default_note_type_prefix) do
|
||||
MessagePack.unpack(first_packed_profile).keys.first.split('.')[0,3] * "."
|
||||
end
|
||||
|
||||
let(:first_profile_tag) do
|
||||
MessagePack.unpack(first_packed_profile).keys.first.split('.')[3]
|
||||
end
|
||||
|
||||
let(:first_profile_info) do
|
||||
MessagePack.unpack(first_packed_profile).values.first
|
||||
end
|
||||
|
||||
let(:cli) do
|
||||
sock = Rex::Socket::Tcp
|
||||
allow(sock).to receive(:peerhost).and_return('0.0.0.0')
|
||||
allow(sock).to receive(:peerport).and_return(4444)
|
||||
sock
|
||||
end
|
||||
|
||||
def create_fake_note(tag, data)
|
||||
note = double('note')
|
||||
allow(note).to receive(:ntype).and_return(tag)
|
||||
allow(note).to receive(:data).and_return(data)
|
||||
|
||||
note
|
||||
end
|
||||
|
||||
|
||||
before(:each) do
|
||||
allow_any_instance_of(described_class).to receive(:vprint_status)
|
||||
@notes = [create_fake_note(first_profile_tag, first_packed_profile)]
|
||||
end
|
||||
|
||||
subject(:server) do
|
||||
mod = Msf::Exploit::Remote.allocate
|
||||
mod.extend described_class
|
||||
mod.send(:initialize, {})
|
||||
mod.send(:initialize)
|
||||
mod.send(:datastore=, {'NoteTypePrefix' => default_note_type_prefix})
|
||||
mod
|
||||
end
|
||||
|
||||
let(:service_double) do
|
||||
service = double('service')
|
||||
service.stub(:server_name=)
|
||||
service.stub(:add_resource)
|
||||
allow(service).to receive(:server_name=)
|
||||
allow(service).to receive(:add_resource)
|
||||
service
|
||||
end
|
||||
|
||||
let(:expected_user_agent) do
|
||||
'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)'
|
||||
end
|
||||
|
||||
let(:profile_name) do
|
||||
'random'
|
||||
end
|
||||
|
||||
let(:expected_os_name) do
|
||||
'linux'
|
||||
end
|
||||
|
||||
let(:exploit_page) do
|
||||
server.instance_variable_get(:@exploit_receiver_page)
|
||||
end
|
||||
|
||||
let(:expected_profile) do
|
||||
{
|
||||
:source =>'script',
|
||||
:os_name =>'Windows XP',
|
||||
:ua_name =>'MSIE',
|
||||
:ua_ver =>'8.0',
|
||||
:arch =>'x86',
|
||||
:office =>'null',
|
||||
:activex => [ {clsid: '{D27CDB6E-AE6D-11cf-96B8-444553540000}', method: 'LoadMovie'} ],
|
||||
:proxy => false,
|
||||
:language => 'en-us',
|
||||
:tried => true
|
||||
}
|
||||
end
|
||||
|
||||
before do
|
||||
Rex::ServiceManager.stub(:start => service_double)
|
||||
end
|
||||
|
@ -58,7 +89,8 @@ describe Msf::Exploit::Remote::BrowserExploitServer do
|
|||
|
||||
it_should_behave_like 'Msf::Exploit::JSObfu'
|
||||
|
||||
describe "#get_module_resource" do
|
||||
|
||||
describe '#get_module_resource' do
|
||||
it "should give me a URI to access the exploit page" do
|
||||
module_resource = server.get_module_resource
|
||||
expect(module_resource).to include(exploit_page)
|
||||
|
@ -67,127 +99,68 @@ describe Msf::Exploit::Remote::BrowserExploitServer do
|
|||
|
||||
describe '#has_bad_activex?' do
|
||||
context 'when there is a bad activex' do
|
||||
let(:js_ax_value) { "#{expected_profile[:activex][0][:clsid]}=>#{expected_profile[:activex][0][:method]}=>false" }
|
||||
let(:js_ax_value) { "#{first_profile_info['activex'][0][:clsid]}=>#{first_profile_info['activex'][0][:method]}=>false" }
|
||||
it 'returns false' do
|
||||
expect(server.has_bad_activex?(js_ax_value)).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there is no bad activex' do
|
||||
let(:js_ax_value) { "#{expected_profile[:activex][0][:clsid]}=>#{expected_profile[:activex][0][:method]}=>true" }
|
||||
let(:js_ax_value) { "#{first_profile_info['activex'][0][:clsid]}=>#{first_profile_info['activex'][0][:method]}=>true" }
|
||||
it 'returns true' do
|
||||
expect(server.has_bad_activex?(js_ax_value)).to be_falsey
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#get_bad_requirements" do
|
||||
let(:rejected_requirements) do
|
||||
server.get_bad_requirements(fake_profile)
|
||||
describe '#get_bad_requirements' do
|
||||
let(:this_profile) do
|
||||
MessagePack.unpack(first_packed_profile)
|
||||
end
|
||||
|
||||
context 'when given the expected profile' do
|
||||
it "should not contain any bad requirements" do
|
||||
expect(server.get_bad_requirements(expected_profile)).to eq([])
|
||||
let(:requirements) { {} }
|
||||
|
||||
before(:each) do
|
||||
r = server.instance_variable_get(:@requirements)
|
||||
requirements.each_pair do |key, value|
|
||||
r[key] = value
|
||||
end
|
||||
|
||||
server.instance_variable_set(:@requirements, r)
|
||||
end
|
||||
|
||||
context 'when all requirements are met' do
|
||||
let(:requirements) { first_profile_info }
|
||||
it 'returns an empty bad requirement array' do
|
||||
expect(server.get_bad_requirements(this_profile)).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
context 'when attempting to match :os_name' do
|
||||
let(:fake_profile) do
|
||||
{ :os_name => expected_os_name }
|
||||
end
|
||||
|
||||
before do
|
||||
server.instance_variable_set(:@requirements, {:os_name => /win/i})
|
||||
end
|
||||
|
||||
it "identifies :os_name as a requirement not met" do
|
||||
expect(rejected_requirements).to eq([:os_name])
|
||||
context 'when the os_name requirement is not met' do
|
||||
let(:requirements) { {'os_name'=>'Linux'} }
|
||||
it 'returns os_name in the array as a bad requirement' do
|
||||
expect(server.get_bad_requirements(this_profile)).to eq(['os_name'])
|
||||
end
|
||||
end
|
||||
|
||||
context 'when attempting to match :ua_ver' do
|
||||
context 'against version 25.0' do
|
||||
let(:expected_ua_ver) { '25.0' }
|
||||
let(:fake_profile) do
|
||||
{ :ua_ver => expected_ua_ver }
|
||||
end
|
||||
|
||||
before do
|
||||
server.instance_variable_set(:@requirements, {:ua_ver => ua_ver})
|
||||
end
|
||||
|
||||
context "with the regex /26\.0$/" do
|
||||
let(:ua_ver) { /26\.0$/ }
|
||||
it "should reject :ua_ver" do
|
||||
expect(rejected_requirements).to include(:ua_ver)
|
||||
end
|
||||
end
|
||||
|
||||
context "with the regex /25\.0$/" do
|
||||
let(:ua_ver) { /25\.0$/ }
|
||||
it "should accept :ua_ver" do
|
||||
expect(rejected_requirements).not_to include(:ua_ver)
|
||||
end
|
||||
end
|
||||
|
||||
context "with a Proc that checks if version is between 1-5" do
|
||||
let(:ua_ver) { lambda{ |ver| ver.to_i.between?(1, 5) } }
|
||||
it "should reject :ua_ver" do
|
||||
expect(rejected_requirements).to include(:ua_ver)
|
||||
end
|
||||
end
|
||||
|
||||
context "with a Proc that checks if version is between 20-26" do
|
||||
let(:ua_ver) { lambda{ |ver| ver.to_i.between?(20, 26) } }
|
||||
it "should accept :ua_ver" do
|
||||
expect(rejected_requirements).not_to include(:ua_ver)
|
||||
end
|
||||
end
|
||||
context 'when a Linux regex cannot match a Winodws os_name' do
|
||||
let(:requirements) { {'os_name'=>/Linux/} }
|
||||
it 'returns os_name in the array as a bad requirement' do
|
||||
expect(server.get_bad_requirements(this_profile)).to eq(['os_name'])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#init_profile" do
|
||||
it "should initialize an empety profile for tag 'random'" do
|
||||
server.init_profile(profile_name)
|
||||
ivar_target_profile = server.instance_variable_get(:@target_profiles)
|
||||
expect(ivar_target_profile).to eq({profile_name=>{}})
|
||||
end
|
||||
end
|
||||
|
||||
describe "#get_profile" do
|
||||
it "should return nil when a profile isn't found" do
|
||||
server.init_profile(profile_name)
|
||||
p = server.get_profile("non_existent_profile")
|
||||
expect(p).to be_nil
|
||||
end
|
||||
|
||||
it "returns a profile if found" do
|
||||
server.init_profile(profile_name)
|
||||
p = server.get_profile(profile_name)
|
||||
expect(p).to eq({})
|
||||
end
|
||||
end
|
||||
|
||||
describe "#update_profile" do
|
||||
it "updates my target profile's :os_name information" do
|
||||
server.init_profile(profile_name)
|
||||
profile = server.get_profile(profile_name)
|
||||
server.update_profile(profile, :os_name, expected_os_name)
|
||||
profile = server.get_profile(profile_name)
|
||||
expect(profile[:os_name]).to eq(expected_os_name)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#get_detection_html" do
|
||||
describe '#get_detection_html' do
|
||||
it "returns the detection code that the client will get" do
|
||||
expected_user_agent = 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)'
|
||||
html = server.get_detection_html(expected_user_agent)
|
||||
expect(html).not_to eq('')
|
||||
end
|
||||
end
|
||||
|
||||
describe "#on_request_exploit" do
|
||||
|
||||
describe '#on_request_exploit' do
|
||||
it "raises a NoMethodError if called" do
|
||||
fake_cli = nil
|
||||
fake_request = nil
|
||||
|
@ -198,130 +171,148 @@ describe Msf::Exploit::Remote::BrowserExploitServer do
|
|||
end
|
||||
end
|
||||
|
||||
describe "#get_target" do
|
||||
describe '#get_target' do
|
||||
it "returns a target" do
|
||||
#
|
||||
# Using Object for Msf::Module::Target
|
||||
#
|
||||
expected_object = Object
|
||||
expected_object = double('Msf::Module::Target')
|
||||
server.instance_variable_set(:@target, expected_object)
|
||||
server.get_target.should eq(expected_object)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#try_set_target" do
|
||||
it "Sets a target based on requirements" do
|
||||
#
|
||||
# This testcase needs to be better somehow, but not sure how to actually create
|
||||
# a Msf::Module::Target. All we're able to test here is making sure the method
|
||||
# doesn't raise anything by exercising the code.
|
||||
#
|
||||
server.instance_variable_set(:@requirements, {:os_name => /win/i})
|
||||
server.instance_variable_set(:@target, Object)
|
||||
server.try_set_target(expected_profile)
|
||||
server.get_target.should eq(Object)
|
||||
describe '#try_set_target' do
|
||||
let(:fake_targets) do
|
||||
target = double('Msf::Module::Target')
|
||||
allow(target).to receive(:opts) { first_profile_info }
|
||||
[target]
|
||||
end
|
||||
|
||||
before(:each) do
|
||||
allow_any_instance_of(described_class).to receive(:targets) { fake_targets }
|
||||
end
|
||||
|
||||
context 'when requirements match a target' do
|
||||
it 'sets @target' do
|
||||
expect(server.get_target).to be_nil
|
||||
server.try_set_target(MessagePack.unpack(first_packed_profile))
|
||||
expect(server.get_target).to eq(fake_targets.first)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#extract_requirements" do
|
||||
it "finds all the recognizable keys" do
|
||||
requirements = {:os_name=>"Windows XP", :ua_name=>"MSIE", :ua_ver=>"8.0"}
|
||||
matches = server.extract_requirements(requirements)
|
||||
expect(matches).to eq(requirements)
|
||||
describe 'extract_requirements' do
|
||||
context 'when a recognizable requirement is given' do
|
||||
it 'returns a hash that contains the recognizable requirement' do
|
||||
expected_hash = {'os_name'=>'Linux'}
|
||||
expect(server.extract_requirements(expected_hash)).to eq(expected_hash)
|
||||
end
|
||||
end
|
||||
|
||||
it "makes sure the keys are always symbols" do
|
||||
requirements = {'os_name'=>"Windows XP", 'ua_name'=>"MSIE"}
|
||||
matches = server.extract_requirements(requirements)
|
||||
matches.each do |k,v|
|
||||
expect(k.class).to eq(Symbol)
|
||||
context 'when a unrecognizable requirement is given' do
|
||||
it 'returns a hash that does not have the unrecognizable requirement' do
|
||||
bad_hash = {'UNKNOWN_KEY'=>'VALUE'}
|
||||
expect(server.extract_requirements(bad_hash)).to be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#retrieve_tag' do
|
||||
context 'when the browser has a cookie that contains our tag' do
|
||||
let(:tag) do
|
||||
'tag'
|
||||
end
|
||||
|
||||
let(:cookie) do
|
||||
"__ua=#{tag};"
|
||||
end
|
||||
|
||||
let(:cli_request) do
|
||||
req = Rex::Proto::Http::Request.new
|
||||
req.headers['Cookie'] = cookie
|
||||
req
|
||||
end
|
||||
|
||||
it 'returns the tag from the cookie' do
|
||||
expect(server.retrieve_tag(cli, cli_request)).to eq(tag)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the browser does not have a tag' do
|
||||
|
||||
let(:cli_request) do
|
||||
Rex::Proto::Http::Request.new
|
||||
end
|
||||
|
||||
it 'returns a new one in MD5' do
|
||||
expect(server.retrieve_tag(cli, cli_request)).to match(/^[0-9a-f]{32}$/)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#on_request_uri' do
|
||||
let(:cli) { double(:peerhost => '0.0.0.0') }
|
||||
let(:cookie) { '' }
|
||||
let(:headers) { {'Cookie' => cookie, 'User-Agent' => ''} }
|
||||
let(:body) { '' }
|
||||
let(:cookie_name) { Msf::Exploit::Remote::BrowserExploitServer::DEFAULT_COOKIE_NAME }
|
||||
let(:request) do
|
||||
double(:body => body, :headers => headers, :uri => server.get_resource )
|
||||
before(:each) do
|
||||
allow(server).to receive(:get_profile_info) { MessagePack.unpack(first_packed_profile) }
|
||||
allow(server).to receive(:init_profile).with(kind_of(String))
|
||||
allow(server).to receive(:update_profile)
|
||||
allow(server).to receive(:process_browser_info)
|
||||
allow(server).to receive(:send_response) { @send_response_called = true }
|
||||
allow(server).to receive(:send_redirect) { @send_redirect_called = true }
|
||||
allow(server).to receive(:send_not_found) { @send_not_found_called = true}
|
||||
allow(server).to receive(:on_request_exploit) { @on_request_exploit_called = true }
|
||||
allow(server).to receive(:on_request_exploit) { @on_request_exploit_called = true }
|
||||
end
|
||||
|
||||
before do
|
||||
server.stub(:send_redirect)
|
||||
server.stub(:send_response)
|
||||
server.stub(:send_not_found)
|
||||
after(:each) do
|
||||
@send_response_called = false
|
||||
@send_redirect_called = false
|
||||
@on_request_exploit_called = false
|
||||
@send_not_found_called = false
|
||||
@on_request_exploit_called = false
|
||||
end
|
||||
|
||||
context 'when a new visitor requests the exploit' do
|
||||
before { JSObfu.disabled = true }
|
||||
after { JSObfu.disabled = false }
|
||||
|
||||
it 'calls send_response once' do
|
||||
server.should_receive(:send_response).once
|
||||
server.on_request_uri(cli, request)
|
||||
end
|
||||
|
||||
it 'serves the os.js detection script' do
|
||||
server.should_receive(:send_response) do |cli, html, headers|
|
||||
expect(html).to include('os_detect')
|
||||
end
|
||||
server.on_request_uri(cli, request)
|
||||
context 'when / is requested' do
|
||||
it 'sends the information gathering page' do
|
||||
cli_request = Rex::Proto::Http::Request.new
|
||||
server.on_request_uri(cli, cli_request)
|
||||
expect(@send_redirect_called).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a returning visitor requests the exploit' do
|
||||
let(:body) { '' }
|
||||
let(:tag) { 'joe' }
|
||||
let(:cookie) { "#{cookie_name}=#{tag}" }
|
||||
|
||||
before { server.init_profile(tag) }
|
||||
|
||||
it 'calls send_redirect once' do
|
||||
server.should_receive(:send_redirect).once
|
||||
server.on_request_uri(cli, request)
|
||||
end
|
||||
|
||||
it 'redirects to the exploit URL' do
|
||||
server.should_receive(:send_redirect) do |cli, url|
|
||||
expect(url).to end_with("#{exploit_page}/")
|
||||
end
|
||||
server.on_request_uri(cli, request)
|
||||
context 'when info_receiver_page is requested' do
|
||||
it 'sends an empty page' do
|
||||
info_receiver_page_var = server.instance_variable_get(:@info_receiver_page)
|
||||
cli_request = Rex::Proto::Http::Request.new
|
||||
cli_request.uri = info_receiver_page_var
|
||||
server.on_request_uri(cli, cli_request)
|
||||
expect(@send_response_called).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a returning visitor from a previous msf run requests the exploit' do
|
||||
let(:body) { '' }
|
||||
let(:tag) { 'joe' }
|
||||
let(:cookie) { "#{cookie_name}=#{tag}" }
|
||||
|
||||
before { JSObfu.disabled = true }
|
||||
after { JSObfu.disabled = false }
|
||||
|
||||
it 'calls send_response once' do
|
||||
server.should_receive(:send_response).once
|
||||
server.on_request_uri(cli, request)
|
||||
end
|
||||
|
||||
it 'serves the os.js detection script' do
|
||||
server.should_receive(:send_response) do |cli, html, headers|
|
||||
expect(html).to include('os_detect')
|
||||
end
|
||||
server.on_request_uri(cli, request)
|
||||
context 'when noscript_receiver_page is requested' do
|
||||
it 'sends a not-found' do
|
||||
noscript_receiver_page_var = server.instance_variable_get(:@noscript_receiver_page)
|
||||
cli_request = Rex::Proto::Http::Request.new
|
||||
cli_request.uri = noscript_receiver_page_var
|
||||
server.on_request_uri(cli, cli_request)
|
||||
expect(@send_not_found_called).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
context 'when exploit_receiver_page is requested' do
|
||||
it 'calls on_request_exploit' do
|
||||
exploit_receiver_page_var = server.instance_variable_get(:@exploit_receiver_page)
|
||||
cli_request = Rex::Proto::Http::Request.new
|
||||
cli_request.uri = exploit_receiver_page_var
|
||||
server.on_request_uri(cli, cli_request)
|
||||
expect(@on_request_exploit_called).to be_truthy
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#get_payload' do
|
||||
let(:cli) {
|
||||
Rex::Socket::Tcp
|
||||
}
|
||||
|
||||
before(:each) do
|
||||
allow(cli).to receive(:peerhost).and_return('0.0.0.0')
|
||||
allow(cli).to receive(:peerport).and_return(4444)
|
||||
target = double('Msf::Module::Target')
|
||||
allow(target).to receive(:arch).and_return(nil)
|
||||
allow(server).to receive(:get_target).and_return(target)
|
||||
end
|
||||
|
||||
let(:encoded) { '@EXE@' }
|
||||
|
@ -330,25 +321,15 @@ describe Msf::Exploit::Remote::BrowserExploitServer do
|
|||
double(:encoded => encoded, :arch => ['x86'])
|
||||
}
|
||||
|
||||
let(:x86_64_payload) {
|
||||
double(:encoded => encoded, :arch => ['x86_64'])
|
||||
let(:normalized_profile_info) {
|
||||
first_profile_info.inject({}){|data,(k,v)| data[k.to_sym] = v; data}
|
||||
}
|
||||
|
||||
context 'when the payload supports the visitor\'s browser architecture' do
|
||||
it 'returns a payload' do
|
||||
allow(server).to receive(:regenerate_payload).and_return(x86_payload)
|
||||
expect(server.get_payload(cli, expected_profile)).to eq(encoded)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the payload does not support the visitor\'s browser architecture' do
|
||||
it 'raises a BESException' do
|
||||
allow(server).to receive(:regenerate_payload).and_return(x86_64_payload)
|
||||
expect{server.get_payload(cli, expected_profile)}.to raise_error(Msf::Exploit::Remote::BrowserExploitServer::BESException)
|
||||
expect(server.get_payload(cli, normalized_profile_info)).to eq(encoded)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue