Javascript, track iframes redirecting top window - javascript

Since there is no way to prevent an iframe from redirecting the top frame besides sandboxing which prevents other features required for viewability tracking I would like to track redirects. Since one site can have more than one iframe, it could be any of these.
Is there any way to track/find out which one (specific iframe) caused the top frame redirect?
Here is a sandbox (use browser console and enable preserve log):
Note the iframe content is usually cross domain. For ease of use its within the sandox.

We can access to the iframe content with somethig like iframe.contentWindow.document but this is possible if we observe Same-origin policy.
Another approach could be setting a Content-Security-Policy header like:
<meta http-equiv="Content-Security-Policy" content="frame-src http://example.com">
This header in the parent page prevents to load sites different to http://example.com in frames, There is also a way to report the refuse behavior sending a post but unfortunately can't be setting with <meta> tag (it's only server side). With this approach we have to perform a white list, so I think maybe it's not useful in this case. But, if the white list is given the first time, is possible to set all sites available, so when the iframe redirect, browser will refuse to load it.
If it's not the case of same-origin and the possibility of performing a white list, then I think the better we can do is calling iframe onunload event, unfortunately this event are going to be fired also when iframe page reloads not only on redirection. I think it's the closest approach. To achieve that, this code works.
var srcs = ["iframe2.html","iframe.html","iframe2.html"];
for (let i = 0; i < srcs.length; i++) {
var iframe = document.createElement('iframe');
iframe.src = srcs[i];
iframe.name = "i"+i;
document.body.appendChild(iframe);
window["i"+i].onunload = function(){console.log("change "+i)}
}
Of course onunload is fired the first time, when all iframes load, so redirections are 2th 3th and so on. But we could exclude that first case.
Here a full example https://codesandbox.io/s/o16yk7mqy , I've created iframe3.html that doesn't refresh neither reload to show clearly the point. Also I've created a simple List of redirect or reload iframes.
UPDATE
As I understand now, what you want is to set iframes with sandbox property and whitelist all what you want but without allow-top-navigation, something like:
<iframe src="iframe.html" sandbox="allow-script allow-forms allow-popups allow-pointer-lock allow-same-origin"></iframe>
This Example doesn't allow allow-top-navigation https://codesandbox.io/s/lpmv6wr6y9
This Example here https://codesandbox.io/s/4x8v1mojq7 allow allow-top-navigation but codesandbox prevents the frame to redirect so if we try https://4x8v1mojq7.codesandbox.io/ that is the url created by codesandbox, we could see the top frame reload.
As I said in comments, at least Chrome 64.0.3282.167, when we delegate all but allow-top-navigation when the iframe attempt to redirect top frame, it throw an exception. The behavior is different in Firefox (at least 58.0.2). Firefox deny top navigation but continues with the code.
So, as conclusion the best approach in my opinion is or a combination of sanbox and onunload or just onunload. Of course, if it could be possible, Content-Security-Policy is the safest and more flexible way. It depends of the implementation. It's almost impossible I think not to involve server side code to perform a perfect solution. There are white list to check, like this API https://developers.google.com/safe-browsing/v4/ and there are black list to check, look at this post https://security.stackexchange.com/questions/32058/looking-for-url-blacklists-of-malicious-websites .

If you have control over all your frames you can implement interaction between frames with postMessage. Flow:
Frame want to execute redirect - it sends a message to parent frame with redirect request.
Parent frame executing redirect and know which frame caused a redirect.
Parent:
window.addEventListener("message", (event) => {
// show message source (your frame)
console.log(event.source);
const message = event.data;
console.log(`Frame ID: ${message.frameId}`);
if(message.messageType === "redirect") {
window.location.href = message.redirectUrl;
}
});
Child frame:
function redirect(url) {
var message = {
messageType: "redirect",
frameId: "frame1"
redirectUrl: url
}
window.parent.postMessage(message, "*");
}

You can show a dialog box before redirecting to another domain/application and then the user can decide - to stay or leave the current application. You can also track the current target (i.e. iframe in your case).
window.onbeforeunload = function (e) {
console.log(e.currentTarget.location.href);
return 'Stop redirection. Show dialog box.';
};

From the individual iframes, can you set a cookie when the redirect happens? Say it happened from iframe1, you may set a cookie like
document.trackFrame = "FrameName=iframe1";
And once the redirect completes, can you try reading the cookie and there by determine which iframe caused the re-direct?

Related

Block iframe src change through inspect element?

want to know how to prevent fetching of new data by changing the <iframe> src value in the webpage, through inspect element?
In web page we are showing videos, in <iframe> element. Videos will available to user after purchasing them. All videos are stored in Vimeo, and through Vimeo settings configured in such way that, it will be available only through our domain.
And the problem is,
When a user purchase the video he/she can access the src value of <iframe> and can share with others. And who ever has that url can just go our website and do inspect element and change src value of any <iframe> element in web page, video will be loaded without purchasing.
How to prevent this? Is there any way to make <iframe> to not load the content after page loads? Also if a user want can edit html and put <iframe> element with url. Or is this not the correct approach to implement payed videos in webpage?
I am totally stuck at this point.
Many Many Thanks for all your input.
update:
Can I use google drive for this option? Using google drive api I can dynamically give access to each user account.
It is not possible to prevent this. I'm not sure you can even detect the change from the outside due to cross-origin security, unless Vimeo has an API through cross-window messaging which you could to check the video ID - in this case the best thing you could do is have the page periodically verify the video ID inside the frame.
But even then, for a slightly more advanced "hacker" this would be no problem whatsoever. One could even defeat this whole system just by creating a local webserver which servers an HTML file containing the desired IFrame and setting up an entry in the hosts file which points your domain to 127.0.0.1... Or just use the NoScript extension and disable whatever is checking the video ID on your end.
Bottom line, once you are sending the Vimeo URL over the ether, you lost control over it and that's it. There is nothing you can do about that other than having the video streamed from your own server instead of Vimeo, where you can implement your own access control systems, e.g. creating one-time tokens valid only for this specific user and time, after verifying they rightfully have access to the content.
Your problem is you are trying to take a third party host and create authentication that does not exist.
It wont work all the time, but you could try to set up a setInterval timer that runs a few times a second, if the url changes to an unexpected value you could force the page to redirect to an unauthorized page.
If you go this route I would recommend initially loading the iframe element through javascript, not markup so if the user disables javascript they simply cant view any videos
window.setInterval(function(){
var frame = document.getElementById("frame");
if(frame.src !== expectedSource){
window.location.href = "unauthorized.html" // or something like that
}
}, 300);
// load iframe
window.onload = function(){
var frame = document.createElement("iframe");
document.body.insertBefore(frame, container); // container implies an existin DOM element
frame.src = expectedSource; // you will need some way to get the expected source and load it
}
If you don't want someone to change your iframe's "src", you can make the "src" property 'writable: false', so third party won't be able to change that.
Example:
var element = document.getElementById('iframe');
Object.defineProperty(element, 'src', {
writable: false
});
Also, someone can change src by calling element.setAttribute('src', value), so you can prevent this by overriding the setAttribute method.
Example:
var set = element.setAttribute;
element.setAttribute = function(attr, val) {
//do whatever
//set.call(this, attr, val);
};

Read iframe redirect (same domain)

I'm working in writing a chrome extension, and as a result I have the peculiar situation of using non-cross domain Iframes, without the ability to alter the page being displayed in the frame.
When a user clicks a certain link, in he iframe, I want to run some JavaScript. The default behavior for clicking that link is to load page targetpage.com. I don't think it's possible, or easy, to read listen for a particular element being clicked inside an iframe
As a workaround, I figure I can check if the iframe reloads, pointing to targetpage.com, and then perform the action.
Note: the action is entirely in the parent page, let's imagine I'm just trying to trigger an alert box.
I know how to trigger JavaScript when an iframe loads. My three questions are:
1) how can I check the url of an iframe?
2) is there a way to check the iframe, url prior to targetpage.com being loaded. Esther than after?
3) is there a better solution?
You could listen to webNavigation.onBeforeNavigate in background page, it fires when a navigation is about to occur, and you could get url and frameId in the callback parameter.
This may not be the best answer because I haven't played around with this much.
Chrome has a webNavigation API that looks to be something which may come in handy for your extension.
However if you want to get the current domain you're on you'd use...
document.domain
If you're in a subdirectory of that domain you can grab that with...
window.location
It also works with an added hash to the url.
If you want the url without the hash you could use document.referrer or if you feel hacky you could do something like...
var str = window.location
var res = str.toString().split(str.hash)
document.body.innerHTML = res

Is there a way to prevent an iframe from redirecting parent window, but in such a way that "top level" redirects still work inside the iframe itself?

So I've read about the HTML5 sandbox property and I understand that if I want to prevent an iframe redirect its parent window I can use the sandbox property leaving allow-top-navigation out. However when this is done, if the iframe was originally relying on top level redirection, what happens in its place is that it redirects to a blank page, effectively breaking navigation.
Can I prevent the iframe from tinkering its parent window while still allowing "top level" redirects, only letting these work within the context of the iframe instead of being top level?
Edit: For context, I'm working with a third party and its page has a form with a target _top. If the iframe is sandboxed, upon submitting the form users get a blank page, if it's not sandboxed the entire page is redirected. I'm looking for something that would allow to submit the form and show the result within the iframe itself.
With HTML5 the iframe sandbox attribute was added.
At the time of writing this works on Chrome, Safari, Firefox and recent versions of IE and Opera but does pretty much what you want:
Allows the iframe content to be treated as being from the same origin as the containing document
<iframe src="url" sandbox="allow-same-origin"></iframe>
Browser Compatibility
Some Useful links
w3schools for sandbox
developer.mozilla.org iframe
-
You can use the onbeforeunload property and determine if you wan to redirect or not.
Here is the docs page for it
Basically what I would try is this:
Make a function that adds the sandbox attribute with everything, just leaving out the allow-top-navigation, to the iframe
Bind a function to the onbeforeunload property of the iframe that calls the function that adds the sandbox attribute (be sure not to return anything because a dialog will pop-up)
This should work because the request is made in the iframe first, and then we can prevent it from carrying over to our top level window.
Another thing you should check is if you maybe left out the allow-formsoption, which can cause what you are describing.
Please let me know if any of this worked.

How iframe can capture keyboard like flash?

I have made a JS/HTML5 game which is controlled by keyboard, I like to make it embeddable like flash games, but when I embed it inside an iframe it only capture keyboard events when iframe is focused. If user interact with other element on the page iframe will lose focus and the game will not receive events.
How can I make it always capture keyboard events from inside iframe and avoid defaults like flash games? For example using space for firing instead of scrolling, from inside iframe.
Why down vote?
This is one way to do it:
function focus()
{
var iframe = document.getElementById('iframe-element')
iframe.contentWindow.focus();
}
window.setInterval(focus, 100);
or jQuery:
$("body").click(function(){
$('#iframe-element').focus();
});
This needs to be run on the host page of course, because allowing an embedded iframe to steal focus is not in the nature of browser/webpage security.
Good luck.
This is not normally possible because of a browser security feature called the Same-Origin-Policy. 1
Basically, one page on one domain can not interact with or intercept the events of another page because it would present a security risk. Imagine opening a frame to a user's bank account and then capturing keypresses they did in that frame -- obviously that would be a Very Bad Thing (tm). It works the other way around too, the contents of a frame can not see events on the parent page unless they are on the same domain.
Now browsers do still want to allow you to interact with contents within a frame. So when you start interacting with contents of one frame, the other frame loses any awareness of what's going on.
So, after all this, you're probably still wondering how to address your problem. Here are a few good options.
Instead of loading your game in an iframe, have the user embed it into their current page instead. You could have them do this by either providing them with the code to your game and having them place it in their page, or by providing a script tag that loads code from your own server and places the game into their page.
Use Cross-Document messaging 2, A relatively new browser feature that allows pages to get around the normal Same-Origin-Policy restrictions. Basically, with Cross Document Messaging, you can run javascript on the parent page that sends events into the page within the iframe.
This is not a complete Solution but an Idea....
What about capturing all Keyboard events on the site and then decide (e.g. by checking the value of some invisible input/element within the iframe) if a game is in progress. If it is, preventDefault of the action and trigger some custom event inside the iframe and just make your game listen for the custom events too...
Actually I just found this:
Access elements of parent window from iframe
That way you could apply my solution from within the Iframe and check for clicks/keys on its parent
Regards

How to load an iframe after another iframe that has loaded

I have an array of URL's and I want to create one iframe for each URL that I have.
But what I want is create and load the next iframe only when the previous was totally loaded.
This is the function that create the iframe:
function loadSubsequencePages(links){
var id = document.getElementsByTagName("iframe").length;
for(var i=0; i<links.length;i++){
var frame = document.createElement("iframe");
frame.setAttribute("id","frame"+id);
frame.setAttribute("src","about:blank");
document.getElementById('frames').appendChild(frame);
changeLoc(document.getElementById("frame"+id),links[i].href);
}
}
I want to move to the next i only when the actual iframe that I create was loaded.
this is the function that change the URL:
function changeLoc(frm,loc) {
frm.webNavigation.loadURI(loc,
frm.webNavigation.LOAD_FLAGS_NONE,
null, null, null
);
}
How can I do that?
That depends.
Do you have control over the pages shown in the IFRAMEs?
Are they
on the same domain as the hosting page?
There are four scenarios:
If they are on the same domain, you can access the content through
JavaScript.
If they are on the same domain, and you can modify the loaded
pages, you can tell the parent frame when the onload event
fires from the page loaded in the IFRAME.
If they are not on the same domain, and you can modify the loaded
page, security restrictions will block direct communication. But you
can ping a central repository on the server from the loaded page when
the onload event fires
If they are not on the same domain, and you can't modify the
loaded page... Well you're in trouble... :) You will not be able to
check when the pages have finished loading.... Well.. You could create some ugly
timer loading the pages at a set interval... But don't tell anybody I
told you so... ;)
If the iframes point to a URL that is another domain, all you can do is put an onload event on your iframe - that will tell you when the Iframe starts to load, but you'll never be able to tell when the load is complete because of the Same Origin Policy. The page inside is isolated from the hosting page in this situation.
If the iframes point to the same domain, you can insert some sort of document.ready code into the page that can then call a JavaScript function in the parent page announcing that it has finished loading.

Categories