javascript - postMessage to sandboxed iframe, why

2020-06-01 08:33发布

问题:

2 postMessage calls in the test: 1 using an asterisk for targetOrigin, one using the same https url of both the parent and child documents.

button 1:

$('.iframed')[0].contentWindow.postMessage( messageData , '*' );

button 2:

$('.iframed')[0].contentWindow.postMessage( messageData , 'https://myurl.net' );

the iframe element in parent html document, which points to child html file on same domain, in same directory:

<iframe name="childFrame" class="iframed" src="child.html" sandbox="allow-scripts"></iframe>

both documents are fully loaded before I am clicking the buttons to trigger postMessage.

==========================================

with iframe element written as above, button 1 performs the postMessage to the child iframe and triggers the child's postMessage listener successfully (though it uses the asterisk for targetOrigin, which I'd prefer not to do.) however, button 2 results in the following error in console:

"Failed to execute ‘postMessage’ on ‘DOMWindow’: The target origin provided (‘https://myurl.net’) does not match the recipient window’s origin (‘null’)."

==========================================

if I add "allow-same-origin" to the iframe's sandbox parameters, both buttons then pass the postMessage data successfully (no "null" error on the button 2 postMessage call with the url provided for the targetOrigin.) however, I don't want to do this, as I am using the iframe's sandboxing behavior to block the iframe content from calling js functions in the parent document. this is for a system allowing "arbitrary" content (html/js/images/pdfs -- nothing server-executable like php though) to be loaded into the child iframe.

perhaps of note, similar buttons inside of the iframe content that postMessage to the parent document work just fine, regardless of the allow-same-origin parameter or the presence of the asterisk/url:

I framed button 1:

parent.postMessage( messageData , 'https://myurl.net' ); 

iframed button 2:

parent.postMessage( messageData , '*' ); 

==========================================

so, why does postMessage from the parent to the iframe results in the error above if I don't add "allow-same-origin" (and why does this issue not affect the iframe postMessage to the parent)? I attempted setting the iframe src to an absolute https url to the child.html document, but results were the same. I also tested the same code on a different, non ssl-cert server location, and had the same results (so don't think it is https contributing...). MUST i have EITHER the asterisk as the targetOrigin, AND/OR use allow-same-origin in the sandbox params?

other conversations about this issue on SO seem to dead-end so hoping to get a new perspective on a solution...

回答1:

The problem is created by the <iframe> itself, with its sandbox attribute:

<iframe name="childFrame" class="iframed" src="child.html" sandbox="allow-scripts"></iframe>

As per the Mozilla Developer Network documentation on this element, the following gives a problem about same-origin policy:

allow-same-origin: If this token is not used, the resource is treated as being from a special origin that always fails the same-origin policy.

You didn't specified allow-same-origin, that means the frame is treated as being from a special origin, and postMessage calls to that frame will fail.

To solve the problem, just add allow-same-origin to the sandbox attribute, like this:

<!-- index.html -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<iframe name="childFrame" class="iframed" src="child.html" sandbox="allow-scripts allow-same-origin"></iframe>
<button class="btn1">A</button>
<button class="btn2">B</button>

<script>
$('.btn1').click(function(event) {
    // This works!
    $('.iframed')[0].contentWindow.postMessage( "something" , '*' );
});
$('.btn2').click(function(event) {
    // This will work too
    $('.iframed')[0].contentWindow.postMessage( "whatever you want" , 'https://myurl.net' );
});
</script>
<!-- child.html -->
<script type="text/javascript">
    window.onmessage = function(event) {
        console.log(event.data);
    }
</script>

That's it!