WebRTC extension FF fix

Thanks to updates in modern FFs handling of WebRTC
the webrtcadapter wrapper had to be updated.
To ensure this would be picked up, also added
WebRTC REST integration test cases.
The tests only run if the extension is enabled,
which is still OFF by default.

See Issue #1134 and #1083
This commit is contained in:
Christian Frichot
2015-07-08 15:13:21 +08:00
parent 526cd42170
commit 13593990e5
7 changed files with 679 additions and 157 deletions

View File

@@ -1,190 +1,327 @@
var RTCPeerConnection = null;
/*
* Copyright (c) 2014 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
/* More information about these options at jshint.com/docs/options */
/* jshint browser: true, camelcase: true, curly: true, devel: true,
eqeqeq: true, forin: false, globalstrict: true, node: true,
quotmark: single, undef: true, unused: strict */
/* global mozRTCIceCandidate, mozRTCPeerConnection, Promise,
mozRTCSessionDescription, webkitRTCPeerConnection, MediaStreamTrack */
/* exported trace,requestUserMedia */
'use strict';
var getUserMedia = null;
var attachMediaStream = null;
var reattachMediaStream = null;
var webrtcDetectedBrowser = null;
var webrtcDetectedVersion = null;
var webrtcMinimumVersion = null;
function maybeFixConfiguration(pcConfig) {
if (pcConfig === null) {
return;
function trace(text) {
// This function is used for logging.
if (text[text.length - 1] === '\n') {
text = text.substring(0, text.length - 1);
}
for (var i = 0; i < pcConfig.iceServers.length; i++) {
if (pcConfig.iceServers[i].hasOwnProperty('urls')){
if (pcConfig.iceServers[i]['urls'].length > 0) {
// In FF - we just take the FIRST STUN Server
pcConfig.iceServers[i]['url'] = pcConfig.iceServers[i]['urls'][0];
} else {
pcConfig.iceServers[i]['url'] = pcConfig.iceServers[i]['urls'];
}
delete pcConfig.iceServers[i]['urls'];
}
if (window.performance) {
var now = (window.performance.now() / 1000).toFixed(3);
beef.debug(now + ': ' + text);
} else {
beef.debug(text);
}
}
if (navigator.mozGetUserMedia) {
webrtcDetectedBrowser = "firefox";
webrtcDetectedBrowser = 'firefox';
// the detected firefox version.
webrtcDetectedVersion =
parseInt(navigator.userAgent.match(/Firefox\/([0-9]+)\./)[1], 10);
parseInt(navigator.userAgent.match(/Firefox\/([0-9]+)\./)[1], 10);
// the minimum firefox version still supported by adapter.
webrtcMinimumVersion = 31;
// The RTCPeerConnection object.
var RTCPeerConnection = function(pcConfig, pcConstraints) {
// .urls is not supported in FF yet.
maybeFixConfiguration(pcConfig);
return new mozRTCPeerConnection(pcConfig, pcConstraints);
}
// The RTCSessionDescription object.
RTCSessionDescription = mozRTCSessionDescription;
// The RTCIceCandidate object.
RTCIceCandidate = mozRTCIceCandidate;
// Get UserMedia (only difference is the prefix).
// Code from Adam Barth.
getUserMedia = navigator.mozGetUserMedia.bind(navigator);
navigator.getUserMedia = getUserMedia;
// Creates iceServer from the url for FF.
createIceServer = function(url, username, password) {
var iceServer = null;
var url_parts = url.split(':');
if (url_parts[0].indexOf('stun') === 0) {
// Create iceServer with stun url.
iceServer = { 'url': url };
} else if (url_parts[0].indexOf('turn') === 0) {
if (webrtcDetectedVersion < 27) {
// Create iceServer with turn url.
// Ignore the transport parameter from TURN url for FF version <=27.
var turn_url_parts = url.split("?");
// Return null for createIceServer if transport=tcp.
if (turn_url_parts.length === 1 ||
turn_url_parts[1].indexOf('transport=udp') === 0) {
iceServer = {'url': turn_url_parts[0],
'credential': password,
'username': username};
window.RTCPeerConnection = function(pcConfig, pcConstraints) {
if (webrtcDetectedVersion < 38) {
// .urls is not supported in FF < 38.
// create RTCIceServers with a single url.
if (pcConfig && pcConfig.iceServers) {
var newIceServers = [];
for (var i = 0; i < pcConfig.iceServers.length; i++) {
var server = pcConfig.iceServers[i];
if (server.hasOwnProperty('urls')) {
for (var j = 0; j < server.urls.length; j++) {
var newServer = {
url: server.urls[j]
};
if (server.urls[j].indexOf('turn') === 0) {
newServer.username = server.username;
newServer.credential = server.credential;
}
newIceServers.push(newServer);
}
} else {
newIceServers.push(pcConfig.iceServers[i]);
}
}
} else {
// FF 27 and above supports transport parameters in TURN url,
// So passing in the full url to create iceServer.
iceServer = {'url': url,
'credential': password,
'username': username};
pcConfig.iceServers = newIceServers;
}
}
return iceServer;
return new mozRTCPeerConnection(pcConfig, pcConstraints);
};
createIceServers = function(urls, username, password) {
var iceServers = [];
// Use .url for FireFox.
for (i = 0; i < urls.length; i++) {
var iceServer = createIceServer(urls[i],
username,
password);
if (iceServer !== null) {
iceServers.push(iceServer);
}
}
return iceServers;
}
// The RTCSessionDescription object.
window.RTCSessionDescription = mozRTCSessionDescription;
// The RTCIceCandidate object.
window.RTCIceCandidate = mozRTCIceCandidate;
// getUserMedia constraints shim.
getUserMedia = (webrtcDetectedVersion < 38) ?
function(c, onSuccess, onError) {
var constraintsToFF37 = function(c) {
if (typeof c !== 'object' || c.require) {
return c;
}
var require = [];
Object.keys(c).forEach(function(key) {
var r = c[key] = (typeof c[key] === 'object') ?
c[key] : {ideal: c[key]};
if (r.exact !== undefined) {
r.min = r.max = r.exact;
delete r.exact;
}
if (r.min !== undefined || r.max !== undefined) {
require.push(key);
}
if (r.ideal !== undefined) {
c.advanced = c.advanced || [];
var oc = {};
oc[key] = {min: r.ideal, max: r.ideal};
c.advanced.push(oc);
delete r.ideal;
if (!Object.keys(r).length) {
delete c[key];
}
}
});
if (require.length) {
c.require = require;
}
return c;
};
beef.debug('spec: ' + JSON.stringify(c));
c.audio = constraintsToFF37(c.audio);
c.video = constraintsToFF37(c.video);
beef.debug('ff37: ' + JSON.stringify(c));
return navigator.mozGetUserMedia(c, onSuccess, onError);
} : navigator.mozGetUserMedia.bind(navigator);
navigator.getUserMedia = getUserMedia;
// Shim for mediaDevices on older versions.
if (!navigator.mediaDevices) {
navigator.mediaDevices = {getUserMedia: requestUserMedia,
addEventListener: function() { },
removeEventListener: function() { }
};
}
navigator.mediaDevices.enumerateDevices =
navigator.mediaDevices.enumerateDevices || function() {
return new Promise(function(resolve) {
var infos = [
{kind: 'audioinput', deviceId: 'default', label:'', groupId:''},
{kind: 'videoinput', deviceId: 'default', label:'', groupId:''}
];
resolve(infos);
});
};
if (webrtcDetectedVersion < 41) {
// Work around http://bugzil.la/1169665
var orgEnumerateDevices =
navigator.mediaDevices.enumerateDevices.bind(navigator.mediaDevices);
navigator.mediaDevices.enumerateDevices = function() {
return orgEnumerateDevices().catch(function(e) {
if (e.name === 'NotFoundError') {
return [];
}
throw e;
});
};
}
// Attach a media stream to an element.
attachMediaStream = function(element, stream) {
beef.debug("Attaching media stream");
beef.debug('Attaching media stream');
element.mozSrcObject = stream;
element.play();
};
reattachMediaStream = function(to, from) {
beef.debug("Reattaching media stream");
beef.debug('Reattaching media stream');
to.mozSrcObject = from.mozSrcObject;
to.play();
};
// Fake get{Video,Audio}Tracks
if (!MediaStream.prototype.getVideoTracks) {
MediaStream.prototype.getVideoTracks = function() {
return [];
};
}
if (!MediaStream.prototype.getAudioTracks) {
MediaStream.prototype.getAudioTracks = function() {
return [];
};
}
} else if (navigator.webkitGetUserMedia) {
webrtcDetectedBrowser = "chrome";
// Temporary fix until crbug/374263 is fixed.
// Setting Chrome version to 999, if version is unavailable.
var result = navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./);
if (result !== null) {
webrtcDetectedVersion = parseInt(result[2], 10);
} else {
webrtcDetectedVersion = 999;
}
webrtcDetectedBrowser = 'chrome';
// Creates iceServer from the url for Chrome M33 and earlier.
createIceServer = function(url, username, password) {
var iceServer = null;
var url_parts = url.split(':');
if (url_parts[0].indexOf('stun') === 0) {
// Create iceServer with stun url.
iceServer = { 'url': url };
} else if (url_parts[0].indexOf('turn') === 0) {
// Chrome M28 & above uses below TURN format.
iceServer = {'url': url,
'credential': password,
'username': username};
}
return iceServer;
};
// the detected chrome version.
webrtcDetectedVersion =
parseInt(navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./)[2], 10);
// Creates iceServers from the urls for Chrome M34 and above.
createIceServers = function(urls, username, password) {
var iceServers = [];
if (webrtcDetectedVersion >= 34) {
// .urls is supported since Chrome M34.
iceServers = {'urls': urls,
'credential': password,
'username': username };
} else {
for (i = 0; i < urls.length; i++) {
var iceServer = createIceServer(urls[i],
username,
password);
if (iceServer !== null) {
iceServers.push(iceServer);
}
}
}
return iceServers;
};
// the minimum chrome version still supported by adapter.
webrtcMinimumVersion = 38;
// The RTCPeerConnection object.
var RTCPeerConnection = function(pcConfig, pcConstraints) {
// .urls is supported since Chrome M34.
if (webrtcDetectedVersion < 34) {
maybeFixConfiguration(pcConfig);
}
return new webkitRTCPeerConnection(pcConfig, pcConstraints);
}
window.RTCPeerConnection = function(pcConfig, pcConstraints) {
var pc = new webkitRTCPeerConnection(pcConfig, pcConstraints);
var origGetStats = pc.getStats.bind(pc);
pc.getStats = function(selector, successCallback, errorCallback) { // jshint ignore: line
// If selector is a function then we are in the old style stats so just
// pass back the original getStats format to avoid breaking old users.
if (typeof selector === 'function') {
return origGetStats(selector, successCallback);
}
// Get UserMedia (only difference is the prefix).
// Code from Adam Barth.
getUserMedia = navigator.webkitGetUserMedia.bind(navigator);
var fixChromeStats = function(response) {
var standardReport = {};
var reports = response.result();
reports.forEach(function(report) {
var standardStats = {
id: report.id,
timestamp: report.timestamp,
type: report.type
};
report.names().forEach(function(name) {
standardStats[name] = report.stat(name);
});
standardReport[standardStats.id] = standardStats;
});
return standardReport;
};
var successCallbackWrapper = function(response) {
successCallback(fixChromeStats(response));
};
return origGetStats(successCallbackWrapper, selector);
};
return pc;
};
// add promise support
['createOffer', 'createAnswer'].forEach(function(method) {
var nativeMethod = webkitRTCPeerConnection.prototype[method];
webkitRTCPeerConnection.prototype[method] = function() {
var self = this;
if (arguments.length < 1 || (arguments.length === 1 &&
typeof(arguments[0]) === 'object')) {
var opts = arguments.length === 1 ? arguments[0] : undefined;
return new Promise(function(resolve, reject) {
nativeMethod.apply(self, [resolve, reject, opts]);
});
} else {
return nativeMethod.apply(this, arguments);
}
};
});
['setLocalDescription', 'setRemoteDescription',
'addIceCandidate'].forEach(function(method) {
var nativeMethod = webkitRTCPeerConnection.prototype[method];
webkitRTCPeerConnection.prototype[method] = function() {
var args = arguments;
var self = this;
return new Promise(function(resolve, reject) {
nativeMethod.apply(self, [args[0],
function() {
resolve();
if (args.length >= 2) {
args[1].apply(null, []);
}
},
function(err) {
reject(err);
if (args.length >= 3) {
args[2].apply(null, [err]);
}
}]
);
});
};
});
// getUserMedia constraints shim.
getUserMedia = function(c, onSuccess, onError) {
var constraintsToChrome = function(c) {
if (typeof c !== 'object' || c.mandatory || c.optional) {
return c;
}
var cc = {};
Object.keys(c).forEach(function(key) {
if (key === 'require' || key === 'advanced') {
return;
}
var r = (typeof c[key] === 'object') ? c[key] : {ideal: c[key]};
if (r.exact !== undefined && typeof r.exact === 'number') {
r.min = r.max = r.exact;
}
var oldname = function(prefix, name) {
if (prefix) {
return prefix + name.charAt(0).toUpperCase() + name.slice(1);
}
return (name === 'deviceId') ? 'sourceId' : name;
};
if (r.ideal !== undefined) {
cc.optional = cc.optional || [];
var oc = {};
if (typeof r.ideal === 'number') {
oc[oldname('min', key)] = r.ideal;
cc.optional.push(oc);
oc = {};
oc[oldname('max', key)] = r.ideal;
cc.optional.push(oc);
} else {
oc[oldname('', key)] = r.ideal;
cc.optional.push(oc);
}
}
if (r.exact !== undefined && typeof r.exact !== 'number') {
cc.mandatory = cc.mandatory || {};
cc.mandatory[oldname('', key)] = r.exact;
} else {
['min', 'max'].forEach(function(mix) {
if (r[mix] !== undefined) {
cc.mandatory = cc.mandatory || {};
cc.mandatory[oldname(mix, key)] = r[mix];
}
});
}
});
if (c.advanced) {
cc.optional = (cc.optional || []).concat(c.advanced);
}
return cc;
};
beef.debug('spec: ' + JSON.stringify(c)); // whitespace for alignment
c.audio = constraintsToChrome(c.audio);
c.video = constraintsToChrome(c.video);
beef.debug('chrome: ' + JSON.stringify(c));
return navigator.webkitGetUserMedia(c, onSuccess, onError);
};
navigator.getUserMedia = getUserMedia;
// Attach a media stream to an element.
attachMediaStream = function(element, stream) {
if (typeof element.srcObject !== 'undefined') {
element.srcObject = stream;
} else if (typeof element.mozSrcObject !== 'undefined') {
element.mozSrcObject = stream;
} else if (typeof element.src !== 'undefined') {
element.src = URL.createObjectURL(stream);
} else {
@@ -195,6 +332,78 @@ if (navigator.mozGetUserMedia) {
reattachMediaStream = function(to, from) {
to.src = from.src;
};
if (!navigator.mediaDevices) {
navigator.mediaDevices = {getUserMedia: requestUserMedia,
enumerateDevices: function() {
return new Promise(function(resolve) {
var kinds = {audio: 'audioinput', video: 'videoinput'};
return MediaStreamTrack.getSources(function(devices) {
resolve(devices.map(function(device) {
return {label: device.label,
kind: kinds[device.kind],
deviceId: device.id,
groupId: ''};
}));
});
});
}};
// in case someone wants to listen for the devicechange event.
navigator.mediaDevices.addEventListener = function() { };
navigator.mediaDevices.removeEventListener = function() { };
}
} else if (navigator.mediaDevices && navigator.userAgent.match(
/Edge\/(\d+).(\d+)$/)) {
webrtcDetectedBrowser = 'edge';
webrtcDetectedVersion =
parseInt(navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)[2], 10);
// the minimum version still supported by adapter.
webrtcMinimumVersion = 12;
attachMediaStream = function(element, stream) {
element.srcObject = stream;
};
reattachMediaStream = function(to, from) {
to.srcObject = from.srcObject;
};
} else {
beef.debug("Browser does not appear to be WebRTC-capable");
// console.log('Browser does not appear to be WebRTC-capable');
}
// Returns the result of getUserMedia as a Promise.
function requestUserMedia(constraints) {
return new Promise(function(resolve, reject) {
getUserMedia(constraints, resolve, reject);
});
}
if (typeof module !== 'undefined') {
module.exports = {
RTCPeerConnection: window.RTCPeerConnection,
getUserMedia: getUserMedia,
attachMediaStream: attachMediaStream,
reattachMediaStream: reattachMediaStream,
webrtcDetectedBrowser: webrtcDetectedBrowser,
webrtcDetectedVersion: webrtcDetectedVersion,
webrtcMinimumVersion: webrtcMinimumVersion
//requestUserMedia: not exposed on purpose.
//trace: not exposed on purpose.
};
} else if ((typeof require === 'function') && (typeof define === 'function')) {
// Expose objects and functions when RequireJS is doing the loading.
define([], function() {
return {
RTCPeerConnection: window.RTCPeerConnection,
getUserMedia: getUserMedia,
attachMediaStream: attachMediaStream,
reattachMediaStream: reattachMediaStream,
webrtcDetectedBrowser: webrtcDetectedBrowser,
webrtcDetectedVersion: webrtcDetectedVersion,
webrtcMinimumVersion: webrtcMinimumVersion
//requestUserMedia: not exposed on purpose.
//trace: not exposed on purpose.
};
});
}

View File

@@ -67,10 +67,12 @@ Beefwebrtc.prototype.initialize = function() {
// Initialise the pcConfig hash with the provided stunservers
var stuns = JSON.parse(this.stunservers);
this.pcConfig = {"iceServers": [{"urls":stuns}]};
this.pcConfig = {"iceServers": [{"urls":stuns, "username":"user",
"credential":"pass"}]};
// We're not getting the browsers to request their own TURN servers, we're specifying them through BeEF
this.forceTurn(this.turnjson);
// this.forceTurn(this.turnjson);
this.turnDone = true;
// Caller is always ready to create peerConnection.
this.signalingReady = this.initiator;
@@ -450,6 +452,18 @@ Beefwebrtc.prototype.onCreateSessionDescriptionError = function(error) {
if (localverbose === true) {beef.debug('Failed to create session description: ' + error.toString());}
}
// If the browser successfully sets a remote description
Beefwebrtc.prototype.onSetRemoteDescriptionSuccess = function() {
var localverbose = false;
for (var k in beefrtcs) {
if (beefrtcs[k].verbose === true) {
localverbose = true;
}
}
if (localverbose === true) {beef.debug('Set remote session description successfully');}
}
// Check for messages - which includes signaling from a calling peer - this gets kicked off in maybeStart()
Beefwebrtc.prototype.calleeStart = function() {
// Callee starts to process cached offer and other messages.
@@ -467,11 +481,55 @@ Beefwebrtc.prototype.processSignalingMessage = function(message) {
if (message.type === 'offer') {
if (this.verbose) {beef.debug("Processing signalling message: OFFER");}
this.setRemote(message);
this.doAnswer();
if (navigator.mozGetUserMedia) { // Mozilla shim fuckn shit - since the new
// version of FF - which no longer works
if (this.verbose) {beef.debug("Moz shim here");}
globalrtc[this.peerid].setRemoteDescription(
new RTCSessionDescription(message),
function() {
// globalrtc[this.peerid].createAnswer(function(answer) {
// globalrtc[this.peerid].setLocalDescription(
var peerid = null;
for (var k in beefrtcs) {
if (beefrtcs[k].allgood === false) {
peerid = beefrtcs[k].peerid;
}
}
globalrtc[peerid].createAnswer(function(answer) {
globalrtc[peerid].setLocalDescription(
new RTCSessionDescription(answer),
function() {
beefrtcs[peerid].sendSignalMsg(answer);
},function(error) {
beef.debug("setLocalDescription error: " + error);
});
},function(error) {
beef.debug("createAnswer error: " +error);
});
},function(error) {
beef.debug("setRemoteDescription error: " + error);
});
} else {
this.setRemote(message);
this.doAnswer();
}
} else if (message.type === 'answer') {
if (this.verbose) {beef.debug("Processing signalling message: ANSWER");}
this.setRemote(message);
if (navigator.mozGetUserMedia) { // terrible moz shim - as for the offer
if (this.verbose) {beef.debug("Moz shim here");}
globalrtc[this.peerid].setRemoteDescription(
new RTCSessionDescription(message),
function() {},
function(error) {
beef.debug("setRemoteDescription error: " + error);
});
} else {
this.setRemote(message);
}
} else if (message.type === 'candidate') {
if (this.verbose) {beef.debug("Processing signalling message: CANDIDATE");}
var candidate = new RTCIceCandidate({sdpMLineIndex: message.label,
@@ -486,11 +544,11 @@ Beefwebrtc.prototype.processSignalingMessage = function(message) {
// Used to set the RTC remote session
Beefwebrtc.prototype.setRemote = function(message) {
globalrtc[this.peerid].setRemoteDescription(new RTCSessionDescription(message),
onSetRemoteDescriptionSuccess, this.onSetSessionDescriptionError);
this.onSetRemoteDescriptionSuccess, this.onSetSessionDescriptionError);
function onSetRemoteDescriptionSuccess() {
if (this.verbose) {beef.debug("Set remote session description success.");}
}
// function onSetRemoteDescriptionSuccess() {
// if (this.verbose) {beef.debug("Set remote session description success.");}
// }
}
// As part of the processSignalingMessage function, we check for 'offers' from peers. If there's an offer, we answer, as below
@@ -585,4 +643,4 @@ beef.webrtc = {
}
}
}
beef.regCmp('beef.webrtc');
beef.regCmp('beef.webrtc');

View File

@@ -9,6 +9,6 @@ beef:
name: 'WebRTC'
enable: false
authors: ["xntrik"]
stunservers: '["stun:stun.l.google.com:19302","stun:stun1.l.google.com:19302"]'
stunservers: '["stun:stun.l.google.com:19302","stun:stun1.l.google.com:19302","turn:numb.viagenie.ca:3478"]'
# stunservers: '["stun:stun.l.google.com:19302"]'
turnservers: '{"username": "someone%40somewhere.com", "password": "somepass", "uris": ["turn:numb.viagenie.ca:3478?transport=udp","turn:numb.viagenie.ca:3478?transport=tcp"]}'
turnservers: '{"username": "someone%40somewhere.com", "password": "somepass", "uris": ["turn:numb.viagenie.ca:3478?transport=udp","turn:numb.viagenie.ca:3478?transport=tcp"]}'

View File

@@ -152,7 +152,7 @@ module BeEF
# +++ Example with curl +++
# curl -H "Content-type: application/json; charset=UTF-8" -v
# -X POST -d '{"from":1,"to":2,"message":"Just a plain message"}'
# http://127.0.0.1:3000/api/webrtc/go\?token\=df67654b03d030d97018f85f0284247d7f49c348
# http://127.0.0.1:3000/api/webrtc/msg\?token\=df67654b03d030d97018f85f0284247d7f49c348
#
# Available client-side "message" options and handling:
# !gostealth - will put the <to> browser into a stealth mode

View File

@@ -23,3 +23,4 @@ RESTAPI_NETWORK = "http://" + ATTACK_DOMAIN + ":3000/api/network"
RESTAPI_DNS = "http://" + ATTACK_DOMAIN + ":3000/api/dns"
RESTAPI_SENG = "http://" + ATTACK_DOMAIN + ":3000/api/seng"
RESTAPI_ADMIN = "http://" + ATTACK_DOMAIN + ":3000/api/admin"
RESTAPI_WEBRTC = "http://" + ATTACK_DOMAIN + ":3000/api/webrtc"

View File

@@ -0,0 +1,252 @@
#
# Copyright (c) 2006-2015 Wade Alcorn - wade@bindshell.net
# Browser Exploitation Framework (BeEF) - http://beefproject.com
# See the file 'doc/COPYING' for copying permission
#
require 'test/unit'
require 'rest-client'
require 'json'
require '../common/test_constants'
require '../common/beef_test'
class TC_WebRTCRest < Test::Unit::TestCase
class << self
# Login to API before performing any tests - and fetch config too
def startup
json = {:username => BEEF_USER, :password => BEEF_PASSWD}.to_json
@@headers = {:content_type => :json, :accept => :json}
response = RestClient.post("#{RESTAPI_ADMIN}/login",
json,
@@headers)
result = JSON.parse(response.body)
@@token = result['token']
$root_dir = '../../'
$:.unshift($root_dir)
require 'core/loader'
BeEF::Core::Configuration.new(File.join($root_dir, 'config.yaml'))
BeEF::Core::Configuration.instance.load_extensions_config
@@config = BeEF::Core::Configuration.instance
@@activated = @@config.get('beef.extension.webrtc.enable') || false
@@victim1 = BeefTest.new_victim
@@victim2 = BeefTest.new_victim
puts "WebRTC Tests: Sleeping for 8 - waiting for 2 browsers to get hooked"
sleep 8.0
# Fetch last online browsers' ids
rest_response = RestClient.get "#{RESTAPI_HOOKS}", {:params => {
:token => @@token}}
result = JSON.parse(rest_response.body)
browsers = result["hooked-browsers"]["online"]
browsers.each_with_index do |elem, index|
if index == browsers.length - 1
@@victim2id = browsers["#{index}"]["id"].to_s
end
if index == browsers.length - 2
@@victim1id = browsers["#{index}"]["id"].to_s
end
end
end
def shutdown
$root_dir = nil
@@victim1.driver.browser.close
@@victim2.driver.browser.close
end
end
def test_1_webrtc_check_for_two_hooked_browsers
return if not @@activated
rest_response = nil
assert_nothing_raised do
rest_response = RestClient.get "#{RESTAPI_HOOKS}", {:params => {
:token => @@token}}
end
check_rest_response(rest_response)
result = JSON.parse(rest_response.body)
browsers = result["hooked-browsers"]["online"]
assert_not_nil browsers
assert_operator browsers.length, :>=, 2
end
def test_2_webrtc_establishing_p2p
return if not @@activated
rest_response = nil
assert_nothing_raised do
rest_response = RestClient.post("#{RESTAPI_WEBRTC}/go?token=#{@@token}",
{:from => @@victim1id, :to => @@victim2id, :verbose => "true"}.to_json,
@@headers)
end
check_rest_response(rest_response)
result = JSON.parse(rest_response.body)
assert_equal true, result["success"]
sleep 20.0
rest_response = nil
assert_nothing_raised do
rest_response = RestClient.get "#{RESTAPI_LOGS}", {:params => {
:token => @@token}}
end
check_rest_response(rest_response)
result = JSON.parse(rest_response.body)
loghitcount = 0
result["logs"].reverse.each {|l|
# Using free-space matching mode /x below to wrap regex.
# therefore need to escape spaces I want to check, hence the \
regex = Regexp.new(/Browser:(#{@@victim1id}|#{@@victim2id})\ received\
message\ from\ Browser:(#{@@victim1id}|#{@@victim2id})
:\ ICE\ Status:\ connected/x)
loghitcount += 1 if (not regex.match(l["event"]).nil?) and
(l["type"].to_s.eql?("WebRTC"))
}
assert_equal 2, loghitcount
end
def test_3_webrtc_send_msg # assumes test 2 has run
return if not @@activated
rest_response = nil
assert_nothing_raised do
rest_response = RestClient.post("#{RESTAPI_WEBRTC}/msg?token=#{@@token}",
{:from => @@victim1id, :to => @@victim2id,
:message => "RTC test message"}.to_json,
@@headers)
end
check_rest_response(rest_response)
result = JSON.parse(rest_response.body)
assert_equal true, result["success"]
sleep 10.0
rest_response = nil
assert_nothing_raised do
rest_response = RestClient.get "#{RESTAPI_LOGS}", {:params => {
:token => @@token}}
end
check_rest_response(rest_response)
result = JSON.parse(rest_response.body)
assert_block do
result["logs"].reverse.each {|l|
# Using free-space matching mode /x below to wrap regex.
# therefore need to escape spaces I want to check, hence the \
regex = Regexp.new(/Browser:(#{@@victim1id}|#{@@victim2id})\ received\
message\ from\ Browser:
(#{@@victim1id}|#{@@victim2id})
:\ RTC\ test\ message/x)
return true if (not regex.match(l["event"]).nil?) and
(l["type"].to_s.eql?("WebRTC"))
}
end
end
def test_4_webrtc_stealthmode # assumes test 2 has run
return if not @@activated
# Test our two browsers are still online
rest_response = nil
assert_nothing_raised do
rest_response = RestClient.get "#{RESTAPI_HOOKS}", {:params => {
:token => @@token}}
end
check_rest_response(rest_response)
result = JSON.parse(rest_response.body)
online = result["hooked-browsers"]["online"]
assert_block do
online.each {|hb|
return true if hb[1]["id"].eql?(@@victim1id)
}
end
assert_block do
online.each {|hb|
return true if hb[1]["id"].eql?(@@victim2id)
}
end
# Command one of the browsers to go stealth
rest_response = nil
assert_nothing_raised do
rest_response = RestClient.post("#{RESTAPI_WEBRTC}/msg?token=#{@@token}",
{:from => @@victim1id, :to => @@victim2id,
:message => "!gostealth"}.to_json,
@@headers)
end
check_rest_response(rest_response)
result = JSON.parse(rest_response.body)
assert_equal true, result["success"]
sleep 40.0 #Wait until that browser is offline.
# Test that the browser is now offline
rest_response = nil
assert_nothing_raised do
rest_response = RestClient.get "#{RESTAPI_HOOKS}", {:params => {
:token => @@token}}
end
check_rest_response(rest_response)
result = JSON.parse(rest_response.body)
offline = result["hooked-browsers"]["offline"]
assert_block do
offline.each {|hb|
return true if hb[1]["id"].eql?(@@victim2id)
}
end
# Test that we can bring it back online (which implies comms are still ok)
rest_response = nil
assert_nothing_raised do
rest_response = RestClient.post("#{RESTAPI_WEBRTC}/msg?token=#{@@token}",
{:from => @@victim1id, :to => @@victim2id,
:message => "!endstealth"}.to_json,
@@headers)
end
check_rest_response(rest_response)
result = JSON.parse(rest_response.body)
assert_equal true, result["success"]
sleep 10.0 # Wait until browser comes back
# Test that the browser is now online
rest_response = nil
assert_nothing_raised do
rest_response = RestClient.get "#{RESTAPI_HOOKS}", {:params => {
:token => @@token}}
end
check_rest_response(rest_response)
result = JSON.parse(rest_response.body)
online = result["hooked-browsers"]["online"]
assert_block do
online.each {|hb|
return true if hb[1]["id"].eql?(@@victim2id)
}
end
end
private
# Standard assertions for verifying response from RESTful API
def check_rest_response(response)
assert_not_nil(response.body)
assert_equal(200, response.code)
end
end

View File

@@ -19,6 +19,7 @@ require './tc_jools' # Basic tests for jools
require './tc_network_rest' # Basic tests for Network extension RESTful API interface
#require './tc_dns_rest' # Basic tests for DNS RESTful API interface
require './tc_social_engineering_rest' # Basic tests for social engineering RESTful API interface
require './tc_webrtc_rest' # Basic tests for WebRTC extension
class TS_BeefIntegrationTests
def self.suite
@@ -31,6 +32,7 @@ class TS_BeefIntegrationTests
suite << TC_NetworkRest.suite
#suite << TC_DnsRest.suite
suite << TC_SocialEngineeringRest.suite
suite << TC_WebRTCRest.suite
return suite
end