# # 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 Extension module Requester # This class handles the routing of RESTful API requests for the requester class RequesterRest < BeEF::Core::Router::Router # Filters out bad requests before performing any routing before do config = BeEF::Core::Configuration.instance # Require a valid API token from a valid IP address halt 401 unless params[:token] == config.get('beef.api_token') halt 403 unless BeEF::Core::Rest.permitted_source?(request.ip) H = BeEF::Core::Models::Http HB = BeEF::Core::Models::HookedBrowser headers 'Content-Type' => 'application/json; charset=UTF-8', 'Pragma' => 'no-cache', 'Cache-Control' => 'no-cache', 'Expires' => '0' end # Returns a request by ID get '/request/:id' do id = params[:id] raise InvalidParamError, 'id' unless BeEF::Filters.nums_only?(id) requests = H.find(id) halt 404 if requests.nil? result = {} result[:count] = requests.length result[:requests] = [] requests.each do |request| result[:requests] << request2hash(request) end result.to_json rescue InvalidParamError => e print_error e.message halt 400 rescue StandardError => e print_error "Internal error while retrieving request with id #{id} (#{e.message})" halt 500 end # Returns all requestes given a specific hooked browser id get '/requests/:id' do id = params[:id] raise InvalidParamError, 'id' unless BeEF::Filters.is_valid_hook_session_id?(id) requests = H.where(hooked_browser_id: id) halt 404 if requests.nil? result = {} result[:count] = requests.length result[:requests] = [] requests.each do |request| result[:requests] << request2hash(request) end result.to_json rescue InvalidParamError => e print_error e.message halt 400 rescue StandardError => e print_error "Internal error while retrieving request list for hooked browser with id #{id} (#{e.message})" halt 500 end # Return a response by ID get '/response/:id' do # super debugging error = {} error[:code] = 0 id = params[:id] raise InvalidParamError, 'id' unless BeEF::Filters.nums_only?(id) error[:code] = 1 responses = H.find(id) || nil error[:code] = 2 halt 404 if responses.nil? error[:code] = 3 result = {} result[:success] = 'true' error[:code] = 4 result[:result] = response2hash(responses) error[:code] = 5 result.to_json rescue InvalidParamError => e print_error e.message halt 400 rescue StandardError => e print_error "Internal error while retrieving response with id #{id} (#{e.message})" error[:id] = id error[:message] = e.message error.to_json # halt 500 end # Deletes a specific response given its id delete '/response/:id' do id = params[:id] raise InvalidParamError, 'id' unless BeEF::Filters.nums_only?(id) responses = H.find(id) || nil halt 404 if responses.nil? result = {} result['success'] = H.delete(id) result.to_json rescue InvalidParamError => e print_error e.message halt 400 rescue StandardError => e print_error "Internal error while removing response with id #{id} (#{e.message})" halt 500 end # Send a new HTTP request to the hooked browser post '/send/:id' do id = params[:id] proto = params[:proto].to_s || 'http' raw_request = params['raw_request'].to_s zombie = HB.where(session: id).first || nil halt 404 if zombie.nil? # @TODO: move most of this to the model raise InvalidParamError, 'raw_request' if raw_request == '' raise InvalidParamError, 'raw_request: Invalid request URL scheme' if proto !~ /\Ahttps?\z/ req_parts = raw_request.split(/ |\n/) verb = req_parts[0] raise InvalidParamError, 'raw_request: Only HEAD, GET, POST, OPTIONS, PUT or DELETE requests are supported' unless BeEF::Filters.is_valid_verb?(verb) uri = req_parts[1] raise InvalidParamError, 'raw_request: Invalid URI' unless BeEF::Filters.is_valid_url?(uri) version = req_parts[2] raise InvalidParamError, 'raw_request: Invalid HTTP version' unless BeEF::Filters.is_valid_http_version?(version) host_str = req_parts[3] raise InvalidParamError, 'raw_request: Invalid HTTP version' unless BeEF::Filters.is_valid_host_str?(host_str) # Validate target hsot host = req_parts[4] host_parts = host.split(/:/) host_name = host_parts[0] host_port = host_parts[1] || nil raise InvalidParamError, 'raw_request: Invalid HTTP HostName' unless BeEF::Filters.is_valid_hostname?(host_name) host_port = host_parts[1] || nil if host_port.nil? || !BeEF::Filters.nums_only?(host_port) host_port = proto.eql?('https') ? 443 : 80 end # Save the new HTTP request http = H.new( hooked_browser_id: zombie.session, request: raw_request, method: verb, proto: proto, domain: host_name, port: host_port, path: uri, request_date: Time.now, allow_cross_origin: 'true' ) print_debug "added new http request for #{zombie.session}" print_debug http.to_json if verb.eql?('POST') || verb.eql?('PUT') req_parts.each_with_index do |value, index| http.content_length = req_parts[index + 1] if value.match(/^Content-Length/i) end end http.save result = request2hash(http) print_debug "[Requester] Sending HTTP request through zombie [ip: #{zombie.ip}] : #{result}" # result.to_json rescue InvalidParamError => e print_error e.message halt 400 rescue StandardError => e print_error "Internal error while removing network host with id #{id} (#{e.message})" halt 500 end # Convert a request object to Hash def request2hash(http) { id: http.id, proto: http.proto, domain: http.domain, port: http.port, path: http.path, has_ran: http.has_ran, method: http.method, request_date: http.request_date, response_date: http.response_date, response_status_code: http.response_status_code, response_status_text: http.response_status_text, response_port_status: http.response_port_status } end # Convert a response object to Hash def response2hash(http) response_data = '' unless http.response_data.nil? if (http.response_data.length > (1024 * 100)) # more than 100K response_data = http.response_data[0..(1024 * 100)] response_data += "\n<---------- Response Data Truncated---------->" else response_data = http.response_data end end response_headers = '' response_headers = http.response_headers unless http.response_headers.nil? { id: http.id, request: http.request.force_encoding('UTF-8'), response: response_data.force_encoding('UTF-8'), response_headers: response_headers.force_encoding('UTF-8'), proto: http.proto.force_encoding('UTF-8'), domain: http.domain.force_encoding('UTF-8'), port: http.port.force_encoding('UTF-8'), path: http.path.force_encoding('UTF-8'), date: http.request_date, has_ran: http.has_ran.force_encoding('UTF-8') } end # Raised when invalid JSON input is passed to an /api/requester handler. class InvalidJsonError < StandardError DEFAULT_MESSAGE = 'Invalid JSON input passed to /api/requester handler'.freeze def initialize(message = nil) super(message || DEFAULT_MESSAGE) end end # Raised when an invalid named parameter is passed to an /api/requester handler. class InvalidParamError < StandardError DEFAULT_MESSAGE = 'Invalid parameter passed to /api/requester handler'.freeze def initialize(message = nil) str = 'Invalid "%s" parameter passed to /api/requester handler' message = format str, message unless message.nil? super(message) end end end end end end