Files
beef/extensions/requester/rest/requester.rb
2019-11-30 15:21:11 +10:00

285 lines
9.6 KiB
Ruby

#
# Copyright (c) 2006-2019 Wade Alcorn - wade@bindshell.net
# Browser Exploitation Framework (BeEF) - http://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
begin
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
end
# Returns all requestes given a specific hooked browser id
get '/requests/:id' do
begin
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
end
# Return a response by ID
get '/response/:id' do
begin
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] = 'true'
result[:result] = response2hash(responses)
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})"
halt 500
end
end
# Deletes a specific response given its id
delete '/response/:id' do
begin
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
end
# Send a new HTTP request to the hooked browser
post '/send/:id' do
begin
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
if raw_request == ''
raise InvalidParamError, 'raw_request'
end
if proto !~ /\Ahttps?\z/
raise InvalidParamError, 'raw_request: Invalid request URL scheme'
end
req_parts = raw_request.split(/ |\n/)
verb = req_parts[0]
if not BeEF::Filters.is_valid_verb?(verb)
raise InvalidParamError, 'raw_request: Only HEAD, GET, POST, OPTIONS, PUT or DELETE requests are supported'
end
uri = req_parts[1]
if not BeEF::Filters.is_valid_url?(uri)
raise InvalidParamError, 'raw_request: Invalid URI'
end
version = req_parts[2]
if not BeEF::Filters.is_valid_http_version?(version)
raise InvalidParamError, 'raw_request: Invalid HTTP version'
end
host_str = req_parts[3]
if not BeEF::Filters.is_valid_host_str?(host_str)
raise InvalidParamError, 'raw_request: Invalid HTTP version'
end
# Validate target hsot
host = req_parts[4]
host_parts = host.split(/:/)
host_name = host_parts[0]
host_port = host_parts[1] || nil
unless BeEF::Filters.is_valid_hostname?(host_name)
raise InvalidParamError, 'raw_request: Invalid HTTP HostName'
end
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_domain => "true",
)
if verb.eql?('POST') || verb.eql?('PUT')
req_parts.each_with_index do |value, index|
if value.match(/^Content-Length/i)
http.content_length = req_parts[index+1]
end
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
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)
if http.response_data.length > (1024 * 100) # more thank 100K
response_data = http.response_data[0..(1024*100)]
response_data += "\n<---------- Response Data Truncated---------->"
else
response_data = http.response_data
end
{
:id => http.id,
:request => http.request.force_encoding('UTF-8'),
:response => response_data.force_encoding('UTF-8'),
:response_headers => http.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'
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'
def initialize(message = nil)
str = "Invalid \"%s\" parameter passed to /api/requester handler"
message = sprintf str, message unless message.nil?
super(message)
end
end
end
end
end
end