285 lines
9.6 KiB
Ruby
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
|