Core: Resolve many Rubocop violations (#2282)
This commit is contained in:
@@ -4,22 +4,18 @@
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
module BeEF
|
||||
module Core
|
||||
module NetworkStack
|
||||
|
||||
module RegisterHttpHandler
|
||||
module Core
|
||||
module NetworkStack
|
||||
module RegisterHttpHandler
|
||||
# Register the http handler for the network stack
|
||||
# @param [Object] server HTTP server instance
|
||||
def self.mount_handler(server)
|
||||
# @note this mounts the dynamic handler
|
||||
server.mount('/dh', BeEF::Core::NetworkStack::Handlers::DynamicReconstruction.new)
|
||||
end
|
||||
end
|
||||
|
||||
# Register the http handler for the network stack
|
||||
# @param [Object] server HTTP server instance
|
||||
def self.mount_handler(server)
|
||||
# @note this mounts the dynamic handler
|
||||
server.mount('/dh', BeEF::Core::NetworkStack::Handlers::DynamicReconstruction.new)
|
||||
BeEF::API::Registrar.instance.register(BeEF::Core::NetworkStack::RegisterHttpHandler, BeEF::API::Server, 'mount_handler')
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
BeEF::API::Registrar.instance.register(BeEF::Core::NetworkStack::RegisterHttpHandler, BeEF::API::Server, 'mount_handler')
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,256 +4,251 @@
|
||||
# See the file 'doc/COPYING' for copying permission
|
||||
#
|
||||
module BeEF
|
||||
module Core
|
||||
module NetworkStack
|
||||
module Handlers
|
||||
|
||||
# @note Class defining BeEF assets
|
||||
class AssetHandler
|
||||
|
||||
# @note call BeEF::Core::NetworkStack::Handlers::AssetHandler.instance
|
||||
include Singleton
|
||||
|
||||
attr_reader :allocations, :root_dir
|
||||
|
||||
# Starts the AssetHandler instance
|
||||
def initialize
|
||||
@allocations = {}
|
||||
@sockets = {}
|
||||
@http_server = BeEF::Core::Server.instance
|
||||
@root_dir = File.expand_path('../../../../', __FILE__)
|
||||
end
|
||||
module Core
|
||||
module NetworkStack
|
||||
module Handlers
|
||||
# @note Class defining BeEF assets
|
||||
class AssetHandler
|
||||
# @note call BeEF::Core::NetworkStack::Handlers::AssetHandler.instance
|
||||
include Singleton
|
||||
|
||||
# Binds a redirector to a mount point
|
||||
# @param [String] target The target for the redirector
|
||||
# @param [String] path An optional URL path to mount the redirector to (can be nil for a random path)
|
||||
# @return [String] URL Path of the redirector
|
||||
# @todo This function, similar to bind(), should accept a hooked browser session to limit the mounted file to a certain session etc.
|
||||
def bind_redirect(target, path=nil)
|
||||
url = build_url(path,nil)
|
||||
@allocations[url] = {'target' => target}
|
||||
@http_server.mount(url,BeEF::Core::NetworkStack::Handlers::Redirector.new(target))
|
||||
@http_server.remap
|
||||
print_info "Redirector to [" + target + "] bound to url [" + url + "]"
|
||||
url
|
||||
rescue => e
|
||||
print_error "Failed to mount #{path} : #{e.message}"
|
||||
print_error e.backtrace
|
||||
end
|
||||
attr_reader :allocations, :root_dir
|
||||
|
||||
# Binds raw HTTP to a mount point
|
||||
# @param [Integer] status HTTP status code to return
|
||||
# @param [String] headers HTTP headers as a JSON string to return
|
||||
# @param [String] body HTTP body to return
|
||||
# @param [String] path URL path to mount the asset to TODO (can be nil for random path)
|
||||
# @todo @param [Integer] count The amount of times the asset can be accessed before being automatically unbinded (-1 = unlimited)
|
||||
def bind_raw(status, header, body, path=nil, count=-1)
|
||||
url = build_url(path,nil)
|
||||
@allocations[url] = {}
|
||||
@http_server.mount(
|
||||
url,
|
||||
BeEF::Core::NetworkStack::Handlers::Raw.new(status, header, body)
|
||||
)
|
||||
@http_server.remap
|
||||
print_info "Raw HTTP bound to url [" + url + "]"
|
||||
url
|
||||
rescue => e
|
||||
print_error "Failed to mount #{path} : #{e.message}"
|
||||
print_error e.backtrace
|
||||
end
|
||||
|
||||
# Binds a file to a mount point
|
||||
# @param [String] file File path to asset
|
||||
# @param [String] path URL path to mount the asset to (can be nil for random path)
|
||||
# @param [String] extension File extension (.x). If == nil content-type is text/plain, otherwise use the right one via MIME::Types.type_for()
|
||||
# @param [Integer] count The amount of times the asset can be accessed before being automatically unbinded (-1 = unlimited)
|
||||
# @return [String] URL Path of mounted asset
|
||||
# @todo This function should accept a hooked browser session to limit the mounted file to a certain session
|
||||
def bind(file, path=nil, extension=nil, count=-1)
|
||||
unless File.exist? "#{root_dir}#{file}"
|
||||
print_error "Failed to mount file #{root_dir}#{file}. File does not exist"
|
||||
return
|
||||
end
|
||||
|
||||
url = build_url(path, extension)
|
||||
@allocations[url] = {'file' => "#{root_dir}#{file}",
|
||||
'path' => path,
|
||||
'extension' => extension,
|
||||
'count' => count}
|
||||
|
||||
resp_body = File.read("#{root_dir}#{file}")
|
||||
|
||||
if extension.nil? || MIME::Types.type_for(extension).empty?
|
||||
content_type = 'text/plain'
|
||||
else
|
||||
content_type = MIME::Types.type_for(extension).first.content_type
|
||||
end
|
||||
|
||||
@http_server.mount(
|
||||
url,
|
||||
BeEF::Core::NetworkStack::Handlers::Raw.new('200', {'Content-Type' => content_type}, resp_body)
|
||||
)
|
||||
|
||||
@http_server.remap
|
||||
print_info "File [#{file}] bound to Url [#{url}] using Content-type [#{content_type}]"
|
||||
|
||||
url
|
||||
rescue => e
|
||||
print_error "Failed to mount file '#{root_dir}#{file}' to #{path} : #{e.message}"
|
||||
print_error e.backtrace
|
||||
end
|
||||
|
||||
# Binds a file to a mount point (cached for 1 year)
|
||||
# @param [String] file File path to asset
|
||||
# @param [String] path URL path to mount the asset to (can be nil for random path)
|
||||
# @param [String] extension File extension (.x). If == nil content-type is text/plain, otherwise use the right one via MIME::Types.type_for()
|
||||
# @param [Integer] count The amount of times the asset can be accessed before being automatically unbinded (-1 = unlimited)
|
||||
# @return [String] URL Path of mounted asset
|
||||
# @todo This function should accept a hooked browser session to limit the mounted file to a certain session
|
||||
def bind_cached(file, path=nil, extension=nil, count=-1)
|
||||
unless File.exist? "#{root_dir}#{file}"
|
||||
print_error "Failed to mount file #{root_dir}#{file}. File does not exist"
|
||||
return
|
||||
end
|
||||
|
||||
url = build_url(path, extension)
|
||||
@allocations[url] = {'file' => "#{root_dir}#{file}",
|
||||
'path' => path,
|
||||
'extension' => extension,
|
||||
'count' => count}
|
||||
|
||||
resp_body = File.read("#{root_dir}#{file}")
|
||||
|
||||
if extension.nil? || MIME::Types.type_for(extension).empty?
|
||||
content_type = 'text/plain'
|
||||
else
|
||||
content_type = MIME::Types.type_for(extension).first.content_type
|
||||
end
|
||||
|
||||
@http_server.mount(
|
||||
url,
|
||||
BeEF::Core::NetworkStack::Handlers::Raw.new(
|
||||
'200', {
|
||||
'Content-Type' => content_type,
|
||||
'Expires' => CGI.rfc1123_date(Time.now+(60*60*24*365)) },
|
||||
resp_body)
|
||||
)
|
||||
|
||||
@http_server.remap
|
||||
print_info "File [#{file}] bound to Url [#{url}] using Content-type [#{content_type}]"
|
||||
|
||||
url
|
||||
rescue => e
|
||||
print_error "Failed to mount file '#{root_dir}#{file}' to #{path} : #{e.message}"
|
||||
print_error e.backtrace
|
||||
end
|
||||
|
||||
# Unbinds a file from a mount point
|
||||
# @param [String] url URL path of asset to be unbinded
|
||||
#TODO: check why is throwing exception
|
||||
def unbind(url)
|
||||
@allocations.delete(url)
|
||||
@http_server.unmount(url)
|
||||
@http_server.remap
|
||||
print_info "Url [#{url}] unmounted"
|
||||
end
|
||||
|
||||
# use it like: bind_socket("irc","0.0.0.0",6667)
|
||||
def bind_socket(name, host, port)
|
||||
unless @sockets[name].nil?
|
||||
print_error "Bind Socket [#{name}] is already listening on [#{host}:#{port}]."
|
||||
return
|
||||
end
|
||||
|
||||
t = Thread.new {
|
||||
server = TCPServer.new(host,port)
|
||||
loop do
|
||||
Thread.start(server.accept) do |client|
|
||||
data = ""
|
||||
recv_length = 1024
|
||||
threshold = 1024 * 512
|
||||
while (tmp = client.recv(recv_length))
|
||||
data += tmp
|
||||
break if tmp.length < recv_length || tmp.length == recv_length
|
||||
# 512 KB max of incoming data
|
||||
break if data > threshold
|
||||
end
|
||||
if data.size > threshold
|
||||
print_error "More than 512 KB of data incoming for Bind Socket [#{name}]. For security purposes client connection is closed, and data not saved."
|
||||
else
|
||||
@sockets[name] = {'thread' => t, 'data' => data}
|
||||
print_info "Bind Socket [#{name}] received [#{data.size}] bytes of data."
|
||||
print_debug "Bind Socket [#{name}] received:\n#{data}"
|
||||
end
|
||||
client.close
|
||||
# Starts the AssetHandler instance
|
||||
def initialize
|
||||
@allocations = {}
|
||||
@sockets = {}
|
||||
@http_server = BeEF::Core::Server.instance
|
||||
@root_dir = File.expand_path('../../..', __dir__)
|
||||
end
|
||||
end
|
||||
}
|
||||
|
||||
print_info "Bind socket [#{name}] listening on [#{host}:#{port}]."
|
||||
end
|
||||
# Binds a redirector to a mount point
|
||||
# @param [String] target The target for the redirector
|
||||
# @param [String] path An optional URL path to mount the redirector to (can be nil for a random path)
|
||||
# @return [String] URL Path of the redirector
|
||||
# @todo This function, similar to bind(), should accept a hooked browser session to limit the mounted file to a certain session etc.
|
||||
def bind_redirect(target, path = nil)
|
||||
url = build_url(path, nil)
|
||||
@allocations[url] = { 'target' => target }
|
||||
@http_server.mount(url, BeEF::Core::NetworkStack::Handlers::Redirector.new(target))
|
||||
@http_server.remap
|
||||
print_info 'Redirector to [' + target + '] bound to url [' + url + ']'
|
||||
url
|
||||
rescue StandardError => e
|
||||
print_error "Failed to mount #{path} : #{e.message}"
|
||||
print_error e.backtrace
|
||||
end
|
||||
|
||||
def get_socket_data(name)
|
||||
if @sockets[name].nil?
|
||||
print_error "Bind Socket [#{name}] does not exists."
|
||||
return
|
||||
end
|
||||
@sockets[name]['data']
|
||||
end
|
||||
# Binds raw HTTP to a mount point
|
||||
# @param [Integer] status HTTP status code to return
|
||||
# @param [String] headers HTTP headers as a JSON string to return
|
||||
# @param [String] body HTTP body to return
|
||||
# @param [String] path URL path to mount the asset to TODO (can be nil for random path)
|
||||
# @todo @param [Integer] count The amount of times the asset can be accessed before being automatically unbinded (-1 = unlimited)
|
||||
def bind_raw(status, header, body, path = nil, _count = -1)
|
||||
url = build_url(path, nil)
|
||||
@allocations[url] = {}
|
||||
@http_server.mount(
|
||||
url,
|
||||
BeEF::Core::NetworkStack::Handlers::Raw.new(status, header, body)
|
||||
)
|
||||
@http_server.remap
|
||||
print_info 'Raw HTTP bound to url [' + url + ']'
|
||||
url
|
||||
rescue StandardError => e
|
||||
print_error "Failed to mount #{path} : #{e.message}"
|
||||
print_error e.backtrace
|
||||
end
|
||||
|
||||
def unbind_socket(name)
|
||||
t = @sockets[name]['thread']
|
||||
if t.alive?
|
||||
print_debug "Thread to be killed: #{t}"
|
||||
Thread.kill(t)
|
||||
print_info "Bind Socket [#{name}] killed."
|
||||
else
|
||||
print_info "Bind Socket [#{name}] ALREADY killed."
|
||||
end
|
||||
end
|
||||
|
||||
# Builds a URL based on the path and extension, if neither are passed a random URL will be generated
|
||||
# @param [String] path URL Path defined by bind()
|
||||
# @param [String] extension Extension defined by bind()
|
||||
# @param [Integer] length The amount of characters to be used when generating a random URL
|
||||
# @return [String] Generated URL
|
||||
def build_url(path, extension, length=20)
|
||||
url = (path == nil) ? '/'+rand(36**length).to_s(36) : path
|
||||
url += (extension == nil) ? '' : '.'+extension
|
||||
url
|
||||
end
|
||||
|
||||
# Checks if the file is allocated, if the file isn't return true to pass onto FileHandler.
|
||||
# @param [String] url URL Path of mounted file
|
||||
# @return [Boolean] Returns true if the file is mounted
|
||||
def check(url)
|
||||
return false unless @allocations.has_key?(url)
|
||||
|
||||
count = @allocations[url]['count']
|
||||
|
||||
if count == -1
|
||||
return true
|
||||
end
|
||||
|
||||
if count > 0
|
||||
if (count - 1) == 0
|
||||
unbind(url)
|
||||
else
|
||||
@allocations[url]['count'] = count - 1
|
||||
# Binds a file to a mount point
|
||||
# @param [String] file File path to asset
|
||||
# @param [String] path URL path to mount the asset to (can be nil for random path)
|
||||
# @param [String] extension File extension (.x). If == nil content-type is text/plain, otherwise use the right one via MIME::Types.type_for()
|
||||
# @param [Integer] count The amount of times the asset can be accessed before being automatically unbinded (-1 = unlimited)
|
||||
# @return [String] URL Path of mounted asset
|
||||
# @todo This function should accept a hooked browser session to limit the mounted file to a certain session
|
||||
def bind(file, path = nil, extension = nil, count = -1)
|
||||
unless File.exist? "#{root_dir}#{file}"
|
||||
print_error "Failed to mount file #{root_dir}#{file}. File does not exist"
|
||||
return
|
||||
end
|
||||
return true
|
||||
|
||||
url = build_url(path, extension)
|
||||
@allocations[url] = { 'file' => "#{root_dir}#{file}",
|
||||
'path' => path,
|
||||
'extension' => extension,
|
||||
'count' => count }
|
||||
|
||||
resp_body = File.read("#{root_dir}#{file}")
|
||||
|
||||
content_type = if extension.nil? || MIME::Types.type_for(extension).empty?
|
||||
'text/plain'
|
||||
else
|
||||
MIME::Types.type_for(extension).first.content_type
|
||||
end
|
||||
|
||||
@http_server.mount(
|
||||
url,
|
||||
BeEF::Core::NetworkStack::Handlers::Raw.new('200', { 'Content-Type' => content_type }, resp_body)
|
||||
)
|
||||
|
||||
@http_server.remap
|
||||
print_info "File [#{file}] bound to Url [#{url}] using Content-type [#{content_type}]"
|
||||
|
||||
url
|
||||
rescue StandardError => e
|
||||
print_error "Failed to mount file '#{root_dir}#{file}' to #{path} : #{e.message}"
|
||||
print_error e.backtrace
|
||||
end
|
||||
|
||||
# Binds a file to a mount point (cached for 1 year)
|
||||
# @param [String] file File path to asset
|
||||
# @param [String] path URL path to mount the asset to (can be nil for random path)
|
||||
# @param [String] extension File extension (.x). If == nil content-type is text/plain, otherwise use the right one via MIME::Types.type_for()
|
||||
# @param [Integer] count The amount of times the asset can be accessed before being automatically unbinded (-1 = unlimited)
|
||||
# @return [String] URL Path of mounted asset
|
||||
# @todo This function should accept a hooked browser session to limit the mounted file to a certain session
|
||||
def bind_cached(file, path = nil, extension = nil, count = -1)
|
||||
unless File.exist? "#{root_dir}#{file}"
|
||||
print_error "Failed to mount file #{root_dir}#{file}. File does not exist"
|
||||
return
|
||||
end
|
||||
|
||||
url = build_url(path, extension)
|
||||
@allocations[url] = { 'file' => "#{root_dir}#{file}",
|
||||
'path' => path,
|
||||
'extension' => extension,
|
||||
'count' => count }
|
||||
|
||||
resp_body = File.read("#{root_dir}#{file}")
|
||||
|
||||
content_type = if extension.nil? || MIME::Types.type_for(extension).empty?
|
||||
'text/plain'
|
||||
else
|
||||
MIME::Types.type_for(extension).first.content_type
|
||||
end
|
||||
|
||||
@http_server.mount(
|
||||
url,
|
||||
BeEF::Core::NetworkStack::Handlers::Raw.new(
|
||||
'200', {
|
||||
'Content-Type' => content_type,
|
||||
'Expires' => CGI.rfc1123_date(Time.now + (60 * 60 * 24 * 365))
|
||||
},
|
||||
resp_body
|
||||
)
|
||||
)
|
||||
|
||||
@http_server.remap
|
||||
print_info "File [#{file}] bound to Url [#{url}] using Content-type [#{content_type}]"
|
||||
|
||||
url
|
||||
rescue StandardError => e
|
||||
print_error "Failed to mount file '#{root_dir}#{file}' to #{path} : #{e.message}"
|
||||
print_error e.backtrace
|
||||
end
|
||||
|
||||
# Unbinds a file from a mount point
|
||||
# @param [String] url URL path of asset to be unbinded
|
||||
# TODO: check why is throwing exception
|
||||
def unbind(url)
|
||||
@allocations.delete(url)
|
||||
@http_server.unmount(url)
|
||||
@http_server.remap
|
||||
print_info "Url [#{url}] unmounted"
|
||||
end
|
||||
|
||||
# use it like: bind_socket("irc","0.0.0.0",6667)
|
||||
def bind_socket(name, host, port)
|
||||
unless @sockets[name].nil?
|
||||
print_error "Bind Socket [#{name}] is already listening on [#{host}:#{port}]."
|
||||
return
|
||||
end
|
||||
|
||||
t = Thread.new do
|
||||
server = TCPServer.new(host, port)
|
||||
loop do
|
||||
Thread.start(server.accept) do |client|
|
||||
data = ''
|
||||
recv_length = 1024
|
||||
threshold = 1024 * 512
|
||||
while (tmp = client.recv(recv_length))
|
||||
data += tmp
|
||||
break if tmp.length < recv_length || tmp.length == recv_length
|
||||
# 512 KB max of incoming data
|
||||
break if data > threshold
|
||||
end
|
||||
if data.size > threshold
|
||||
print_error "More than 512 KB of data incoming for Bind Socket [#{name}]. For security purposes client connection is closed, and data not saved."
|
||||
else
|
||||
@sockets[name] = { 'thread' => t, 'data' => data }
|
||||
print_info "Bind Socket [#{name}] received [#{data.size}] bytes of data."
|
||||
print_debug "Bind Socket [#{name}] received:\n#{data}"
|
||||
end
|
||||
client.close
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
print_info "Bind socket [#{name}] listening on [#{host}:#{port}]."
|
||||
end
|
||||
|
||||
def get_socket_data(name)
|
||||
if @sockets[name].nil?
|
||||
print_error "Bind Socket [#{name}] does not exists."
|
||||
return
|
||||
end
|
||||
@sockets[name]['data']
|
||||
end
|
||||
|
||||
def unbind_socket(name)
|
||||
t = @sockets[name]['thread']
|
||||
if t.alive?
|
||||
print_debug "Thread to be killed: #{t}"
|
||||
Thread.kill(t)
|
||||
print_info "Bind Socket [#{name}] killed."
|
||||
else
|
||||
print_info "Bind Socket [#{name}] ALREADY killed."
|
||||
end
|
||||
end
|
||||
|
||||
# Builds a URL based on the path and extension, if neither are passed a random URL will be generated
|
||||
# @param [String] path URL Path defined by bind()
|
||||
# @param [String] extension Extension defined by bind()
|
||||
# @param [Integer] length The amount of characters to be used when generating a random URL
|
||||
# @return [String] Generated URL
|
||||
def build_url(path, extension, length = 20)
|
||||
url = path.nil? ? '/' + rand(36**length).to_s(36) : path
|
||||
url += extension.nil? ? '' : '.' + extension
|
||||
url
|
||||
end
|
||||
|
||||
# Checks if the file is allocated, if the file isn't return true to pass onto FileHandler.
|
||||
# @param [String] url URL Path of mounted file
|
||||
# @return [Boolean] Returns true if the file is mounted
|
||||
def check(url)
|
||||
return false unless @allocations.has_key?(url)
|
||||
|
||||
count = @allocations[url]['count']
|
||||
|
||||
return true if count == -1
|
||||
|
||||
if count > 0
|
||||
if (count - 1) == 0
|
||||
unbind(url)
|
||||
else
|
||||
@allocations[url]['count'] = count - 1
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
false
|
||||
end
|
||||
|
||||
@http_server
|
||||
@allocations
|
||||
end
|
||||
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
@http_server
|
||||
@allocations
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -7,18 +7,16 @@ module BeEF
|
||||
module Core
|
||||
module NetworkStack
|
||||
module Handlers
|
||||
|
||||
# @note DynamicHandler is used reconstruct segmented traffic from the hooked browser
|
||||
class DynamicReconstruction < BeEF::Core::Router::Router
|
||||
|
||||
# @note holds packet queue
|
||||
PQ = Array.new()
|
||||
PQ = []
|
||||
|
||||
# @note obtain dynamic mount points from HttpHookServer
|
||||
MOUNTS = BeEF::Core::Server.instance.mounts
|
||||
|
||||
before do
|
||||
error 404 unless !params.empty?
|
||||
error 404 if params.empty?
|
||||
headers 'Pragma' => 'no-cache',
|
||||
'Cache-Control' => 'no-cache',
|
||||
'Expires' => '0'
|
||||
@@ -34,55 +32,50 @@ module BeEF
|
||||
'Access-Control-Allow-Methods' => 'POST, GET'
|
||||
begin
|
||||
PQ << {
|
||||
:beefhook => params[:bh],
|
||||
:stream_id => Integer(params[:sid]),
|
||||
:packet_id => Integer(params[:pid]),
|
||||
:packet_count => Integer(params[:pc]),
|
||||
:data => params[:d]
|
||||
beefhook: params[:bh],
|
||||
stream_id: Integer(params[:sid]),
|
||||
packet_id: Integer(params[:pid]),
|
||||
packet_count: Integer(params[:pc]),
|
||||
data: params[:d]
|
||||
}
|
||||
rescue TypeError, ArgumentError => e
|
||||
print_error "Hooked browser returned an invalid argument: #{e}"
|
||||
end
|
||||
|
||||
Thread.new {
|
||||
check_packets()
|
||||
}
|
||||
Thread.new do
|
||||
check_packets
|
||||
end
|
||||
end
|
||||
|
||||
# Check packets goes through the PQ array and attempts to reconstruct the stream from multiple packets
|
||||
def check_packets()
|
||||
checked = Array.new()
|
||||
def check_packets
|
||||
checked = []
|
||||
PQ.each do |packet|
|
||||
if (checked.include?(packet[:beefhook]+':'+String(packet[:stream_id])))
|
||||
next
|
||||
end
|
||||
checked << packet[:beefhook]+':'+String(packet[:stream_id])
|
||||
next if checked.include?(packet[:beefhook] + ':' + String(packet[:stream_id]))
|
||||
|
||||
checked << (packet[:beefhook] + ':' + String(packet[:stream_id]))
|
||||
pc = 0
|
||||
PQ.each do |p|
|
||||
if (packet[:beefhook] == p[:beefhook] and packet[:stream_id] == p[:stream_id])
|
||||
pc += 1
|
||||
end
|
||||
pc += 1 if packet[:beefhook] == p[:beefhook] and packet[:stream_id] == p[:stream_id]
|
||||
end
|
||||
if (packet[:packet_count] == pc)
|
||||
packets = expunge(packet[:beefhook], packet[:stream_id])
|
||||
data = ''
|
||||
packets.each_with_index do |sp, i|
|
||||
if (packet[:beefhook] == sp[:beefhook] and packet[:stream_id] == sp[:stream_id])
|
||||
data += sp[:data]
|
||||
end
|
||||
end
|
||||
b64 = Base64.decode64(data)
|
||||
begin
|
||||
res = JSON.parse(b64).first
|
||||
res['beefhook'] = packet[:beefhook]
|
||||
res['request'] = request
|
||||
res['beefsession'] = request[BeEF::Core::Configuration.instance.get('beef.http.hook_session_name')]
|
||||
execute(res)
|
||||
rescue JSON::ParserError => e
|
||||
print_debug 'Network stack could not decode packet stream.'
|
||||
print_debug 'Dumping Stream Data [base64]: '+data
|
||||
print_debug 'Dumping Stream Data: '+b64
|
||||
end
|
||||
next unless packet[:packet_count] == pc
|
||||
|
||||
packets = expunge(packet[:beefhook], packet[:stream_id])
|
||||
data = ''
|
||||
packets.each_with_index do |sp, _i|
|
||||
data += sp[:data] if packet[:beefhook] == sp[:beefhook] and packet[:stream_id] == sp[:stream_id]
|
||||
end
|
||||
b64 = Base64.decode64(data)
|
||||
begin
|
||||
res = JSON.parse(b64).first
|
||||
res['beefhook'] = packet[:beefhook]
|
||||
res['request'] = request
|
||||
res['beefsession'] = request[BeEF::Core::Configuration.instance.get('beef.http.hook_session_name')]
|
||||
execute(res)
|
||||
rescue JSON::ParserError => e
|
||||
print_debug 'Network stack could not decode packet stream.'
|
||||
print_debug 'Dumping Stream Data [base64]: ' + data
|
||||
print_debug 'Dumping Stream Data: ' + b64
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -100,8 +93,8 @@ module BeEF
|
||||
# @param [Hash] data Hash of data that has been rebuilt by the dynamic reconstruction
|
||||
def execute(data)
|
||||
handler = get_param(data, 'handler')
|
||||
if (MOUNTS.has_key?(handler))
|
||||
if (MOUNTS[handler].class == Array and MOUNTS[handler].length == 2)
|
||||
if MOUNTS.has_key?(handler)
|
||||
if MOUNTS[handler].instance_of?(Array) and MOUNTS[handler].length == 2
|
||||
MOUNTS[handler][0].new(data, MOUNTS[handler][1])
|
||||
else
|
||||
MOUNTS[handler].new(data)
|
||||
@@ -115,6 +108,7 @@ module BeEF
|
||||
# @return Value associated with `key`
|
||||
def get_param(query, key)
|
||||
return nil if query[key].nil?
|
||||
|
||||
query[key]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -7,32 +7,27 @@ module BeEF
|
||||
module Core
|
||||
module NetworkStack
|
||||
module Handlers
|
||||
|
||||
class Raw
|
||||
def initialize(status, header = {}, body = nil)
|
||||
@status = status
|
||||
@header = header
|
||||
@body = body
|
||||
end
|
||||
|
||||
def initialize(status, header={}, body=nil)
|
||||
@status = status
|
||||
@header = header
|
||||
@body = body
|
||||
end
|
||||
def call(_env)
|
||||
# [@status, @header, @body]
|
||||
@response = Rack::Response.new(
|
||||
body = @body,
|
||||
status = @status,
|
||||
header = @header
|
||||
)
|
||||
end
|
||||
|
||||
def call(env)
|
||||
# [@status, @header, @body]
|
||||
@response = Rack::Response.new(
|
||||
body = @body,
|
||||
status = @status,
|
||||
header = @header
|
||||
)
|
||||
end
|
||||
@request
|
||||
|
||||
private
|
||||
|
||||
@request
|
||||
|
||||
@response
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@response
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -7,36 +7,31 @@ module BeEF
|
||||
module Core
|
||||
module NetworkStack
|
||||
module Handlers
|
||||
|
||||
# @note Redirector is used as a Rack app for mounting HTTP redirectors, instead of content
|
||||
# @todo Add new options to specify what kind of redirect you want to achieve
|
||||
class Redirector
|
||||
@target = ''
|
||||
|
||||
@target = ""
|
||||
def initialize(target)
|
||||
@target = target
|
||||
end
|
||||
|
||||
def initialize(target)
|
||||
@target = target
|
||||
end
|
||||
def call(_env)
|
||||
@response = Rack::Response.new(
|
||||
body = ['302 found'],
|
||||
status = 302,
|
||||
header = {
|
||||
'Content-Type' => 'text',
|
||||
'Location' => @target
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
def call(env)
|
||||
@response = Rack::Response.new(
|
||||
body = ['302 found'],
|
||||
status = 302,
|
||||
header = {
|
||||
'Content-Type' => 'text',
|
||||
'Location' => @target
|
||||
}
|
||||
)
|
||||
end
|
||||
@request
|
||||
|
||||
private
|
||||
|
||||
@request
|
||||
|
||||
@response
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@response
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -27,32 +27,28 @@ module BeEF
|
||||
secure = @@config.get('beef.http.websocket.secure')
|
||||
|
||||
# @note Start a WSS server socket
|
||||
if (secure)
|
||||
if secure
|
||||
cert_key = @@config.get('beef.http.https.key')
|
||||
unless cert_key.start_with? '/'
|
||||
cert_key = File.expand_path cert_key, $root_dir
|
||||
end
|
||||
cert_key = File.expand_path cert_key, $root_dir unless cert_key.start_with? '/'
|
||||
unless File.exist? cert_key
|
||||
print_error "Error: #{cert_key} does not exist"
|
||||
exit 1
|
||||
end
|
||||
|
||||
cert = @@config.get('beef.http.https.cert')
|
||||
unless cert.start_with? '/'
|
||||
cert = File.expand_path cert, $root_dir
|
||||
end
|
||||
cert = File.expand_path cert, $root_dir unless cert.start_with? '/'
|
||||
unless File.exist? cert
|
||||
print_error "Error: #{cert} does not exist"
|
||||
exit 1
|
||||
end
|
||||
|
||||
ws_secure_options = {
|
||||
:host => @@config.get('beef.http.host'),
|
||||
:port => @@config.get('beef.http.websocket.secure_port'),
|
||||
:secure => true,
|
||||
:tls_options => {
|
||||
:cert_chain_file => cert,
|
||||
:private_key_file => cert_key,
|
||||
host: @@config.get('beef.http.host'),
|
||||
port: @@config.get('beef.http.websocket.secure_port'),
|
||||
secure: true,
|
||||
tls_options: {
|
||||
cert_chain_file: cert,
|
||||
private_key_file: cert_key
|
||||
}
|
||||
}
|
||||
start_websocket_server(ws_secure_options)
|
||||
@@ -60,9 +56,9 @@ module BeEF
|
||||
|
||||
# @note Start a WS server socket
|
||||
ws_options = {
|
||||
:host => @@config.get('beef.http.host'),
|
||||
:port => @@config.get('beef.http.websocket.port'),
|
||||
:secure => false
|
||||
host: @@config.get('beef.http.host'),
|
||||
port: @@config.get('beef.http.websocket.port'),
|
||||
secure: false
|
||||
}
|
||||
start_websocket_server(ws_options)
|
||||
end
|
||||
@@ -77,119 +73,117 @@ module BeEF
|
||||
|
||||
EventMachine.run do
|
||||
EventMachine::WebSocket.start(ws_options) do |ws|
|
||||
begin
|
||||
ws.onopen do |handshake|
|
||||
print_debug("[WebSocket] New #{secure ? 'WebSocketSecure' : 'WebSocket'} channel open.")
|
||||
ws.onopen do |_handshake|
|
||||
print_debug("[WebSocket] New #{secure ? 'WebSocketSecure' : 'WebSocket'} channel open.")
|
||||
end
|
||||
|
||||
ws.onerror do |error|
|
||||
print_error "[WebSocket] Error: #{error}"
|
||||
end
|
||||
|
||||
ws.onclose do |msg|
|
||||
print_debug "[WebSocket] Connection closed: #{msg}"
|
||||
end
|
||||
|
||||
ws.onmessage do |msg, _type|
|
||||
begin
|
||||
msg_hash = JSON.parse(msg)
|
||||
print_debug "[WebSocket] New message: #{msg_hash}" if @@debug
|
||||
rescue StandardError => e
|
||||
print_error "[WebSocket] Failed parsing WebSocket message: #{e.message}"
|
||||
print_error e.backtrace
|
||||
next
|
||||
end
|
||||
|
||||
ws.onerror do |error|
|
||||
print_error "[WebSocket] Error: #{error}"
|
||||
end
|
||||
# new zombie
|
||||
unless msg_hash['cookie'].nil?
|
||||
print_debug('[WebSocket] Browser says hello! WebSocket is running')
|
||||
# insert new connection in activesocket
|
||||
@@activeSocket[(msg_hash['cookie']).to_s] = ws
|
||||
print_debug("[WebSocket] activeSocket content [#{@@activeSocket}]")
|
||||
|
||||
ws.onclose do |msg|
|
||||
print_debug "[WebSocket] Connection closed: #{msg}"
|
||||
end
|
||||
|
||||
ws.onmessage do |msg, type|
|
||||
begin
|
||||
msg_hash = JSON.parse(msg)
|
||||
print_debug "[WebSocket] New message: #{msg_hash}" if @@debug
|
||||
rescue => e
|
||||
print_error "[WebSocket] Failed parsing WebSocket message: #{e.message}"
|
||||
print_error e.backtrace
|
||||
hb_session = msg_hash['cookie']
|
||||
hooked_browser = BeEF::Core::Models::HookedBrowser.where(session: hb_session).first
|
||||
if hooked_browser.nil?
|
||||
print_error '[WebSocket] Fingerprinting not finished yet.'
|
||||
print_more 'ARE rules were not triggered. You may want to trigger them manually via REST API.'
|
||||
next
|
||||
end
|
||||
|
||||
# new zombie
|
||||
unless msg_hash['cookie'].nil?
|
||||
print_debug("[WebSocket] Browser says hello! WebSocket is running")
|
||||
# insert new connection in activesocket
|
||||
@@activeSocket["#{msg_hash["cookie"]}"] = ws
|
||||
print_debug("[WebSocket] activeSocket content [#{@@activeSocket}]")
|
||||
browser_name = BeEF::Core::Models::BrowserDetails.get(hb_session, 'browser.name')
|
||||
browser_version = BeEF::Core::Models::BrowserDetails.get(hb_session, 'browser.version')
|
||||
os_name = BeEF::Core::Models::BrowserDetails.get(hb_session, 'host.os.name')
|
||||
os_version = BeEF::Core::Models::BrowserDetails.get(hb_session, 'host.os.version')
|
||||
BeEF::Core::AutorunEngine::Engine.instance.run(hooked_browser.id, browser_name, browser_version, os_name, os_version)
|
||||
|
||||
hb_session = msg_hash["cookie"]
|
||||
hooked_browser = BeEF::Core::Models::HookedBrowser.where(:session => hb_session).first
|
||||
if hooked_browser.nil?
|
||||
print_error '[WebSocket] Fingerprinting not finished yet.'
|
||||
print_more 'ARE rules were not triggered. You may want to trigger them manually via REST API.'
|
||||
next
|
||||
end
|
||||
|
||||
browser_name = BeEF::Core::Models::BrowserDetails.get(hb_session, 'browser.name')
|
||||
browser_version = BeEF::Core::Models::BrowserDetails.get(hb_session, 'browser.version')
|
||||
os_name = BeEF::Core::Models::BrowserDetails.get(hb_session, 'host.os.name')
|
||||
os_version = BeEF::Core::Models::BrowserDetails.get(hb_session, 'host.os.version')
|
||||
BeEF::Core::AutorunEngine::Engine.instance.run(hooked_browser.id, browser_name, browser_version, os_name, os_version)
|
||||
|
||||
next
|
||||
end
|
||||
|
||||
# polling zombie
|
||||
unless msg_hash['alive'].nil?
|
||||
hooked_browser = BeEF::Core::Models::HookedBrowser.where(:session => msg_hash["alive"]).first
|
||||
|
||||
# This will happen if you reset BeEF database (./beef -x),
|
||||
# and existing zombies try to connect. These zombies will be ignored,
|
||||
# as they are unknown and presumed invalid.
|
||||
#
|
||||
# @todo: consider fixing this. add zombies instead of ignoring them
|
||||
# and report "Hooked browser X appears to have come back online"
|
||||
if hooked_browser.nil?
|
||||
# print_error "Could not find zombie with ID #{msg_hash['alive']}"
|
||||
next
|
||||
end
|
||||
|
||||
hooked_browser.lastseen = Time.new.to_i
|
||||
hooked_browser.count!
|
||||
hooked_browser.save!
|
||||
|
||||
# Check if new modules need to be sent
|
||||
zombie_commands = BeEF::Core::Models::Command.where(:hooked_browser_id => hooked_browser.id, :instructions_sent => false)
|
||||
zombie_commands.each { |command| add_command_instructions(command, hooked_browser) }
|
||||
|
||||
# Check if there are any ARE rules to be triggered. If is_sent=false rules are triggered
|
||||
are_body = ''
|
||||
are_executions = BeEF::Core::Models::Execution.where(:is_sent => false, :session_id => hooked_browser.session)
|
||||
are_executions.each do |are_exec|
|
||||
are_body += are_exec.mod_body
|
||||
are_exec.update(:is_sent => true, :exec_time => Time.new.to_i)
|
||||
end
|
||||
@@activeSocket[hooked_browser.session].send(are_body) unless are_body.empty?
|
||||
|
||||
# @todo antisnatchor:
|
||||
# @todo - re-use the pre_hook_send callback mechanisms to have a generic check for multipl extensions
|
||||
# Check if new forged requests need to be sent (Requester/TunnelingProxy)
|
||||
if @@config.get('beef.extension.requester.loaded')
|
||||
dhook = BeEF::Extension::Requester::API::Hook.new
|
||||
dhook.requester_run(hooked_browser, '')
|
||||
end
|
||||
|
||||
# Check if new XssRays scan need to be started
|
||||
if @@config.get('beef.extension.xssrays.loaded')
|
||||
xssrays = BeEF::Extension::Xssrays::API::Scan.new
|
||||
xssrays.start_scan(hooked_browser, '')
|
||||
end
|
||||
|
||||
next
|
||||
end
|
||||
|
||||
# received request for a specific handler
|
||||
# the zombie is probably trying to return command module results
|
||||
# or call back to a running BeEF extension
|
||||
unless msg_hash['handler'].nil?
|
||||
# Call the handler for websocket cmd response
|
||||
# Base64 decode, parse JSON, and forward
|
||||
execute(msg_hash)
|
||||
next
|
||||
end
|
||||
|
||||
print_error "[WebSocket] Unexpected WebSocket message: #{msg_hash}"
|
||||
next
|
||||
end
|
||||
|
||||
# polling zombie
|
||||
unless msg_hash['alive'].nil?
|
||||
hooked_browser = BeEF::Core::Models::HookedBrowser.where(session: msg_hash['alive']).first
|
||||
|
||||
# This will happen if you reset BeEF database (./beef -x),
|
||||
# and existing zombies try to connect. These zombies will be ignored,
|
||||
# as they are unknown and presumed invalid.
|
||||
#
|
||||
# @todo: consider fixing this. add zombies instead of ignoring them
|
||||
# and report "Hooked browser X appears to have come back online"
|
||||
if hooked_browser.nil?
|
||||
# print_error "Could not find zombie with ID #{msg_hash['alive']}"
|
||||
next
|
||||
end
|
||||
|
||||
hooked_browser.lastseen = Time.new.to_i
|
||||
hooked_browser.count!
|
||||
hooked_browser.save!
|
||||
|
||||
# Check if new modules need to be sent
|
||||
zombie_commands = BeEF::Core::Models::Command.where(hooked_browser_id: hooked_browser.id, instructions_sent: false)
|
||||
zombie_commands.each { |command| add_command_instructions(command, hooked_browser) }
|
||||
|
||||
# Check if there are any ARE rules to be triggered. If is_sent=false rules are triggered
|
||||
are_body = ''
|
||||
are_executions = BeEF::Core::Models::Execution.where(is_sent: false, session_id: hooked_browser.session)
|
||||
are_executions.each do |are_exec|
|
||||
are_body += are_exec.mod_body
|
||||
are_exec.update(is_sent: true, exec_time: Time.new.to_i)
|
||||
end
|
||||
@@activeSocket[hooked_browser.session].send(are_body) unless are_body.empty?
|
||||
|
||||
# @todo antisnatchor:
|
||||
# @todo - re-use the pre_hook_send callback mechanisms to have a generic check for multipl extensions
|
||||
# Check if new forged requests need to be sent (Requester/TunnelingProxy)
|
||||
if @@config.get('beef.extension.requester.loaded')
|
||||
dhook = BeEF::Extension::Requester::API::Hook.new
|
||||
dhook.requester_run(hooked_browser, '')
|
||||
end
|
||||
|
||||
# Check if new XssRays scan need to be started
|
||||
if @@config.get('beef.extension.xssrays.loaded')
|
||||
xssrays = BeEF::Extension::Xssrays::API::Scan.new
|
||||
xssrays.start_scan(hooked_browser, '')
|
||||
end
|
||||
|
||||
next
|
||||
end
|
||||
|
||||
# received request for a specific handler
|
||||
# the zombie is probably trying to return command module results
|
||||
# or call back to a running BeEF extension
|
||||
unless msg_hash['handler'].nil?
|
||||
# Call the handler for websocket cmd response
|
||||
# Base64 decode, parse JSON, and forward
|
||||
execute(msg_hash)
|
||||
next
|
||||
end
|
||||
|
||||
print_error "[WebSocket] Unexpected WebSocket message: #{msg_hash}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
rescue => e
|
||||
rescue StandardError => e
|
||||
print_error "[WebSocket] Error: #{e.message}"
|
||||
raise e
|
||||
end
|
||||
@@ -198,7 +192,7 @@ module BeEF
|
||||
# @note retrieve the right websocket channel given an hooked browser session
|
||||
# @param [String] session the hooked browser session
|
||||
#
|
||||
def getsocket (session)
|
||||
def getsocket(session)
|
||||
!@@activeSocket[session].nil?
|
||||
end
|
||||
|
||||
@@ -207,7 +201,7 @@ module BeEF
|
||||
# @param [String] fn the module to execute
|
||||
# @param [String] session the hooked browser session
|
||||
#
|
||||
def send (fn, session)
|
||||
def send(fn, session)
|
||||
@@activeSocket[session].send(fn)
|
||||
end
|
||||
|
||||
@@ -228,27 +222,25 @@ module BeEF
|
||||
# "jkERa2PIdTtwnwxheXiiGZsm4ukfAD6o84LpgcJBW0g7S8fIh0Uq1yUZxnC0Cr163FxPWCpPN3uOVyPZ"}
|
||||
# => {"handler"=>"/command/test_beef_debug.js", "cid"=>"1", "result"=>"InJlc3VsdD1jYWxsZWQgdGhlIGJlZWYuZGVidWcoKSBmdW5jdGlvbi4gQ2hlY2sgdGhlIGRldmVsb3BlciBjb25zb2xlIGZvciB5b3VyIGRlYnVnIG1lc3NhZ2UuIg==", "status"=>"undefined", "callback"=>"undefined", "bh"=>"jkERa2PIdTtwnwxheXiiGZsm4ukfAD6o84LpgcJBW0g7S8fIh0Uq1yUZxnC0Cr163FxPWCpPN3uOVyPZ"}
|
||||
#
|
||||
def execute (data)
|
||||
if @@debug
|
||||
print_debug data.inspect
|
||||
end
|
||||
def execute(data)
|
||||
print_debug data.inspect if @@debug
|
||||
|
||||
hooked_browser = data['bh']
|
||||
unless BeEF::Filters.is_valid_hook_session_id?(hooked_browser)
|
||||
print_error "[Websocket] BeEF hook is invalid"
|
||||
print_error '[Websocket] BeEF hook is invalid'
|
||||
return
|
||||
end
|
||||
|
||||
command_id = data['cid'].to_s
|
||||
unless BeEF::Filters::nums_only?(command_id)
|
||||
print_error "[Websocket] command_id is invalid"
|
||||
unless BeEF::Filters.nums_only?(command_id)
|
||||
print_error '[Websocket] command_id is invalid'
|
||||
return
|
||||
end
|
||||
command_id = command_id.to_i
|
||||
|
||||
handler = data['handler']
|
||||
if handler.to_s.strip == ''
|
||||
print_error "[Websocket] handler is invalid"
|
||||
print_error '[Websocket] handler is invalid'
|
||||
return
|
||||
end
|
||||
|
||||
@@ -260,20 +252,20 @@ module BeEF
|
||||
when '2'
|
||||
status = 2
|
||||
else
|
||||
print_error "[Websocket] command status is invalid"
|
||||
print_error '[Websocket] command status is invalid'
|
||||
return
|
||||
end
|
||||
|
||||
command_results = {}
|
||||
|
||||
command_results['data'] = JSON.parse(Base64.decode64(data['result']).force_encoding('UTF-8'))
|
||||
|
||||
|
||||
if command_results.empty?
|
||||
print_error "[Websocket] command results are empty"
|
||||
print_error '[Websocket] command results are empty'
|
||||
return
|
||||
end
|
||||
|
||||
command_mod = "beef.module.#{handler.gsub('/command/','').gsub('.js','')}"
|
||||
command_mod = "beef.module.#{handler.gsub('/command/', '').gsub('.js', '')}"
|
||||
command_name = @@config.get("#{command_mod}.class")
|
||||
|
||||
# process results from command module
|
||||
@@ -289,9 +281,7 @@ module BeEF
|
||||
nil
|
||||
)
|
||||
|
||||
if command_obj.respond_to?(:post_execute)
|
||||
command_obj.post_execute
|
||||
end
|
||||
command_obj.post_execute if command_obj.respond_to?(:post_execute)
|
||||
|
||||
BeEF::Core::Models::Command.save_result(
|
||||
hooked_browser,
|
||||
@@ -301,18 +291,18 @@ module BeEF
|
||||
status
|
||||
)
|
||||
else # processing results from extensions, call the right handler
|
||||
data["beefhook"] = hooked_browser
|
||||
data["results"] = command_results['data']
|
||||
data['beefhook'] = hooked_browser
|
||||
data['results'] = command_results['data']
|
||||
|
||||
if MOUNTS.has_key?(handler)
|
||||
if MOUNTS[handler].class == Array and MOUNTS[handler].length == 2
|
||||
if MOUNTS[handler].instance_of?(Array) and MOUNTS[handler].length == 2
|
||||
MOUNTS[handler][0].new(data, MOUNTS[handler][1])
|
||||
else
|
||||
MOUNTS[handler].new(data)
|
||||
end
|
||||
end
|
||||
end
|
||||
rescue => e
|
||||
rescue StandardError => e
|
||||
print_error "Error in BeEF::Core::Websocket: #{e.message}"
|
||||
raise e
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user