I've read up on how to avoid security issues when using window.postMessage() -- particularly the suggestions in this MDN doc.
But given all the preventative tips are all client-side, I'm having trouble understanding how they'd stop bad actors from simply editing changing the code in their developer tools.
Here's the situation I'm dealing with. I have a page that will contain an embedded iframe, and I have control over that iframe (it lives on a separate domain, but the vendor that provides it allows me to put custom JavaScript in the iframe source). The parent window and the iframe will communicate back and forth.
/**
window at https://firstgoodorigin.com
Receives message from iframe to indicate
its contents have loaded.
Once that message has been received,
send a message back to the iframe.
*/
function handleMessage(message) {
if (message.origin === 'https://secondgoodorigin.com') {
// verify and sanitize what's in message.data
// (it'll be something like "loaded")
// if it's good, send a message back
message.source.postMessage('foo', 'https://secondgoodorigin.com');
}
}
window.addEventListener('message', handleMessage, false);
/**
iframe at https://secondgoodorigin.com
Tell parent window it has loaded. Once that happens
it will receive a message from the parent window, for
which we add an event listener.
*/
window.addEventListener('load', () => {
window.parent.postMessage('loaded', https://firstgoodorigin.com);
});
window.addEventListener('message', (message) => {
if (message.origin === 'https://firstgoodorigin.com') {
// verify and sanitize what's in message.data
// do stuff
}
});
Given both the window source and iframe source will be editable inside someone's web inspector, what's to stop them from removing all the validation logic and replacing it with something malicious? What am I missing here?
As mentioned in the comment by Will any code in the browser can be edited by the end user if he or she may want to. The point of the locking down postmessages is to stop a third web site from posting unwanted messages.
If a user is logged into the site in question, and then loads a malicious website, that website could load the web site in question in an iframe or a popup, and then post messages unauthorized to the site.
Related
Use case
Existing web page / URL is opened from a 3rd party app. Upon completion of the work on the web page, it is expected to close
itself.
Javascript does not allow it using window.close() or alike, ref.
window.close and self.close do not close the window in Chrome
Browser limitation (Firefox/Chrome) is well documented and explored over the past years. For the given use case, per my understanding closing of a browser tab can only be achieved from a background script, calling chrome.tabs.remove() API.
Browser extension
The approach that seemed logical to me is using the following artifacts.
content.js
document.addEventListener("myCloseTabEvent", function(e){
console.log("[content] originating domain: " + this.domain);
// Prepare message for background script
var myMsgContent = {
type: "myAction",
value: "CloseBrowserTab"}
browser.runtime.sendMessage(myMsgContent);
}, false);
background.js
chrome.runtime.onMessage.addListener(
function(msgData, sender, sendResponse) {
if (msgData.type == "myAction" && msgData.value == "CloseBrowserTab") {
chrome.tabs.remove(sender.tab.id);
} else {
console.log("[background] No action because !(myAction && CloseBrowserTab)");
}
});
Changes to web page to raise the new event
function mySendEvent() {
const myEvent = new Event("myCloseTabEvent"); // OR CustomEvent()
// Dispatch the event
document.dispatchEvent(myEvent);
}
In summary, the following happens:
The web page loads
Content script adds the event listener for a custom event myCloseTabEvent
Background script adds an onMessage listener using chrome.runtime.onMessage.addListener()
Once all work is done on the page, existing Javascript code dispatches the custom event by calling mySendEvent().
Content script runs its listener function(e), sending a message to the background script.
Background script onMessage listener function(msgData, sender, sendResponse) uses the sender object to determine the tabId, and closes the browser tab using chrome.tabs.remove(sender.tab.id);
Questions
In concept, is this approach the best option we have to achieve the goal of closing the browser tab? Would there be any better ways of achieving the same?
A background script is require to be able to close the tab (correct me if I'm wrong here). Therefore the browser extension cannot be restricted to be active only on a specific set of domains using content_scripts -> matches. What best practices exists to restrict the functionality of the browser extension like this to specific domains? (domain names are known to the users of the extension, but not while packaging the artifacts). This is especially is of interest, to prevent other (malicious) web pages from closing them selves by sending the same message to the background script of the extension.
I'm trying to send postmessage from the opened window to opener in facebook app browser, but the "opener window" never receives messages. What can be the cause of the problem?
Receiver side:
window.addEventListener('message', function (e) {
window.console.log("on message: " + e.data);
}, false)
Sender side:
window.opener.postMessage('any Message', document.location.origin);
It's hard to tell without seeing more of your code, but as this Opening facebook connect window via javascript? answer states, if you're trying to access the oAuth page, it's not possible.
Show us where you get the variable window.opener, that might add some context.
If you opened it from window.open(/page/), it appears that it is specifically blocked: How do I get around window.opener cross-domain security
as mentioned in that question:
NOTE
Social signups do not work for google, FB, etc within an iframe. I
believe they disallow them for security reasons.
Also from window.opener is null after redirect
window.opener is removed whenever you navigate to a different host
(for security reasons), there is no way around it. The only option
should be doing the payment in a frame if it is possible. The top
document needs to stay on the same host.
But as mentioned in the second answer quoted, instead of using window.opener on the opened page, do everything from the origninal page, and (IF you have access to the source of the popup), make an onmessage on the other page, like mentioned in the accepted answer there, that the correct way to do it is only in reverse:
Do it the other way around. Track the state of the child popup window
from the main (opener) window, and you could easily know when the
child window has been navigated back to you domain, so you could
"talk" to it again. But don't close the child window by itself. Let
the opener window obtain the result from the child window and then
close it.
Reference: https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage#Example
For example, in your started page, do something like:
var popup = window.open(/*some URL*/);
popup.postMessage("hi");
addEventListener("message", function(e) {
console.log(e);
e.source.postMessage("Hi there"); //official workaround for window.opener on other page
})
then in your "/some URL/" source code page, do something like:
addEventListener("message", function(e) {
console.log(e);
e.source.postMessage("hi back");
});
and just play around with that strategy, but it appears window.opener is out of the picture. Just try console.logging it, it just say null.
I am looking for a way to access a button inside the iFrame and trigger a click event when that button is clicked inside the iFrame that is on another domain.
Trying to go deeper into an element within the iFrame has proven difficult. Has anyone had success taking it this far?
Use an ajax call to the iframe's src to get its content, and render it as part of your site (which you then can hook).
You can't access the contents from an iframe from a different domain directly because that would be a security violation.
If i understand your requirements correctly
You can add a $('#iframe').load(function(){} which will watch the loading of iframe into your DOM.
After loading iframe you can attach an event listener to button click
var iframe = $('#iframe').contents();
iframe.find("#button").click(function(){
//write code to close the popup
});
The above process can be summarized as follows
$('#iframe').load(function(){
var iframe = $('#iframe').contents();
iframe.find("#button").click(function(){
$('#popup').close() //May depend on your popup implementation
});
});
The Problem here is that the same-origin policy blocks scripts from accessing contents of site with other origin.
Actually origin consists of the following parts.
origin:<protocol(http/https)>://<hostname>:<port number>/path/to/page.html
The origin is considered to be different if protocol,host name and port number are not same.
In such cases you can not access the contents of one website from other website due to same-origin security policy.
In order to overcome it you have to use parent-child communication using window.postMessage().
FYI : https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage.
The Window.postMessage() method safely enables cross-origin communication.
Suppose that your parent website is example-parent.com and In Iframe your loading website example-iframe.com and let both are using http protocol. Below is how I solved the problem.
In parent website add event listener for messages to receive as follows.
window.addEventListener('message',receiveMessage,false);
function receiveMessage(event){
var origin = event.origin || event.originalEvent.origin;
if(origin.indexOf('http://example-iframe.com')>=0) // check if message received from intended sender
{
if(event.data=="intended message format") // check if data received is in correct format
{
// call functionality that closes popup containing iframe
}else{ // data received is malacious
return;
}
}else{ // message is not received from intended sender
return;
}
}
From Iframe post message to the parent website as follows.
Post message syntax : otherWindow.postMessage(message, targetOrigin, [transfer]);
function sendMessage(){
parent.postMessage('intended message format','http://example-parent.com');
}
Use postMessage() properly,otherwise it may lead to cross-site scripting attack.
I am Sorry to say this to you but, you can't.
Since that will be violating CORS (Cross-origin resource sharing) rules that browser has set and it won't let you break those. Since its the almighty.
It will give an error 'Access-Control-Allow-Origin' in your console .
Hope you find it helpful.
Still if want to do something in your website you can ask below, I might give you an alternate to do so.
I'm integrating a third party photo upload service with my app. So I'm loading it in my page via iframe.
When the upload service is done with uploading my photo it can either trigger certain event to my parent page i.e :
parent.$('body').trigger('photoUpload.complete');
or it triggers a function in the parent page i.e :
window.parent.reloadParentPage();
In any case I get this warning in my chrome console :
Uncaught SecurityError: Blocked a frame with origin "https://photoupload.com" from accessing a frame with origin "https://website.com".
I realize this is a security issue as described here :
http://www.w3.org/TR/2008/WD-access-control-20080912/
So I wanted to enable the origin https://photoupload.com to access my site. I did this in my controller :
after_filter :set_access_control_headers
Then the method :
def set_access_control_headers
headers['Access-Control-Allow-Origin'] = "https://photoupload.com"
headers['Access-Control-Request-Method'] = '*'
end
Please not that https://photoupload.com is the photo upload service and https://website.com is my website. (Imaginary names for example sake), but they are both hosted on heroku.
How do I make this work?
Saw similar questions that people had success with this :
Triggering a jQuery event from iframe
Update
Maybe a better question would be, in which app should I set the headers? I was assuming in my app?
Update II
Is there a better way to do this? Send action/event/something from iframe to the parent page, so the parent page can react in some way
As long as you don't have to support IE6 or IE7, the preferred way to send cross-domain messages between an iframe and its parent is to use window.postMessage(...).
Since you have the ability to modify the upload service, you should have it invoke something like this:
window.parent.postMessage('photoUpload.complete', 'https://website.com');
(the second parameter can be set to '*' to allow the iframe to send messages regardless of the containing page's domain, but that's correspondingly less secure - may not be relevant in your case though as no actual data is being sent).
and your site would use something like
if (!window.addEventListener) {
// IE8 support (could also use an addEventListener shim)
window.attachEvent('onmessage', handleMessage);
} else {
window.addEventListener('message', handleMessage, false);
}
function handleMessage(event) {
// check where the message is coming from - may be overkill in your case
if (event.origin==='https://photoupload.org') {
// check event type - again probably not required
if (event.data==='photoUpload.complete') {
// do your thing
}
}
}
And if you want to send messages back from the outer page to the iframe, it's basically the same setup but you send with:
iframe.contentWindow.postMessage(...)
If IE7 or IE6 support is required, postMessage() is not supported but you could use something like http://easyxdm.net/wp/
I guess this line should work as well
window.parent.$(window.parent.document).trigger('photoUpload.complete');
Explanation: In your code parent.$('body').trigger('photoUpload.complete');
'body' is referring to iframe body and not the parent window body.
I have a rather interesting problem. I have a parent page that will create a modal jquery dialog with an iframe contained within the dialog. The iframe will be populated with content from a 3rd party domain. My issue is that I need to create some dialog level javascript that can detect if the content of the iframe loaded successfully and if it hasn't within a 5 second time frame, then to close the dialog and return the user to the parent page.
I have researched numerous solutions and only two are of any true value.
Get the remote site to include a javascript line of document.domain = 'our-domain.com'.
Use a URL Fragment hack, but again I would need the request that the remote site
able to modify the URL by appending '#some_value' to the end of the URL and my dialog window would have to poll the URL until it either sees it or times out.
Are these honestly the only options I have to work with? Is there not a simpler way to just detect this?
I have been researching if there's a way to poll for http response errors, but this still remains confined to the same restrictions.
Any help would be immensely appreciated.
Thanks
The easiest way (if you can get code added to the external sites) is to have them add an invisible iframe pointing to a special html file on your domain. This could then use parent.parent.foo() to notify the original window about the load event.
Listening for the "load" event will only tell you if the window loaded, not what was loaded or if the document is ready for interaction.
Nicholas Zakas has an article about detecting if an iframe loaded: http://www.nczonline.net/blog/2009/09/15/iframes-onload-and-documentdomain/. Basically you have this code snippet:
var iframe = document.createElement("iframe");
iframe.src = "simpleinner.htm";
if (iframe.attachEvent){
iframe.attachEvent("onload", function(){
alert("Local iframe is now loaded.");
});
} else {
iframe.onload = function(){
alert("Local iframe is now loaded.");
};
}
document.body.appendChild(iframe);
I haven't tested it, but I'm pretty sure jQuery should be able to handle it by doing something like $("#iframe").load(function () { alert("Local iframe is now loaded."); });
You could try using postMessage for communication between frames.
This will require the remote site to include some specific JavaScript to post a message to the parent document when it has finished loading.
It's possible to do this with an onload handler on the iframe itself. Unfortunately (surprise!) IE makes it difficult. The only way I could get this to work was to compose HTML for the iframe, then append it to the document with innerHTML. Then I have to poll to see when the iframe appears in the DOM, which varies depending on if the page is loading. Here's a link to the source: http://svn.openlaszlo.org/openlaszlo/trunk/lps/includes/source/iframemanager.js
See create(), __finishCreate() and gotload(). Feel free to take a copy of this and use it yourself!
Regards,
Max Carlson
OpenLaszlo.org
This is how I detected the loading of a Cross-Domain Iframe,
Set a unique id for the iframe ( U may use any sort of identifier, it doesn't matter )
<iframe id="crossDomainIframe" src=""> </iframe>
Set window event listener:
document.getElementById("crossDomainIframe").addEventListener('load',
function actionToPerform(){
//Do your onLoad actions here
}
)
In any case you will need some sort of cooperation from the other domain's server, as you are trying to abuse the Same Origin Policy (SOP)
The first solution document.domain=... won't work if domains are different. It works only for subdomains and ports, as described in the link above.
The only option that allows cross domain communication without polling is JSONP or script injection with a JS function callback. This method is available in all Google APIs and works well.
We've explained on our blog a way to sandbox those calls in an iframe to secure them. While postMessage is better now, the window.name hack has the advantage of working on old browsers.
Ironically, SOP does not prevent you to POST anything to another domain. But you won't be able to read the response.