ふるつき

v(*'='*)v かに

zer0pts CTF 2020 開催記

zer0pts CTF 2020を開催しました。 今回はその反省記事です。

ctftime.org

ちゃんとした記事はこちら

ptr-yudai.hatenablog.com

CTFをどうやって開けばよいのか、ということについては上記 id:ptr-yudai のブログも参考になりますが、TSG CTFの開催記がとても細かく書かれているので、そちらを参照すると良いと思います。この記事は、zer0pts CTF 2020における私の役割と反省になります *1

hakatashi.hatenadiary.com

CTFの規模感

  • 開催期間: 2020-03-07 09:00:00 +09:00 〜 2020-03-09 09:00:00 +09:00 (48h)
  • 参加(=1点以上得点した)チーム数: 432チーム

48時間という設定は、「決して短くはない」「これ以上継続してCTFに臨むのは厳しいだろう」という時間でした。個人的には24時間で十分だと思っていますが、このあたりは色々あります。432チームは、「ctftime.orgに載るCTFとしては少ない」という感じだと思います。これは開催告知が遅くなったのも原因だと思っています(あとUTCTFと丸かぶりした)。

スコアサーバ

スコアサーバは2月に必死に書きました。個人的な経験値から、 Go(echo) + Vue という構成になっています。(zer0pts CTF 2020のためだけの値がcommit logに含まれていてそれを公開するのは嫌なので、そういった情報を消したものを公開しています。ただ、随所にzer0pts CTFという名前は見られると思う)。

gitlab.com

CTFを開催するたびにスコアサーバを書いている気がしますが、これは私たちのCTFdに対する信用の無さから来るもので、CTFdは重たいのではないか、という疑念がありました。というのもCTFdを採用しているCTFで、スコアサーバのレスポンスが極端に悪いものがこれまでいくらか見受けられたためです。ただし TSG CTFではCTFdのこれまでの成果を評価して採用しているので、CTFdが不十分、ということはないはずです。一度CTFdを使ってCTFを開催してみて、CTFdの安定感・使用感を知っておくのがまず最初で、その結果CTFdを採用するかどうかを考えるべきでした。このあたり、スコアサーバを実装したいという気持ちが先行していて良くないですね。

ちなみにスコアサーバの見た目は、良いCTF体験を追求した結果、 watevr CTFのパクリになってしまいました。watevr CTFのスコアサーバ (https://ctf.watevr.xyz/)は個人的に好感度が高くて格好良い&使いやすいを両立していると思います。

スコアサーバをスケールさせたい欲求

前回開催した InterKosenCTF 2019 では、gunicornの引数に並列動作に関する要件 (--worker-class=gevent --worker-connections=500 --workers=3 など)を指定し忘れて開始後数時間、スコアサーバのレスポンスを極端に悪くしましたが、このときの悪い体験と、また今回からは ctftime.org にも掲載するグローバルなCTFになり参加者の増加が予想されるということで、スコアサーバの安定性は大きな課題でした。

そこで今回はAWSのFargateという、コンテナを自動スケールするサービスを利用して、スコアサーバのスペック確保を試みました。結果としては 1GiB RAM / 0.5 vCPUのコンテナ1台で全てのリクエストを捌けていたので、スペック的な問題からコンテナのスケールが活躍することはありませんでしたが、何度かコンテナが不具合で停止したタイミングがあり、その際に自動で新しいインスタンスが起動したことには大いに助けられました。

Fargateは値段も安いので(RDSやElastiCacheを併用することになるので、全体で見たら決して安くはないのですが)、どうせRDSを使う構成にするなら、EC2ではなくてFargateを検討するのもアリかもしれません。

コンテナの不具合

前節で触れましたが、5時間に一度くらいの頻度でコンテナが503を返すようになっていました。コンテナのログからは echo: http: Accept error: accept tcp [::]:80: accept4: too many open files; retrying in 1s というエラーメッセージを拾うことが出来ましたが、これがどういう問題で、何を原因としているのかは調査しきれていません。このような現象をご存知の方は助けてください

問題の扱い方

https://gitlab.com/zer0pts/zer0pts-ctf-2020 で公開しているように、全ての問題は challenges.yaml に詳細を書き、<問題名のディレクトリ>に配布ファイルやサーバの設定を書いてもらうようにしています。これはInterKosenCTFのときとほとんど変わりません *2ディレクトリの構成などは BSidesSF CTFのものが見やすいと感じたため、真似をしています(こちらはk8sなどを使ってデプロイしており、より偉いですが)。

github.com

配布ファイルの取扱い

当初、 transfer.sh をEC2サーバにデプロイしてファイルの配布を行う構想でしたが、途中で怖くなってS3 Bucketにアップロードする方法に変更しました。transfer.shをデプロイするのも悪い手法ではないと思っていて、特に学内CTF等で短時間だけ動作して、スペックを要求しないのであれば Docker で簡単にデプロイ出来て良いと思います。開発中は docker-compose で立ち上がるサーバ一式にtransfer.shも含まれていて、ここにファイルをアップロードして動作確認を行っていました。

前回もそうでしたが、今回作成したzer0ptsctfdでも、問題ディレクトリ中の distfiles 以下のファイルを自動的に tar.gz 形式に固めて 問題名_MD5.tar.gz としてアップロード、 distarchive 以下の .tar.gz をそのままアップロードするプログラムを組んでいます。ただし今回突貫で作業した結果、gz圧縮が行われていない不具合と、配布ファイルの中身が変わってハッシュが変わった場合に、古い配布ファイルの情報を消していなかったため配布ファイルが複数になる不具合を生み出しました。前者は修正済みですが後者はまだなおしていません修正しました。

問題サーバ

サーバを必要とする問題は基本的に docker-compose up で動作するようにしてもらい*3 、前回と同様、いくつかのEC2インスタンス上で docker-compose up を発行して起動していました。これに起因するトラブルもありましたが後述。

また、問題サーバ自体の構築には ansibleスクリプトを書きました。Dockerのインストール、EC2における特殊なIPアドレスへのアクセス禁止tcpdumpの設定などを行っています。

問題サーバを動作させる上で恐れていたのもアクセスの集中によって十分なレスポンスができないのではないか、ということでしたが、t2.medium1台あたり3〜5問程度を十分にさばいてくれました。haproxyなどを挿入して負荷を分散させようという試みもありましたが、うまくデプロイや設定反映をできる気がしなかったため諦めています。

試行錯誤の中には問題デプロイ&実行用k8s クラスタを建てる、という試みもあって、ボタンひとつで git clone、kanikoによるイメージビルド、クラスタリポジトリへのpush、クラスタリポジトリからのpull & デプロイが行われるような仕組みを構築しようとしていましたが、クラスタリポジトリからイメージをpullする方法がわからないことと、デプロイした後のポート公開等のアクセス制御がよくわからない、という点から諦めています。こんなのに2週間持っていかれたのもったいないな。

監視

よくわからないので CloudWatchで収集できるメトリクスだけを監視していました。もうちょっとまともにやったほうがいい感じはありますが何をすべきかよくわかっていません。

f:id:Furutsuki:20200313110418p:plain

AWSへの連絡

忘れていました。

運営中のトラブル

チーム名の制限

当初チーム名は [A-Za-z0-9_-]{1,20} という正規表現に従う文字列に限定していましたが、 ctftime.org に提出したスコアボードからチームにレーティングが割り当てられることを受けて、チーム名に関する制約を撤廃する変更を加え、またチーム名も変更可能にしました。 ctftime.org に登録するCTFを開催する場合には気をつける必要があります。

urlapp

urlappというRedis Command Injection Puzzleを出題しましたが、いくつかのredisコマンドを禁止するための redis.conf${PWD}/redis.conf:/redis.conf などとしてvolumeをマウントして認識させようとしていたところ、sudo 時に PWDが引き継がれない問題に気が付かず、 redis.conf が未設定となりやりたい放題できる状況でした。当初よりもずいぶん難易度が下がったのではないかと思っています。

この問題に対する対策はよくわかりません。そもそも volumeのマウントなどをしている時点で悪いのかも知れない。

動的配点の式を変更した後、点数に反映されない

ptr-yudaiの記事にもありましたが、途中で配点の式を変更しましたが、問題のスコアはRedisでキャッシュして、解かれる度に再計算、というプロセスを採用していたために、計算式の変更後に解かれた問題とそうでない問題で、一時的に配点に差が生じました。結局「全問題の得点を再計算する」エンドポイントを突貫で作成して解決しましたが、こういうエンドポイントを事前に用意しておくべきです。

ちなみに私は動的配点の式を理解していなかったので、このときに点数が負になるなどのバグがありました

EC2のインスタンスタイプを変更したらIPアドレスも変わった

それはそうなんですが、普通に経験不足で気がついていませんでした。CTF終了後も1ヶ月くらいはサーバを動かしているのですが、アクセス数も減ってきたため、EC2インスタンスを減らしたり、小さいインスタンスに変更したりとといったオペレーションを行って費用を抑えようとしたところ、Elastic IPを利用していなかったため、インスタンスのPublic IPアドレスが変わってしまいました。いくつかの問題では、IPアドレスがハードコードされていたため、修正が必要になってしまい、開催期間中でなくて良かったと別の方向で安堵しました。

ちゃんと何かしらのドメインをとってそちらを使うようにするか、Elastic IPを確保していれば防げた問題でした。

資金繰り

私の反省かどうかはさておき、書いておくべきかなと思います。今回は賞金付きCTFとしたため、スポンサーの強力を仰ぐ必要がありました。CTFに賞金をつける確固たる理由は私の中にはまだありませんが、参加者の質の向上や、CTFの質の指標という意味で重要だと思っています。

結果として株式会社アクティブディフェンス研究所様、株式会社ヘマタイト様からの協賛をいただきました。ありがとうございました。協賛をいただくに当たっては、CTFにおける賞金が法に触れないことの説明をしており、今後会計報告を行う予定です。前者に関して、我々の解釈に自信があるわけではないので公開はしませんがお問い合わせいただければどのような説明を行ったのかの説明はできると思います。

値段感

結果としてAWS上で全てを完結させましたが、利用したサービスは EC2 / Fargate / ECR / RDS / ElastiCache / Route 53/ S3 / CloudFront 位になるかと思います。結局 EC2 / RDS / ElastiCacheがほとんどを占めており、 t2.medium 7台程度 、RDS 1つ、ElastiCache 1つで 48h〜を動作させる分を概算すれば今回程度の規模のCTFの開催にかかる費用の見積もりができそうな感じがあります。zer0pts CTF 2020はまだもう少し問題サーバ・スコアサーバ共に動作させる予定があるのでまだ値段は確定していません。

IRC / Slack / Discord

これも私の反省かどうかは微妙ですが、zer0pts CTFでは質問や連絡の場としてIRC / Slack / Discord を検討して Discord を選択しました。

  • IRC はもはや我々のようなひよっこには経験が不足しており、十分な運営ができるか怪しい
  • Slack はワークスペース毎にアカウントを作れるので、CTFerとしてはその方が嬉しい人がいるのではないか
  • と思っていたがDiscordでも同様のことができるらしい
  • じゃあDiscordでいいか

というような選定経過だったと思います。Slack / Discordを利用する際の不安定感に関してですが、我々のスコアサーバ・問題サーバの不安定感に比べればどうということはなく、無視できると思っています。

結び

ptr-yudaiの記事と相補的な内容になったと思います(なっていれば良いが……)。私の個人的な反省としては着手の遅さによるトラブルの数々と、提供した問題の量・質です。

*1:TSG CTFの開催記に言及することが何度かあるので引用しましたが、これでhakatashiさんに通知が飛ぶの怖すぎるな。無視してください

*2:スコアサーバが要求する引数が変化したので、そこだけ変わっています。ちなみにこの変更が十分に伝わっていなかったため、誤って一時的に静的配点になってしまった問題が存在しました

*3:例外としてKernel Exploitの問題があります。このあたりはptr-yudaiが詳しいです