Another new feature from the HTML 5 specification, that just landed for Firefox 3, is the cross-origin postMessage API.
This particular API adds a new method to every window (including the current window, popups, iframes, and frames) that allows you to send textual messages from your current window to any other – regardless of any cross-domain policies that might exist.
Specifically, you’re given a new window.postMessage(“string”) method that generates a message
DOM event on the document
of the receiving document. This event object contains the message as a property: event.data
which the receiving document can use however they see fit.
Obviously communicating in a cross-domain fashion is prone to abuse so there’s additional information passed along that can be used to verify the integrity of the message. The full list of properties include:
.data
– A string holding the message passed from the other window..domain
– The domain name of the window that sent the message..uri
– The full URI for the window that sent the message..source
– A reference to the window object of the window that sent the message.
The last property is especially important as it allows for two-way communication to occur between these windows.
Simple Demo
I’ve constructed a simple demo that you can try (requires a nightly of Firefox 3) in which you can send a message – through an iframe – to another domain, having the results be received and rendered by it. Thus, this demos consists of two pages: One acting as the sender (on ejohn.org), one acting as the receiver (on dev.jquery.com).
This first page is the sender – it’s calling postMessage (sending the textual message) and also holds the iframe within which the receiving window is held.
<iframe src="http://dev.jquery.com/~john/message/" id="iframe"></iframe> <form id="form"> <input type="text" id="msg" value="Message to send"/> <input type="submit"/> </form> <script> window.onload = function(){ var win = document.getElementById("iframe").contentWindow; document.getElementById("form").onsubmit = function(e){ win.postMessage( document.getElementById("msg").value ); e.preventDefault(); }; }; </script>
The follow page is the receiver – it has an event listener bound which watches for messages being passed to it and injects them in to the DOM.
<b>This iframe is located on dev.jquery.com</b> <div id="test">Send me a message!</div> <script> document.addEventListener("message", function(e){ document.getElementById("test").textContent = e.domain + " said: " + e.data; }, false); </script>
Security Concerns
Some care was taken in the construction of this API to prevent some basic forms of spoofing and security circumvention. In total, there’s a couple points to remember:
- If you’re expecting a message from a specific domain, set of domains, or even a specific url, please remember to verify the
.domain
or.uri
properties as they come in, otherwise another page will be bound to spoof this event for malicious purposes. - Just because a string is coming in, as a message, doesn’t mean that it’s completely safe. Note that in the example, above, I inject the string using
.textContent
, this is intentional. If I were to inject it using.innerHTML
, and the message contained a script tag, it would execute immediately upon injection. This is a critical point: You’ll need to be sure to purify all your incoming messages before they are used and injected into the DOM (or sent to the server). This is the same that you would do on the server-side of your application, be sure to take the same precautions here, as well. Update: Ok, this isn’t so much of an issue anymore – it was in Firefox 2, but since only Firefox 3 supports this new feature, it’s perfectly ok – false alarm.
Security concerns aside, I think this feature has a ton of potential, especially in the way of developing scalable widget solutions (especially ones that continue to uphold security and cross-domain principles). I suspect that upon further adoption we’ll begin to see a number of resources (such as iGoogle and Netvibes) take advantage of this incredible power, allowing for more-complex widgets and browser-based applications.
Update: The following browsers support, or will support, postMessage:
- Opera 9 supports
document.postMessage()
(although, the rest appears to be the same). - Firefox 3 beta 3 will have support for
window.postMessage()
. - WebKit Nightlies currently supports
window.postMessage()
which will land in some future release of Safari.
Spez Smartman (February 10, 2008 at 9:21 am)
John, I love you and I love Jquery.
But why do I want to use this cross posting script? I am missing the revelation so please explain.
Also why are you using window.onload = function(){ when you should be using $(document).ready(function() { ?
David Bloom (February 10, 2008 at 9:45 am)
It’s worth noting that Opera has supported this API since Opera 9 (via document.postMessage, according to an earlier revision of the spec).
Evan Walsh (February 10, 2008 at 11:11 am)
Spez: He uses window.onload… because he isn’t using jQuery. He’s not using any library for this example. He just uses straight up JS.
Scott T. (February 10, 2008 at 11:22 am)
@Spez, I suppose John is not using $().ready() because he wanted to write non-library dependent code to demonstrate this new technique since it is of wide interest to JavaScript programmers, not just us jQuery fiends.
And this new capability, as John mentions, creates some interesting potential for things like page-embeddable iframe widgets, for example.
Kjell Bublitz (February 10, 2008 at 2:02 pm)
Very interesting, but when will this API be available / useful? I mean.. couldn’t it take up to two years till everyone has an HTML5 capable browser? Thinking of that i doubt that this will be of any use until that point (like with everything relying on browser specs). Please correct me if i am wrong.
2634892 (February 10, 2008 at 3:46 pm)
“If I were to inject it using .innerHTML, and the message contained a script tag, it would execute immediately upon injection.”
This is not true. Assigning a string containing a “” to .innerHTML does not run the script. I’ve just tested this, and I’ve also seen special cases in JS libraries to parse out script tags and run them, given ajax “throw this in a div” helpers.
John Resig (February 10, 2008 at 4:21 pm)
@David: Thanks for the heads up – that’s why I missed it in my primary tests – I’ve updated the blog post to represent this.
@Kjell: I’ve updated the blog post with a list of the browsers that support this – so yeah, it’ll be a while before it’s available to practical usage.
@2634892: You are correct – I was referring to the innerHTML functionality that was in Firefox 2 – but that has been rectified in Firefox 3, making this point no longer valid. I’ve updated the blog post to represent this.
Kris Zyp (February 10, 2008 at 8:14 pm)
Fantastic! This is a huge feature.
Mike Purvis (February 10, 2008 at 8:24 pm)
Kjell: Google is already doing cross-domain communication with the igoogle/mapplets stuff. It’s just that the current method (iirc) requires a nasty polling loop that introduces latency and unnecessary computation.
This new postMessage functionality has the ability to supplant that. So older browsers can continue to poll, while newer ones can receive code that takes advantage of this more straightforward method.
Alex (February 11, 2008 at 12:04 am)
A while ago I blogged about how it’s possible to perform cross domain javascript without proxies, flash, HTML 5. The novel part of this method is that it uses DNS to create a facade by using a sub-domain that is really just a pointer to the domain you’re trying to reach.
The details are here:
http://www.alexpooley.com/2007/08/07/how-to-cross-domain-javascript/
It’s worth noting that this technique requires both domain owners to co-operate, which from security perspective is great. Admittedly it’s weakness is in having to modify DNS records.
It would be great if someone were to create a library for this *hint* *hint* …
Jeff Walden (February 11, 2008 at 1:49 am)
Nice writeup; two thing I’d change:
1. Make your demo only accept messages from a specific domain, and provide an example of how a post from a different domain will be rejected. The security of postMessage *absolutely* depends on not trusting unexpected input.
2. I’d change your first security note to not conditionalize with “If you’re expecting a message from a specific domain, set of domains, or even a specific url, please remember to verify the .domain or .uri properties as they come in”. Instead, I’d say, “*Always* check the .domain or .uri properties to verify that the message isn’t being spoofed by a malicious third party. Otherwise, the .data property must *always* be assumed to be malicious until you do a syntax check to verify otherwise.” It’s trivial for any malicious webpage to send messages to a window listening for messages, sanity-checking the .data property is error-prone, and most of the time you’re only working with a specific other site or set of sites. Anyone who ignores these directions *must* know that they do so at their own peril.
As you can see, I treat this feature with a fair amount of paranoia. It’s easy to use it safely if you know what you’re doing, and I want to make sure anyone who uses it does indeed know exactly what they’re doing. See also the (bolded!) paranoia I injected into the writeup mentioned in the first paragraph:
http://developer.mozilla.org/en/docs/DOM:window.postMessage
Wladimir Palant (February 11, 2008 at 5:46 am)
John, you should remove the update – using innerHTML for unverified HTML code is still very problematic, regardless of the Firefox version you use. Even if .innerHTML doesn’t execute scripts directly (which I cannot confirm –
javascript:document.body.innerHTML='<script>alert("XSS")</script>'
still runs the script for me in Firefox 3), there is always the option of passing<div onmouseover='alert("XSS")'>
for example (with some tricks you might still be able to achieve instantaneous execution).David Bloom (February 11, 2008 at 9:33 am)
Wladimir:
Your example javascript URI probably runs script because it is redirecting to the return value of the assignment statement.
Try this instead…
javascript:document.body.innerHTML='<script>alert("XSS")<\/script>';void 0;
Damien B (February 11, 2008 at 11:09 am)
In fact, from a security design point of view, .data should be available only after a check of .uri in a programmatic way. Designing such an API without any programmatic barrier is just a call for XSS. Ideally, I would say that it must be the duty of the event to check .uri against a list of patterns of known sources provided by the receiver, and not expose .uri at all.
Eric Shepherd (February 11, 2008 at 11:38 am)
I’ve updated the documentation for this method with more information based on this post. Thanks, John.
Malte (February 28, 2008 at 2:37 pm)
Hey, thanks very much for jQuery!
I’ve created a library that wraps postMessage and a fallback mechanism that works in older browsers (using very evil cookie magic) behind a single interface at http://code.google.com/p/xssinterface/
Hope you find it interesting.
futtta (May 20, 2008 at 4:44 am)
just read that MS IE8 will support postMessage as well.