スプーキーズのちょっとTech。

SPOOKIES社内のより技工的な、専門的なブログページです。

[Daily AlpacaHack] One More Login Challenge & HTML2PNG Writeup

こんにちは、Nemoolaです! 前回の記事に引き続き、AlpacaHackのデイリーチャレンジに挑戦しようと思います!


One More Login Challenge

問題の概要

ログインフォームがあるWebアプリで、バックエンドにMongoDBが使われている問題。

アプローチ

MongoDBを使ったことがなかったので、まず公式ドキュメントを読むところからスタート。

ドキュメントを読み進めていくと クエリ演算子 というものを見つけた。

その中でも$exists演算子が気になった。「フィールドが存在するかどうか」を条件にクエリを書ける演算子で、これを使えば何かできそうな予感がした。

攻撃方針を組み立てる

次に、この演算子をどうやってリクエストに乗せるかを考えた。ソースコードに以下の記述があった。

// Allow both application/x-www-form-urlencoded and application/json
app.use(urlencoded({ extended: false }));
app.use(express.json());

ここで2つのミドルウェアが登録されている点が重要だ。

urlencoded({ extended: false })の制約

Express公式ドキュメントによると、extended: falseの場合、パース結果の値は 文字列か配列のみ になる。つまり通常のHTMLフォーム送信(application/x-www-form-urlencoded)ではpasswordに渡せるのは文字列だけで、{"$exists": true}のようなオブジェクトは注入できない。

express.json()が突破口になる

一方でexpress.json()Content-Type: application/jsonのリクエストをJSON.parse()でパースし、任意のオブジェクト構造をそのままreq.bodyに展開する。つまりpasswordの値を文字列ではなく{"$exists": true}というオブジェクトとして渡せる。

この2つのミドルウェアが共存していることで、JSONで送るだけで型の制約を回避できる状態になっていた。

実行

curl -X POST http://34.170.146.252:19790/ \
  -H 'Content-Type: application/json' \
  -d '{"username":"admin","password":{"$exists": true}}'

passwordフィールドが「存在する」というクエリになるため、実際のパスワードと照合せずに条件を満たしてしまう。NoSQL Injection成立!

FLAG: Alpaca{M0ng0_is_a_w3ird_D4taba5e...}


HTML2PNG

問題の概要

HTMLを送るとPNGに変換して返してくれるWebアプリ。Puppeteerを使ってサーバーサイドでレンダリングしている。

ソースコードを読む

ソースコードに以下の記述があった。

await page.goto(`file://${htmlPath}`, { waitUntil: "networkidle0" });

file://スキームでローカルファイルを開いている。ここで「ブラウザでfile://のディレクトリを開くとファイル一覧が見れる仕様があったな」と思い出した。

Step 1: ファイル名を特定する

PuppeteerはJavaScriptを実行できるので、window.location.hrefでルートディレクトリに飛ばせばファイル一覧が取得できるはず。

<html>
  <body>
    <script>
      window.location.href = "/";
    </script>
  </body>
</html>

返ってきたPNGを確認すると、ルートディレクトリのファイル一覧が写っていた。その中にflag-3f1816a5.txtを発見。

Step 2: フラグを読む

ファイル名がわかったので、あとは直接開くだけ。

<html>
  <body>
    <script>
      window.location.href = "/flag-3f1816a5.txt";
    </script>
  </body>
</html>

FLAG: Alpaca{Puppet3er_m4g1C!}


まとめ

問題 分類 ポイント
One More Login Challenge Web / NoSQL Injection JSONボディでMongoDBクエリ演算子を注入
HTML2PNG Web / LFI file://スキームとJavaScriptリダイレクトでサーバー内ファイルを読み取り

どちらもソースコードの「ちょっとした仕様」を突いた問題だった。ドキュメントを丁寧に読む習慣が活きた回だったと思う。