This website uses cookies

To provide the highest level of service we use cookies on this site.
Your continued use of the site means that you agree to their use in accordance with our terms and conditions.


Marginwidth/marginheight – the unexpected cross-origin communication channel

Michał Bentkowski

July 13, 2020

On 6th July 2020 I've announced a XSS challenge on my Twitter. So far only four people were able to solve it and every single one of them told me that they had never heard about the quirk used in the challenge before. So here's a writeup explaining this quirk along with some backstory.

The core of the challenge was in the following lines of JavaScript:

document.addEventListener("DOMContentLoaded", () => {
                            for (let attr of document.body.attributes) {

The code just iterates over all attributes of the <body> element and evaluates values of all these attributes as JavaScript. Because there was no other sources in the challenge, it meant that solving it requires finding a way to inject arbitrary attribute value into the document.body. So how's that possible?

It all started when I noticed an interesting snippet in the HTML specification. The 14th section of the spec, called "Rendering", describes default styles for some elements. For instance it says that <style> or <script> elements are not displayed by default (that is, they have display:none). The interesting bit was how margin of <body> is determined.

The table says that if the <body> has an attribute called marginheight then it maps to the margin-top CSS property of the element. If it doesn't exist, then topmargin attribute is checked. If it doesn't exist either, then (and here's the surprise), if current page is in a nested browser context (so <frame> or <iframe>) browser looks at the marginwidth attribute of the container element. This also works cross-origin, which is directly admitted in the spec:

At first, I thought that this is a historical artifact and that no modern browser actually implements it this way.

Browsers behavior

To test browsers behavior I had a simple code, which lets me check whether the marginwidth attribute is taken into account.

<iframe src="" marginwidth="100px"></iframe>

In Chromium, the marginwidth attribute is reflected in the <body> element, but it is parsed to integer before. What's interesting is that Chromium listens to changes of this value, so if you change it dynamically, it is also reflected in the iframe. Here's an example:

                          iframe, input {
                        <iframe id=ifr src="" marginwidth="0"></iframe>
                        <input type=range 
                               oninput="ifr.setAttribute('marginwidth', this.value)">


In Firefox, the value of <iframe marginwidth> is not reflected in the nested document DOM tree at all. But it is taken into account and could be retrieved via getComputedStyle(). So the example with the slider works exactly the same way as in Chromium.


In Safari, the value of <iframe marginwidth> is reflected in the nested <body> element without any modification.

Contrary to Firefox and Chromium, Safari doesn't listen to changes of the attribute, hence the slider example wouldn't work.

Challenge solution

So, the solution of the challenge is as simple as:

<iframe src=""

Congratulations to @terjanq, @shafigullin, @BenHayak and @steike for finding the expected solution!

For those who tried to find the solution but didn't manage to; the hint was in a bullet that said "it might be marginally better to use Safari" 😀.

Marginwidth/marginheight as cross-origin communication channel

An interesting "side-effect" of marginwidth/marginheight is the possibility to use the attributes as cross-origin communication channel. This can be done in every browser:

    > In Safari, just set marginwidth in the parent and check marginwidth of the <body> in the child.
    > In Chrome, set marginwidth byte by byte in the parent, and observe mutation of <body marginwidth> attribute in the child
    > In Firefox, set marginwidth byte by byte in the parent, and check getComputedStyle(document.body).marginLeft in the child.

I implemented it and hosted at


I think the main take-away from this article is that HTML spec still has some hidden gems that might be possible in some obscure attacks.

Also I think that marginwidth specifically has some potential for XS-Leaks but I couldn't find a viable scenario.

Other Insights

Helping secure DOMPurify


December 21, 2020

Within last year I shared a a few writeups of my bypasses of HTML sanitizers, including: > Write-up of DOMPurify 2.0.0 bypass using mutation XSS > Mutation XSS via namespace confusion – DOMPurify < 2.0.17 bypass While breaking sanitizers is fun and I thoroughly enjoy doing it, I reached a point where I began to think whether I can contribute even more and propose a fix that will kill an entire class of bypasses.

Pyscript - or rather Python in your browser + what can be done with it

Michał Bentkowski

September 10, 2022

A few days ago, the Anaconda project announced the PyScript framework, which allows Python code to be executed directly in the browser. Additionally, it also covers its integration with HTML and JS code. An execution of the Python code in the browser is not new; the pyodide project has allowed this for a long time...

Art of bug bounty a way from JS file analysis to XSS


July 1, 2020

Summary: During my research on other bug bounty program I've found Cross-Site Scripting vulnerability in cmp3p.js file, which allows attacker to execute arbitrary javascript code in context of domain that include mentioned script. Below you can find the way of finding bug bounty vulnerabilities from the beginning to the ...

Any questions?

Happy to get a call or email
and help!

Terms and conditions
© 2023 Securitum. All rights reserved.