こういうのは大したためにもなりませんがブログに残しておくと残さないより良いことがありそう。
私の所属する研究室は OCamlab ということになっていますが、 師匠と私が所属しているのでCTFにそれなりに力を入れています。今日は近くに迫った高専セキュリティコンテストを見据えて、 OCamlab CTF #2 を開催しました。
OCamlab CTF は OCamlab CTF勉強会の番外編のようなものです。CTF勉強会は夏休みに入ってから始まったもので(それまでは各位の受験があったので)、私と師匠がCTFの過去問を解説したり、師匠が OCamlab CTF #1 を開催したりしていました。参加者は主に研究室同期(キングと呼ばれているのでここでもそう呼称します)や部活(joken)の後輩です。
本来であれば今回も師匠が問題をつくって無事開催、となるところだったのですが、一昨日の夜に「mysqlが動かなくなってやる気が死んだのでふるつきがやって」と投げられてそこからほぼフル稼働で急いで準備を行いました。去年の高専セキュリティコンテストが King of the Hill 形式だったので今年もそうだろうと思って King of the Hill 形式の問題を3問と、スコアサーバやDefense Flagのチェッカ*1 を作りました。開催前に指摘されて気がついたんですが、今年は Jeopardy 形式だったんですね。いや申込み時点では King of the Hill って書いてたんですよ(大嘘)。
そんな感じで 3時間程度の CTF を開催したのでスコアサーバ頑張ったよという報告と、どんな問題を作ったのか振り返りをやります。
スコアサーバ
私はWebアプリケーションは PHP でしか作れないので、PHPでやりました。毎回 Laravel に挑んでは挫折しているので、今回は時間が少ないことも踏まえて最初からLaravelを選択肢から外し、簡単なRouterとTemplate Engine だけが乗っている Slim3 を利用することにしました。 Slim も初めて触りましたが、小さくて何をすれば動くかがわかりやすくてよかったです。 コマンドを叩いて scaffold を作って、というのは私には早すぎた。
データベースにはいつものように sqlite を使って、一応 wrapper として medoo を採用しましたが、 join や group by が必要になるところでは横着をして 生SQL を書いたのでどちらでも変わらなかったかもしれません。とにかく頑張ったので、それなりに見られるスコアサーバができました。全部書くのに5時間くらいかかった割には機能が少ないのがチャームポイントです。
KoHのキモであるDefense Flag の集計は cron で php スクリプトを実行することで行いました。ディレクトリ構造を後から変更する可能性があったので、各問題について socat TCP-LISTEN:XXXX,fork,reuseaddr,bind=localhost EXEC:'cat defense_flags'
のような感じで Defense Flag の一覧を取れるようにしておく戦術をやったのですが、途中で一回止まったので安定性には欠けます。
各問題もWeb問題はApache としても、 python で書いて運用する必要があるものは socat で動かしていました。こちらは安定していました。
Crypto + Crypto [300]
最初に考えた問題です。去年の高専セキュリティコンテストで準同型性を持つオカモトウチヤマ暗号が出題されていたので、同じ準同型性を持つ暗号として Paillier 暗号をテーマに、
- サーバで生成した秘密鍵・公開鍵が送られるので Enc("pascall_paillier") を送ると Attack Flag
- 送られてきた文字列に Enc("Takoyakitabetai") を乗じたあと Decrypt したものを Defense Flag として書き込み
のようなスクリプトを書いてソースコードを公開する形で問題にしました。 Paillier 暗号については 英語版 Wikipedia を信じればよいのか、 公開鍵暗号 - Paillier暗号 - ₍₍ (ง ˘ω˘ )ว ⁾⁾ < 暗号楽しいです を信じればよいのかというところでしたが、とりあえず平易なWikipedia版の式を試したらうまくいったのでそちらを採用しました。
これは師匠が割とすんなり解いていました。
Microblog [200]
Web 問題です。 なにかしらのマイクロブログのようなものをイメージしていました。
- admin としての 最新の 20件 の投稿にDefense Flagが含まれていれば得点
- admin のパスワードが Attack Flag
という感じでやりました。 ログイン時の SQL クエリを文字列の埋め込みで組み立てているという脆弱性があり、 ユーザ名を admin
・パスワードを 'OR'a'='a
などとするとログインができるのでDefense Flagを書き込むことができます。 admin のパスワードは ' OR password like 'OCamlab{%
などとして Blind SQL Injection で求める想定でしたが、このスクリプトを実行したところ、先頭 10文字程度が適当でも通りました。なんでだろう。 OR password like 'a%
でなぜ通るんだ。わからず仕舞いです。
師匠は開始早々あっさりとDefense Point を獲得しましたが、Attack Flagの取得に苦戦していました。 UNION SELECT をつなげてどこかしらにパスワードをそのまま印字しようとしていたのですがそのような仕組みはなかったので仕方ないですね。 Blind SQL Injection の存在を思い出してからスクリプトを書くのはすぐでした。
キングと後輩氏も後半になって 'OR 'a'='a
に成功していましたが、 Defense Flag は師匠の妨害で流されてしまっていました。
Knock Knock [100]
中途半端な問題です。 python スクリプトで ICMP のパケットを監視していて、
- データ部の末尾が
requestflag
なら ICMP を送ってきたアドレスの TCP 50000 番に Flag を送信 - ICMPを送ってきたアドレスが
1.2.3.4
なら データ部末尾のnバイトを Defense Flag として受付け
しています。 Scapy の練習のような問題でした。 ICMP のデータ部を活用した問題を作りたかったんですね。
これも師匠は楽々解いていました。自分の環境が pyenv なことを忘れて sudo pip install scapy
して「インポートできない〜」って言っていたことを除いて。後輩氏は手が出ず、キングには助言をたくさんして解いてもらいました。
Flag Comparator[200]
今朝ぎりぎりに追加した問題で、 KoH ではなく Jeopardy 問題です。 GDB の練習用に作ったような問題で、 文字列を受付けて Flag かどうかを判定します。文字を 4バイトずつ取得して配列に保存しておいたデータと比較するのですが、前回の入力との xor を取るようにして strings で文字列が見えないようにしています。
師匠は IDA で解いて、キングは時間外にIDAで取り組んでいましたが、助言の甲斐も報われず持ち帰りとなりました。
以上になります。 CTF としての結果は師匠が圧倒的で、キングと後輩氏はかろうじて得点できていた、という感じです。今回は問題を作るのはとてもむずかしいということを学びました。師匠は内輪に向けていろいろ問題をつくってくれていますがこれは神だったんだなぁ。
今回のスコアサーバや問題はストレージサービスのように利用している GitHub にあります。
GitHub - theoldmoon0602/OCamlabCTF2
GitHub - theoldmoon0602/Crypto-Crypto
GitHub - theoldmoon0602/microblog
GitHub - theoldmoon0602/knock_knock
GitHub - theoldmoon0602/flag_comparator
*1:これ名前がついていませんでしたか?