CTFの問題を作るときに気をつけること

たまにポエムっぽい記事を書きたくなることがあるんですけど、今回はそういう回です。長いので結論だけ知りたい人はこちらへどうぞ。

CTFブームもここ数年の話ではなくなり、日本でもCTF開催側にチャレンジするチームや会社などが増えてきました。TokyoWesternsを結成して本格的にCTFを始めた2014年終わり頃に比べると、世界的にも日本的にもその数はだいぶ増えたんじゃないかなと思います。日本に限った話で言えば、やはりSECCONをベースとした団体の貢献が多いのは言うまでもないでしょう。Twitterのbioに「CTF」というワードを追加している人の数もかなり増えてきたように思います。
さて、こうした中で多くのCTFを経験すると「問題の質」というのがなんとなくわかるようになってきます。日本のCTFだけだと正直なところまだまだ数が少ないので難しいですが、海外で開かれている多くのCTFに参加するとなんとなく良い問題とそうでない問題がわかるようになってくると思います。そして、CTFを開催する時は「そうでない問題」側に取られてしまう問題の数をなるべく減らしたい、できれば0になるように努力します。

では一体どのような問題が「良い問題」とされるのでしょうか?(ここでは「CTFは現実世界で役に立つ・立たない問題」を議論する気はなくて、そういうのを期待している人は別のところでやってください。)
これについては正直なところ個人の価値観に大きく依存すると思いますが、なるべく一般論として言える(と僕は思っている)ポイントについて言及します。(これは自戒の念も込めて、多くのブーメランを含んでいます)

パズル要素がある

まず一番重要かつ難しいポイントです。我々は基本的には「ハッカー」という、創意工夫によって面白いなにかを達成することが好きな生き物ですから、解法が自明でないことが求められます。この解法が自明足るか否かは参加者によるところが大きくて、多くにとって非自明であればあるほどその問題は難しいと言えます。もし他のCTFで同様のものが出題されている場合はいわゆる「既出」と言われ、そのCTFに参加した人にとっては自明・簡単になります。逆に言えば、より多くのCTFに参加し解法パターンの引き出しを増やすことで、より多くの問題を解けるようになるということです。大学入試とかと同じ原理です。
このパズル要素を適度に含めるのは非常に難しくて、自明でない要素を考えつくには自明な要素をなるべく多く知っている必要があるからです。「CTFに参加したことがない運営が良い問題を作るのは難しい」と言われる所以だと僕は思っています。

本質から外れた妨害要素を入れない

問題を作る上でよく勘違いする人が多いのですが、難しいから良い問題、多くのチームに解かれなかったから良い問題というわけではありません。
たとえばpwnableやreversingの問題があったとして、PowerPCアーキテクチャとか出てきたらうんざりします。なぜうんざりするかというと、「PowerPCは今では一般的ではない」からです。その問題がもしPowerPCの特徴に関連した、あるいは実際にPowerPCで動いていた何かを模して作られたというのならばまだ良いのですが、単純に解析やエクスプロイトを面倒にする目的でそのようなアーキテクチャを選択するのは避けるべきです。もちろんこれはPowerPCに限ったことではなくて、思考停止してVMProtectを噛ませるとかそういうのも含みます。本質的でないところで時間を溶かすのはプレイヤーにとって非常に悪い体験ですし、なにより作問者がその問題の本質部分に自信がないことの現れです。もし作問者が、プレイヤーがこの妨害を突破することにも意味があると考えているならば、突破した時点で部分点を与えるべきでしょう。部分点がない場合は、途中のチェックポイントを通過していようがいまいが解けてなければ0点です。

問題の使い回しをしない

一番やってはいけないことです。CTFの問題を作るというのは非常に大変な作業ですが、埋め合わせのために過去問を適当に変えて出すというのは禁じ手です。少し手を加えるだけで解法が異なってくるような場合はハッカー心をくすぐられて大いに歓迎されますが、例えばプログラミング系の問題でパラメータを適当に変更しただけとか、そういうのは非常に嫌がられます。
なぜかというと、過去問は既にある程度問題を理解しているチームが存在するためです。そのようなチームは過去のコードを再利用しないはずがなくて、これは大きなハンデになってしまいます。有利になったチームは「ラッキー」くらいにしか思いませんが、不利になったチームからすると相手がチートしたときくらい悔しい思いをするでしょう。もちろんこの場合は運営に責任がありますが。

「面白くない」要素が可能な限り排除されている

できれば問題はなるべく楽しく解きたいものです。もちろん何が楽しい・楽しくないかは個人にもよると思いますが、少なくとも無意味な作業は誰だってやりたくないでしょう。 例えばパスワード辞書を使ってブルートフォースをするとログインできて、そこから問題が始まりますみたいなのは最悪です。adminは適当なパスワードを使っているらしいみたいな文言があればまだマシですが、そう言った文脈を一切抜きに辞書攻撃を求めるのはもってのほかです。パスワード辞書なんて正直無限にありますし、片っ端から試せばサーバに負荷もかかります。CTFはペネトレーションテストではありません。どうしてもそういう問題を出したいのならば、参加者数を限定した状態で、相当のスペックのサーバを用意すべきでしょう。もっともそのような環境が用意できたとしても僕は出題したいとは絶対に思いませんが。
他には .index.php.swp などの典型的なURL予測も含まれると思いますが、これは僕が過去にやってしまったので強い言及は避けます。

解いたときに達成感が得られる

CTFに熱中している人の多くは、「問題を解いた時に得られる達成感が忘れられない」と言います。この達成感は問題を解いたこと自体もそうですが、問題を解く過程における試行錯誤の一つ一つが積み重なって最終的な解を導き出した、という体験を多く含んでいると思います。この試行錯誤の過程が長ければ長いほど問題は難しく感じ、また解けた時の達成感も大きいです。
では試行錯誤の過程をどうやって長くするかというと、

  1. パズル要素部分を難しくする
  2. 要素の数自体を増やす

という2種類の方法があります。
1は先に説明した通りで、なるべく典型ではない新規性のある要素が求められます。これは非常に難しいので、多くのCTFでは2の方法をよく見ます。
2は必ずしも良い方法ではなくて、単純に問題の組み合わせになっているだけ、みたいなのは避けるべきです。よく見かけるパターンの一つに「Webパートを解くとPwnパートが出てくる」といったものがありますが、例えばWebがインターフェースとなって呼び出しているバイナリをexploitする問題といった、何らかの関連性やストーリー性を持たせるべきでしょう(全く独立した2つの問題ならば最初から別々に出すべきです)。ただし各要素がそれなりの難易度を持っている場合、前述したように部分点を与えた方がよいです。

まとめ

少し長くなりましたが、完結にまとめると

  • 運営するなら少しでもいいからCTFに出ろ
  • どうでもいい要素を入れるな
  • 使い回しをするな
  • エスパーをやめろ(CTFはpentestではない)
  • 単に要素をくっつけただけの問題は複合問題ではない

です。よろしくお願いします。

また、CTFを開催するときに気をつけるべきポイントについては、アメリカのPlaid Parliament of Pwning (PPP) というチームの良い資料がこちらにあるので、読んだことない人はぜひ一度目を通して見てください。