demo · v138

The mXSS round-trip

A real mutation-XSS pattern. A “safe” user input is put into an attribute. The DOM is then re-serialised via outerHTML — into an iframe, a server round-trip, a cache, anywhere. Pre-138, the round-trip silently became different markup. 138 closes the loop.

This is a security fix. Several real-world XSS bugs followed the “put attacker input in an attribute, then assume the DOM’s textual form is faithful” pattern. Sanitisers running on the textual form saw clean attributes; the actual reconstructed DOM had a tag boundary inside the attribute value.

1. attribute set with user input


      

2. outerHTML serialisation


      

3. browser re-parses that string


      

4. did the DOM round-trip safely?

awaiting…

scenario

A wiki accepts a footnote whose content is dropped into img.title — an attribute. The HTML is then cached for cross-region replicas by reading document.documentElement.outerHTML. The replicas inject that HTML into other iframes via iframe.contentDocument.write(...). Without 138’s escaping, the replica iframe ends up with different DOM than the origin — including a runnable <img onerror>.

the fix in one line

// Pre-138: attribute serialisation escaped " and & only.
// 138: also escapes < and >, killing the round-trip mutation vector.
el.title = '<img src=x onerror=alert(1)>';
el.outerHTML;
//  pre-138: <span title="<img src=x onerror=alert(1)>"></span>     // reparsed: TWO elements!
//  138+:    <span title="&lt;img src=x onerror=alert(1)&gt;"></span>  // reparsed: same span, same attribute

see also