Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
beefproject
GitHub Repository: beefproject/beef
Path: blob/master/extensions/admin_ui/controllers/modules/modules.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 Extension
8
module AdminUI
9
module Controllers
10
class Modules < BeEF::Extension::AdminUI::HttpController
11
BD = BeEF::Core::Models::BrowserDetails
12
13
def initialize
14
super({
15
'paths' => {
16
'/getRestfulApiToken.json' => method(:get_restful_api_token),
17
'/select/commandmodules/all.json' => method(:select_all_command_modules),
18
'/select/commandmodules/tree.json' => method(:select_command_modules_tree),
19
'/select/commandmodule.json' => method(:select_command_module),
20
'/select/command.json' => method(:select_command),
21
'/select/command_results.json' => method(:select_command_results),
22
'/commandmodule/commands.json' => method(:select_command_module_commands),
23
'/commandmodule/new' => method(:attach_command_module),
24
'/commandmodule/dynamicnew' => method(:attach_dynamic_command_module),
25
'/commandmodule/reexecute' => method(:reexecute_command_module)
26
}
27
})
28
29
@session = BeEF::Extension::AdminUI::Session.instance
30
end
31
32
# @note Returns the RESTful api key. Authenticated call, so callable only
33
# from the admin UI after successful authentication (cookie).
34
# -> http://127.0.0.1:3000/ui/modules/getRestfulApiToken.json
35
# response
36
# <- {"token":"800679edbb59976935d7673924caaa9e99f55c32"}
37
def get_restful_api_token
38
@body = {
39
'token' => BeEF::Core::Configuration.instance.get('beef.api_token')
40
}.to_json
41
end
42
43
# Returns the list of all command_modules in a JSON format
44
def select_all_command_modules
45
@body = command_modules2json(BeEF::Modules.get_enabled.keys)
46
end
47
48
# Set the correct icon for the command module
49
def set_command_module_icon(status)
50
path = BeEF::Extension::AdminUI::Constants::Icons::MODULE_TARGET_IMG_PATH # add icon path
51
path += case status
52
when BeEF::Core::Constants::CommandModule::VERIFIED_NOT_WORKING
53
BeEF::Extension::AdminUI::Constants::Icons::VERIFIED_NOT_WORKING_IMG
54
when BeEF::Core::Constants::CommandModule::VERIFIED_USER_NOTIFY
55
BeEF::Extension::AdminUI::Constants::Icons::VERIFIED_USER_NOTIFY_IMG
56
when BeEF::Core::Constants::CommandModule::VERIFIED_WORKING
57
BeEF::Extension::AdminUI::Constants::Icons::VERIFIED_WORKING_IMG
58
when BeEF::Core::Constants::CommandModule::VERIFIED_UNKNOWN
59
BeEF::Extension::AdminUI::Constants::Icons::VERIFIED_UNKNOWN_IMG
60
else
61
BeEF::Extension::AdminUI::Constants::Icons::VERIFIED_UNKNOWN_IMG
62
end
63
# return path
64
path
65
end
66
67
# Set the correct working status for the command module
68
def set_command_module_status(mod)
69
hook_session_id = @params['zombie_session'] || nil
70
return BeEF::Core::Constants::CommandModule::VERIFIED_UNKNOWN if hook_session_id.nil?
71
72
BeEF::Module.support(mod, {
73
'browser' => BD.get(hook_session_id, 'browser.name'),
74
'ver' => BD.get(hook_session_id, 'browser.version'),
75
'os' => [BD.get(hook_session_id, 'host.os.name')]
76
})
77
end
78
79
# If we're adding a leaf to the command tree, and it's in a subfolder, we need to recurse
80
# into the tree to find where it goes
81
# @param tree [Array] The tree to recurse into
82
# @param category [Array] The category to add the leaf to
83
# @param leaf [Hash] The leaf to add to the tree
84
def update_command_module_tree_recurse(tree, category, leaf)
85
86
# get a single folder from the category array
87
working_category = category.shift
88
89
tree.each do |t|
90
if t['text'].eql?(working_category) && category.count > 0
91
# We have deeper to go
92
update_command_module_tree_recurse(t['children'], category, leaf)
93
elsif t['text'].eql? working_category
94
# Bingo
95
t['children'].push(leaf)
96
break
97
else
98
# Not here, keep looking
99
end
100
end
101
102
# return tree
103
end
104
105
# Add the command to the tree
106
def update_command_module_tree(tree, cmd_category, cmd_icon_path, cmd_status, cmd_name, cmd_id)
107
# construct leaf node for the command module tree
108
leaf_node = {
109
'text' => cmd_name,
110
'leaf' => true,
111
'icon' => cmd_icon_path,
112
'status' => cmd_status,
113
'id' => cmd_id
114
}
115
116
# add the node to the branch in the command module tree
117
# if the category is an array it means it's likeyl a sub-folderised category
118
# so we need to recurse into the tree to find where it goes
119
if cmd_category.is_a?(Array)
120
# The category is an array, therefore it's a sub-folderised category
121
cat_copy = cmd_category.dup # Don't work with the original array, because, then it breaks shit
122
update_command_module_tree_recurse(tree, cat_copy, leaf_node)
123
else
124
# simply add the command to the tree as it hangs of one of the root folders
125
tree.each do |x|
126
if x['text'].eql? cmd_category
127
x['children'].push(leaf_node)
128
break
129
end
130
end
131
end
132
end
133
134
# Recursive function to build the tree now with sub-folders
135
# this only build the folders and not the leaf command modules
136
def build_recursive_tree(parent, input)
137
cinput = input.shift.chomp('/')
138
if cinput.split('/').count == 1 # then we have a single folder now
139
if parent.detect { |p| p['text'] == cinput }.nil?
140
parent << { 'text' => cinput, 'cls' => 'folder', 'children' => [] }
141
elsif input.count > 0
142
parent.each do |p|
143
p['children'] = build_recursive_tree(p['children'], input) if p['text'] == cinput
144
end
145
end
146
else
147
# we have multiple folders
148
newinput = cinput.split('/')
149
newcinput = newinput.shift
150
parent << { 'text' => newcinput, 'cls' => 'folder', 'children' => [] } if parent.detect { |p| p['text'] == newcinput }.nil?
151
parent.each do |p|
152
p['children'] = build_recursive_tree(p['children'], newinput) if p['text'] == newcinput
153
end
154
end
155
156
if input.count > 0
157
build_recursive_tree(parent, input)
158
else
159
parent
160
end
161
end
162
163
# Recursive function to sort all the parent's children
164
def sort_recursive_tree(parent)
165
# sort the children nodes by status and name
166
parent.each do |x|
167
# print_info "Sorting: " + x['children'].to_s
168
next unless x.is_a?(Hash) && x.has_key?('children')
169
170
x['children'] = x['children'].sort_by do |a|
171
fldr = a['cls'] || 'zzzzz'
172
"#{fldr}#{a['status']}#{a['text']}"
173
end
174
x['children'].each do |c|
175
sort_recursive_tree([c]) if c.has_key?('cls') && c['cls'] == 'folder'
176
end
177
end
178
end
179
180
# Recursive function to retitle folders with the number of children
181
def retitle_recursive_tree(parent)
182
# append the number of command modules so the branch name results in: "<category name> (num)"
183
parent.each do |command_module_branch|
184
next unless command_module_branch.is_a?(Hash) && command_module_branch.has_key?('children')
185
186
num_of_subs = 0
187
command_module_branch['children'].each do |c|
188
# add in the submodules and subtract 1 for the folder node
189
num_of_subs += c['children'].length - 1 if c.has_key?('children')
190
retitle_recursive_tree([c]) if c.has_key?('cls') && c['cls'] == 'folder'
191
end
192
num_of_command_modules = command_module_branch['children'].length + num_of_subs
193
command_module_branch['text'] = command_module_branch['text'] + ' (' + num_of_command_modules.to_s + ')'
194
end
195
end
196
197
# Returns the list of all command_modules for a TreePanel in the interface.
198
def select_command_modules_tree
199
blanktree = []
200
tree = []
201
202
# Due to the sub-folder nesting, we use some really badly hacked together recursion
203
# Note to the bored - if someone (anyone please) wants to refactor, I'll buy you cookies. -x
204
tree = build_recursive_tree(blanktree, BeEF::Modules.get_categories)
205
206
BeEF::Modules.get_enabled.each do |k, mod|
207
# get the hooked browser session id and set it in the command module
208
hook_session_id = @params['zombie_session'] || nil
209
if hook_session_id.nil?
210
print_error 'hook_session_id is nil'
211
return
212
end
213
214
# create url path and file for the command module icon
215
command_module_status = set_command_module_status(k)
216
command_module_icon_path = set_command_module_icon(command_module_status)
217
218
update_command_module_tree(tree, mod['category'], command_module_icon_path, command_module_status, mod['name'], mod['db']['id'])
219
end
220
221
# if dynamic modules are found in the DB, then we don't have yaml config for them
222
# and loading must proceed in a different way.
223
dynamic_modules = BeEF::Core::Models::CommandModule.where('path LIKE ?', 'Dynamic/')
224
225
unless dynamic_modules.nil?
226
all_modules = BeEF::Core::Models::CommandModule.all.order(:id)
227
all_modules.each do |dyn_mod|
228
next unless dyn_mod.path.split('/')[1].match(/^metasploit/)
229
230
command_mod_name = dyn_mod['name']
231
dyn_mod_category = 'Metasploit'
232
command_module_status = set_command_module_status(command_mod_name)
233
command_module_icon_path = set_command_module_icon(command_module_status)
234
235
update_command_module_tree(tree, dyn_mod_category, command_module_icon_path, command_module_status, command_mod_name, dyn_mod.id)
236
end
237
end
238
239
# sort the parent array nodes
240
tree.sort! { |a, b| a['text'] <=> b['text'] }
241
242
sort_recursive_tree(tree)
243
244
retitle_recursive_tree(tree)
245
246
# return a JSON array of hashes
247
@body = tree.to_json
248
end
249
250
# Returns the inputs definition of an command_module.
251
def select_command_module
252
command_module_id = @params['command_module_id'] || nil
253
if command_module_id.nil?
254
print_error 'command_module_id is nil'
255
return
256
end
257
command_module = BeEF::Core::Models::CommandModule.find(command_module_id)
258
key = BeEF::Module.get_key_by_database_id(command_module_id)
259
260
payload_name = @params['payload_name'] || nil
261
@body = if payload_name.nil?
262
command_modules2json([key])
263
else
264
dynamic_payload2json(command_module_id, payload_name)
265
end
266
end
267
268
# Returns the list of commands for an command_module
269
def select_command_module_commands
270
commands = []
271
i = 0
272
273
# get params
274
zombie_session = @params['zombie_session'] || nil
275
if zombie_session.nil?
276
print_error 'Zombie session is nil'
277
return
278
end
279
command_module_id = @params['command_module_id'] || nil
280
if command_module_id.nil?
281
print_error 'command_module id is nil'
282
return
283
end
284
# validate nonce
285
nonce = @params['nonce'] || nil
286
if nonce.nil?
287
print_error 'nonce is nil'
288
return
289
end
290
if @session.get_nonce != nonce
291
print_error 'nonce incorrect'
292
return
293
end
294
295
# get the browser id
296
zombie = Z.where(session: zombie_session).first
297
if zombie.nil?
298
print_error 'Zombie is nil'
299
return
300
end
301
302
zombie_id = zombie.id
303
if zombie_id.nil?
304
print_error 'Zombie id is nil'
305
return
306
end
307
308
C.where(command_module_id: command_module_id, hooked_browser_id: zombie_id).each do |command|
309
commands.push({
310
'id' => i,
311
'object_id' => command.id,
312
'creationdate' => Time.at(command.creationdate.to_i).strftime('%Y-%m-%d %H:%M').to_s,
313
'label' => command.label
314
})
315
i += 1
316
end
317
318
@body = {
319
'success' => 'true',
320
'commands' => commands
321
}.to_json
322
end
323
324
# Attaches an command_module to a zombie.
325
def attach_command_module
326
definition = {}
327
328
# get params
329
zombie_session = @params['zombie_session'] || nil
330
if zombie_session.nil?
331
print_error 'Zombie id is nil'
332
return
333
end
334
335
command_module_id = @params['command_module_id'] || nil
336
if command_module_id.nil?
337
print_error 'command_module id is nil'
338
return
339
end
340
341
# validate nonce
342
nonce = @params['nonce'] || nil
343
if nonce.nil?
344
print_error 'nonce is nil'
345
return
346
end
347
if @session.get_nonce != nonce
348
print_error 'nonce incorrect'
349
return
350
end
351
352
@params.keys.each do |param|
353
unless BeEF::Filters.has_valid_param_chars?(param)
354
print_error 'invalid key param string'
355
return
356
end
357
if BeEF::Filters.first_char_is_num?(param)
358
print_error 'first char is num'
359
return
360
end
361
definition[param[4..-1]] = params[param]
362
oc = BeEF::Core::Models::OptionCache.first_or_create(name: param[4..-1])
363
oc.value = params[param]
364
oc.save
365
end
366
367
mod_key = BeEF::Module.get_key_by_database_id(command_module_id)
368
# Hack to rework the old option system into the new option system
369
def2 = []
370
definition.each do |k, v|
371
def2.push({ 'name' => k, 'value' => v })
372
end
373
# End hack
374
exec_results = BeEF::Module.execute(mod_key, zombie_session, def2)
375
@body = exec_results.nil? ? '{success: false}' : '{success: true}'
376
end
377
378
# Re-execute an command_module to a zombie.
379
def reexecute_command_module
380
# get params
381
command_id = @params['command_id'] || nil
382
if command_id.nil?
383
print_error 'Command id is nil'
384
return
385
end
386
387
command = BeEF::Core::Models::Command.find(command_id.to_i) || nil
388
if command.nil?
389
print_error 'Command is nil'
390
return
391
end
392
# validate nonce
393
nonce = @params['nonce'] || nil
394
if nonce.nil?
395
print_error 'nonce is nil'
396
return
397
end
398
if @session.get_nonce != nonce
399
print_error 'nonce incorrect'
400
return
401
end
402
403
command.instructions_sent = false
404
command.save
405
406
@body = '{success : true}'
407
end
408
409
def attach_dynamic_command_module
410
definition = {}
411
412
# get params
413
zombie_session = @params['zombie_session'] || nil
414
if zombie_session.nil?
415
print_error 'Zombie id is nil'
416
return
417
end
418
419
command_module_id = @params['command_module_id'] || nil
420
if command_module_id.nil?
421
print_error 'command_module id is nil'
422
return
423
end
424
425
# validate nonce
426
nonce = @params['nonce'] || nil
427
if nonce.nil?
428
print_error 'nonce is nil'
429
return
430
end
431
432
if @session.get_nonce != nonce
433
print_error 'nonce incorrect'
434
return
435
end
436
437
@params.keys.each do |param|
438
unless BeEF::Filters.has_valid_param_chars?(param)
439
print_error 'invalid key param string'
440
return
441
end
442
443
if BeEF::Filters.first_char_is_num?(param)
444
print_error "first char is num: #{param}"
445
return
446
end
447
448
definition[param[4..-1]] = params[param]
449
oc = BeEF::Core::Models::OptionCache.first_or_create(name: param[4..-1])
450
oc.value = params[param]
451
oc.save
452
end
453
454
zombie = Z.where(session: zombie_session).first
455
if zombie.nil?
456
print_error 'Zombie is nil'
457
return
458
end
459
460
zombie_id = zombie.id
461
if zombie_id.nil?
462
print_error 'Zombie id is nil'
463
return
464
end
465
466
command_module = BeEF::Core::Models::CommandModule.find(command_module_id)
467
468
return { 'success' => 'false' }.to_json if command_module.nil?
469
470
unless command_module.path.match(/^Dynamic/)
471
print_info "Command module path is not dynamic: #{command_module.path}"
472
return { 'success' => 'false' }.to_json
473
end
474
475
dyn_mod_name = command_module.path.split('/').last
476
e = BeEF::Modules::Commands.const_get(dyn_mod_name.capitalize).new
477
e.update_info(command_module_id)
478
e.update_data
479
ret = e.launch_exploit(definition)
480
481
if ret['result'] != 'success'
482
print_info 'mount failed'
483
return { 'success' => 'false' }.to_json
484
end
485
486
basedef = {}
487
basedef['sploit_url'] = ret['uri']
488
489
C.new(
490
data: basedef.to_json,
491
hooked_browser_id: zombie_id,
492
command_module_id: command_module_id,
493
creationdate: Time.new.to_i
494
).save
495
496
@body = { 'success' => true }.to_json
497
end
498
499
# Returns the results of a command
500
def select_command_results
501
results = []
502
503
# get params
504
command_id = @params['command_id'] || nil
505
if command_id.nil?
506
print_error 'Command id is nil'
507
return
508
end
509
510
command = BeEF::Core::Models::Command.find(command_id.to_i) || nil
511
if command.nil?
512
print_error 'Command is nil'
513
return
514
end
515
516
# get command_module
517
command_module = BeEF::Core::Models::CommandModule.find(command.command_module_id)
518
if command_module.nil?
519
print_error 'command_module is nil'
520
return
521
end
522
523
resultsdb = BeEF::Core::Models::Result.where(command_id: command_id)
524
if resultsdb.nil?
525
print_error 'Command id result is nil'
526
return
527
end
528
529
resultsdb.each { |result| results.push({ 'date' => result.date, 'data' => JSON.parse(result.data) }) }
530
531
@body = {
532
'success' => 'true',
533
'command_module_name' => command_module.name,
534
'command_module_id' => command_module.id,
535
'results' => results
536
}.to_json
537
end
538
539
# Returns the definition of a command.
540
# In other words it returns the command that was used to command_module a zombie.
541
def select_command
542
# get params
543
command_id = @params['command_id'] || nil
544
if command_id.nil?
545
print_error 'Command id is nil'
546
return
547
end
548
549
command = BeEF::Core::Models::Command.find(command_id.to_i) || nil
550
if command.nil?
551
print_error 'Command is nil'
552
return
553
end
554
555
command_module = BeEF::Core::Models::CommandModule.find(command.command_module_id)
556
if command_module.nil?
557
print_error 'command_module is nil'
558
return
559
end
560
561
if command_module.path.split('/').first.match(/^Dynamic/)
562
dyn_mod_name = command_module.path.split('/').last
563
e = BeEF::Modules::Commands.const_get(dyn_mod_name.capitalize).new
564
else
565
command_module_name = command_module.name
566
e = BeEF::Core::Command.const_get(command_module_name.capitalize).new(command_module_name)
567
end
568
569
@body = {
570
'success' => 'true',
571
'command_module_name' => command_module_name,
572
'command_module_id' => command_module.id,
573
'data' => BeEF::Module.get_options(command_module_name),
574
'definition' => JSON.parse(e.to_json)
575
}.to_json
576
end
577
578
private
579
580
# Takes a list of command_modules and returns them as a JSON array
581
def command_modules2json(command_modules)
582
command_modules_json = {}
583
i = 1
584
config = BeEF::Core::Configuration.instance
585
command_modules.each do |command_module|
586
h = {
587
'Name' => config.get("beef.module.#{command_module}.name"),
588
'Description' => config.get("beef.module.#{command_module}.description"),
589
'Category' => config.get("beef.module.#{command_module}.category"),
590
'Data' => BeEF::Module.get_options(command_module)
591
}
592
command_modules_json[i] = h
593
i += 1
594
end
595
596
return { 'success' => 'false' }.to_json if command_modules_json.empty?
597
598
{ 'success' => 'true', 'command_modules' => command_modules_json }.to_json
599
end
600
601
# return the input requred for the module in JSON format
602
def dynamic_modules2json(id)
603
command_modules_json = {}
604
605
mod = BeEF::Core::Models::CommandModule.find(id)
606
607
# if the module id is not in the database return false
608
return { 'success' => 'false' }.to_json unless mod
609
610
# the path will equal Dynamic/<type> and this will get just the type
611
dynamic_type = mod.path.split('/').last
612
613
e = BeEF::Modules::Commands.const_get(dynamic_type.capitalize).new
614
e.update_info(mod.id)
615
e.update_data
616
command_modules_json[1] = JSON.parse(e.to_json)
617
if command_modules_json.empty?
618
{ 'success' => 'false' }.to_json
619
else
620
{ 'success' => 'true', 'dynamic' => 'true', 'command_modules' => command_modules_json }.to_json
621
end
622
end
623
624
def dynamic_payload2json(id, payload_name)
625
command_module = BeEF::Core::Models::CommandModule.find(id)
626
if command_module.nil?
627
print_error 'Module does not exists'
628
return { 'success' => 'false' }.to_json
629
end
630
631
payload_options = BeEF::Module.get_payload_options(command_module.name, payload_name)
632
# get payload options in JSON
633
# e = BeEF::Modules::Commands.const_get(dynamic_type.capitalize).new
634
payload_options_json = []
635
payload_options_json[1] = payload_options
636
# payload_options_json[1] = e.get_payload_options(payload_name)
637
{ 'success' => 'true', 'command_modules' => payload_options_json }.to_json
638
end
639
end
640
end
641
end
642
end
643
end
644
645