Path: blob/master/extensions/requester/rest/requester.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 Extension7module Requester8# This class handles the routing of RESTful API requests for the requester9class RequesterRest < BeEF::Core::Router::Router10# Filters out bad requests before performing any routing11before do12config = BeEF::Core::Configuration.instance1314# Require a valid API token from a valid IP address15halt 401 unless params[:token] == config.get('beef.api_token')16halt 403 unless BeEF::Core::Rest.permitted_source?(request.ip)1718H = BeEF::Core::Models::Http19HB = BeEF::Core::Models::HookedBrowser2021headers 'Content-Type' => 'application/json; charset=UTF-8',22'Pragma' => 'no-cache',23'Cache-Control' => 'no-cache',24'Expires' => '0'25end2627# Returns a request by ID28get '/request/:id' do29id = params[:id]30raise InvalidParamError, 'id' unless BeEF::Filters.nums_only?(id)3132requests = H.find(id)33halt 404 if requests.nil?3435result = {}36result[:count] = requests.length37result[:requests] = []38requests.each do |request|39result[:requests] << request2hash(request)40end4142result.to_json43rescue InvalidParamError => e44print_error e.message45halt 40046rescue StandardError => e47print_error "Internal error while retrieving request with id #{id} (#{e.message})"48halt 50049end5051# Returns all requestes given a specific hooked browser id52get '/requests/:id' do53id = params[:id]54raise InvalidParamError, 'id' unless BeEF::Filters.is_valid_hook_session_id?(id)5556requests = H.where(hooked_browser_id: id)57halt 404 if requests.nil?5859result = {}60result[:count] = requests.length61result[:requests] = []62requests.each do |request|63result[:requests] << request2hash(request)64end6566result.to_json67rescue InvalidParamError => e68print_error e.message69halt 40070rescue StandardError => e71print_error "Internal error while retrieving request list for hooked browser with id #{id} (#{e.message})"72halt 50073end7475# Return a response by ID76get '/response/:id' do77# super debugging7879error = {}8081error[:code] = 08283id = params[:id]84raise InvalidParamError, 'id' unless BeEF::Filters.nums_only?(id)8586error[:code] = 18788responses = H.find(id) || nil89error[:code] = 290halt 404 if responses.nil?91error[:code] = 392result = {}93result[:success] = 'true'94error[:code] = 49596result[:result] = response2hash(responses)97error[:code] = 59899result.to_json100rescue InvalidParamError => e101print_error e.message102halt 400103rescue StandardError => e104print_error "Internal error while retrieving response with id #{id} (#{e.message})"105106error[:id] = id107error[:message] = e.message108error.to_json109# halt 500110end111112# Deletes a specific response given its id113delete '/response/:id' do114id = params[:id]115raise InvalidParamError, 'id' unless BeEF::Filters.nums_only?(id)116117responses = H.find(id) || nil118halt 404 if responses.nil?119120result = {}121result['success'] = H.delete(id)122result.to_json123rescue InvalidParamError => e124print_error e.message125halt 400126rescue StandardError => e127print_error "Internal error while removing response with id #{id} (#{e.message})"128halt 500129end130131# Send a new HTTP request to the hooked browser132post '/send/:id' do133id = params[:id]134proto = params[:proto].to_s || 'http'135raw_request = params['raw_request'].to_s136137zombie = HB.where(session: id).first || nil138halt 404 if zombie.nil?139140# @TODO: move most of this to the model141142raise InvalidParamError, 'raw_request' if raw_request == ''143144raise InvalidParamError, 'raw_request: Invalid request URL scheme' if proto !~ /\Ahttps?\z/145146req_parts = raw_request.split(/ |\n/)147148verb = req_parts[0]149raise InvalidParamError, 'raw_request: Only HEAD, GET, POST, OPTIONS, PUT or DELETE requests are supported' unless BeEF::Filters.is_valid_verb?(verb)150151uri = req_parts[1]152raise InvalidParamError, 'raw_request: Invalid URI' unless BeEF::Filters.is_valid_url?(uri)153154version = req_parts[2]155raise InvalidParamError, 'raw_request: Invalid HTTP version' unless BeEF::Filters.is_valid_http_version?(version)156157host_str = req_parts[3]158raise InvalidParamError, 'raw_request: Invalid HTTP version' unless BeEF::Filters.is_valid_host_str?(host_str)159160# Validate target hsot161host = req_parts[4]162host_parts = host.split(/:/)163host_name = host_parts[0]164host_port = host_parts[1] || nil165166raise InvalidParamError, 'raw_request: Invalid HTTP HostName' unless BeEF::Filters.is_valid_hostname?(host_name)167168host_port = host_parts[1] || nil169if host_port.nil? || !BeEF::Filters.nums_only?(host_port)170host_port = proto.eql?('https') ? 443 : 80171end172173# Save the new HTTP request174http = H.new(175hooked_browser_id: zombie.session,176request: raw_request,177method: verb,178proto: proto,179domain: host_name,180port: host_port,181path: uri,182request_date: Time.now,183allow_cross_origin: 'true'184)185186print_debug "added new http request for #{zombie.session}"187print_debug http.to_json188189if verb.eql?('POST') || verb.eql?('PUT')190req_parts.each_with_index do |value, index|191http.content_length = req_parts[index + 1] if value.match(/^Content-Length/i)192end193end194195http.save196197result = request2hash(http)198print_debug "[Requester] Sending HTTP request through zombie [ip: #{zombie.ip}] : #{result}"199200# result.to_json201rescue InvalidParamError => e202print_error e.message203halt 400204rescue StandardError => e205print_error "Internal error while removing network host with id #{id} (#{e.message})"206halt 500207end208209# Convert a request object to Hash210def request2hash(http)211{212id: http.id,213proto: http.proto,214domain: http.domain,215port: http.port,216path: http.path,217has_ran: http.has_ran,218method: http.method,219request_date: http.request_date,220response_date: http.response_date,221response_status_code: http.response_status_code,222response_status_text: http.response_status_text,223response_port_status: http.response_port_status224}225end226227# Convert a response object to Hash228def response2hash(http)229response_data = ''230231unless http.response_data.nil?232if (http.response_data.length > (1024 * 100)) # more than 100K233response_data = http.response_data[0..(1024 * 100)]234response_data += "\n<---------- Response Data Truncated---------->"235else236response_data = http.response_data237end238end239240response_headers = ''241response_headers = http.response_headers unless http.response_headers.nil?242243{244id: http.id,245request: http.request.force_encoding('UTF-8'),246response: response_data.force_encoding('UTF-8'),247response_headers: response_headers.force_encoding('UTF-8'),248proto: http.proto.force_encoding('UTF-8'),249domain: http.domain.force_encoding('UTF-8'),250port: http.port.force_encoding('UTF-8'),251path: http.path.force_encoding('UTF-8'),252date: http.request_date,253has_ran: http.has_ran.force_encoding('UTF-8')254}255end256257# Raised when invalid JSON input is passed to an /api/requester handler.258class InvalidJsonError < StandardError259DEFAULT_MESSAGE = 'Invalid JSON input passed to /api/requester handler'.freeze260261def initialize(message = nil)262super(message || DEFAULT_MESSAGE)263end264end265266# Raised when an invalid named parameter is passed to an /api/requester handler.267class InvalidParamError < StandardError268DEFAULT_MESSAGE = 'Invalid parameter passed to /api/requester handler'.freeze269270def initialize(message = nil)271str = 'Invalid "%s" parameter passed to /api/requester handler'272message = format str, message unless message.nil?273super(message)274end275end276end277end278end279end280281282