Path: blob/master/core/main/handlers/browserdetails.rb
1154 views
#1# Copyright (c) 2006-2025 Wade Alcorn - [email protected]2# Browser Exploitation Framework (BeEF) - https://beefproject.com3# See the file 'doc/COPYING' for copying permission4#5module BeEF6module Core7module Handlers8# @note Retrieves information about the browser (type, version, plugins etc.)9class BrowserDetails10@data = {}1112HB = BeEF::Core::Models::HookedBrowser13BD = BeEF::Core::Models::BrowserDetails1415def initialize(data)16@data = data17setup18end1920def err_msg(error)21print_error "[Browser Details] #{error}"22end2324def setup25print_debug '[INIT] Processing Browser Details...'26config = BeEF::Core::Configuration.instance2728# validate hook session value29session_id = get_param(@data, 'beefhook')3031print_debug "[INIT] Processing Browser Details for session #{session_id}"32unless BeEF::Filters.is_valid_hook_session_id?(session_id)33err_msg 'session id is invalid'34return35end3637hooked_browser = HB.where(session: session_id).first38return unless hooked_browser.nil? # browser is already registered with framework3940# create the structure representing the hooked browser41zombie = BeEF::Core::Models::HookedBrowser.new(ip: @data['request'].ip, session: session_id)42zombie.firstseen = Time.new.to_i4344# hooked window host name45log_zombie_port = 046if !@data['results']['browser.window.hostname'].nil?47log_zombie_domain = @data['results']['browser.window.hostname']48elsif !@data['request'].referer.nil? and !@data['request'].referer.empty?49referer = @data['request'].referer50log_zombie_port = if referer.start_with?('https://')5144352else538054end55log_zombie_domain = referer.gsub('http://', '').gsub('https://', '').split('/')[0]56else57log_zombie_domain = 'unknown' # Probably local file open58end5960# hooked window host port61if @data['results']['browser.window.hostport'].nil?62log_zombie_domain_parts = log_zombie_domain.split(':')63log_zombie_port = log_zombie_domain_parts[1].to_i if log_zombie_domain_parts.length > 164else65log_zombie_port = @data['results']['browser.window.hostport']66end6768zombie.domain = log_zombie_domain69zombie.port = log_zombie_port7071# Parse http_headers. Unfortunately Rack doesn't provide a util-method to get them :(72@http_headers = {}73http_header = @data['request'].env.select { |k, _v| k.to_s.start_with? 'HTTP_' }74.each do |key, value|75@http_headers[key.sub(/^HTTP_/, '')] = value.force_encoding('UTF-8')76end77zombie.httpheaders = @http_headers.to_json78zombie.save!79# print_debug "[INIT] HTTP Headers: #{zombie.httpheaders}"8081# add a log entry for the newly hooked browser82BeEF::Core::Logger.instance.register('Zombie', "#{zombie.ip} just joined the horde from the domain: #{log_zombie_domain}:#{log_zombie_port}", zombie.id.to_s)8384# get and store browser name85browser_name = get_param(@data['results'], 'browser.name')86if BeEF::Filters.is_valid_browsername?(browser_name)87BD.set(session_id, 'browser.name', browser_name)8889# lookup and store browser friendly name90browser_friendly_name = BeEF::Core::Constants::Browsers.friendly_name(browser_name)91BD.set(session_id, 'browser.name.friendly', browser_friendly_name)92else93err_msg "Invalid browser name returned from the hook browser's initial connection."94end9596if BeEF::Filters.is_valid_ip?(zombie.ip)97BD.set(session_id, 'network.ipaddress', zombie.ip)98else99err_msg "Invalid IP address returned from the hook browser's initial connection."100end101102# lookup zombie host name103if config.get('beef.dns_hostname_lookup')104begin105host_name = Resolv.getname(zombie.ip).to_s106BD.set(session_id, 'host.name', host_name) if BeEF::Filters.is_valid_hostname?(host_name)107rescue StandardError108print_debug "[INIT] Reverse lookup failed - No results for IP address '#{zombie.ip}'"109end110end111112# geolocation113BD.set(session_id, 'location.city', 'Unknown')114BD.set(session_id, 'location.country', 'Unknown')115if BeEF::Core::GeoIp.instance.enabled?116geoip = BeEF::Core::GeoIp.instance.lookup(zombie.ip)117if geoip.nil?118print_debug "[INIT] Geolocation failed - No results for IP address '#{zombie.ip}'"119else120# print_debug "[INIT] Geolocation results: #{geoip}"121BeEF::Core::Logger.instance.register('Zombie', "#{zombie.ip} is connecting from: #{geoip}", zombie.id.to_s)122BD.set(123session_id,124'location.city',125(begin126geoip['city']['names']['en']127rescue StandardError128'Unknown'129end).to_s130)131BD.set(132session_id,133'location.country',134(begin135geoip['country']['names']['en']136rescue StandardError137'Unknown'138end).to_s139)140BD.set(141session_id,142'location.country.isocode',143(begin144geoip['country']['iso_code']145rescue StandardError146'Unknown'147end).to_s148)149BD.set(150session_id,151'location.country.registered_country',152(begin153geoip['registered_country']['names']['en']154rescue StandardError155'Unknown'156end).to_s157)158BD.set(159session_id,160'location.country.registered_country.isocode',161(begin162geoip['registered_country']['iso_code']163rescue StandardError164'Unknown'165end).to_s166)167BD.set(168session_id,169'location.continent',170(begin171geoip['continent']['names']['en']172rescue StandardError173'Unknown'174end).to_s175)176BD.set(177session_id,178'location.continent.code',179(begin180geoip['continent']['code']181rescue StandardError182'Unknown'183end).to_s184)185BD.set(186session_id,187'location.latitude',188(begin189geoip['location']['latitude']190rescue StandardError191'Unknown'192end).to_s193)194BD.set(195session_id,196'location.longitude',197(begin198geoip['location']['longitude']199rescue StandardError200'Unknown'201end).to_s202)203BD.set(204session_id,205'location.timezone',206(begin207geoip['location']['time_zone']208rescue StandardError209'Unknown'210end).to_s211)212end213end214215# detect browser proxy216using_proxy = false217%w[218CLIENT_IP219FORWARDED_FOR220FORWARDED221FORWARDED_FOR_IP222PROXY_CONNECTION223PROXY_AUTHENTICATE224X_FORWARDED225X_FORWARDED_FOR226VIA227].each do |header|228unless JSON.parse(zombie.httpheaders)[header].nil?229using_proxy = true230break231end232end233234# retrieve proxy client IP235proxy_clients = []236%w[237CLIENT_IP238FORWARDED_FOR239FORWARDED240FORWARDED_FOR_IP241X_FORWARDED242X_FORWARDED_FOR243].each do |header|244proxy_clients << (JSON.parse(zombie.httpheaders)[header]).to_s unless JSON.parse(zombie.httpheaders)[header].nil?245end246247# retrieve proxy server248proxy_server = JSON.parse(zombie.httpheaders)['VIA'] unless JSON.parse(zombie.httpheaders)['VIA'].nil?249250# store and log proxy details251if using_proxy == true252BD.set(session_id, 'network.proxy', 'Yes')253proxy_log_string = "#{zombie.ip} is using a proxy"254unless proxy_clients.empty?255BD.set(session_id, 'network.proxy.client', proxy_clients.sort.uniq.join(',').to_s)256proxy_log_string += " [client: #{proxy_clients.sort.uniq.join(',')}]"257end258unless proxy_server.nil?259BD.set(session_id, 'network.proxy.server', proxy_server.to_s)260proxy_log_string += " [server: #{proxy_server}]"261if config.get('beef.extension.network.enable') == true && (proxy_server =~ /^([\d.]+):(\d+)$/)262print_debug("Hooked browser [id:#{zombie.id}] is using a proxy [ip: #{Regexp.last_match(1)}]")263BeEF::Core::Models::NetworkHost.create(hooked_browser_id: session_id, ip: Regexp.last_match(1), type: 'Proxy')264end265end266BeEF::Core::Logger.instance.register('Zombie', proxy_log_string.to_s, zombie.id.to_s)267end268269# get and store browser version270browser_version = get_param(@data['results'], 'browser.version')271if BeEF::Filters.is_valid_browserversion?(browser_version)272BD.set(session_id, 'browser.version', browser_version)273else274err_msg "Invalid browser version returned from the hook browser's initial connection."275end276277# get and store browser string278browser_string = get_param(@data['results'], 'browser.name.reported')279if BeEF::Filters.is_valid_browserstring?(browser_string)280BD.set(session_id, 'browser.name.reported', browser_string)281else282err_msg "Invalid value for 'browser.name.reported' returned from the hook browser's initial connection."283end284285# get and store browser engine286browser_engine = get_param(@data['results'], 'browser.engine')287if BeEF::Filters.is_valid_browserstring?(browser_engine)288BD.set(session_id, 'browser.engine', browser_engine)289else290err_msg "Invalid value for 'browser.engine' returned from the hook browser's initial connection."291end292293# get and store browser language294browser_lang = get_param(@data['results'], 'browser.language')295BD.set(session_id, 'browser.language', browser_lang)296297# get and store the cookies298cookies = get_param(@data['results'], 'browser.window.cookies')299if BeEF::Filters.is_valid_cookies?(cookies)300BD.set(session_id, 'browser.window.cookies', cookies)301else302err_msg "Invalid cookies returned from the hook browser's initial connection."303end304305# get and store the OS name306os_name = get_param(@data['results'], 'host.os.name')307if BeEF::Filters.is_valid_osname?(os_name)308BD.set(session_id, 'host.os.name', os_name)309else310err_msg "Invalid operating system name returned from the hook browser's initial connection."311end312313# get and store the OS family314os_family = get_param(@data['results'], 'host.os.family')315if BeEF::Filters.is_valid_osname?(os_family)316BD.set(session_id, 'host.os.family', os_family)317else318err_msg "Invalid value for 'host.os.family' returned from the hook browser's initial connection."319end320321# get and store the OS version322# - without checks as it can be very different, for instance on linux/bsd)323os_version = get_param(@data['results'], 'host.os.version')324BD.set(session_id, 'host.os.version', os_version)325326# get and store the OS arch - without checks327os_arch = get_param(@data['results'], 'host.os.arch')328BD.set(session_id, 'host.os.arch', os_arch)329330# get and store default browser331default_browser = get_param(@data['results'], 'host.software.defaultbrowser')332BD.set(session_id, 'host.software.defaultbrowser', default_browser)333334# get and store the hardware type335hw_type = get_param(@data['results'], 'hardware.type')336if BeEF::Filters.is_valid_hwname?(hw_type)337BD.set(session_id, 'hardware.type', hw_type)338else339err_msg "Invalid value for 'hardware.type' returned from the hook browser's initial connection."340end341342# get and store the date343date_stamp = get_param(@data['results'], 'browser.date.datestamp')344if BeEF::Filters.is_valid_date_stamp?(date_stamp)345BD.set(session_id, 'browser.date.datestamp', date_stamp)346else347err_msg "Invalid date returned from the hook browser's initial connection."348end349350# get and store page title351page_title = get_param(@data['results'], 'browser.window.title')352if BeEF::Filters.is_valid_pagetitle?(page_title)353BD.set(session_id, 'browser.window.title', page_title)354else355err_msg "Invalid value for 'browser.window.title' returned from the hook browser's initial connection."356end357358# get and store page origin359origin = get_param(@data['results'], 'browser.window.origin')360if BeEF::Filters.is_valid_url?(origin)361BD.set(session_id, 'browser.window.origin', origin)362else363err_msg "Invalid value for 'browser.window.uri' returned from the hook browser's initial connection."364end365366# get and store page uri367page_uri = get_param(@data['results'], 'browser.window.uri')368if BeEF::Filters.is_valid_url?(page_uri)369BD.set(session_id, 'browser.window.uri', page_uri)370else371err_msg "Invalid value for 'browser.window.uri' returned from the hook browser's initial connection."372end373374# get and store the page referrer375page_referrer = get_param(@data['results'], 'browser.window.referrer')376if BeEF::Filters.is_valid_pagereferrer?(page_referrer)377BD.set(session_id, 'browser.window.referrer', page_referrer)378else379err_msg "Invalid value for 'browser.window.referrer' returned from the hook browser's initial connection."380end381382# get and store hooked window host port383host_name = get_param(@data['results'], 'browser.window.hostname')384if BeEF::Filters.is_valid_hostname?(host_name)385BD.set(session_id, 'browser.window.hostname', host_name)386else387err_msg "Invalid valid for 'browser.window.hostname' returned from the hook browser's initial connection."388end389390# get and store hooked window host port391host_port = get_param(@data['results'], 'browser.window.hostport')392if BeEF::Filters.is_valid_port?(host_port)393BD.set(session_id, 'browser.window.hostport', host_port)394else395err_msg "Invalid valid for 'browser.window.hostport' returned from the hook browser's initial connection."396end397398# get and store the browser plugins399browser_plugins = get_param(@data['results'], 'browser.plugins')400if BeEF::Filters.is_valid_browser_plugins?(browser_plugins)401BD.set(session_id, 'browser.plugins', browser_plugins)402elsif browser_plugins == "[]"403err_msg "No browser plugins detected."404else405err_msg "Invalid browser plugins returned from the hook browser's initial connection."406end407408# get and store the system platform409system_platform = get_param(@data['results'], 'browser.platform')410if BeEF::Filters.is_valid_system_platform?(system_platform)411BD.set(session_id, 'browser.platform', system_platform)412else413err_msg "Invalid browser platform returned from the hook browser's initial connection."414end415416# get and store the zombie screen color depth417screen_colordepth = get_param(@data['results'], 'hardware.screen.colordepth')418if BeEF::Filters.nums_only?(screen_colordepth)419BD.set(session_id, 'hardware.screen.colordepth', screen_colordepth)420else421err_msg "Invalid value for 'hardware.screen.colordepth' returned from the hook browser's initial connection."422end423424# get and store the zombie screen width425screen_size_width = get_param(@data['results'], 'hardware.screen.size.width')426if BeEF::Filters.nums_only?(screen_size_width)427BD.set(session_id, 'hardware.screen.size.width', screen_size_width)428else429err_msg "Invalid value for 'hardware.screen.size.width' returned from the hook browser's initial connection."430end431432# get and store the zombie screen height433screen_size_height = get_param(@data['results'], 'hardware.screen.size.height')434if BeEF::Filters.nums_only?(screen_size_height)435BD.set(session_id, 'hardware.screen.size.height', screen_size_height)436else437err_msg "Invalid value for 'hardware.screen.size.height' returned from the hook browser's initial connection."438end439440# get and store the window height441window_height = get_param(@data['results'], 'browser.window.size.height')442if BeEF::Filters.nums_only?(window_height)443BD.set(session_id, 'browser.window.size.height', window_height)444else445err_msg "Invalid value for 'browser.window.size.height' returned from the hook browser's initial connection."446end447448# get and store the window width449window_width = get_param(@data['results'], 'browser.window.size.width')450if BeEF::Filters.nums_only?(window_width)451BD.set(session_id, 'browser.window.size.width', window_width)452else453err_msg "Invalid value for 'browser.window.size.width' returned from the hook browser's initial connection."454end455456# store and log IP details of host457print_debug("Hooked browser [id:#{zombie.id}] has IP [ip: #{zombie.ip}]")458459if !os_name.nil? and !os_version.nil?460BeEF::Core::Models::NetworkHost.create(hooked_browser: zombie, ip: zombie.ip, ntype: 'Host', os: os_name + '-' + os_version)461elsif !os_name.nil?462BeEF::Core::Models::NetworkHost.create(hooked_browser: zombie, ip: zombie.ip, ntype: 'Host', os: os_name)463else464BeEF::Core::Models::NetworkHost.create(hooked_browser: zombie, ip: zombie.ip, ntype: 'Host')465end466467# get and store the yes|no value for browser capabilities468capabilities = [469'browser.capabilities.vbscript',470# 'browser.capabilities.java',471'browser.capabilities.flash',472'browser.capabilities.silverlight',473'browser.capabilities.phonegap',474'browser.capabilities.googlegears',475'browser.capabilities.activex',476'browser.capabilities.quicktime',477'browser.capabilities.realplayer',478'browser.capabilities.wmp',479'browser.capabilities.vlc',480'browser.capabilities.webworker',481'browser.capabilities.websocket',482'browser.capabilities.webgl',483'browser.capabilities.webrtc'484]485capabilities.each do |k|486v = get_param(@data['results'], k)487if BeEF::Filters.is_valid_yes_no?(v)488BD.set(session_id, k, v)489else490err_msg "Invalid value for #{k} returned from the hook browser's initial connection."491end492end493494# get and store the value for hardware.memory495memory = get_param(@data['results'], 'hardware.memory')496if BeEF::Filters.is_valid_memory?(memory)497BD.set(session_id, 'hardware.memory', memory)498else499err_msg "Invalid value for 'hardware.memory' returned from the hook browser's initial connection."500end501502# get and store the value for hardware.gpu503gpu = get_param(@data['results'], 'hardware.gpu')504if BeEF::Filters.is_valid_gpu?(gpu)505BD.set(session_id, 'hardware.gpu', gpu)506else507err_msg "Invalid value for 'hardware.gpu' returned from the hook browser's initial connection."508end509510# get and store the value for hardware.gpu.vendor511gpu_vendor = get_param(@data['results'], 'hardware.gpu.vendor')512if BeEF::Filters.is_valid_gpu?(gpu_vendor)513BD.set(session_id, 'hardware.gpu.vendor', gpu_vendor)514else515err_msg "Invalid value for 'hardware.gpu.vendor' returned from the hook browser's initial connection."516end517518# get and store the value for hardware.cpu.arch519cpu_arch = get_param(@data['results'], 'hardware.cpu.arch')520if BeEF::Filters.is_valid_cpu?(cpu_arch)521BD.set(session_id, 'hardware.cpu.arch', cpu_arch)522else523err_msg "Invalid value for 'hardware.cpu.arch' returned from the hook browser's initial connection."524end525526# get and store the value for hardware.cpu.cores527cpu_cores = get_param(@data['results'], 'hardware.cpu.cores')528if BeEF::Filters.alphanums_only?(cpu_cores)529BD.set(session_id, 'hardware.cpu.cores', cpu_cores)530else531err_msg "Invalid value for 'hardware.cpu.cores' returned from the hook browser's initial connection."532end533534# get and store the value for hardware.battery.level535battery_level = get_param(@data['results'], 'hardware.battery.level')536if battery_level == 'unknown' || battery_level =~ /\A[\d.]+%\z/537BD.set(session_id, 'hardware.battery.level', battery_level)538else539err_msg "Invalid value for 'hardware.battery.level' returned from the hook browser's initial connection."540end541542# get and store the value for hardware.screen.touchenabled543touch_enabled = get_param(@data['results'], 'hardware.screen.touchenabled')544if BeEF::Filters.is_valid_yes_no?(touch_enabled)545BD.set(session_id, 'hardware.screen.touchenabled', touch_enabled)546else547err_msg "Invalid value for hardware.screen.touchenabled returned from the hook browser's initial connection."548end549550# log a few info of newly hooked zombie in the console551print_info "New Hooked Browser [id:#{zombie.id}, ip:#{zombie.ip}, browser:#{browser_name}-#{browser_version}, os:#{os_name}-#{os_version}], hooked origin [#{log_zombie_domain}:#{log_zombie_port}]"552553# add localhost as network host554if config.get('beef.extension.network.enable')555print_debug('Hooked browser has network interface 127.0.0.1')556BeEF::Core::Models::NetworkHost.create(hooked_browser_id: session_id, ip: '127.0.0.1', hostname: 'localhost',557os: BeEF::Core::Models::BrowserDetails.get(session_id, 'host.os.name'))558end559560# check if any ARE rules shall be triggered only if the channel is != WebSockets (XHR). If the channel561# is WebSockets, then ARe rules are triggered after channel is established.562BeEF::Core::AutorunEngine::Engine.instance.find_and_run_all_matching_rules_for_zombie(zombie.id) unless config.get('beef.http.websocket.enable')563end564565def get_param(query, key)566(query.instance_of?(Hash) and query.has_key?(key)) ? query[key].to_s : nil567end568end569end570end571end572573574