Is there a way to prevent an iframe sandbox from sending postMessages? - javascript

Let's say I have an Iframe with the attribute sandbox="allow-scripts".
I might or might not be in control of the page loading that iframe.
Is there any possibility how to prevent the iframe from sending postMessages other than overwriting the parents postMessage function - which I might not be able to do if the parent is not my domain.

A colleague came up with an idea. One can sum it up with: make the parent part of your domain so that you can overwrite its postMessage method.
If it is not possible to control the top window containing the iframe, why not put the original iframe within another iframe. The additional frame-layer can act as a firewall. By overwriting the postMessage-Method of that intermediate firewall-iframe one can make sure that the original iframe can postMessage whatever it wants but the firewall-iframe only forwards incomming messages that are wished to be postable.
Of course this comes with some constraints as to what has to be the destination domain of the postMessage-call.

You should probably just check the read-only source property of the MessageEvent.
<iframe src="evil.html" class="ignore-messages"></iframe>
<iframe src="safe.html"></iframe>
window.addEventListener('message', function(e)
{
// Blacklist messages if e.source matches iframe.ignore-messages
if(Array.from(document.querySelector('iframe.ignore-messages'))
.map(f => f.contentWindow)
.indexOf(e.source) !== -1
) return;
// handle message from safe.html
// this message cannot come from evil.html,
// since that iframe has the class "ignore-messages"
};
Other ways to achieve this are through a whitelist such as:
// allow only from parent
if(e.source !== parent) return;
// allow only from a given set of iframes
if(Array.from(document.querySelectorAll('iframe.accept-messages')).map(f => f.contentWindow).indexOf(e.source) === -1) return;
The advantage of a whitelist over a blacklist, is that the iframe which sent a message may not be in the DOM-tree anymore, or the classList may have been changed, etc. Which is more common when working with dynamic HTML generated by javascript. Therefore, using a whitelist is a foolproof approach.

Related

Access an Iframe variable from parent window

I have a webpage at url https://parent.com and it has an iframe injected into it with source https://iframe.com. Iframe has a global variable defined called iframe_variable. I want to access the iframe_variable from parent document.
I know browsers don't allow cross origin communication and they provide a postMessage API to do it securely.
Constraint: I do not have access to any of parent or iframe code.
On Browser console, I somehow want to access iframe_variable
I have tried the following:
Get reference of iframe first.
var iframe = document.getElementsByTagName('iframe')[0]; // There is only one iframe on document
Create a listener for message event posted from parent window.
var iframeListener = function(e) {
console.log("Got message from parent");
e.source.postMessage(JSON.stringify({'IFRAME_VARIABLE': window.IFRAME_VARIABLE}));
}
Create a listener for parent window to accept 'message' posted from iframe.
parentListener = function(e) {
console.log('Got message from iframe');
var data = JSON.parse(e.data);
window.VARIABLE = data.IFRAME_VARIABLE;
}
Attach parent_listener to message event.
window.addEventListener('message', parentListener, false);
Now if i try to post a message to iframe from parent as follows:
iframe.contentWindow.postMessage('test message', '*')
It doesn't trigger 'iframeListener'. The reason is because it is not registered against the message event in iframe.
I don't think I can even do that from the browser console when I am on parent.com as any attempt to do iframe.contentWindow.addEventListener will result in an error as it will be an attempt to access a different domain.
Is there a workaround that? Is there anything that I am missing in my understanding and research.
P.S: I have not written the origin checks for simplicity. I know I must check for the origin a message is posted from. Not doing that leaves a huge security hole.

How to determine if JS code executes in iframe? [duplicate]

I am writing an iframe based facebook app. Now I want to use the same html page to render the normal website as well as the canvas page within facebook. I want to know if I can determine whether the page has been loaded inside the iframe or directly in the browser?
Browsers can block access to window.top due to same origin policy. IE bugs also take place. Here's the working code:
function inIframe () {
try {
return window.self !== window.top;
} catch (e) {
return true;
}
}
top and self are both window objects (along with parent), so you're seeing if your window is the top window.
When in an iframe on the same origin as the parent, the window.frameElement method returns the element (e.g. iframe or object) in which the window is embedded. Otherwise, if browsing in a top-level context, or if the parent and the child frame have different origins, it will evaluate to null.
window.frameElement
? 'embedded in iframe or object'
: 'not embedded or cross-origin'
This is an HTML Standard with basic support in all modern browsers.
if ( window !== window.parent )
{
// The page is in an iframe
}
else
{
// The page is not in an iframe
}
I'm not sure how this example works for older Web browsers but I use this for IE, Firefox and Chrome without an issue:
var iFrameDetection = (window === window.parent) ? false : true;
RoBorg is correct, but I wanted to add a side note.
In IE7/IE8 when Microsoft added Tabs to their browser they broke one thing that will cause havoc with your JS if you are not careful.
Imagine this page layout:
MainPage.html
IframedPage1.html (named "foo")
IframedPage2.html (named "bar")
IframedPage3.html (named "baz")
Now in frame "baz" you click a link (no target, loads in the "baz" frame) it works fine.
If the page that gets loaded, lets call it special.html, uses JS to check if "it" has a parent frame named "bar" it will return true (expected).
Now lets say that the special.html page when it loads, checks the parent frame (for existence and its name, and if it is "bar" it reloads itself in the bar frame. e.g.
if(window.parent && window.parent.name == 'bar'){
window.parent.location = self.location;
}
So far so good. Now comes the bug.
Lets say instead of clicking on the original link like normal, and loading the special.html page in the "baz" frame, you middle-clicked it or chose to open it in a new Tab.
When that new tab loads (with no parent frames at all!) IE will enter an endless loop of page loading! because IE "copies over" the frame structure in JavaScript such that the new tab DOES have a parent, and that parent HAS the name "bar".
The good news, is that checking:
if(self == top){
//this returns true!
}
in that new tab does return true, and thus you can test for this odd condition.
The accepted answer didn't work for me inside the content script of a Firefox 6.0 Extension (Addon-SDK 1.0): Firefox executes the content script in each: the top-level window and in all iframes.
Inside the content script I get the following results:
(window !== window.top) : false
(window.self !== window.top) : true
The strange thing about this output is that it's always the same regardless whether the code is run inside an iframe or the top-level window.
On the other hand Google Chrome seems to execute my content script only once within the top-level window, so the above wouldn't work at all.
What finally worked for me in a content script in both browsers is this:
console.log(window.frames.length + ':' + parent.frames.length);
Without iframes this prints 0:0, in a top-level window containing one frame it prints 1:1, and in the only iframe of a document it prints 0:1.
This allows my extension to determine in both browsers if there are any iframes present, and additionally in Firefox if it is run inside one of the iframes.
I'm using this:
var isIframe = (self.frameElement && (self.frameElement+"").indexOf("HTMLIFrameElement") > -1);
Use this javascript function as an example on how to accomplish this.
function isNoIframeOrIframeInMyHost() {
// Validation: it must be loaded as the top page, or if it is loaded in an iframe
// then it must be embedded in my own domain.
// Info: IF top.location.href is not accessible THEN it is embedded in an iframe
// and the domains are different.
var myresult = true;
try {
var tophref = top.location.href;
var tophostname = top.location.hostname.toString();
var myhref = location.href;
if (tophref === myhref) {
myresult = true;
} else if (tophostname !== "www.yourdomain.com") {
myresult = false;
}
} catch (error) {
// error is a permission error that top.location.href is not accessible
// (which means parent domain <> iframe domain)!
myresult = false;
}
return myresult;
}
Best-for-now Legacy Browser Frame Breaking Script
The other solutions did not worked for me. This one works on all browsers:
One way to defend against clickjacking is to include a "frame-breaker" script in each page that should not be framed. The following methodology will prevent a webpage from being framed even in legacy browsers, that do not support the X-Frame-Options-Header.
In the document HEAD element, add the following:
<style id="antiClickjack">body{display:none !important;}</style>
First apply an ID to the style element itself:
<script type="text/javascript">
if (self === top) {
var antiClickjack = document.getElementById("antiClickjack");
antiClickjack.parentNode.removeChild(antiClickjack);
} else {
top.location = self.location;
}
</script>
This way, everything can be in the document HEAD and you only need one method/taglib in your API.
Reference: https://www.codemagi.com/blog/post/194
I actually used to check window.parent and it worked for me, but lately window is a cyclic object and always has a parent key, iframe or no iframe.
As the comments suggest hard comparing with window.parent works. Not sure if this will work if iframe is exactly the same webpage as parent.
window === window.parent;
Since you are asking in the context of a facebook app, you might want to consider detecting this at the server when the initial request is made. Facebook will pass along a bunch of querystring data including the fb_sig_user key if it is called from an iframe.
Since you probably need to check and use this data anyway in your app, use it to determine the the appropriate context to render.
function amiLoadedInIFrame() {
try {
// Introduce a new propery in window.top
window.top.dummyAttribute = true;
// If window.dummyAttribute is there.. then window and window.top are same intances
return !window.dummyAttribute;
} catch(e) {
// Exception will be raised when the top is in different domain
return true;
}
}
Following on what #magnoz was saying, here is a code implementation of his answer.
constructor() {
let windowLen = window.frames.length;
let parentLen = parent.frames.length;
if (windowLen == 0 && parentLen >= 1) {
this.isInIframe = true
console.log('Is in Iframe!')
} else {
console.log('Is in main window!')
}
}
It's an ancient piece of code that I've used a few times:
if (parent.location.href == self.location.href) {
window.location.href = 'https://www.facebook.com/pagename?v=app_1357902468';
}
If you want to know if the user is accessing your app from facebook page tab or canvas check for the Signed Request. If you don't get it, probably the user is not accessing from facebook.
To make sure confirm the signed_request fields structure and fields content.
With the php-sdk you can get the Signed Request like this:
$signed_request = $facebook->getSignedRequest();
You can read more about Signed Request here:
https://developers.facebook.com/docs/reference/php/facebook-getSignedRequest/
and here:
https://developers.facebook.com/docs/reference/login/signed-request/
This ended being the simplest solution for me.
<p id="demofsdfsdfs"></p>
<script>
if(window.self !== window.top) {
//run this code if in an iframe
document.getElementById("demofsdfsdfs").innerHTML = "in frame";
}else{
//run code if not in an iframe
document.getElementById("demofsdfsdfs").innerHTML = "no frame";
}
</script>
if (window.frames.length != parent.frames.length) { page loaded in iframe }
But only if number of iframes differs in your page and page who are loading you in iframe. Make no iframe in your page to have 100% guarantee of result of this code
Write this javascript in each page
if (self == top)
{ window.location = "Home.aspx"; }
Then it will automatically redirects to home page.

Send JavaScript to an iFrame that uses a different port?

I have been having some issues sending JavaScript to an iFrame that uses a different port and after searching online it seems that the 'different port' part is causing the issue.
Here is the code sending JavaScript to the iFrame:
<script>
var network = document.getElementById("1").contentWindow.kiwi.components.Network();
$(".irc-channel-selector").click(function(event){
network.join('#' + $(this).attr('data-irc-channel'));
});
</script>
The iFrame does not use port 80 which appears to be the problem:
<iframe id="1" src="http://www.example.com:7888">
I understand that I can use something called postMessage to do the same as what I need but having read up on it online I'm not sure how it should be used, it seems pretty complex whereas I'm only used to basic JavaScript such as the code that I wrote above.
Can someone provide an example on how I can use this postMessage to mimic the behaviour above? Reading online documentation I do not understand how to use it in my scenario! :(
It's not very complicated to achieve this with postMessage. First, inside the iframe, you must expect a message:
var network = kiwi.components.Network();
function receive(event) {
// it is important to check the origin. Here I'm assuming that the parent window has that origin (same address, default port).
// if you don't check the origin any other site could include your iframe and send messages to it
if (event.origin === "http://www.example.com") {
var message = event.data;
// Assuming you might want different message types in the future. Otherwise message could be just the channel itself.
if (message.type === "JOIN") {
network.join(message.channel);
}
}
}
window.addEventListener("message", receive, false);
Now your iframe page is expecting a message to make it join a channel. The parent page can send that message with:
$(".irc-channel-selector").click(function(event){
var message = {
type: "JOIN",
channel: '#' + $(this).attr('data-irc-channel')
};
// Notice that again we're using the specific origin you used in your iframe
document.getElementById("1").contentWindow.postMessage(message, "http://www.example.com:7888");
});
Here's a far more simple fiddle where a message is sent to the same window, since I'd have to host a page somewhere to have an iframe in jsfiddle: https://jsfiddle.net/3h1Lw0j4/1/ -- Anyway it's useful to see how event.origin behaves.

Making a Same Domain iframe Secure

tl;dr Can I execute un-trusted scripts on an iframe safely?
Back story:
I'm trying to make secure JSONP requests. A lot of older browsers do not support Web Workers which means that the current solution I came up with is not optimal.
I figured I could create an <iframe> and load a script inside it. That script would perform a JSONP request (creating a script tag), which would post a message to the main page. The main page would get the message, execute the callback and destroy the iframe. I've managed to do this sort of thing.
function jsonp(url, data, callback) {
var iframe = document.createElement("iframe");
iframe.style.display = "none";
document.body.appendChild(iframe);
var iframedoc = iframe.contentDocument || iframe.contentWindow.document;
sc = document.createElement("script");
sc.textContent = "(function(p){ cb = function(result){p.postMessage(result,'http://fiddle.jshell.net');};})(parent);";
//sc.textContent += "alert(cb)";
iframedoc.body.appendChild(sc);
var jr = document.createElement("script");
var getParams = ""; // serialize the GET parameters
for (var i in data) {
getParams += "&" + i + "=" + data[i];
}
jr.src = url + "?callback=cb" + getParams;
iframedoc.body.appendChild(jr);
window.onmessage = function (e) {
callback(e.data);
document.body.removeChild(iframe);
}
}
jsonp("http://jsfiddle.net/echo/jsonp/", {
foo: "bar"
}, function (result) {
alert("Result: " + JSON.stringify(result));
});
The problem is that since the iframes are on the same domain, the injected script still has access to the external scope through .top or .parent and such.
Is there any way to create an iframe that can not access data on the parent scope?
I want to create an iframe where scripts added through script tags will not be able to access variables on the parent window (and the DOM). I tried stuff like top=parent=null but I'm really not sure that's enough, there might be other workarounds. I tried running a for... in loop, but my function stopped working and I was unable to find out why.
NOTE:
I know optimally WebWorkers are a better isolated environment. I know JSONP is a "bad" technique (I even had some random guy tell me he'd never use it today). I'm trying to create a secure environment for scenarios where you have to perform JSONP queries.
You can't really delete the references, setting null will just silently fail and there is always a way to get the reference to the parent dom.
References like frameElement and frameElement.defaultView etc. cannot be deleted. Attempting to do so will either silently fail or throw exception depending on browser.
You could look into Caja/Cajita though.
tl;dr no
Any untrusted script can steal cookies (like a session id!) or read information from the DOM like the value of a credit card input field.
JavaScript relies on the security model that all code is trusted code. Any attempts at access from another domain requires explicit whitelisting.
If you want to sandbox your iframe you can serve the page from another domain. This does mean that you can't share a session or do any kind of communication because it can be abused. It's just like including an unrelated website. Even then there are possibilities for abuse if you allow untrusted JavaScript. You can for instance do: window.top.location.href = 'http://my.phishing.domain/';, the user might not notice the redirect.

isInIframe Detection in Javascript

Javascript:
inFrame = true;
try {
if(window.top == self) {
inFrame = false;
}
} catch (err){}
try {
if(window.parent.location == self.location){
inFrame = false;
}
} catch (err){}
This is a piece of javascript code I use to detect whether my library is inside an iframe.
1 out of 100% of requests report that my library is inside an iframe which I think is not possible.
Is there a possibility that this code to fail [report true on false or vice versa]?
From access log [I log every such iframe requests]
I found that it happens in all modern browsers [ie 6-9, chrome, ff, safari] from user agent string.
I couldn't make any sense out of referrer because they are same as my publisher site.
In some cases I found for same referrer, same url requested & same client ip my library had been once inside an iframe & not in other. This made me doubt the above code.
Will there be any difference while window or document is being loaded in the properties [self, location, top, parent] I use to check? Because my script loads and executes synchronously mostly before the document is ready or window is completely loaded and that does the iniframe test to proceed further.
this is how I get the is in frame result and it works for me inFrame = window.top != window
If all you're trying to do is detect when your code is in a frame of any kind, all you need is this:
if (window.top !== window) {
// you are embedded in a frame
}
or to set your variable inFrame like this:
inFrame = (window.top !== window);
or in function form:
function isInFrame() {
return (window.top !== window);
}
As long as you aren't accessing properties on the window.top object, you shouldn't need exception handlers to protect from cross domain parents. In a cross-origin situation, you can access window.top, but you cannot access properties such as window.top.location.href.
As for your other questions:
Is there a possibility that this code to fail [report true on false or vice versa]?
The first part of your code looks OK though the exception handler is not required. The second part of your code will throw an exception every time in modern browsers when the window and parent frame are different origins.
Will there be any difference while window or document is being loaded
in the properties [self, location, top, parent] I use to check?
Because my script loads and executes synchronously mostly before the
document is ready or window is completely loaded and that does the
iniframe test to proceed further.
At the point your window is loading, it is safe to make this check. Both window and window.top will be valid at that point even if neither has completed loading yet. So, there should not be any issues with inFrame detection that require you to wait for anything to complete loading.
You know most modern browsers allow you to "View Frame" right? As in, view the frame content without the parent. That's an entirely plausable answer for 3. (I do it all the time).
A plausible cause of 1 and 2 is that self is not really defined in javascript therefore may not always be equal to window. You should therefore be checking window or better yet window.self not self like Özgür suggests.

Categories

Resources