Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
beefproject
GitHub Repository: beefproject/beef
Path: blob/master/core/main/network_stack/assethandler.rb
1154 views
1
#
2
# Copyright (c) 2006-2025 Wade Alcorn - [email protected]
3
# Browser Exploitation Framework (BeEF) - https://beefproject.com
4
# See the file 'doc/COPYING' for copying permission
5
#
6
module BeEF
7
module Core
8
module NetworkStack
9
module Handlers
10
# @note Class defining BeEF assets
11
class AssetHandler
12
# @note call BeEF::Core::NetworkStack::Handlers::AssetHandler.instance
13
include Singleton
14
15
attr_reader :allocations, :root_dir
16
17
# Starts the AssetHandler instance
18
def initialize
19
@allocations = {}
20
@sockets = {}
21
@http_server = BeEF::Core::Server.instance
22
@root_dir = File.expand_path('../../..', __dir__)
23
end
24
25
# Binds a redirector to a mount point
26
# @param [String] target The target for the redirector
27
# @param [String] path An optional URL path to mount the redirector to (can be nil for a random path)
28
# @return [String] URL Path of the redirector
29
# @todo This function, similar to bind(), should accept a hooked browser session to limit the mounted file to a certain session etc.
30
def bind_redirect(target, path = nil)
31
url = build_url(path, nil)
32
@allocations[url] = { 'target' => target }
33
@http_server.mount(url, BeEF::Core::NetworkStack::Handlers::Redirector.new(target))
34
@http_server.remap
35
print_info 'Redirector to [' + target + '] bound to url [' + url + ']'
36
url
37
rescue StandardError => e
38
print_error "Failed to mount #{path} : #{e.message}"
39
print_error e.backtrace
40
end
41
42
# Binds raw HTTP to a mount point
43
# @param [Integer] status HTTP status code to return
44
# @param [String] headers HTTP headers as a JSON string to return
45
# @param [String] body HTTP body to return
46
# @param [String] path URL path to mount the asset to TODO (can be nil for random path)
47
# @todo @param [Integer] count The amount of times the asset can be accessed before being automatically unbinded (-1 = unlimited)
48
def bind_raw(status, header, body, path = nil, _count = -1)
49
url = build_url(path, nil)
50
@allocations[url] = {}
51
@http_server.mount(
52
url,
53
BeEF::Core::NetworkStack::Handlers::Raw.new(status, header, body)
54
)
55
@http_server.remap
56
print_info 'Raw HTTP bound to url [' + url + ']'
57
url
58
rescue StandardError => e
59
print_error "Failed to mount #{path} : #{e.message}"
60
print_error e.backtrace
61
end
62
63
# Binds a file to a mount point
64
# @param [String] file File path to asset
65
# @param [String] path URL path to mount the asset to (can be nil for random path)
66
# @param [String] extension File extension (.x). If == nil content-type is text/plain, otherwise use the right one via MIME::Types.type_for()
67
# @param [Integer] count The amount of times the asset can be accessed before being automatically unbinded (-1 = unlimited)
68
# @return [String] URL Path of mounted asset
69
# @todo This function should accept a hooked browser session to limit the mounted file to a certain session
70
def bind(file, path = nil, extension = nil, count = -1)
71
unless File.exist? "#{root_dir}#{file}"
72
print_error "Failed to mount file #{root_dir}#{file}. File does not exist"
73
return
74
end
75
76
url = build_url(path, extension)
77
@allocations[url] = { 'file' => "#{root_dir}#{file}",
78
'path' => path,
79
'extension' => extension,
80
'count' => count }
81
82
resp_body = File.read("#{root_dir}#{file}")
83
84
content_type = if extension.nil? || MIME::Types.type_for(extension).empty?
85
'text/plain'
86
else
87
MIME::Types.type_for(extension).first.content_type
88
end
89
90
@http_server.mount(
91
url,
92
BeEF::Core::NetworkStack::Handlers::Raw.new('200', { 'Content-Type' => content_type }, resp_body)
93
)
94
95
@http_server.remap
96
print_info "File [#{file}] bound to Url [#{url}] using Content-type [#{content_type}]"
97
98
url
99
rescue StandardError => e
100
print_error "Failed to mount file '#{root_dir}#{file}' to #{path} : #{e.message}"
101
print_error e.backtrace
102
end
103
104
# Binds a file to a mount point (cached for 1 year)
105
# @param [String] file File path to asset
106
# @param [String] path URL path to mount the asset to (can be nil for random path)
107
# @param [String] extension File extension (.x). If == nil content-type is text/plain, otherwise use the right one via MIME::Types.type_for()
108
# @param [Integer] count The amount of times the asset can be accessed before being automatically unbinded (-1 = unlimited)
109
# @return [String] URL Path of mounted asset
110
# @todo This function should accept a hooked browser session to limit the mounted file to a certain session
111
def bind_cached(file, path = nil, extension = nil, count = -1)
112
unless File.exist? "#{root_dir}#{file}"
113
print_error "Failed to mount file #{root_dir}#{file}. File does not exist"
114
return
115
end
116
117
url = build_url(path, extension)
118
@allocations[url] = { 'file' => "#{root_dir}#{file}",
119
'path' => path,
120
'extension' => extension,
121
'count' => count }
122
123
resp_body = File.read("#{root_dir}#{file}")
124
125
content_type = if extension.nil? || MIME::Types.type_for(extension).empty?
126
'text/plain'
127
else
128
MIME::Types.type_for(extension).first.content_type
129
end
130
131
@http_server.mount(
132
url,
133
BeEF::Core::NetworkStack::Handlers::Raw.new(
134
'200', {
135
'Content-Type' => content_type,
136
'Expires' => CGI.rfc1123_date(Time.now + (60 * 60 * 24 * 365))
137
},
138
resp_body
139
)
140
)
141
142
@http_server.remap
143
print_info "File [#{file}] bound to Url [#{url}] using Content-type [#{content_type}]"
144
145
url
146
rescue StandardError => e
147
print_error "Failed to mount file '#{root_dir}#{file}' to #{path} : #{e.message}"
148
print_error e.backtrace
149
end
150
151
# Unbinds a file from a mount point
152
# @param [String] url URL path of asset to be unbinded
153
# TODO: check why is throwing exception
154
def unbind(url)
155
@allocations.delete(url)
156
@http_server.unmount(url)
157
@http_server.remap
158
print_info "Url [#{url}] unmounted"
159
end
160
161
# use it like: bind_socket("irc","0.0.0.0",6667)
162
def bind_socket(name, host, port)
163
unless @sockets[name].nil?
164
print_error "Bind Socket [#{name}] is already listening on [#{host}:#{port}]."
165
return
166
end
167
168
t = Thread.new do
169
server = TCPServer.new(host, port)
170
loop do
171
Thread.start(server.accept) do |client|
172
data = ''
173
recv_length = 1024
174
threshold = 1024 * 512
175
while (tmp = client.recv(recv_length))
176
data += tmp
177
break if tmp.length < recv_length || tmp.length == recv_length
178
# 512 KB max of incoming data
179
break if data > threshold
180
end
181
if data.size > threshold
182
print_error "More than 512 KB of data incoming for Bind Socket [#{name}]. For security purposes client connection is closed, and data not saved."
183
else
184
@sockets[name] = { 'thread' => t, 'data' => data }
185
print_info "Bind Socket [#{name}] received [#{data.size}] bytes of data."
186
print_debug "Bind Socket [#{name}] received:\n#{data}"
187
end
188
client.close
189
end
190
end
191
end
192
193
print_info "Bind socket [#{name}] listening on [#{host}:#{port}]."
194
end
195
196
def get_socket_data(name)
197
if @sockets[name].nil?
198
print_error "Bind Socket [#{name}] does not exists."
199
return
200
end
201
@sockets[name]['data']
202
end
203
204
def unbind_socket(name)
205
t = @sockets[name]['thread']
206
if t.alive?
207
print_debug "Thread to be killed: #{t}"
208
Thread.kill(t)
209
print_info "Bind Socket [#{name}] killed."
210
else
211
print_info "Bind Socket [#{name}] ALREADY killed."
212
end
213
end
214
215
# Builds a URL based on the path and extension, if neither are passed a random URL will be generated
216
# @param [String] path URL Path defined by bind()
217
# @param [String] extension Extension defined by bind()
218
# @param [Integer] length The amount of characters to be used when generating a random URL
219
# @return [String] Generated URL
220
def build_url(path, extension, length = 20)
221
url = path.nil? ? '/' + rand(36**length).to_s(36) : path
222
url += extension.nil? ? '' : '.' + extension
223
url
224
end
225
226
# Checks if the file is allocated, if the file isn't return true to pass onto FileHandler.
227
# @param [String] url URL Path of mounted file
228
# @return [Boolean] Returns true if the file is mounted
229
def check(url)
230
return false unless @allocations.has_key?(url)
231
232
count = @allocations[url]['count']
233
234
return true if count == -1
235
236
if count > 0
237
if (count - 1) == 0
238
unbind(url)
239
else
240
@allocations[url]['count'] = count - 1
241
end
242
return true
243
end
244
245
false
246
end
247
248
@http_server
249
@allocations
250
end
251
end
252
end
253
end
254
end
255
256