I am seeing an issue that, by my reading, shouldn't be happening. In short, I have a web application with nested iframes. The frame containership is as shown below:
http://mysite.doh
https://othersite.duh
https://mysite.doh/Panel.html?urlparam1=x
https://mysite.doh/Panel.html?urlparam2=y
Note: I have been careful about indicating the http vs. https, since protocol is part of what is considered for the same origin policy. These are indeed HTTPS iframes inside of an HTTP main page. When the Panel.html opens, it attempts to record a couple of query parameters so that its twin panel(s) also knows them. So there is code like:
urlparam1 = $.urlParam('urlparam1 ');
if (urlparam1 == null || urlparam1 == ''){
urlparam1 = localStorage.getItem("urlparam1");
} else {
localStorage.setItem("urlparam1", urlparam1);
}
My goal is to transfer the value of urlparam1 to all versions of Panel.html. This is actually the case any time a panel opens on the browser, and each version of the Panel has a polling function to look for changed data in the localStorage.
However, localStorage seems to not be getting the job done correctly. Changes to localStorage on one panel are not reflected in its counterpart. This seems mighty odd, since it is my understanding that localStorage is to be shared by all pages with the same origin. The two panels are definitely the same origin: they're literally the same URL, just with different query parameters.
Anyone know why this might be the case? I have looked at the localStorage dictionaries, and they have completely different data inside of them (e.g., one has urlparam1, the other urlparam2- they should both have the same data, with both values available). I can think of no reason why this might be the case. The origins are identical (including the protocol). The browsers I have been testing with are FireFox and Chrome (mostly FireFox).
EDIT: As an update, it seems like it might be due to the settings for the iframe built by otherside.duh. They are allowing scripts, but may be disallowing allow-same-origin. (Source: https://readable-email.org/list/whatwg/topic/cross-origin-iframe-and-sandbox-allow-same-origin) This appears to be advised on many older sites, since it indicates that if both allow scripts and allow-same-origin are enabled, the iframe can then remove its own sandbox property. Is that still true (sources were 2013)? Because that sounds like an incredibly bad design decision, if so. I can't imagine why anyone would want that to be desired behavior. To clarify what allow-same-origin does, it is
If you don't have allow-same-origin, the content ends up in a unique origin, not its "real" origin.
So it's as if your frame runs on some arbitrary site, rather than the one that you actually use.
Related
I'm replacing cookies with localStorage on browsers that can support it (anyone but IE). The problem is site.example and www.site.example store their own separate localStorage objects. I believe www is considered a subdomain (a stupid decision if you ask me). If a user was originally on site.example and decides to type in www.site.example on her next visit, all her personal data will be inaccessible. How do I get all my "subdomains" to share the same localStorage as the main domain?
This is how I use it across domains...
Use an iframe from your parent domain - say parent.example
Then on each child.example domain, just do a postMessage to your parent.example iframe
All you need to do is setup a protocol of how to interpret your postMessage messages to talk to the parent.example iframe.
If you're using the iframe and postMessage solution just for this particular problem, I think it might be less work (both code-wise and computation-wise) to just store the data in a subdomain-less cookie and, if it's not already in localStorage on load, grab it from the cookie.
Pros:
Doesn't need the extra iframe and postMessage set up.
Cons:
Will make the data available across all subdomains (not just www) so if you don't trust all the subdomains it may not work for you.
Will send the data to the server on each request. Not great, but depending on your scenario, maybe still less work than the iframe/postMessage solution.
If you're doing this, why not just use the cookies directly? Depends on your context.
4K max cookie size, total across all cookies for the domain (Thanks to Blake for pointing this out in comments)
I agree with other commenters though, this seems like it should be a specifiable option for localStorage so work-arounds aren't required.
I suggest making site.example redirect to www.site.example for both consistency and for avoiding issues like this.
Also, consider using a cross-browser solution like PersistJS that can use each browser native storage.
Set to cookie in the main domain:
document.cookie = "key=value;domain=.mydomain.example"
and then take the data from any main domain or sub domain and set it on the localStorage
This is how:
[November 2020 Update: This solution relies on being able to set document.domain. The ability to do that has now been deprecated, unfortunately. NOTE ALSO that doing so removes the "firewall" between domains and subdomains for vulnerability to XSS attacks or other malicious script, and has further security implications for shared hosting, as described on the MDN page. September 2022 Update: From Chrome v109, setiing document.domain will only be possible on pages that also send an Origin-Agent-Cluster: ?0 header.]
For sharing between subdomains of a given superdomain (e.g. example.com), there's a technique you can use in that situation. It can be applied to localStorage, IndexedDB, SharedWorker, BroadcastChannel, etc, all of which offer shared functionality between same-origin pages, but for some reason don't respect any modification to document.domain that would let them use the superdomain as their origin directly.
(1) Pick one "main" domain to for the data to belong to: i.e. either https://example.com or https://www.example.com will hold your localStorage data. Let's say you pick https://example.com.
(2) Use localStorage normally for that chosen domain's pages.
(3) On all https://www.example.com pages (the other domain), use javascript to set document.domain = "example.com";. Then also create a hidden <iframe>, and navigate it to some page on the chosen https://example.com domain (It doesn't matter what page, as long as you can insert a very little snippet of javascript on there. If you're creating the site, just make an empty page specifically for this purpose. If you're writing an extension or a Greasemonkey-style userscript and so don't have any control over pages on the example.com server, just pick the most lightweight page you can find and insert your script into it. Some kind of "not found" page would probably be fine).
(4) The script on the hidden iframe page need only (a) set document.domain = "example.com";, and (b) notify the parent window when this is done. After that, the parent window can access the iframe window and all its objects without restriction! So the minimal iframe page is something like:
<!doctype html>
<html>
<head>
<script>
document.domain = "example.com";
window.parent.iframeReady(); // function defined & called on parent window
</script>
</head>
<body></body>
</html>
If writing a userscript, you might not want to add externally-accessible functions such as iframeReady() to your unsafeWindow, so instead a better way to notify the main window userscript might be to use a custom event:
window.parent.dispatchEvent(new CustomEvent("iframeReady"));
Which you'd detect by adding a listener for the custom "iframeReady" event to your main page's window.
(NOTE: You need to set document.domain = "example.com" even if the iframe's domain is already example.com: Assigning a value to document.domain implicitly sets the origin's port to null, and both ports must match for the iframe and its parent to be considered same-origin. See the note here: https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy#Changing_origin)
(5) Once the hidden iframe has informed its parent window that it's ready, script in the parent window can just use iframe.contentWindow.localStorage, iframe.contentWindow.indexedDB, iframe.contentWindow.BroadcastChannel, iframe.contentWindow.SharedWorker instead of window.localStorage, window.indexedDB, etc. ...and all these objects will be scoped to the chosen https://example.com origin - so they'll have the this same shared origin for all of your pages!
The most awkward part of this technique is that you have to wait for the iframe to load before proceeding. So you can't just blithely start using localStorage in your DOMContentLoaded handler, for example. Also you might want to add some error handling to detect if the hidden iframe fails to load correctly.
Obviously, you should also make sure the hidden iframe is not removed or navigated during the lifetime of your page... OTOH I don't know what the result of that would be, but very likely bad things would happen.
And, a caveat: setting/changing document.domain can be blocked using the Feature-Policy header, in which case this technique will not be usable as described.
However, there is a significantly more-complicated generalization of this technique, that can't be blocked by Feature-Policy, and that also allows entirely unrelated domains to share data, communications, and shared workers (i.e. not just subdomains off a common superdomain). #Mayank Jain already described it in their answer, namely:
The general idea is that, just as above, you create a hidden iframe to provide the correct origin for access; but instead of then just grabbing the iframe window's properties directly, you use script inside the iframe to do all of the work, and you communicate between the iframe and your main window only using postMessage() and addEventListener("message",...).
This works because postMessage() can be used even between different-origin windows. But it's also significantly more complicated because you have to pass everything through some kind of messaging infrastructure that you create between the iframe and the main window, rather than just using the localStorage, IndexedDB, etc. APIs directly in your main window's code.
I'm using xdLocalStorage, this is a lightweight js library which implements LocalStorage interface and support cross domain storage by using iframe post message communication.( angularJS support )
https://github.com/ofirdagan/cross-domain-local-storage
this kind of solution causes many problems like this. for consistency and SEO considerations
redirect on the main domain is the best solution.
do it redirection at the server level
How To Redirect www to Non-www with Nginx
https://www.digitalocean.com/community/tutorials/how-to-redirect-www-to-non-www-with-nginx-on-centos-7
or
any other level like route 53 if are using
This is how I solved it for my website. I redirected all the pages without www to www.site.example. This way, it will always take localstorage of www.site.example
Add the following to your .htaccess, (create one if you already don't have it) in root directory
RewriteEngine On
RewriteCond %{HTTP_HOST} !^www\. [NC]
RewriteRule ^(.*)$ http://www.%{HTTP_HOST}/$1 [R=301,L]
This is really, really weird. I developed a site offline using Apache / Strawberry Perl / Firefox. It displays data in object tags as expected, calling data from external sites.
Upload the code to server, and it will display objects where the code is on the same site ... but refuses to "pull" in data from other domains, (which I own)
If you try "http://demo.coadmem.com/members" and click on a circle on the left, a product listing appears, (served from same domain) but the ones from "cbwizard.cristofayre.com" and "cristoafayre.com/poppa" on same page refuse to appear. Yet when I set up a test using YouTube, it loads OK.
I asked the host if there was some sort of "block" that stopped the objects from loading external site, and of course their response was "we can see no problems reported in the error logs".
Also, at "http://coadmem.com/admin", there is another object at the top - which works fine offline, but from server displays a white empty "about" bar!!.
Here are two of the codes I'm using: (the cb-wizard one uses a bit of javascript to select a random keyword; too much to add here, so please view source code. Anyone see a glaring error as to why it won't work online?
<object data="http://www.cristofayre.com/cgi-bin/poppa/banner_ad.pl?u=1:aa00" width=480 height=90></object>
<object style="position:fixed;top:0px;width:100%;height:20px;" width="100%" height="20px" data="http://www.cristofayre.com/cgi-bin/admin_ad.pl?t=w"></object>
I know the scripts work, 'cos if you type the data line into a browser, the correct HTML is displayed; it just refuses to appear when on the server!
I'm wondering if there is some sort of "list" that the host might be applying that allows the majors such as Google / Youtube to be loaded into iframes / objects, but disallows 'minor' sites. (The suport team didn't seem to know anything about such a list)
** I also wondered if the "same-origin" policy is coming into play, but surely it shouldn't as that defeats the whole point of using iframes and objects in the first place.
I was "sort of" right. It wasn't that certain URL's were being blocked, rather that the frames were set up ONLY to show data that originated from the same domain as the browser was set too, (don't ask me how the YouTube iframe circumvented this rule, but there you go!)
As you can see from the above comments, the solution was to create a .htaccess file and use the command "Header always unset X-Frame-Options" which in simple terms (that I can follow) is saying "Whatever the header for the X-Frame-Option is set to, ignore it! and display the data anyway"
Perhaps this might help someone in a similar dilemma. It was certainly baffling as to why the ActiveState / Strawberry Perl version worked OFFLINE, but not online.
I'm replacing cookies with localStorage on browsers that can support it (anyone but IE). The problem is site.example and www.site.example store their own separate localStorage objects. I believe www is considered a subdomain (a stupid decision if you ask me). If a user was originally on site.example and decides to type in www.site.example on her next visit, all her personal data will be inaccessible. How do I get all my "subdomains" to share the same localStorage as the main domain?
This is how I use it across domains...
Use an iframe from your parent domain - say parent.example
Then on each child.example domain, just do a postMessage to your parent.example iframe
All you need to do is setup a protocol of how to interpret your postMessage messages to talk to the parent.example iframe.
If you're using the iframe and postMessage solution just for this particular problem, I think it might be less work (both code-wise and computation-wise) to just store the data in a subdomain-less cookie and, if it's not already in localStorage on load, grab it from the cookie.
Pros:
Doesn't need the extra iframe and postMessage set up.
Cons:
Will make the data available across all subdomains (not just www) so if you don't trust all the subdomains it may not work for you.
Will send the data to the server on each request. Not great, but depending on your scenario, maybe still less work than the iframe/postMessage solution.
If you're doing this, why not just use the cookies directly? Depends on your context.
4K max cookie size, total across all cookies for the domain (Thanks to Blake for pointing this out in comments)
I agree with other commenters though, this seems like it should be a specifiable option for localStorage so work-arounds aren't required.
I suggest making site.example redirect to www.site.example for both consistency and for avoiding issues like this.
Also, consider using a cross-browser solution like PersistJS that can use each browser native storage.
Set to cookie in the main domain:
document.cookie = "key=value;domain=.mydomain.example"
and then take the data from any main domain or sub domain and set it on the localStorage
This is how:
[November 2020 Update: This solution relies on being able to set document.domain. The ability to do that has now been deprecated, unfortunately. NOTE ALSO that doing so removes the "firewall" between domains and subdomains for vulnerability to XSS attacks or other malicious script, and has further security implications for shared hosting, as described on the MDN page. September 2022 Update: From Chrome v109, setiing document.domain will only be possible on pages that also send an Origin-Agent-Cluster: ?0 header.]
For sharing between subdomains of a given superdomain (e.g. example.com), there's a technique you can use in that situation. It can be applied to localStorage, IndexedDB, SharedWorker, BroadcastChannel, etc, all of which offer shared functionality between same-origin pages, but for some reason don't respect any modification to document.domain that would let them use the superdomain as their origin directly.
(1) Pick one "main" domain to for the data to belong to: i.e. either https://example.com or https://www.example.com will hold your localStorage data. Let's say you pick https://example.com.
(2) Use localStorage normally for that chosen domain's pages.
(3) On all https://www.example.com pages (the other domain), use javascript to set document.domain = "example.com";. Then also create a hidden <iframe>, and navigate it to some page on the chosen https://example.com domain (It doesn't matter what page, as long as you can insert a very little snippet of javascript on there. If you're creating the site, just make an empty page specifically for this purpose. If you're writing an extension or a Greasemonkey-style userscript and so don't have any control over pages on the example.com server, just pick the most lightweight page you can find and insert your script into it. Some kind of "not found" page would probably be fine).
(4) The script on the hidden iframe page need only (a) set document.domain = "example.com";, and (b) notify the parent window when this is done. After that, the parent window can access the iframe window and all its objects without restriction! So the minimal iframe page is something like:
<!doctype html>
<html>
<head>
<script>
document.domain = "example.com";
window.parent.iframeReady(); // function defined & called on parent window
</script>
</head>
<body></body>
</html>
If writing a userscript, you might not want to add externally-accessible functions such as iframeReady() to your unsafeWindow, so instead a better way to notify the main window userscript might be to use a custom event:
window.parent.dispatchEvent(new CustomEvent("iframeReady"));
Which you'd detect by adding a listener for the custom "iframeReady" event to your main page's window.
(NOTE: You need to set document.domain = "example.com" even if the iframe's domain is already example.com: Assigning a value to document.domain implicitly sets the origin's port to null, and both ports must match for the iframe and its parent to be considered same-origin. See the note here: https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy#Changing_origin)
(5) Once the hidden iframe has informed its parent window that it's ready, script in the parent window can just use iframe.contentWindow.localStorage, iframe.contentWindow.indexedDB, iframe.contentWindow.BroadcastChannel, iframe.contentWindow.SharedWorker instead of window.localStorage, window.indexedDB, etc. ...and all these objects will be scoped to the chosen https://example.com origin - so they'll have the this same shared origin for all of your pages!
The most awkward part of this technique is that you have to wait for the iframe to load before proceeding. So you can't just blithely start using localStorage in your DOMContentLoaded handler, for example. Also you might want to add some error handling to detect if the hidden iframe fails to load correctly.
Obviously, you should also make sure the hidden iframe is not removed or navigated during the lifetime of your page... OTOH I don't know what the result of that would be, but very likely bad things would happen.
And, a caveat: setting/changing document.domain can be blocked using the Feature-Policy header, in which case this technique will not be usable as described.
However, there is a significantly more-complicated generalization of this technique, that can't be blocked by Feature-Policy, and that also allows entirely unrelated domains to share data, communications, and shared workers (i.e. not just subdomains off a common superdomain). #Mayank Jain already described it in their answer, namely:
The general idea is that, just as above, you create a hidden iframe to provide the correct origin for access; but instead of then just grabbing the iframe window's properties directly, you use script inside the iframe to do all of the work, and you communicate between the iframe and your main window only using postMessage() and addEventListener("message",...).
This works because postMessage() can be used even between different-origin windows. But it's also significantly more complicated because you have to pass everything through some kind of messaging infrastructure that you create between the iframe and the main window, rather than just using the localStorage, IndexedDB, etc. APIs directly in your main window's code.
I'm using xdLocalStorage, this is a lightweight js library which implements LocalStorage interface and support cross domain storage by using iframe post message communication.( angularJS support )
https://github.com/ofirdagan/cross-domain-local-storage
this kind of solution causes many problems like this. for consistency and SEO considerations
redirect on the main domain is the best solution.
do it redirection at the server level
How To Redirect www to Non-www with Nginx
https://www.digitalocean.com/community/tutorials/how-to-redirect-www-to-non-www-with-nginx-on-centos-7
or
any other level like route 53 if are using
This is how I solved it for my website. I redirected all the pages without www to www.site.example. This way, it will always take localstorage of www.site.example
Add the following to your .htaccess, (create one if you already don't have it) in root directory
RewriteEngine On
RewriteCond %{HTTP_HOST} !^www\. [NC]
RewriteRule ^(.*)$ http://www.%{HTTP_HOST}/$1 [R=301,L]
We are implementing clickjacking protection using the X-Frame-Options header and possibly the JS/CSS setup described on this page:
https://www.owasp.org/index.php/Clickjacking_Defense_Cheat_Sheet
For reference, the JS/CSS solution looks like this:
<style id="antiClickjack">body{display:none !important;}</style>
<script type="text/javascript">
if (self === top) {
var antiClickjack = document.getElementById("antiClickjack");
antiClickjack.parentNode.removeChild(antiClickjack);
} else {
top.location = self.location;
}
</script>
Given the widespread support of the X-Frame-Options header in all modern browsers, I'm wondering if we should even keep the JS frame killer in place. Maybe just for good measure? If we do, we have the caveat that we use colorbox to generate iframe popups from within our own domain. This can be accommodated in the X-Frame-Options header by setting it to SAMEORIGIN, but I'm at a loss as to how I would modify the JS to allow frames from the same domain without it being easily circumvented.
As it is, the script compares self === top and removes the display:none; from the body style if they are the same. I can modify the condition to also check for the domain like so:
if (self === top || this.top.location.hostname === 'www.example.com')
But this uses the location object which can be changed from the framing page, effectively bypassing the script. Basically, I'm looking for something similar to this downvoted suggestion here:
https://stackoverflow.com/a/21900420/998048
So, 2 questions:
1.) Is it still considered best practice or even necessary to implement frame killing with JS?
2.) If so, is there a better, more secure way to keep the JS in place but still allow frames from our own domain?
I still use the Javascript method because my setup doesn't allow me to use server side languages.
But this uses the location object which can be changed from the
framing page
How so? If the location object is changed, your browser will redirect you to the new location object. If location.hostname was changed in the top window, it would redirect to your website. However I'd recommend using parent rather than top - otherwise all frames within other frames may redirect to themselves instead.
If the user has JS disabled, however, none of this will happen, so it may be a good idea to keep using X-Frame-Options. But for browsers that don't care about the X-Frame-Options header, keep the JS solution around I'd say.
So I'm toying around with HTML 5 and the localStorage and I'm noticing that my values only get stored when I run the page in Firefox on the local host (i.e. http://127.0.0.1:8000/test/index.html), but when I run the file locally (file:///C:/test/index.html) my values don't get stored. Safari 4 has no problems with both setups.
So does anybody know if this is by design -> DOM Storage on the Mozilla Developer Center
(Firefox 2 permitted access to storage
objects higher in the domain hierarchy
than the current document. This is no
longer allowed in Firefox 3, for
security reasons. In addition, this
proposed addition to HTML 5 has been
removed from the HTML 5 specification
in favor of localStorage, which is
implemented in Firefox 3.5.)
Or if there is a workaround?
I wonder because offline storage that works only online sounds silly :P
If anybody wonders, the code is as easy as it gets:
function save()
{
localStorage.setItem('foo','bar');
}
function load()
{
var test = localStorage.getItem('foo');
alert(test);
}
It seems a bug: Bug 507361 - localStorage doesn't work in file:/// documents
Hope is fixed soon!
2011-09-13: Bug fixed, implemented in 'Mozilla8'. I tested this with Firefox 8 and it works now.
Well, the linked document does say that
localStorage is the same as globalStorage[location.hostname], with the exception of being scoped to an HTML5 origin (scheme + hostname + non-standard port)
I don't want to claim that I understand 100% what that means, but the bit in brackets would suggest that the URL needs to have certain properties - in particular that the scheme and hostname are what Firefox considers an HTML 5 origin. I suspect that file:/// URLs don't match this, while your http://127.0.0.1/ does.
edit: Looking at the W3C's description of the Origin property, step 7 looks like it might be causing the problem. Depending on how the localStorage handling is implemented, it may be expecting a 3-tuple as returned by step 12, but for a file:// URL the return value may be just about anything.
So, er, I suppose it is by design. On reflection, chances are that this isn't really by design; there's no reason why localStorage shouldn't work for file:// URLs. It might just be a case of the output of one browser-specific implementation not matching the expectations of another.
As for workarounds, would globalStorage not do what you want here?
As of Oct 5 2020, localStorage on Firefox seems to be broken again. Try this:
Download Mozilla demo page: https://mdn.github.io/dom-examples/web-storage/
Change the animal/color to something other than default.
close the page's tab (or the browser).
Download the page again. It's back to defaults. (Firefox 81:0 et.al.)
Even worse, if you do step 1 & 2 above and then open another copy of the demo in a new tab, not only does the new tab not get the saved data, but the original demo page (refresh it) has gone back to the defaults; as though the new tab STEPPED on the saved data.