281 lines
9.3 KiB
Ruby
281 lines
9.3 KiB
Ruby
#
|
|
# 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
|