博多電光

blog.hkt.sh

TSG CTF 開催記

※めっちゃ長いです。

博多市です。

先日5月4日から5日にかけて、東京大学のサークルTSGの主催により、TSG CTF が開催されました。博多市はイベント自体の発案から全体の進行を司るなど、実質的に TSG CTF のリーダー的役割を果たしました。

今回、CTFを開催するという決して前例の多くない仕事を遂行する上で、貴重な先人たちの開催記やブログ記事に助けられました。なので、恩返しする意味も込めて、今回僕が TSG CTF を開催しての知見や感想、反省点をなるべく細かく書いていこうと思います。

TL;DR

  • CTFを主催するのは、大変だけど、楽しい!

開催に至るまでの経緯

まずは自信をつけるということ

さて、まず前提として共有しておきたいのは、CTFの大会を主催するというのは、とてもハードルが高いということです。

我らがTSGは、これまでチームTSGとして多くのCTFに参加してきましたが、逆に我々がCTFを主催する側に立つというのは去年の半ばくらいまで全く考えもしていませんでした。これは他の多くのCTFチームでも同じことだと思います。

理由はいくつかありますが、第一に、CTFを主催する際には、サーバーインフラの管理やチームマネジメントなど、CTFに参加するのと全く別の能力が必要になることが挙げられるでしょう。そしてさらに大事なこととして、CTFを主催するチームはCTFにとても強くなくてはいけないということです。

「普段CTFに参加しない人/団体が主催するCTFはクオリティが低い」というのはこの界隈ではよく取り沙汰される話です。実際に僕自身の経験からしても、どのような問題がCTFの問題として優れているか、というのはCTFに何度も参加しないとわからない話だと思いますし、難しい問題を作るにはその分野に関する相応の知識や経験を持っていないといけないという側面もあります。

実際にCTFを主催しているMMAやDragonSectorなどのCTFチームの強さや、FacebookGoogleなどの企業のビッグネームを眺めるにつけ、やはりぽっと出の新参チームがCTFを主催などしてよいのかと怖気づいてしまいます。もちろんこれらの重圧を振り切って軽々にCTFを主催しろ、などと主張するつもりはありませんが、今回僕がTSGでCTFを主催しようと心を固めるまでに、そしてTSGのメンバーを説得する上で一番の障害となったのは、やはりこの「CTF開催における心理的障壁の高さ」でした。

二度のライブCTF

そういうことなので、我々TSGがCTFを主催するに相応しいチームであるという自信をつけるまでには、結構な紆余曲折がありました。一番のきっかけになったのは、東京大学の学園祭でTSGが行ったイベント「ライブCTF」でした。

TSGでは昨年から「TSG LIVE!」というライブコーディング企画を行っています。これはCTFほか競技プログラミングコードゴルフなどのプログラミングにまつわる競技を解く様子を実況解説付きで放送するというもので、このライブCTFもその放送枠の中の企画の1つでした (詳しくは以前書いたブログ記事を参照)。

昨年は五月祭と駒場祭の2回このライブCTFを行いました。CTFを開催するのはハードルが高い、と言いつつも、第1回の五月祭ライブでは「まあ、内輪向けのCTFだし」ということで割と気軽に、それこそ半分くらいは過去問のコピーで良いや、という気持ちで問題を用意しました (ちなみに、我々のほとんどはこのときのライブCTFがはじめての作問経験でした)。

ところが放送当日、多くの視聴者から「解く様子を見たり解説を聞いたりするよりも実際に解いてみたい」という声を頂いて、多くのCTF競技者が実際に放送をご覧になってくれたことに喜びを覚えるとともに、彼らのアグレッシブさに驚かされました。これを受けて第2回の TSG LIVE! 2 では、第1回の反省を踏まえ、プレイヤー用に用意した問題を外部にも公開し、プレイヤーと同時に自由に解いてもらえるようにしました。準備段階では「参加するとしてもせいぜい数チームやろ」などと話し合っていましたが、本番では予想を遥かに超えて様々なチームやプレイヤーに参加してもらえるなど、多くの反響を得ました。ありがたい限りです。

このとき作問を担当していたのが、今回の TSG CTF でも大いに貢献してくれた@kczと@satosで、実際にライブCTFの問題を解いてくださった皆さんからも彼らの問題はかなり評価が高かったようです。特に好評だったのは@satos作問の「ROP4」「ROP1」で、国内の著名なCTFプレイヤーからもよい評価を頂くことが出来ました。(なお、これらの問題のwriteupは https://moraprogramming.hateblo.jp/entry/2018/12/04/121940 で公開されています。)

半ば偶然のような形ですが、実際に作問を行い外部の人に解いてもらうという得難い経験をこうして得たことにより、イベント気質の博多市は「これだけの良問を作れるメンバーが大学内に揃うタイミングは今しかないかもしれない。TSG CTF を開催するべきではないか⋯⋯?」という考えを得ることになります。

SECCON優勝

この気持ちを後押ししたのが、昨年12月に開催された SECCON CTF 2018 Finals です。すでに見知った方も多いでしょうが、この大会で我々チームTSGは栄えある国際優勝の冠を戴くことができました。

この大会に関しては必ずしも良きように風聞される限りではなく、おそらく運営や出題に対して何かしら思うところのある方も多いかもしれませんが、我々としては何はともあれ国内最大の大会で優勝、という実績を得たことにより、対外的なCTF開催の自信を得ました。

CTF開催に向けて具体的なアクションを取り始めたのもこのあたりからだったように思います。

開催スケジュール

CTFを開催する上で一番最初に決めるべきなのは日程です。この際に最も重要なのは他のオンラインのCTFと被らないような日程を選ぶということです。

今回我々は日取りに関しておおよそ運営側の制約がなく、ほぼ自由に開催日を選ぶことができましたが、逆に言うとそのぶん適切なスケジュールを決めるのには難航しました。

24時間ないし48時間のCTFを開催するなら、土日に行うべきなのはまず間違いないでしょう。しかし後述するCTFTimeに登録されている大会だけでも毎週末何かしらのCTFが開催されるため、新規のCTFが他のイベントと被らないようにするためには相当に気を使う必要があります。

このとき、やはりCTFTimeに登録されている大会スケジュールを参照するのですが、数ヶ月後の大会となると登録も告知もされていない大会が多く、予測はかなり困難を極めます。このあたりは昨年の開催実績を見ながらある程度エスパーしたりするのですが、今回実際に開催して気づいたのは、定期的に行われる年次大会でも意外と毎年違う時期に開催されているということです。これは特に有志のCTFチームによって開催されるCTFに顕著なのですが、やはり TSG CTF と同じように日取りに関してはかなり自由に決められるのか、非常に不規則な時期に予定を突っ込んできます。昨年度の開催記録は半分あてにならないと考えたほうがいいでしょう。

今回 TSG CTF が5月上旬という日程を選んだのには、いくつかの理由があります。

  • スケジュール確定時点で他のCTFとかぶらないような日程だったこと
  • 作問者が大学生である都合上、試験や学園祭などの大型の予定と被らないものであること
  • ゴールデンウィークの10連休の最後の土日だったこともあり、準備作業にたっぷり時間を取れるスケジュール感にできること

しかし、先も言ったとおり数ヶ月先のCTFのカレンダーは予測がつかないので、スケジュールを決めたらあとは他のCTFと被らないよう祈るしかないと思います。TSG CTF は蓋を開けてみれば残念ながら InsH'ack CTF とバッティングしてしまいましたが、これはある程度仕方のないことだと思います。また結果論ですが、かぶっていたのが72時間のCTFの後半部分ということで、あまり深刻にバッティングした感じはありませんでした。

ちなみに、実は当初の予定だと TSG CTF は12月ごろに企画して2月か3月ごろに開催するつもりだったのですが、あまりに予定が急すぎたのと計画時点ですでに候補日が他のCTFで埋められていたため延期になりました。やはりCTF大会の準備は数カ月は必要ですね⋯⋯。

CTFTime

CTFTimeはCTFerなら誰もが知るCTFのポータルサイトであり、ここでの宣伝はプレイヤーへの認知のためにも非常に重要です。特に国内向けCTFなど一部のCTFでは意図的にCTFTimeに登録しないことも多いようですが、後述するように TSG CTF は世界を相手にした国際CTFとすることを当初から考えていたため、CTFTimeに登録することを決めていました。

CTFTimeでは通常のインターフェイスの他に自身が主催するCTFの管理ができる特権チームが存在し、CTFを登録するためには個別に運営に問い合わせてこの権限昇格をお願いする必要があります。おそらく場合によるでしょうがTSGの場合この手続きに1週間以上かかりました。TSG CTF では開催スケジュールの公示と同時に問い合わせを送りましたが、CTFTimeに登録できないとそもそも存在が認知されず、他大会とスケジュールがかぶる可能性が高まることを考えると、なるべく早めに、もしくはスケジュールを告知する前にこの手続きをしたほうがいいでしょう。

まあ、こんな経験そうそうする人がいるものではない気がしますが⋯⋯。

プレスリリース

今回の TSG CTF では、他の多くのCTFと同じく、スコアサーバーと別に特設サイトを設置しました (ちなみに、こちらの特設サイトもFlatt社のみなさんに制作していただきました。感謝!)。そして比較的挑戦的な試みとして、特設サイト公開と同時にプレスリリースを打っています。

こういった事を行うのは完全に初めての経験だったのですが、TSGが今後一般向けの活動を行う上で、広報手段の一つとしてどういったものなのかちゃんと学んでおきたい、というのが大きなモチベーションでした。

今回、Flatt側とTSG側で2種類のプレスリリースを作成し、Flatt側からは主にビジネス的な内容を、TSG側からは主に技術的な内容を中心とする文面で公開しました。このうちTSG側のプレスリリースは以下のウェブメディアに対して内容を送付しました。

うち、実際に記事にして頂いたのは Security NEXT の一社でした。この場を借りてお礼申し上げます。また、Flatt側のプレスリリースはPRTimesによる有償掲載を行っています。

これらはCVRなどちゃんと数値が取れているわけではありませんが、特設ページへのアクセス数などから察するにあまり費用対効果の高いものではなかったと思います。こういう狭い界隈に対する広報手段としてよく言われることではありますが、やはり一般向けの広告などはあまり効果が高くなく、地道に口コミで耳目を集めるしかないのかな、と思います。もし次回以降があるとしたらおそらくプレスリリースは打たないと思います。

ちなみに、TSG側のプレスリリースには「不正プログラム摘発に関するTSGの声明」と題した怪文書が添付されていますが、こちらは僕個人の強い要望により掲載したものです。昨今の事件に関して個人的にも物言いしたい気持ちがあったため、いいチャンスだと思ってTSG内で意見をまとめて掲出したのですが、そもそもプレスリリース自体があまり注目されなかったこともあって世間からの反響はほぼありませんでした。出すならもっと大々的にやらないといけませんね。

そして、これは完全に余談ですが、プレスリリース公開に合わせてTSGの公式サイト全体のフルHTTPS化を行いました。まあ、体面を飾る意味でもそうですし、こういう機会に少しずつメンテを行っていくのは大事だと思います。

TSG CTF のコンセプト

今回 TSG CTF を主催する上で、大会のコンセプトとして常に心に留めていたことがいくつかあります。

クソ問を出題しない

まあ、これはコンセプトと言うよりは大前提だと思いますが、やはりCTF大会をCTF大会として「良いもの」にする上で最も重要なことだと思いますし、先述したとおり「良い問題」を作るというのは熟練したCTFプレイヤーでないとなかなか難しいです。我々としても問題のクオリティに関しては一切妥協したくなかったので、リリースする問題に対して必ず複数人によるレビューを加え、クソ問的要素をなるべく排除するようにしました。結果的にボツになってしまい日の目を見なかった問題も複数問あります。

なおCTF界隈では有名な、クソ問を出題することを避けるためのドキュメントとしてpwning/docsの Suggestions for Running a CTF がありますが、博多市は開催にあたってこの文書を熟読しました。現在となってはいささか古い記述や実状に即していないアドバイスもありますが、実際に非常に有用なことも書いてあるので、みなさんもCTFの作問をする機会があれば一度読んでおくべきだと思います。

世界をターゲットに

次に重要な点として、TSG CTF を国際大会にするということです。

先も言ったとおり、日本のCTFの中には主に国内のユーザーをターゲットとし、CTFTimeへの登録なども行わない「国内大会」も存在しますが、一般的な事実として、世界は広いです。下に記す「中~上級者向けの大会にする」というコンセプトも相まって、やるからには世界を相手にしたいと思いましたし、我々にはそれを行う能力と義務があると思いました。

なので、スコアサーバーのインターフェイスや特設サイトの言語、公式からのアナウンスなど、全て第一言語を英語とし、サポートも英語で行うことを原則としました。幸いにしてTSGでは英語が堪能とは言わずともそこそこできるメンバーが大半だったため、これは大きなハードルにはなりませんでした。

中~上級者を対象に

これは主に我々のモチベーションによるものです。我々TSGのメンバーにとってCTFを主催するモチベーションは、「我々の作った面白い問題を解いて楽しんでもらいたい」というものであり、「セキュリティを啓蒙したい」とか「CTFを布教したい」といった類のものではありませんでした。面白い問題というのは往々にして難易度が高い (必ずしもそうではないですが) ため、このあたりは自然に決まっていったといえます。

結果として大会に参加した感想として「難しすぎた」「全く解けなかった」という声が散見されたのはいささか反省すべき点ではあります。これらはどちらかというと事前の告知の問題なので、仮に次回以降があるとしても全体的に難易度を下げて初心者向けの大会にする、ということは無いと思います (簡単な問題をいくらか増やして、初心者でも何問かは解けるようにする、というのはあるかもしれません)。

尖った問題を出題する

これはどちらかと言うと僕の個人的な嗜好の問題です。「真面目にセキュリティを勉強していれば解ける問題」「その道の専門家であれば解ける問題」よりも、「問題を開いた瞬間に『なんじゃこりゃ』って思ってもらえる問題」「その場で特殊知識を仕入れて新たな解決手段を導く問題」のほうを僕は好む傾向があります。例えば今年のPCTFのTriggeredなんかはすごく好きです (解けませんでしたが⋯⋯)。

なので、TSG CTF もそういった類の問題が多く出題される大会にしようと思っていました。特にTSG内外で何回か口にしたのは「特定のジャンルに縛られない問題、特にMisc問を増やしたい」ということです。MiscジャンルといえばCTFにおいてはどちらかというと「エトセトラ」といった扱いが多く、あまり注目されないジャンルかと思いますが、個人的にはCTFの総合格闘技的な側面を強く象徴するジャンルだと思っています。

結果的にはMisc問題はあまり増やすことができず2問しか出題できませんでしたが、どちらも良問だったと思います。

チャットコミュニケーションのDiscord化

CTFにおいては公式によるチャットサーバーを設け、運営によるサポートや参加者同士の軽いコミュニケーションなどが行われるのが慣例ですが、ほとんどの大会ではこのチャットサーバーにIRCを用いています。

が、今回の TSG CTF ではこの慣例を敢えて破り、Discord上にチャットサーバーを設けることにしました。この判断のもとになったのは主に、同じくDiscordを採用したHarekazeCTFの開催記に「登録や使い方についても特に問題は起こりませんでした」とあったためです。

結論から言うと TSG CTF においても、Discordを採用しての満足度は非常に高く、決断を下して良かったと感じています。しかし、大会終了後、数名のプレイヤーから「DiscordはIRCに劣っている」という旨の意見を頂戴しました。

これらの意見に関してですが、主催側の経験から言わせてもらうとはっきり言って理解に苦しみます。プレイヤーのUXにも係ることですので、ここではっきりと主張しておきたいと思います。

今回IRCをやめてDiscordを採用したことには、実際に多くの利点がありました。

  • Discord参加者がサーバーに参加する以前の発言を閲覧することができる
  • 誰が主催者なのか、つまり問題についての質問を誰に投げればいいのか一目で分かる
  • チャンネルごとに話題を切り分けることができる
    • TSG CTF の場合、英語でのコミュニケーション、日本語でのコミュニケーション、運営からの通知の3つのチャンネルで運用しました。これもHarekazeCTFにならったものです。
  • お知らせチャンネルに投稿できるユーザーを権限で切り分けることができる
  • 接続時に使用したIPアドレスを容易に秘匿することができる
  • (今回は使用しませんでしたが) 必要に応じてDiscordサーバーの認証レベルを引き上げてスパム対策を講じることができる
  • リアクションやリンクの展開などモダンなチャットコミュニケーションの機能を使用することができる

個人的には、一番上の「参加する以前の発言を閲覧することができる」というのが特に大きいと考えています。僕はIRCでの会話はなんであれCTFを解く際の重要なヒントになると考えているので (なので博多市はTSGでもIRC監視記録係のような役割をしています)、CTF開始時点でIRCに参加していたか? というような、問題に非本質な部分で参加者間の情報の格差が生じるのがとても嫌でした。おそらく多くのCTFにおいて「IRCに参加してフラグを得る」という半ば強制的にIRCに参加させるような問題を採用しているのも同じような理由かと思いますが、絶対的な意味でCTFを公正な「競技」にするためにはIRCはあまりに非力だと思います。ここは賞金を出す立場でもある以上絶対に譲れない部分でもあります。

一方で、IRCを捨てたことで失ったものも確かにあると思います。TSG内でも協議しましたが、特に大きいと思われるのは「アカウントの使い捨て感」でしょう。DiscordもIRCも名前を入力すると仮アカウントが発行されて入室できるようになるのは同じですが、Discordの場合はCookieが生きている限り同じ仮アカウントが使い回されるなど、アカウントの「強度」がやや違います。IRCではサーバーから切断するとチャンネルのメンバー一覧からも抹消されて煙のようにいなくなるのも、この意味では好ましいかもしれません。

これは純粋に参加者の気持ちの問題なので、上で挙げたような利点と総合してプレイヤー側から評価して貰う必要があると私は考えます。今回、CTF終了直前にSurveyを設け、プレイヤーにアンケート調査を行いましたが、その中に「Discordでのユーザー体験はいかがでしたか?」という内容の設問がありました。結果は以下の通りです。

英語アンケート

日本語アンケート

アンケートに回答していただいたのは最後までCTFに参加していただいたプレイヤーの皆さん、というバイアスはあるものの、それを加味しても全体で好印象を (特に海外のユーザーから) 頂いているように思います。

たしかに、IRCというツールの「ハッカー感」はCTFというイベントには似つかわしいと僕も思いますし、今でも多くのエンジニアコミュニティが稼働している、エンジニアにとっての「ホームグラウンド」であることは重々承知です。しかしことこのようなコミュニケーションを取る場としては、IRCが時代遅れの産物であることは論を俟たないでしょう。後述するように、現在次回開催の目処は立っていませんが、もし次回があるとすれば同じようにDiscordを選択する (あるいはIRCともDiscordとも違う選択肢を検討する) ことは断言できます。

Writeup提出の義務化

今回の TSG CTF では、賞金獲得圏の上位チームにWriteupの提出を義務付け、さらにその内容を一般に公開しました。これは最近のCTFではよくある話だと思いますが、とりわけ直前に開催されたPCTFの様子を参考にした部分が大きいです。

賞金を渡す上での最低限の認証⋯⋯という側面もありますが、どちらかというと、我々が読み返して学びを得たいのと、ちゃんと (解いた人による) Writeupが存在する過去問として TSG CTF の問題を世に残しておきたかったというのが大きいです。なので、全チームのWriteupが提出される前に作問者側のWriteupを公開することに関しても躊躇いはありませんでした。

解答した全問題に対してWriteupを書くのは入賞者には結構な負担となったと思いますが、やはり問題を作った側からは見えてこない部分も多いので (今回のHarekaze問とか)、簡単にでも良いので入賞者の人々にちゃんとWriteupを書いてもらうというのは非常に良かったと思っています。Writeupを提出してくださったチームのみなさんには改めて感謝の意を表したいと思います。

賞金のBitCoin送金

今回のCTFでは、優勝者への賞金の送金手段としてBitCoinを採用しました。これは主に海外への送金を考えてと、送金の公的証明が得られるのを考えてのことですが、あまり他の送金手段と真剣に比較したわけではないので正直良い選択だったのかわかりません。実際に送金を行った感想として、BitCoinの送金の仕組みが意外とややこしい (ウォレットアドレスにもSegWitアドレスなどいくつか種類があったり、取引所がそれに対応していたりいなかったり) のと、手数料がかなり高いのが若干ネガティブに感じられました。

開催までに行ったこと

スコアサーバーの技術選択

CTFを開催するためには、当然ながらスコアサーバーが必要不可欠です。CTFの問題と並んでCTFのキモになる部分でもあるので、これをどうするかはかなり悩んだり相談したりしました。世の中にはCTF用のオープンなスコアボード実装が多く存在していますが、いろいろと試してみた結果、結局、最もメジャーな実装であるCTFdを採用することにしました。これは主に以下のような理由です。

  • 最もよく使用されているCTFスコアサーバー実装であり、安全性を高める観点からも好ましいため
  • 過去行ったライブCTFでもCTFdを立てた経験があるため
  • CTFdのバージョンアップにより比較的リッチなAPIが実装されており、後述するSPA化が容易であること

ただ、皆さんご存知のCTFdのあのデフォルトのテーマを採用するのは、ひと目で「こいつCTFd使ってるな」となるユニクロ的ダサさがあるなと思ったので、できれば避けたいと思っていました。そこで、CTFdのテーマ機能を利用し、見た目だけでもCTFdっぽくない感じにすることにしました。結果できあがったのが、みなさんご覧になったであろうこちらのスコアサーバーの画面です。

実装とデザインはわたくし博多市が全面的に行いました。開催直前に突貫で作った割には比較的いい感じの見た目にできたのではないかと思っています。

CTFdカスタムテーマを支える技術

こちらのカスタムテーマですが、せっかくなので現代的な構成にしたいのと、同時に僕自身が得意とする技術分野でもあったことから、Nuxt.jsのSPAモードで実装してあります。

CTFdのテーマはJinja2のテンプレートとして実装されていますが、CTFd v2 ではアプリケーション駆動に必要な JSON API が非常に充実していることもあり、TSG CTF のテーマではテンプレートに渡されるパラメーターを全て無視し、サーバーとの通信は全て JSON API 越しに行っています。(ちなみに、CSRFトークンだけAPI越しに取得できなかったのでテンプレートに直接埋め込んであります)

API越しに取得したデータはNuxtの流儀に乗っ取りVuexにストアされ、VueのバインディングによってDOMの内容が動的に書き換わるようになっています。これによって、結果的にCTFdの堅牢性とSPAの軽量さを兼ね備えたスコアサーバーを提供できたと思っています。

また、CTF終了後、ある程度のSSR化実装とNuxt.jsのgenerate機能によって比較的簡単に静的サイトの生成を行うことができました。現在もスコアサーバーは静的サイトの状態で公開されていますが、これはNuxt.jsの恩恵に預からなければ実現不可能だったと思います。実装の過程で各種Vueライブラリを大幅に活用できましたし、やはり書きやすさの面でも機能面でもNuxtは非常に優れたフレームワークだと感じました。

今回実装したカスタムテーマのソースコードはこちらで公開しています。

github.com

問題添付ファイル

今回の TSG CTF では、CTFd v2 で導入された新しい機能をふんだんに使わせてもらっていますが、そのうちの一つが添付ファイルのS3アップロード機能です。

TSG CTF の開催直前に行われたSECCON令和CTFが開始直後にサーバーダウンしたことを受けて、サーバー負荷に対しては我々としてもかなり慎重になっていました。その過程で、特に問題の添付ファイルのダウンロードの負荷だけでも下げたいと思ったため、アプリケーションを通さず負荷が小さいS3バックエンドを利用することにしました。

ちなみに今回のインフラ構成では全面的にGCPを利用していますが、実はCTFdの添付ファイルのアップロード先にも (S3ではなく) Google Cloud Storage を用いています。CTFdからのアクセスにはS3互換APIを用いました。最終的にソースコードを書き換える必要もなく、特に問題もなくS3バックエンド機能を利用できたため非常に満足度が高かったです。

注意点として、Google Cloud Storage でバケットを公開するとデフォルトで Storage Obejct Getter 権限が付与されますが、この権限にはstorage.buckets.list権限が付与されているためAPIを叩くとバケットのファイル一覧を取得することができてしまいます。特にCTFdのバックエンドサーバーとして利用した場合はまだ公開されていない問題のファイルが漏洩してしまうため、必ずstorage.buckets.list権限を外した状態で公開しましょう。

問題のスコア配分

今回の TSG CTF では、近年のCTFで主流の、回答したチーム数が多いほどスコアが下がる動的スコアを採用しています。このシステムはオリジナルの「ジョパディ!」のルールからは逸脱していますが、やはり作問者側から問題の難易度を事前評価することの難しさを考えると、「難しい問題ほど得点が高い」という原則を遵守する上で最もフェアなシステムなのではないかと思います。

ちなみに、気づいた人も多いと思いますが、今回出題した問題のスコアは最後まで初期スコアからほとんど下がりませんでした。

全体的に回答チームが少なかったというのもありますが、例えば11チームが回答した Super Smash Bros でも 500pts → 496pts と、初期スコアから1%も変動していません。ある程度下がっているとは言え一般的なCTFと比べてあまりに下げ幅が小さすぎるという意見もあるかと思いますが、これは主に我々の事前チェックが不十分だったためです。スコアの計算にはCTFdの動的スコアプラグインを利用したのですが、このプラグインは回答チーム数に対して線形に減少するように作られておらず、最初は緩やかに減少し、最終スコアに近づくにつれて急激にスコアが下がるようになっています。

今回は全問題に対して {初期スコア = 500pts, 最終スコア = 100pts, 最終スコアまでのチーム数 = 100チーム} という設定を行いましたが、設定に対して最初の下がりペースが我々の予想以上に緩やかでした。「線形に減少するわけではない」という特性を把握した上で、事前に計算式に手を加えるなどの対策をしておくべきだったと反省しています。

インフラ

前述の通り、今回の TSG CTF のインフラにはGCPを用いました。スポンサーのFlattさんのおかげでインフラ用の予算にはかなり余裕があったので、我々としてもかなり自由にいろいろやることができました。具体的な取り組みの例を以下に紹介します。

アクセス監視

個人的に、CTFに参加するプレイヤーは歴戦のならず者集団だと思っている (褒め言葉です) ので、怪しいアクセスの監視に関しては通常の運用以上に気を使っています。このあたりは主に本大会のインフラ担当である@kcz146が担当してくれました。

大会中、Discordで以下のようなアナウンスを何度か目にしたと思います。

今回の大会では、サーバーに対するアクセスに敏感にかつ精度よく反応することができました。大会中は、GCP Computing Engine へのアクセスをStackdriverを通して可視化し、以下のような画面を常にモニタリングしていました。

グラフの1本の線が1IPアドレスのアクセス数を表しています。通常のアクセス時には上のようにアクセスはまばらになりますが、DoSまがいの異常アクセスが続いた場合はグラフが以下のように跳ね上がります。

実際の運用では、このダッシュボードの情報をもとにDiscord上でアナウンスを行ったりファイアウォールの設定を変更したりしました。結果、(問題の構成について後述するようないくつかの反省点はあるものの) 不正なアクセスに関して大きなトラブルもなく無事に大会を終えることができました。

負荷監視・死活監視

サーバーへのアクセスとともに、サーバー自体の負荷についても同様に監視を行いました。負荷監視に関してはmackerelを採用しました。

Mackerelでは上の画面のようなダッシュボードで自由にレイアウトを変更することができ、ひと目でサーバーの状態がわかるようになっています。今回は全体で4台のサーバーでオペレーションを行ったので、上のようなダッシュボードを作成して常にモニターに映し出していました。

また、Dockerイメージを用いて、クラウド環境でも簡単に監視環境を導入することができました。サーバー監視にはあまり多くの労力を費やしたくなかったため、この点でもMackerelは非常に優秀なサービスであるように感じました。

また、いくつかの問題に関しては別途死活監視を行っています。特に@y0n3uchyおよび@moratorium08作成の問題に関しては定期的にソルバスクリプトを走らせて、問題として回答可能かどうかを監視してSlackに状況を通知するようになっていました。このあたりは彼のSECCONでの運営経験が大いに活きています。

(ちなみに SECCON Beginner CTF での各問題のステータスバッジがすごく良かったので、今後パクらせてもらうかもしれません)

Docker

TSG CTF では、作問からデプロイまでDockerを大いに活用しています。作問時にはdocker-composeとして実行可能な状態で提出してもらい、どの問題も1コマンドで立ち上げられるように準備しました。環境の異なる問題を大量に作成する上で、やはりDockerイメージによる仕組みは非常に便利ですし、本番環境で動かないなどのトラブルもなく非常に安定してデプロイすることができました。

一方で、CTFの運営においてDockerをランタイム環境として使用することはセキュリティの観点から賛否両論あるかと思います。これに関してはgVisorやVMの採用なども含めて今後もさらに検討していくべき事項だと思いますが、特に今回の TSG CTF で行った対策として、問題のデプロイ先の環境にGoogleが提供する Container-Optimized OS を採用しました。

このOSはDockerへのセキュリティ的な懸念からシステムやファイアウォールに様々な改善が加えられたOSであり、特に/tmpと/home以外のほとんどのファイルシステムがread-onlyでマウントされ、またDocker以外の実行ファイルの実行もできないようになっているため、仮にDockerがjailbreakされても破壊的な操作ができないようになっています。

これに伴いdocker-composeの立ち上げやインストールなどの手間が若干面倒になりますが、個人的にはこのあたりがCTFという特殊な大会を運営する上で丁度いい妥協点なのではないかと思いました。

ロードバランス

上で述べたとおり、今回の TSG CTF ではサーバーの負荷に関してだいぶ気を配っていたので、本番では負荷分散のため複数台構成によるロードバランシングを構成しました。これには Google Computing Engine の Load Balancer 機能を、データベースの集約には Google Cloud SQL を使用しています。

本番中、複数台で捌いていたのは特に高負荷が予想される大会開始から数時間程度のみで、それ以外の時間は (コスト削減のため) 負荷を見ながらインスタンスを減らしてゆき、最終的には一台のサーバーで運用しました。

この手のロードバランスの調整や新規インスタンスの投入は通常かなりの手間がかかるものですが、GCPのロードバランス機能を用いることで特に何も考えずWebコンソールからポチポチするだけでクラスターが構成できましたし、(今回そのような事態にはなりませんでしたが) 仮にスコアサーバーにDoS並のアクセスがあったとしても即座に新規インスタンスを立てて投入できる体制になっていました。まさにIaaS様々といった感じです。

裏話として、実はこのクラスタ構成をする上でいくつかサボった点がありまして、今回の運用ではCTFdの公式リポジトリで配布されているdocker-composeの構成をほぼそのまま流用しているため、セッションストアとしても利用しているRedisを一台に集約するのをサボっていました。ロードバランサーではアクセス元のIPアドレスごとに固定のサーバーが割り当てられるように設定していたので、アクセスごとにセッションが切れるということはなかったはずですが、運営側で新規サーバーの投入や変更ごとにセッションが切れるのが頻発したかと思います。ひょっとするとたびたびログアウトさせられてストレスになったかと思いますが、今後はRedisサーバーを集約するなどして正しく構成していきたいなと考えています。

プッシュ通知

今までCTFプレイヤーとしてCTFに参加してきた経験から、「新しい問題がリリースされたのにしばらく気づかない」という問題に悩まされ続けてきました。そこで TSG CTF では、新しい問題をリリースしたという情報がなるべく即座に全プレイヤーに行き渡るよう、しつこく通知を送る仕組みを構築しようと考えていました。

その中でも特に挑戦的な試みとして、今回の TSG CTF ではスコアサーバーからWebプッシュを配信しました。CTFd自体にも通知を表示する機能がありますが、それと併用してOneSignalという通知プラットフォームを利用し、問題を追加するごとにブラウザレベルの「ちゃんとした」WebPushがモバイルでもデスクトップでも通知されるようになっていました。

ただ、サイトにアクセスしてすぐに「通知を許可しますか?」のような画面が表示されるのに面食らった人も多いかもしれません。実際OneSignalのコンソール画面から確認できる通知を有効にした人数は、CTF終了時点で248人と、1チームあたり平均0.5人程度しか通知を有効にしていませんでした。このあたりは一般的な通知戦略と同じだと思いますが、ページを表示してすぐに通知許可を求めず、ユーザーが任意に通知を許可できるようにするなどの仕組みが必要だと感じました。

また、スコアサーバーでWebプッシュを送信するというのが一般的でないのも通知有効化率が低い遠因だと思われるので、CTFプレイヤーとしてもこのような仕組みが世の中にも広まって欲しいと願う次第であります。

ちなみに TSG CTF ではこのWebPushを含め、スコアサーバー、Twitter、Discordと同一の通知を4箇所に配信しています。手作業でこれらを管理するのは大変なので、簡単なスクリプトを書き、通知文を (英語と日本語で) 書けば自動で通知を送信してくれる仕組みになっています。

当日の流れ

⋯⋯というようなことを当日までに行ったのですが、ゴールデンウィーク効果もあって直前は比較的余裕のあるスケジュールで本番を迎えることができました。CTF運営者として24時間状況を監視しないといけないということもあり、本番直前はよく睡眠を取り、東大の本郷キャンパスに場所を借りて数名で泊まりがけの運営体制を取りました。メンバー全員が同じ大学の大学生だとこういう時に集まるのが容易なのでありがたい限りです。

直前に問題の最終チェックを行い、スコアサーバーへの問題登録、ロードバランスの設定、アクセス監視の設定などを行い、CTF開始後はDiscordでの応対、開放する問題の選定などを集まったメンバーで行いました。

(後半はだいぶ暇だったので主に@satosが持ち込んだ Baba is You で遊んでました。)

トラブル: Obliterated File

今回の TSG CTF, 全体を通して大きなトラブルはなかったと思いますが、いくつかの問題では我々の想定していない事態に発展したものもあります。そのうちの1つが Obliterated File でした。

詳しくは作問者によるWriteUpを参照してもらいたいのですが、一言で言うとこの問題はGitリポジトリファイルシステム内を調査する問題で、Git上から完全に消去されたと覚しきファイルを復元せよというものです。こちらの問題では実際には適切にコミットログからフラグファイルが削除されておらず、想定より容易に辿れるコミットをチェックアウトするだけでフラグが手に入るという、我々の想定外の解法が存在する不備がありました。

問題公開から十数分後、あまりに回答ペースが早いことから不審に思い、運営側で問題の調査を行って発覚しました。不備発覚後、作問者の@taiyoslimeが新たに修正したファイルを新しい問題として公開し、リカバリーを行いました。問題の公開停止や点数差し戻しなどの事態に発展しなかったので大きな問題ではなかったと思いますが、事前にチェックできなかったのは運営側のミスだったと思います。

この問題も他の問題と同じく、事前にテスターによるテストを行っていたのですが、テスターには想定解法で解かれてしまったためこの不備に気づくことができませんでした。我々はどちらかというと「問題のクオリティ」に主眼をおいて問題のテストを行っていたのですが、それ以外にも「想定外の解法が存在しないか」「問題を破壊することができないか」といった運営上の都合にも目を配るべきだと反省しました。

また、この問題はForensicsというより比較的Miscに近い問題ということもあり、特別に得意なメンバーがいたわけではなく、テストがなかなか進まなかったのも今回の不備の遠因ではないかと考えています。そういった意味で、上に挙げたような「尖った問題」を作ることの難しさを感じたとも言えます。

トラブル: Secure Bank

もう一問、Secure Bank という問題でも小さなトラブルがありました。こちらはRubyで書かれた普通のWeb問題なのですが、問題公開からしばらくして「サーバーが重くて繋がらない」というフィードバックがDiscordに多く寄せられました。本大会では上の通りサーバー負荷を常に監視しており、サーバー自体はほとんど負荷が上がっていなかったため訝しんだのですが、問題のページにアクセスしてみると実際にとても重い。

原因は、DoS防止のためアプリケーション中に挿入していたsleep関数の処理待ちによるものでした。問題のアプリケーションはSinatraで記述されていたのですが、通常Sinatraでのレスポンスは同期的に逐次処理されるため、sleepが完了するまで他のリクエストがブロッキングされていたのです。幸い作問者の@kczがRubyを完全に理解していたので、原因を特定したあと彼が急遽Sinatraの外側にpumaサーバーを実装して並列化を行い (512並列 (!) 設定にしました)、大幅にリクエスト処理数を上げることに成功しました。

このあたりもやはり、多くの人がアクセスして初めて発覚する問題ということもあり、事前の問題チェックでは気付きにくいポイントだったなあと感じています。

反省点など

n億回アクセスしたら解けるよ! という問題設定にしない

本大会のアクセス監視全体を通して、DoSまがいの高頻度アクセスが検知されたのは主に Secure Bank に対するものでした。改めて整理すると、この問題の設定は以下のようなものでした。

  • ユーザーは自由にアカウントを作成することができる
  • アカウントを作成するとそのアカウントに100コインが入る
  • アカウントから別のアカウントに送金することができる
  • 100億コイン集めるとフラグが手に入る

お気づきかと思いますが、この問題ではアカウントは自由に作成できるため、特殊なハックなどを行わなくともアカウントを1億個作成して1つのアカウントに100コインずつ送金することでフラグが手に入ります (上に述べたとおりアカウント登録には1秒のsleepが挿入されていましたが、最終的にサーバーで512並列処理を行ったため最大で512倍の速度が出せます)。経験を積んだCTFプレイヤーならば、「24時間の大会で1億回アクセスするのは時間的に現実的でないし、そもそもそのような高頻度アクセスはルールにより禁止されているので想定解法ではないはずだ」と斟酌するところかと思いますが、大会に参加するのは必ずしもそのようなCTFの文化に慣れ親しんだプレイヤーだけではないことを十分に考慮するべきだったと思います。実際、事前の問題チェックにおいても、CTF初心者のTSGメンバーに見せたところ「要するに1億回アクセスすればいいんでしょ?」という反応が帰ってくるなど、CTF初心者にとっては「サーバーの論理的な脆弱性を利用する」というのは非自明な解法なのだと感じました。

このような問題設定がプレイヤーによるDoSまがいの攻撃や、ひいてはアクセスBANに繋がったとも言えるため、ちゃんとCTFに入門したばかりの人の立場にも立って考えて、「◯億回アクセスしたら解ける」というような問題設定はなるべく避ける、あるいはその過程で適切にCAPTCHAを挿入するなどの対策を行うべきだったと感じました。

フラグに解法を記述するのは相応のリスクを覚悟するべき

CTFにおける一般的な慣習として、問題を解いて得られるフラグに、問題の解法のまとめ的な文章を記述するというものがあります。例えば今回の TSG CTF のRECONは、XS-Leaksを利用する問題だったためフラグは TSGCTF{x5_l34k5_4R3_4M421ng} にする、と言った具合です。

この慣習は、作問者としてもプレイヤーとしても、作問者による「ネタばらし」的な側面を持っており非常に楽しいものなのですが、問題に想定外の解法があった場合などはトラブルの元になります。実際、大会中に想定外の解法が発覚した今回の Obliterated File では、直接的な解法ではないものの当初のフラグに解法のヒントになるようなフレーズが含まれていたため、相対的に修正後の問題を解くのが簡単になってしまいました。

それ以外にもフラグがリークする危険を考慮して万全を期すならば、フラグはランダムな文字列などにするべきかもしれませんが、Suggestions for Running a CTF にもある通り、フラグフォーマットは一目で「正しいフラグ」とわかるようなものが望ましいため、悩ましいところです。「良いフラグ」を作るというのは存外に難しいことなのかもしれません。

事前にスコアサーバーへの問題登録チェックまで済ませておく

想定外のトラブルというのは常につきものです。我々としても多くのプレイヤーを相手にしている以上そういったトラブルはなるべくゼロにしたいですし、そのために問題のレビュー、スコアサーバーのバグテスト、負荷の確認など一通りのチェックは行っていたのですが、スコアサーバーへの問題登録だけは事前に行わず、問題公開の直前に行いました。

これについては、オペレーションミスによって公開する予定のない問題が公開されてしまうのを避けたかったのと、問題登録の段階でトラブルになる可能性は低いだろうと踏んでのことだったのですが、実際には一部の問題においてNginXのアップロードファイル制限に引っかかって問題の添付ファイルがアップロードできないというトラブルに見舞われました。

もちろん、そもそものリスクマネジメントとして、どんな場合においても想定外のトラブルというのはつきものですし、それをどこまで事前に勘案するかというのは常にトレードオフかとは思いますが、今回の場合はせめてちゃんとプレイする側に立って問題をダウンロードして解いてフラグを送信できる、というところまで事前に本番サーバーで保証してあげるべきだったかなと思っています。リスク管理、本当に難しいです。

CTF問題のレビューチェックポイント

以上のようなことを踏まえ、我々が今回学んだCTFの問題をテストする際のチェックポイントをまとめると以下のようになります。

  • エスパー問でないか
  • 想定外の解法が存在しないか
  • 問題を破壊することができないか
  • サーバーに過度な負荷がかからないか

その他の気づき

Discordでは英語でのコミュニケーションが主流だった

先述したとおり、今回設けたDiscordサーバーでは、日本語と英語の2言語をサポートし、それぞれでのコミュニケーション用にチャンネルを2つに分けました。結果から言うと、このうち日本語用のチャンネルはほとんど使われず、参加者同士のコミュニケーションの中心は英語で行われていたようです。日本人とおぼしき人たちもだいたいは英語チャンネルにいました。

これに関しては、CTF全体の難易度設定もあって、あまり英語でのコミュニケーションに不慣れな初心者が多く参加する感じではなかったのも原因の一つかなと思います。あまり日本語を話しすぎるのも海外からの参加者に不親切ですし、もし次回開催するならばチャンネルは英語一本に絞ってもいいかもしれません。

問題文について

これは割とどうでもいい話ですが、今回の TSG CTF の問題文、物によってはだいぶふざけた感じになっています。

  • 0000000000000000
    • ja: 天気がいい日に大学で写真を撮ったよ
    • en: I took a photo at university on a sunny day~~
  • OMEGA
    • ja: Ω < ゴロンゴロン(環っかが転がる音)
    • en: A ring in the shape of omega is there...
  • Curved
    • ja: 曲がり角は駐車禁止ですよ?
    • en: If you get curved, then...

だいたい日本語の説明文と英語の説明文が対応してないですし、そもそも問題に対して何のヒントも与えてないので完全にただのお遊びなのですが、終了後のアンケートでは比較的参加者に好評でした。特に日本語の問題文も併記したのが日本語ネイティブにも楽しい感じになってよかったかなと思っています。

今後について

次回開催?

総合的な自己評価として、TSG CTF は大成功だった⋯⋯と思います。ぜひ来年も開催、と行きたいところですが、このままだとリーダーの私やCTFをやるTSGの主力メンバーが大学を卒業してしまうこともあり、現状、次回の開催はかなり不透明な状態になっています。我々としても世の中に楽しいCTFを増やしたいという気持ちは大きいので、今後の TSG CTF 開催に関してはなるべく前向きに検討して行きたいと思います。

今年のSECCONについて

なお、来年の話をする前に今年の話をしておくと、今年の10月に SECCON CTF 2019 の予選が、12月に本戦が開催される予定です。例年、前年度の本戦優勝チームが予選の作問に協力する慣例となっているようですが、先述の通り去年の SECCON CTF で我々が優勝したため、10月に開催される SECCON CTF Quals でTSGからも何問か出題する予定です。こちらも TSG CTF と同じくクオリティに関しては一切妥協せずやっていくつもりなので、皆さんぜひ楽しみにしていてください!

謝辞

細かいことまでつらつらと書いてたらたいへん長くなりました (この記事を書き始めてから2ヶ月が経ちました)。最後に、今回の TSG CTF に協力していただいたTSGのメンバーのみなさんをご紹介します。

また、スポンサーを含め全面的かつ甚大な協力を頂いたFlatt社のみなさん、および TSG CTF に参加していただいたみなさんにも感謝の気持ちが尽きません。本当にありがとうございました!