サイト内の現在位置

CTF作問での新たな挑戦と得られた気づき

NECセキュリティブログ

2020年8月28日

NEC サイバーセキュリティ戦略本部セキュリティ技術センターの木津です。
CTF(*1)は競技者として参加する方が多いですが、毎年、NECセキュリティスキルチャレンジ(*2)は、運営側として関わっています。今期も作問側として参戦しようと新たな問題コンテンツの作成に着手し始めました。作問の前に前回分を振り返っていたところ、前回色々な気づきが得られたことを思い出しました。
blogでその一部を共有してみたいと思い、書いてみることにしました。

*1: 情報セキュリティの技術力を競う競技"CTF(Capture The Flag)"のこと
*2: NECセキュリティスキルチャレンジは、2015年度の初開催から過去5年開催してきた大会であり、参加者に2週間で約100問の問題に挑戦してもらう、年に一度の大規模な社内CTFイベントです。

参考: https://jpn.nec.com/cybersecurity/blog/200327/index.html

CTF作問での新たな挑戦

作問を始めた時期は、NECセキュリティスキルチャレンジの立ち上げ時からですので5年前くらいになります。作問メンバが足りなくて開始したというのがきっかけでした。

今まで、色々失敗してきました。
難しくし過ぎてしまった結果、競技期間中に誰にも解かれず悲しい経験をしたことがありました。最終的に得られるフラグが曖昧になり、参加者アンケートの結果で低評価になったこともありました。

失敗ばかりではなく、PPAPという名前のReversing分野の問題を作ってみたところ、多くのコメントをもらって嬉しかったこともありました。競技参加者アンケートの結果で決まる優秀作問賞に選出されたこともありました。

様々な経験を通し、できる限り今までにないタイプの問題を作って、「あの問題、おもしろかった!勉強になった!」と感じてもらえるような問題を作ってみたいと、作問への思いは変化していきました。
上記のような流れで、前回はGitLab Community Edition(以降GitLabと記載*3)の自動デプロイ/自動テストの機能を使った問題作りに挑戦してみました。

*3: GitLab はDevOpsライフサイクル全体をカバーするオールインワンのアプリケーションです。ソース管理だけでなく、wikiの機能やタスク管理機能、CI/CD(Continuous Integration/Continuous Delivery)機能など多くの機能をもつツールです。
URL : new windowhttps://gitlab.com/gitlab-org/gitlab
ライセンス: 機能制限のあるCommunity EditionはMITで、全機能が使える有償版が存在します。

GitLabを使って作問を進めることにした理由は概ね次の通りです。

  1. CTFの世界大会でGitLabを使った問題コンテンツが出題され始めたのは2018年のTAMUctf 18からで、GitLabのCI/CD機能を使って見事にセキュアコーディングの問題を作成されていて、衝撃を受けたこと
  2. クイズ形式でポイントを重ねていくJeopardy形式では出題しにくかったタイプの問題が作れそうなこと
    <作成可能な問題コンテンツの例>
    + Webアプリの脆弱性をついて任意コマンドが実行できるタイプの問題(競技の参加者に環境を破壊される可能性がある問題)
    + セキュアコーディングの問題で、穴埋め式ではなく、実際にコーディングしてもらい、その結果を自動検証するタイプの問題
    + Exploitの精度を問う問題
  3. 技術者育成目的として有効な新しいタイプの学習コンテンツを作るための基盤にすることができると感じたこと
  4. 仕事でGitLabを触るケースがでてきたこと
  5. 同じ出題形式の問題だけではあきてしまうため、変化をつけたかったこと

GitLab組み込み時の課題

CTFで使うことを想定しつつ、GitLabを触りはじめて感じた主な課題を以下に記載します。
GitLabが想定している使用目的から外れ、想定外の利用になりますので色々課題がでてきます。

  • a.
    自動デプロイ・自動テストを行う際の設定ファイルがユーザの触れるスペースに存在し、自動デプロイ・自動テストの過程で任意コマンドを実行可能
    設定ファイル.gitlab-ci.ymlの例を記載します。
=== ここから ===
stages:
- build

variables:
CI_DEBUG_TRACE: "false"

Job01:
 stage: build
 script:
  wget -d -O - --post-data
   "job_id=${CI_JOB_ID}&pj_path=${CI_PROJECT_PATH}&pj_name=${CI_PROJECT_NAME}"
   http://127.0.0.1:28080/build_and_test

 only:
  - master
 tags:
  - ssh-runner
 artifacts:
  paths:
  - output
=== ここまで ===

例えば、上記のscriptの部分に任意のコードを埋め込んで、デプロイ先端末で指定コマンドを実行し、GitLab上で結果を得ることなどが可能ですので危険です。

  • b.
    参加者が1000人を超える規模になるため、CI/CDのキュー上にあるJobが上限数を超えない仕掛けにする必要有
    Jobを並列処理する方法の検討や、処理待ち時間1Hになった場合の対策などを検討しておく必要があります。
  • c.
    競技時間中に、GitLabの操作で混乱が生じないよう不要なメニューを隠す必要有
図1 GitLabのWebインターフェース

図1の通り高機能ですので、何をしたらよいか混乱を招く可能性があります。例えばwiki機能やSnippets機能は、CTFで必要ありません。

  • d.
    競技者それぞれがアップロード可能なファイルサイズの制限を設ける必要有。ディスク残量の監視とディスク溢れ対策が必要
    無制限にするとディスク溢れが生じる可能性があります。
    など

    a以外は簡単に対応できそうに見えましたが、bもはまりました。

課題に対する対策

すべて記載すると長くなりそうですので、aとbを中心に記載します。

前述の課題a「自動デプロイ・自動テストを行う際の設定ファイルの改ざん」については次の3つの対策で対応しました。

  • x.
    不正ができないよう自動デプロイ・自動テストを行う装置を4つに分けて構成し、通信や実行できるコマンドの制限を実施
  • y.
    自動デプロイ・自動テストの設定ファイルの改ざん検知リカバリ機能を実装
  • z.
    GitLabのSystem hooks APIの機能を利用してリポジトリの更新と同時に、不正の確認を行い、確認結果を通知するよう設定

まずxについてですが、改ざんが発生しても影響がでないようにするため、次のようなシステム構成にしました。

図2. 自動デプロイ・自動テストのシステム概要

すべてを1つの装置として構成することも可能ですが、セキュリティの観点から分けるべきと考え、分けて構成するようにしました。
前述のschroot、テスト制御用Webサーバ、Dockerコンテナから外部NW(インターネット)にパケットが出て行かないようファイアウォールやルーティングテーブルの設定などを工夫しました。

また、どんなテストがDockerコンテナ上で実施されているか競技参加者に見えないよう実装しました。実施されているテスト内容が分からない場合、どこまで修正したらよいか見通しが立たず、問題難度が跳ね上がると思いましたので、そのように設定しました。逆にテスト内容が分かると対処方法が明確になります。テスト内容は競技参加者が減点覚悟でヒントをオープンした際に参照できるようにしました。

yの改ざん検知およびリカバリ処理は、 自動デプロイ/自動テストの手前で呼ばれるプログラムで行い、必要に応じてリカバリ処理を行った後、デプロイおよびテストを実行するよう実装しました。
改ざん検知は単純にファイル差分を比較して行い、リカバリ処理は元の設定ファイル.gitlab-ci.ymlをリポジトリに再登録するというシンプルな内容にしました。

設定ファイル.gitlab-ci.ymlの具体例を以下に記載します。

=== ここから ===
 stages:
   - test

 TestJob01:
  stage: test
  script:
   - wget -d -O - --post-data
    "job_id=${CI_JOB_ID}&pj_path=${CI_PROJECT_PATH}&pj_name=${CI_PROJECT_NAME}"
    http://${BUILD_HOST}:57017/testweb
  only:
   - master
  tags:
   - runner05
=== ここまで ===

zのSystem hooks APIの機能を利用した通知は、GitLabのAPI機能が充実しているため、簡単にスクリプトで実装できました。リポジトリの更新を検知してすぐ改ざん検知・リカバリまで行う方法も検討しましたが、タイミング的な制御が難しく、検知・リカバリ漏れが出てきそうでしたので、最終的には前述の通り、自動デプロイ・自動テストの直前で行う方法にしました。

課題b「自動デプロイ・自動テストのJobの並列処理」については、設定のみで対応できそうでしたが、思い通りにGitLabが動作してくれず、多くの時間を要しました。

GitLabで自動デプロイ・自動テストを行うコンポーネントはRunnerと呼ばれています。試行錯誤の結果、一つのRunnerで同時に処理できるJobの数を一つにして、複数のRunnerでJobを処理する構成にしました。

設定の具体例を以下に記載します。

設定ファイル/etc/gitlab-runner/config.toml
=== ここから ===
concurrent = 8 # Runner全体で同時に処理することのできるジョブの数を指定
check_interval = 0

[session_server]
 session_timeout = 600

[[runners]]
 (省略)
 limit = 1 # 不正操作を防ぐため、各Runnerでは並列処理を行わず、
              # シーケンシャルに一つずつJobを処理するよう設定
 [runners.ssh]
 (省略)

(以降、同様に7つRunnerの設定が続いて長くなるため省略)
=== ここまで ===

Runner全体で同時に処理することのできるジョブの数を設定ファイル中のconcurrent で指定できます。concurrentの値を増加させて、各Runnerに複数のJobを同時に処理させた場合、他の競技参加者のソースコードが参照できてしまう可能性があるという問題が見つかり、最終的に各Runnerが同時に処理できるJob数を一つに制限し、runnerの数を増やすことで対応することにしました。

最初はRunnerを8つ作っておき、足りなくなった場合は随時追加していく方針にしました。各Runnerにそれぞれ負荷が偏らないよう各ユーザのネームスペースにあるプロジェクトを割り当てていくことにしました。

cとdは設定および簡単なスクリプトの作成で対応しました。

上記の他、競技開始前に何度か問題の微調整を行ったのですが、問題コンテンツ(ソースコード)の更新履歴にフラグやヒントに類する情報が残ってしまったため、一部の更新履歴をファイルごと一旦消して対応するという方法をとりました。
普段の開発では行わない操作と思いますので、以下に記載しておきたいと思います。

出題した問題の概要

登録した問題コンテンツの概要を以下に記載します。4つの問題を登録しました。

  • セキュアコーディング関連 2問
    a-1. SQLインジェクションの脆弱性があるプログラムを修正する問題
    a-2. OS Command Injectionの脆弱性があるプログラムを修正する問題

a-1,a-2はソースコード修正後に予め用意してある正常系および異常系の自動テストを実行し、すべてのテストにパスするとフラグが表示される問題です。
a-2は、シェルが取れるとなんでもできてしまうため、出題しづらかったタイプの問題です。更新されたプログラムを基に正常系および異常系のテストを実施するだけにしましたので、安心して見ていることができる問題になりました。

  • Exploitの精度を問う問題 2問
    b-1. リモートサーバの脆弱性をつくプログラムを作成し、作成したプログラムを10回試行して、1回以上リモートサーバ上のファイルの中身を参照できることを証明する問題
    b-2. リモートサーバの脆弱性をつくプログラムを作成し、作成したプログラムを10回試行して、ミスなく10回リモートサーバ上のファイルの中身を参照できることを証明する問題

b-1,b-2はバッファーオーバーフローに関する問題ですが、ASCIIコードしか入力できないように入力チェックを加えた問題です。CTFで稀に見かける問題にしました。
b-2はb-1とリモートサーバの作りは同じですが、10回中10回確実に攻略できるプログラムを作成するためには一工夫必要で、その解き方は検索エンジンで探しても見つからないことを確認した上で出題しました。

どのような別解がでてくるか、どのような攻撃(不正にフラグを取得しようとする試み)があるか、期待できそうな問題を色々考えた結果、上記で結果を見てみることにしました。

対策の効果と得られた気づき

対策は概ね機能したものの、競技期間中に、自動デプロイ・自動テストの設定ファイル改ざん事象が発生しました。

改ざんおよび自動リカバリをする設定にしていましたが、改ざんチェックが一部の問題コンテンツで外れていたこと(問題の最終調整時に一旦外して、そのままの設定になってしまっていたこと)が原因で、検査をすり抜けたことが分かり手動対応しました。ホストの隔離設定やWebサーバでの厳密な入力チェックにより、不正にフラグが取得される可能性は少ないと思っていましたが、想定外の事象でしたので冷や汗がでました。無事、不正にフラグを取得されることなくリカバリできましたが、急ぎ対応が必要なプレッシャーで対応時に手が震えました。

対応後も数回、改ざんを検知する事象が発生しましたが、以降は想定通り対策が動いてくれて、安心してみていることができました。

上記の他、今回の実装の穴をつく攻略法でフラグ奪取を狙う競技参加者も現れました。

例えば、b-2の問題ですが、一つのexploit中で複数回接続試行してフラグを奪取する、という試みが行われました。
exploitのエントリポイントになっているシェルスクリプトに次のような行が複数行入っていました。
(printf
"1234¥nRRYh00AAX1A0hA004X1A4hA00AX1A8QX44Pj0X40PZPjAX4znoNDnRYZnCXA¥n"; sleep $n ; printf "cat ./info.txt¥n") | nc $1 $2

コネクション数の制御も必要と気づかされました。想定できていませんでした。1回の試行のタイムアウトを3秒にしていましたので、攻略まではできなかったようです。
その他にも色々な別解、想定外の攻略法がでてきて大変勉強になりました。
競技後に2週間くらい問題コンテンツのヒントおよび解説を全公開するのですが、公開のタイミングで、別解として問題コンテンツの解説に追記して競技参加者にも別解を共有することができました。

その他の得られた気づきを以下に記載します。

  • GitLabのRunnerを複数事前に準備して自動デプロイ・自動テストのJobの投入先を制御することで負荷を分散可能で、スケールアウトしていくことも可能と分かりました。実際競技期間中Jobがキューにほとんど溜まることなく、有効に機能することが分かりました。
  • 攻める側のアプローチの仕方を見ることができて参考になりました。自動テストの実行中に複数回接続試行できないようにするなど、更に対策が追加で必要ということが分かりました。
  • GitLab(gitサーバ)を使うことで色々な別解を入手できました。競技者に共有することもできました。
  • 4問作成し、1200ユーザ登録して それぞれ4つのプロジェクトをforkした結果。ディスクの使用量が約4GB増加しました。更に2週間運用してみた結果、実施した対策で最後までディスク溢れの心配はなく、運用可能ということを確認できました。ディスクは1TB用意していました。

初めての試みということもあり手探りで動いていますので、まだ気づいていない課題やもっとエレガントな対策があったかもしれません。様々な視点で議論してみるといろいろ面白いアイデア出てきそうな気がします。もし、CTFについてお話できる機会がありましたら、競技に関することだけでなく、過去に出題した問題などについても意見交換できるとうれしいです。

まとめ

GitLabのようなCI/CD機能を持つツールで、今までできなかったタイプのCTFの問題が色々作れることが分かりました。少しでも参考にしていただけるような情報がありましたら幸いです。

まだまだ同じ仕掛けで違うタイプの問題が作れそうです。セキュリティとAIを絡めて、作成したモデルの予測精度を問う問題とかもできそうです。色々アイデアが浮かんできます。
競技者としてではなく、作問をやっていても、色々な気づきが得られます。視点を変えると色々見えてきて視野が拡がります。
作問仲間もできますし、勉強会で話のネタになりそうな経験ができるかもしれません。
競技者の想定外の行動にドキドキすることがありますが、それも楽しみの一つになりました。

最近、Hack The Boxをやってみて、ペンテスト力向上につながるタイプの問題作成、ハードウェア分野の問題作りにも興味を持つようになりました。得られた経験を基に、新しいタイプの問題作成に挑戦し続けていきたいと思います。

執筆者プロフィール

木津 由也(きづ よしや)
セキュリティ技術センター リスクハンティングチーム

主にネットワークセキュリティ製品・サービスの開発に従事してきたが、最近はCTFに取り組んできた経験を活かし、ペネトレーションテスト、脆弱性診断などの領域にも仕事の範囲を拡大中。
2013年にnoraneco という社会人CTF チームを立ち上げ、現在は主にPwn/Reversing 問を担当。
SANS - Cyber Defense NetWars 2019.10 1位(Team)
SECCON 2019 国際決勝5位
Hack The Box - Omniscient