diff --git a/spec/beef/core/main/handlers/browserdetails_spec.rb b/spec/beef/core/main/handlers/browserdetails_spec.rb index 2d3bc0599..f89ee9f5d 100644 --- a/spec/beef/core/main/handlers/browserdetails_spec.rb +++ b/spec/beef/core/main/handlers/browserdetails_spec.rb @@ -140,5 +140,272 @@ RSpec.describe BeEF::Core::Handlers::BrowserDetails do described_class.new(data) expect(BeEF::Core::Models::HookedBrowser).to have_received(:new).with(ip: '127.0.0.1', session: session_id) end + + it 'extracts domain from referer when hostname is missing' do + referer_data = data.dup + referer_data['results'].delete('browser.window.hostname') + referer_data['results'].delete('browser.window.hostport') + referer_data['request'] = double('Request', ip: '127.0.0.1', referer: 'https://example.com/page', env: {}) + allow(BeEF::Core::Models::HookedBrowser).to receive(:where).and_return([]) + allow(BeEF::Filters).to receive(:is_valid_browsername?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_browserversion?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_ip?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_browserstring?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_cookies?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_osname?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_hwname?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_date_stamp?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_pagetitle?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_url?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_pagereferrer?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_hostname?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_port?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_browser_plugins?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_system_platform?).and_return(true) + allow(BeEF::Filters).to receive(:nums_only?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_yes_no?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_memory?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_gpu?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_cpu?).and_return(true) + allow(BeEF::Filters).to receive(:alphanums_only?).and_return(true) + allow(BeEF::Core::Models::BrowserDetails).to receive(:set) + allow(BeEF::Core::Constants::Browsers).to receive(:friendly_name).and_return('Firefox') + zombie = double('HookedBrowser', id: 1, ip: '127.0.0.1') + allow(zombie).to receive(:firstseen=) + allow(zombie).to receive(:domain=).with('example.com') + allow(zombie).to receive(:port=).with(443) + allow(zombie).to receive(:httpheaders=) + allow(zombie).to receive(:httpheaders).and_return('{}') + allow(zombie).to receive(:save!) + allow(JSON).to receive(:parse).with('{}').and_return({}) + allow(BeEF::Core::Models::HookedBrowser).to receive(:new).and_return(zombie) + described_class.new(referer_data) + expect(zombie).to have_received(:domain=).with('example.com') + expect(zombie).to have_received(:port=).with(443) + end + + it 'falls back to unknown domain when hostname and referer are missing' do + unknown_data = data.dup + unknown_data['results'].delete('browser.window.hostname') + unknown_data['request'] = double('Request', ip: '127.0.0.1', referer: nil, env: {}) + allow(BeEF::Core::Models::HookedBrowser).to receive(:where).and_return([]) + allow(BeEF::Filters).to receive(:is_valid_browsername?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_browserversion?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_ip?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_browserstring?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_cookies?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_osname?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_hwname?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_date_stamp?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_pagetitle?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_url?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_pagereferrer?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_hostname?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_port?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_browser_plugins?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_system_platform?).and_return(true) + allow(BeEF::Filters).to receive(:nums_only?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_yes_no?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_memory?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_gpu?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_cpu?).and_return(true) + allow(BeEF::Filters).to receive(:alphanums_only?).and_return(true) + allow(BeEF::Core::Models::BrowserDetails).to receive(:set) + allow(BeEF::Core::Constants::Browsers).to receive(:friendly_name).and_return('Firefox') + zombie = double('HookedBrowser', id: 1, ip: '127.0.0.1') + allow(zombie).to receive(:firstseen=) + allow(zombie).to receive(:domain=).with('unknown') + allow(zombie).to receive(:port=) + allow(zombie).to receive(:httpheaders=) + allow(zombie).to receive(:httpheaders).and_return('{}') + allow(zombie).to receive(:save!) + allow(JSON).to receive(:parse).with('{}').and_return({}) + allow(BeEF::Core::Models::HookedBrowser).to receive(:new).and_return(zombie) + described_class.new(unknown_data) + expect(zombie).to have_received(:domain=).with('unknown') + end + + it 'parses HTTP headers from request env' do + env_data = data.dup + env_data['request'] = double('Request', + ip: '127.0.0.1', + referer: 'http://example.com', + env: { 'HTTP_USER_AGENT' => 'Mozilla/5.0', 'HTTP_ACCEPT' => 'text/html' }) + allow(BeEF::Core::Models::HookedBrowser).to receive(:where).and_return([]) + allow(BeEF::Filters).to receive(:is_valid_browsername?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_browserversion?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_ip?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_browserstring?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_cookies?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_osname?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_hwname?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_date_stamp?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_pagetitle?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_url?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_pagereferrer?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_hostname?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_port?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_browser_plugins?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_system_platform?).and_return(true) + allow(BeEF::Filters).to receive(:nums_only?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_yes_no?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_memory?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_gpu?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_cpu?).and_return(true) + allow(BeEF::Filters).to receive(:alphanums_only?).and_return(true) + allow(BeEF::Core::Models::BrowserDetails).to receive(:set) + allow(BeEF::Core::Constants::Browsers).to receive(:friendly_name).and_return('Firefox') + zombie = double('HookedBrowser', id: 1, ip: '127.0.0.1') + allow(zombie).to receive(:firstseen=) + allow(zombie).to receive(:domain=) + allow(zombie).to receive(:port=) + allow(zombie).to receive(:httpheaders=) do |headers| + parsed = JSON.parse(headers) + expect(parsed).to have_key('USER_AGENT') + expect(parsed).to have_key('ACCEPT') + expect(parsed['USER_AGENT']).to eq('Mozilla/5.0') + end + allow(zombie).to receive(:httpheaders).and_return('{}') + allow(zombie).to receive(:save!) + allow(JSON).to receive(:parse).and_call_original + allow(JSON).to receive(:parse).with('{}').and_return({}) + allow(BeEF::Core::Models::HookedBrowser).to receive(:new).and_return(zombie) + described_class.new(env_data) + end + + it 'performs DNS hostname lookup when enabled' do + allow(config).to receive(:get).with('beef.dns_hostname_lookup').and_return(true) + allow(BeEF::Core::Models::HookedBrowser).to receive(:where).and_return([]) + allow(BeEF::Filters).to receive(:is_valid_browsername?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_browserversion?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_ip?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_hostname?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_browserstring?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_cookies?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_osname?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_hwname?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_date_stamp?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_pagetitle?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_url?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_pagereferrer?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_port?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_browser_plugins?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_system_platform?).and_return(true) + allow(BeEF::Filters).to receive(:nums_only?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_yes_no?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_memory?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_gpu?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_cpu?).and_return(true) + allow(BeEF::Filters).to receive(:alphanums_only?).and_return(true) + allow(BeEF::Core::Models::BrowserDetails).to receive(:set) + allow(BeEF::Core::Constants::Browsers).to receive(:friendly_name).and_return('Firefox') + allow(Resolv).to receive(:getname).with('127.0.0.1').and_return('localhost') + zombie = double('HookedBrowser', id: 1, ip: '127.0.0.1') + allow(zombie).to receive(:firstseen=) + allow(zombie).to receive(:domain=) + allow(zombie).to receive(:port=) + allow(zombie).to receive(:httpheaders=) + allow(zombie).to receive(:httpheaders).and_return('{}') + allow(zombie).to receive(:save!) + allow(JSON).to receive(:parse).with('{}').and_return({}) + allow(BeEF::Core::Models::HookedBrowser).to receive(:new).and_return(zombie) + expect(Resolv).to receive(:getname).with('127.0.0.1') + expect(BeEF::Core::Models::BrowserDetails).to receive(:set).with(session_id, 'host.name', 'localhost') + described_class.new(data) + end + + it 'handles GeoIP lookup when enabled' do + allow(BeEF::Core::GeoIp.instance).to receive(:enabled?).and_return(true) + geoip_data = { + 'city' => { 'names' => { 'en' => 'San Francisco' } }, + 'country' => { 'names' => { 'en' => 'United States' }, 'iso_code' => 'US' }, + 'registered_country' => { 'names' => { 'en' => 'United States' }, 'iso_code' => 'US' }, + 'continent' => { 'names' => { 'en' => 'North America' }, 'code' => 'NA' }, + 'location' => { 'latitude' => 37.7749, 'longitude' => -122.4194, 'time_zone' => 'America/Los_Angeles' } + } + allow(BeEF::Core::GeoIp.instance).to receive(:lookup).with('127.0.0.1').and_return(geoip_data) + allow(BeEF::Core::Models::HookedBrowser).to receive(:where).and_return([]) + allow(BeEF::Filters).to receive(:is_valid_browsername?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_browserversion?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_ip?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_browserstring?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_cookies?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_osname?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_hwname?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_date_stamp?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_pagetitle?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_url?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_pagereferrer?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_hostname?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_port?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_browser_plugins?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_system_platform?).and_return(true) + allow(BeEF::Filters).to receive(:nums_only?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_yes_no?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_memory?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_gpu?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_cpu?).and_return(true) + allow(BeEF::Filters).to receive(:alphanums_only?).and_return(true) + allow(BeEF::Core::Models::BrowserDetails).to receive(:set) + allow(BeEF::Core::Constants::Browsers).to receive(:friendly_name).and_return('Firefox') + zombie = double('HookedBrowser', id: 1, ip: '127.0.0.1') + allow(zombie).to receive(:firstseen=) + allow(zombie).to receive(:domain=) + allow(zombie).to receive(:port=) + allow(zombie).to receive(:httpheaders=) + allow(zombie).to receive(:httpheaders).and_return('{}') + allow(zombie).to receive(:save!) + allow(JSON).to receive(:parse).with('{}').and_return({}) + allow(BeEF::Core::Models::HookedBrowser).to receive(:new).and_return(zombie) + expect(BeEF::Core::Models::BrowserDetails).to receive(:set).with(session_id, 'location.city', 'San Francisco') + expect(BeEF::Core::Models::BrowserDetails).to receive(:set).with(session_id, 'location.country', 'United States') + described_class.new(data) + end + + it 'detects and stores proxy information' do + proxy_data = data.dup + proxy_data['request'] = double('Request', + ip: '127.0.0.1', + referer: 'http://example.com', + env: { 'HTTP_X_FORWARDED_FOR' => '192.168.1.1', 'HTTP_VIA' => 'proxy.example.com' }) + allow(BeEF::Core::Models::HookedBrowser).to receive(:where).and_return([]) + allow(BeEF::Filters).to receive(:is_valid_browsername?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_browserversion?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_ip?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_browserstring?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_cookies?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_osname?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_hwname?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_date_stamp?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_pagetitle?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_url?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_pagereferrer?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_hostname?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_port?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_browser_plugins?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_system_platform?).and_return(true) + allow(BeEF::Filters).to receive(:nums_only?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_yes_no?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_memory?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_gpu?).and_return(true) + allow(BeEF::Filters).to receive(:is_valid_cpu?).and_return(true) + allow(BeEF::Filters).to receive(:alphanums_only?).and_return(true) + allow(BeEF::Core::Models::BrowserDetails).to receive(:set) + allow(BeEF::Core::Constants::Browsers).to receive(:friendly_name).and_return('Firefox') + zombie = double('HookedBrowser', id: 1, ip: '127.0.0.1') + allow(zombie).to receive(:firstseen=) + allow(zombie).to receive(:domain=) + allow(zombie).to receive(:port=) + headers_json = '{"X_FORWARDED_FOR":"192.168.1.1","VIA":"proxy.example.com"}' + allow(zombie).to receive(:httpheaders=) + allow(zombie).to receive(:httpheaders).and_return(headers_json) + allow(zombie).to receive(:save!) + allow(JSON).to receive(:parse).with(headers_json).and_return({ 'X_FORWARDED_FOR' => '192.168.1.1', 'VIA' => 'proxy.example.com' }) + allow(BeEF::Core::Models::HookedBrowser).to receive(:new).and_return(zombie) + expect(BeEF::Core::Models::BrowserDetails).to receive(:set).with(session_id, 'network.proxy', 'Yes') + expect(BeEF::Core::Models::BrowserDetails).to receive(:set).with(session_id, 'network.proxy.client', '192.168.1.1') + expect(BeEF::Core::Models::BrowserDetails).to receive(:set).with(session_id, 'network.proxy.server', 'proxy.example.com') + described_class.new(proxy_data) + end end end