Path: blob/master/core/main/network_stack/assethandler.rb
1154 views
#1# Copyright (c) 2006-2025 Wade Alcorn - [email protected]2# Browser Exploitation Framework (BeEF) - https://beefproject.com3# See the file 'doc/COPYING' for copying permission4#5module BeEF6module Core7module NetworkStack8module Handlers9# @note Class defining BeEF assets10class AssetHandler11# @note call BeEF::Core::NetworkStack::Handlers::AssetHandler.instance12include Singleton1314attr_reader :allocations, :root_dir1516# Starts the AssetHandler instance17def initialize18@allocations = {}19@sockets = {}20@http_server = BeEF::Core::Server.instance21@root_dir = File.expand_path('../../..', __dir__)22end2324# Binds a redirector to a mount point25# @param [String] target The target for the redirector26# @param [String] path An optional URL path to mount the redirector to (can be nil for a random path)27# @return [String] URL Path of the redirector28# @todo This function, similar to bind(), should accept a hooked browser session to limit the mounted file to a certain session etc.29def bind_redirect(target, path = nil)30url = build_url(path, nil)31@allocations[url] = { 'target' => target }32@http_server.mount(url, BeEF::Core::NetworkStack::Handlers::Redirector.new(target))33@http_server.remap34print_info 'Redirector to [' + target + '] bound to url [' + url + ']'35url36rescue StandardError => e37print_error "Failed to mount #{path} : #{e.message}"38print_error e.backtrace39end4041# Binds raw HTTP to a mount point42# @param [Integer] status HTTP status code to return43# @param [String] headers HTTP headers as a JSON string to return44# @param [String] body HTTP body to return45# @param [String] path URL path to mount the asset to TODO (can be nil for random path)46# @todo @param [Integer] count The amount of times the asset can be accessed before being automatically unbinded (-1 = unlimited)47def bind_raw(status, header, body, path = nil, _count = -1)48url = build_url(path, nil)49@allocations[url] = {}50@http_server.mount(51url,52BeEF::Core::NetworkStack::Handlers::Raw.new(status, header, body)53)54@http_server.remap55print_info 'Raw HTTP bound to url [' + url + ']'56url57rescue StandardError => e58print_error "Failed to mount #{path} : #{e.message}"59print_error e.backtrace60end6162# Binds a file to a mount point63# @param [String] file File path to asset64# @param [String] path URL path to mount the asset to (can be nil for random path)65# @param [String] extension File extension (.x). If == nil content-type is text/plain, otherwise use the right one via MIME::Types.type_for()66# @param [Integer] count The amount of times the asset can be accessed before being automatically unbinded (-1 = unlimited)67# @return [String] URL Path of mounted asset68# @todo This function should accept a hooked browser session to limit the mounted file to a certain session69def bind(file, path = nil, extension = nil, count = -1)70unless File.exist? "#{root_dir}#{file}"71print_error "Failed to mount file #{root_dir}#{file}. File does not exist"72return73end7475url = build_url(path, extension)76@allocations[url] = { 'file' => "#{root_dir}#{file}",77'path' => path,78'extension' => extension,79'count' => count }8081resp_body = File.read("#{root_dir}#{file}")8283content_type = if extension.nil? || MIME::Types.type_for(extension).empty?84'text/plain'85else86MIME::Types.type_for(extension).first.content_type87end8889@http_server.mount(90url,91BeEF::Core::NetworkStack::Handlers::Raw.new('200', { 'Content-Type' => content_type }, resp_body)92)9394@http_server.remap95print_info "File [#{file}] bound to Url [#{url}] using Content-type [#{content_type}]"9697url98rescue StandardError => e99print_error "Failed to mount file '#{root_dir}#{file}' to #{path} : #{e.message}"100print_error e.backtrace101end102103# Binds a file to a mount point (cached for 1 year)104# @param [String] file File path to asset105# @param [String] path URL path to mount the asset to (can be nil for random path)106# @param [String] extension File extension (.x). If == nil content-type is text/plain, otherwise use the right one via MIME::Types.type_for()107# @param [Integer] count The amount of times the asset can be accessed before being automatically unbinded (-1 = unlimited)108# @return [String] URL Path of mounted asset109# @todo This function should accept a hooked browser session to limit the mounted file to a certain session110def bind_cached(file, path = nil, extension = nil, count = -1)111unless File.exist? "#{root_dir}#{file}"112print_error "Failed to mount file #{root_dir}#{file}. File does not exist"113return114end115116url = build_url(path, extension)117@allocations[url] = { 'file' => "#{root_dir}#{file}",118'path' => path,119'extension' => extension,120'count' => count }121122resp_body = File.read("#{root_dir}#{file}")123124content_type = if extension.nil? || MIME::Types.type_for(extension).empty?125'text/plain'126else127MIME::Types.type_for(extension).first.content_type128end129130@http_server.mount(131url,132BeEF::Core::NetworkStack::Handlers::Raw.new(133'200', {134'Content-Type' => content_type,135'Expires' => CGI.rfc1123_date(Time.now + (60 * 60 * 24 * 365))136},137resp_body138)139)140141@http_server.remap142print_info "File [#{file}] bound to Url [#{url}] using Content-type [#{content_type}]"143144url145rescue StandardError => e146print_error "Failed to mount file '#{root_dir}#{file}' to #{path} : #{e.message}"147print_error e.backtrace148end149150# Unbinds a file from a mount point151# @param [String] url URL path of asset to be unbinded152# TODO: check why is throwing exception153def unbind(url)154@allocations.delete(url)155@http_server.unmount(url)156@http_server.remap157print_info "Url [#{url}] unmounted"158end159160# use it like: bind_socket("irc","0.0.0.0",6667)161def bind_socket(name, host, port)162unless @sockets[name].nil?163print_error "Bind Socket [#{name}] is already listening on [#{host}:#{port}]."164return165end166167t = Thread.new do168server = TCPServer.new(host, port)169loop do170Thread.start(server.accept) do |client|171data = ''172recv_length = 1024173threshold = 1024 * 512174while (tmp = client.recv(recv_length))175data += tmp176break if tmp.length < recv_length || tmp.length == recv_length177# 512 KB max of incoming data178break if data > threshold179end180if data.size > threshold181print_error "More than 512 KB of data incoming for Bind Socket [#{name}]. For security purposes client connection is closed, and data not saved."182else183@sockets[name] = { 'thread' => t, 'data' => data }184print_info "Bind Socket [#{name}] received [#{data.size}] bytes of data."185print_debug "Bind Socket [#{name}] received:\n#{data}"186end187client.close188end189end190end191192print_info "Bind socket [#{name}] listening on [#{host}:#{port}]."193end194195def get_socket_data(name)196if @sockets[name].nil?197print_error "Bind Socket [#{name}] does not exists."198return199end200@sockets[name]['data']201end202203def unbind_socket(name)204t = @sockets[name]['thread']205if t.alive?206print_debug "Thread to be killed: #{t}"207Thread.kill(t)208print_info "Bind Socket [#{name}] killed."209else210print_info "Bind Socket [#{name}] ALREADY killed."211end212end213214# Builds a URL based on the path and extension, if neither are passed a random URL will be generated215# @param [String] path URL Path defined by bind()216# @param [String] extension Extension defined by bind()217# @param [Integer] length The amount of characters to be used when generating a random URL218# @return [String] Generated URL219def build_url(path, extension, length = 20)220url = path.nil? ? '/' + rand(36**length).to_s(36) : path221url += extension.nil? ? '' : '.' + extension222url223end224225# Checks if the file is allocated, if the file isn't return true to pass onto FileHandler.226# @param [String] url URL Path of mounted file227# @return [Boolean] Returns true if the file is mounted228def check(url)229return false unless @allocations.has_key?(url)230231count = @allocations[url]['count']232233return true if count == -1234235if count > 0236if (count - 1) == 0237unbind(url)238else239@allocations[url]['count'] = count - 1240end241return true242end243244false245end246247@http_server248@allocations249end250end251end252end253end254255256