Kategori
Webb
Beskrivning
Ett helt nytänkande koncept - en webbsida där man kan lagra sina filer. Det har ingen tänkt på förut!
Det kanske råkar finnas en flagga i /flag.txt.
Lösning
Utmaningssidan består av en webbsida där vi kan ladda upp en fil. Laddar vi upp en fil så får listas den på sidan och vi kan laddda ner den igen.
Det finns även en länk till källkoden för sidan.
import { readFile, readdir, mkdir } from "node:fs/promises";
import { randomUUID } from "node:crypto";
import express from "express";
import fileUpload from "express-fileupload";
import cookieParser from "cookie-parser";
let cookieSecret = process.env.SECRET;
let app = express();
app.use(fileUpload({ limits: { fileSize: 1024 * 100, files: 1 }, abortOnLimit: true }));
app.use(cookieParser(cookieSecret));
/**
* @param f {express.RequestHandler}
* @returns {express.RequestHandler}
*/
function eh(f) {
return (req, res, next) => {
let r = f(req, res, next);
if (r instanceof Promise) {
r.catch(err => {
console.error(err);
res.status(500).send(err.message);
});
}
};
}
app.use(eh(async (req, res, next) => {
let cookies = new Map(req.headers.cookie?.split(/; ?/g)?.map(c => c.split("=").map(decodeURIComponent)));
let session = cookieParser.signedCookie(cookies.get("session"), cookieSecret);
if (session) {
req.session = session;
} else {
let id = randomUUID();
res.cookie("session", id, { signed: true });
req.session = id;
await mkdir(`./uploaded/${req.session}`);
}
next();
}));
app.get("/", eh(async (req, res) => {
let entries = await readdir(`./uploaded/${req.session}`);
res.send(`
<!DOCTYPE html>
<html lang="sv">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Fillagringstjänst™™</title>
</head>
<body>
<h1>Fillagringstjänst™ - en tjänst som lagrar filer åt dig (det hade du aldrig kunnat gissa!)</h1>
<ul>
${entries.map(entry => `<li><a href="/uploaded/${entry}">${entry}</a></li>`).join("")}
</ul>
<form action="/upload" method="post" encType="multipart/form-data">
<input
onchange="if (this.files[0].size > 1024 * 100) {
alert('Filen får inte vara större än 100 KiB');
this.value = null;
}"
type="file"
name="file"
required
>
<button>Ladda upp</button>
</form>
<footer style="position: absolute; bottom: 1em">
Fillagringstjänst™ är <a href="/src">öppen sås</a>!
</footer>
</body>
</html>
`);
}));
app.get("/src", eh(async(req, res) => {
res.header("Content-Type", "text/plain");
res.send(await readFile(import.meta.filename));
}));
app.post("/upload", eh(async (req, res) => {
if (!req.files) return res.status(400).send("Missing file");
let file = /** @type{fileUpload.UploadedFile} */ (req.files.file);
if (!file) return res.status(400).send("Missing file");
if (file.name.includes("/")) return res.status(400).send("Fippel förbjudet");
await file.mv(`./uploaded/${req.session}/${file.name}`);
res.redirect(303, "/");
}));
app.get("/uploaded/*", eh(async (req, res) => {
let path = /** @type{string} */ (req.params[0]);
if (path.includes("/")) return res.status(400).send("Fippel förbjudet");
const contents = await readFile(`./uploaded/${req.session}/${path}`);
res.send(contents);
}));
app.listen(2580, () => console.log("Listening for connections on port 2580"));
Här ser vi att vi får en session-cookie som används i sökvägen för att lagra filer. Den är dock signerad med en hemlig nyckel.
Läser vi hur cookie-parser fungerar så kan vi lära oss att om vi skickar in en session-cookie som inte är signerad så kommer den att anända värdet som skickades in istället för att verifiera signaturen.
Ändrar vi på vår session-cookie till ../../../ så får vi nu fillistningen av root-mappen på servern.

Vi kan nu ladda ner filen /flag.txt och läsa flaggan.
cratectf{mamma_sa_att_det_är_taskigt_att_hacka_och_jag_tror_att_du_nyss_gjorde_det}