国際地域対抗CTFのICC2022に参加しました

cdi-maeda_20220623155622

こんにちは、技術部の前田です。

ICC2022というCTFの地域対抗の国際大会にCTFプレイヤーとして参加したのでその一部始終や簡易Writeupを報告します。

はじめに

International Cybersecurity Challenge (ICC)はICC委員会が主催するCTF大会です。 ICC委員会は世界の各地域を統括する団体から構成され、2022年度はアフリカ、アジア、カナダ、ヨーロッパ、ラテンアメリカ、オセアニア、アメリカの7地域の団体が参加しました。 開催されたCTFへもこれらの7つの地域から1チームずつが参加しています。

このICC2022は今年度が初開催のイベントで、ギリシャのアテネで開催されました。 ヨーロッパでの開催なので、ICCの他にヨーロッパチームをバックアップするENISAもICC2022の主催として参加しています。 なお2023年も開催されることが決まっていて、既に次の予選が目前に迫っている地域もあります。

アジアチームについて

アジアチームはAsian Cyber Security Challenge (ACSC)という団体がCTFによる予選会を開催し、その中の成績優秀者8カ国15名から構成されています。 出場可能な選手には年齢制限が設けられており、1995年1月1日以降の生まれの人が参加できます。

  • インド: 2名
  • 日本: 3名
  • マレーシア: 1名
  • シンガポール: 2名
  • 韓国: 2名
  • 台湾: 1名
  • タイ: 1名
  • ベトナム: 3名

他の地域、特にヨーロッパやアメリカは15人以上のメンバーを集めて2次選抜を行ったり、メンバーが決まった後にCTFの練習などをしたそうですが、アジアチームでは特にそのようなイベントはありませんでした。 アジアチームの特徴としてメンバーの分布範囲が広く、集まって何かをするのは難しかったのと、更にはコロナ禍の状況も相まって気づいたら本戦が目の前にあったという印象でした。

というわけで、アジアチームは現地アテネで初顔合わせとなりました。 時差ボケ対策やメンバー間の交流を深めるため、ICC2022自体の開始と比べると丸2日ほど早く現地入りしました。 その空きスケジュールを利用してギリシャ入り初日はアジアチーム全員集合して顔合わせ兼食事会、翌日は日本勢と台湾・タイ勢と共にアテナイのアクロポリスへ観光に行きました。 私は大勢の人を一気に覚えるのは苦手なのですが、この期間のおかげで徐々にメンバーを覚えられたので助かりました。

Jeopady (1日目)

cdi-maeda_20220623161025

JeopardyはオンラインCTFなどでもよく採用されるルールで、十数問〜数十問の問題が用意され、解いて得られるフラグを提出すると1問ごとに得点を入手できます。 この日は朝9時から夜18時までの9時間で競技が行われました。

昼食休憩等は特になく、代わりに常時ビュッフェスタイルで軽食が提供され、好きなときに取りに行って食べる形式でした。 トイレ等で離席する帰りに取って来られるのはとても便利で、さらに串に刺さっている料理や一口大のトルティーヤが食べやすかったため、競技中はそればっかり食べていました。 また、飲み物もカウンターで注文するとすぐに受け取れるようになっていました。

cdi-maeda_20220624122826

ビュッフェではクッキーやフルーツも提供されていました

CTFでは基本の5ジャンル(Forensic, Reversing, Pwnable, Crypto, Web)から35問の問題が出題され、更にハードウェアで7問、脱出ゲーム形式のESCAPE ROOMチャレンジも出題されました。 これらの内容は事前に告知されており、特に基本5ジャンルについてはそれぞれの出題割合も公開されていました。

得点形式にはDynamicScoringが採用されていました。 DynamicScoringでは問題を解いたチーム数が多いほどその問題は簡単だったとみなして得点を低くします。 この得点の計算式がかなり厄介で、4チーム解くだけで点数が元の半分以下になってしまうため、どれだけ単独に近い状況で問題を解けるかが鍵になっていました。

私が主に担当したCryptoジャンルでは、暗号の学習プラットフォームであるCryptoHackの運営陣が作問に入っており、高難易度の問題が多数出題されました。 Cryptoジャンルの問題は7問出題されましたが、そのうち解かれた問題が2問しかなかったことがその難しさを物語っていると思います。

私はその中でもUnbalancedという「p,qのビット数が異なるRSAに対するSmall private exponent attackを行え」という問題にチャレンジしました。 この手の問題は既に論文が発表されていて、それを実装すると解けるというタイプの問題です。 すぐに攻撃を行う論文を見つけ実装してみましたが、理解力の甘さやミスが重なり、最終的に解くことはできませんでした。

Hardware Challenge

cdi-maeda_20220623155104

前述のUnbalancedが行き詰まってしまったため、代わりにハードウェアの問題に取り組み3問を解きました。

ハードウェアとは言っていますが、実際にはESP32上で動作するMicroPythonからハードウェア上に隠されたフラグを探し出すという形式で、真にハードウェア的なハックは要求されませんでした。

1つ目の問題は下のようなコードが提供された上で、「EFUSE_BLK2からデータを読みだせ」という問題でした。

def secure_read_efuse_block(block, start_offset, length):
    if block == efuse.EFUSE_BLK2 and length > 0:
        print('No access allowed to secure efuse region!')
    else:
        length = length if length > 0 else 256

最初はそもそもこの関数を使わずにeFuseからデータを読み出せばいいと思い試行錯誤していましたが、よく見るとlengthに負数を入れるとif文をバイパスできることに気づき、フラグを入手できました。 ちなみにハードウェア要素は全くありません。

2つ目の問題は「AES-CBCを使ったログインシステムでrootというユーザ名でログインしろ」という問題でした。 これもCBCに対するビットフリップ攻撃をすればよく、どちらかといえば暗号の問題でハードウェア的な要素はありませんでした。

3つ目の問題は「ESP32のRTC_FASTメモリ領域(0x3FF80000)からフラグを読みだせ」という問題で、その領域を読み出す関数read_rtc_memory(addr, size)は提供されているものの、実際にフラグがある領域を読み出そうとすると関数内の処理でブロックされるようになっていました。

ESP32のデータシートを見ると、メモリマップのRTC_FASTとして8KB*2の領域が定義されているのに実際は8KBとして扱われていることから、2つとも同じ物理領域を指していると予想しました。 そこでアドレスとして0x400C0000を与えて関数を呼び出すことでフラグが表示されました。

ハードウェア問題が出ると聞いて身構えていましたが、ハードウェア上でCTFの問題が出題されるというような形式でした。 他のメンバーが解いた1問もBrainf**kで足し算をするプログラムを書けという問題だったらしく、こちらもハードウェア要素はなさそうでした。 ただ、解けなかった問題の中には不正にFlashの中身を書き換えることを要求する問題も含まれていたため、解法が気になるところです。 Jeopardyは今後問題が公開されるそうなのでそのときに確認してみたいと思います。

ESCAPE ROOM Challenge

cdi-maeda_20220623155149

ESCAPE ROOM Challengeは、日本の脱出ゲームに着想を得た(本当に係の人が日本のやつって言ってました)チャレンジで、謎を解き明かしつつ爆弾を止めるという設定がありました。 もう少し踏み込んだことも言っていましたが、私のリスニング能力の低さから大部分をスルーしてしまいました。

このチャレンジにはチームから5~7人の代表者を選出して挑戦します。 制限時間は45分で、その間に車内を捜索しダイヤル錠や金庫の番号を見つけて開けるのを繰り返すと爆弾解除コードのヒントが揃っていき、最後に解除コードを入力すると爆弾が停止するという内容でした。

CTFの一環で出題された問題ですが、情報セキュリティ的な要素はほぼ必要なく、一般の人も参加できそうな形式の脱出ゲームでした。 後で調べたところ、モノとしてはこれをそのまま呼んでいたようです。 https://escaperoommobiel.nl/en/professional/cyber-security/

アジアチームからは6人が参加し、約16分を残して脱出に成功しました。 他のチームのクリアタイムは不明ですが、スコアボード上では4チームが脱出に成功していたようです。

Attack & Defense (2日目)

Attack & Defense (A&D)は、各チームに複数のサービスが動作するサーバが渡され、互いに攻撃して相手のサーバからフラグを奪う形式のルールです。 サービス内の脆弱性を探し、見つかれば攻撃コードを書きつつ脆弱性を修正し(パッチ)、さらに未知の脆弱性で点数を奪われればパケットも凝視して脆弱性を探すという大変忙しい形式でもあります。

今回のA&Dでは、大きく4つのサービスが用意され、その中の8ヶ所に運営からのフラグが書き込まれるという形式でした。 A&Dの各問題や競技結果はGitHub上で公開されています。

それぞれの問題の概要は次の通りです。

  • ClosedSea (Web, Crypto)
    • NFTの売買ができる(という体の)Webサービス
  • CyberUni (Crypto, Web)
    • 1つのシングルサインオン基盤と3つの小サービスからなるサービス群
  • RPN (Pwn)
    • 数値や文字列を保存可能なスタックマシン
  • Trademark (Crypto, Web)
    • データを登録してライセンスキーを持っている人がそれを閲覧できるサービス

ICC2022ではroot権限のあるVMがチームに1つ与えられました。 VMの上には4つのサービスのDocker環境が用意されていて、場合に応じてソースコードやバイナリファイルをパッチしてビルドし直すことができます。 また競技時間はJeopardy同様9時から18時で、開始から1時間は各チーム間のネットワークは閉鎖され互いに攻撃できないようになっています。

ネットワークがオープンされると、運営チームは1ラウンドごとに各チームのサービスの動作確認をし、その後フラグを書き込む処理を行います(SLAチェック)。 この書き込まれたフラグを他チームから奪って運営に提出することで点数をそのチームから奪い取ることができます。 なお、古すぎるフラグは受け付けないようになっているため、後から攻撃を成功させて一気にフラグを奪い取るようなことはできません。

A&D全体の総括をすると、かなりバランス良くできていて競技性が高かったと感じました。 ただA&Dの1ラウンドが2分と短めに設定されていたため、攻撃が遅いとフラグを取りこぼす可能性があったのは、暗号系で計算量が多い問題セットに対してはやや大変でした。

Trademark (1)

Trademarkは私が主に取り組んだサービスで、ライセンスキーの発行・管理を行うサービスになっています。 また、このサービスはC#で書かれていて、.NETを経由してLinux上で動作します。 競技終了後に運営に聞いたところ一番簡単なサービスだったらしく、実際に時間内に問題の脆弱性がほぼ全部発掘された上にA&Dらしい攻防も発生するなど、非常に楽しめた問題でした。

最初に見つけた脆弱性は AAAAAAA-AAAAAAA-AAAAAAA-AAAAAAA というライセンスキーが必ず妥当なものとして判定されるという脆弱性でした。 チェックアルゴリズムはn個のライセンスキーLnを使って表される、f(x)=A(x-L1)(x-L2)...(x-Ln) mod p という多項式のxにライセンスキーを数値化したものを入れて、値が0になればOKというものでした。 (x-Ln) という形の掛け算のうち1個でも0になれば式全体が0になることを利用した認証アルゴリズムです。 この多項式の実装を読んでいると、定数項の扱いが存在しない(定数項は必ず0)ことがわかったため、x=0を入力すると結果は0になることが予想できました。

この脆弱性に気づいたのは10:14頃で、既にネットワークがオープンになっている状況でした。 取り急ぎチームメンバーと情報を共有し、私は攻撃コード、チームメンバーがそのパッチを担当することになりました。

caronte が重すぎる問題

アジアチームではパケットの解析のため、caronteをVM上に導入していました。 caronteはA&D用のパケット解析OSSツールで、チームリーダーがセットアップしてくれたものです。 事前に行われたA&D環境のデモ会である程度の使い方は知っていましたが、実戦で使うといろいろトラブルが起きるものです。

caronteはDocker上で動作しており、VM側でキャプチャしたパケットファイルをボリュームマウントするように設定されていました。 caronteではキャプチャしたpcapファイルを適宜手動で読み込ませる必要があるのですが、これを実行しても10:06以降のデータが読み込まれないという問題が発生します。 また、読み込み動作をするとVMに相当な負荷が掛かっている様子も見受けられました。

原因が不明なので最初はそのまま放置してパケットを見ないでいたのですが、ある時チームのサービスすべてで一気にSLAチェックが失敗する現象が発生し始めました。 ここでやっとcaronteが重く、それによりサービスの動作が停止しSLAチェックに影響を及ぼしているであろうことに気が付きます。

すぐにリーダーに相談し、caronteを外部サーバへ移動させて事なきを得ました。 恐らくパケットが10:06までしか読み込まれなかったのはファイルが大きすぎてタイムアウトしたからだと考えられます。 開始から2時間ほどパケット解析ができなかったのはA&Dにおいてはかなり痛手でしたが、序盤は思っていたよりも攻撃が飛んでこなかったのでなんとか助かりました。

Trademark (2)

1つ目のパッチ後しばらく経ってもフラグの流出は止まりませんでした。 多くのチームが他の攻撃に着手していると考え、パケットキャプチャを読むことを考えます。 ちなみに自分の環境ではなぜかcarnoteへのパケットアップロードが機能しなかったので結局はWiresharkで読んでいます。 すると次のようなリクエストを発見しました。

POST /api/products/41/download?/api/login HTTP/1.1
...

このリクエストはユーザ認証なしでいきなりフラグを抜き取るものでした。 事前にこの辺りの挙動はコードリーディングでディレクトリトラバーサル的なことが起きるんじゃないか?と予想してメンバー内で確認していた場所でもあったため、すぐに確認しに行くと次のようなコード片を発見しました。

    public async Task InvokeAsync(HttpContext context, ApplicationDbContext db)
    {
        if (_blacklist.Any(context.Request.FullPath().Contains))
        {
            await _next(context);
            return;
        }
        //...
        //ユーザ認証処理
    }

_blacklist はリクエストに認証を必要としないURLが羅列される配列です。 要はAPIリクエストでログインや登録の部分は認証の処理をスキップするというコードになっています。 問題なのはContainsメソッドを使ってチェックしているという点です。 FullPathメソッドは他のファイルでURLのパス部分とクエリ文字列を結合するように定義されていたため、Containsを使ってしまうとクエリ文字列部分に /api/login のような_blacklistの文字列が紛れ込んだ場合に認証をスルーしてしまいます。

ContainsEqualsに変えるだけで直せる脆弱性ですが、パスの処理を書き換えてしまうため入念にローカルチェックをした後にデプロイしました。 ちなみに結果は動いたのでよかったですが、StartsWithメソッドを使うほうが適切だったかもしれません。

この脆弱性は11:30頃に発見し、約30分後にはパッチや攻撃スクリプトを完成させることができました。

Trademark (v.s. ヨーロッパチーム)

Trademarkの3つ目の脆弱性は、多項式を直接解くことでライセンスキーを復元するというものでした。 これについてはチームメンバーが発見し、パッチや攻撃スクリプトの用意が完了しています。

しかし問題はこの後で、ヨーロッパチームからのフラグが途絶えます。 この時間帯ではヨーロッパチームがフラグ出力のSLAに失敗しており、実際にアジアチームから提出したフラグは無効として扱われていました。

その後しばらくするとSLAが下がることを危惧したのかヨーロッパチームのサービスが復活し、また少しフラグが盗めるようになります。 しかし、またすぐにフラグが入手できなくなりました。 ここからはTrademarkに関わった全員でヨーロッパの挙動をチェックするようになります。

結論から言うと、ヨーロッパチームはアジアチームのスクリプトのパスワード長が一定なことを利用して、特定の長さのパスワードのときに登録が失敗するようにサービスを書き換えていました。 ユーザ名とパスワードの長さをランダムにするようにスクリプトを変更し、引き続きヨーロッパからフラグを盗めるようになりました。

その後もアジアチームの些細なスクリプトの挙動を察知してヨーロッパチームがフィルタを作成し、それをアジアチームが回避するということが数回続いて競技が終了しました。 簡易的なフィルタを書いて攻撃スクリプトを弾くのは攻撃側のリソースを削る効果もあり、実際にTrademarkチームは他の問題にあまり集中できませんでした。

ちなみに競技最後の1時間はスコアボードの点数が変動しなくなる(フリーズする)ようになっていたのですが、その間にアジアチームのパッチはオセアニアチームに破られていたようです。 完全にパッチしきったと思っても油断は禁物ですね。

競技結果

競技の結果はA&D部門で優勝、総合では準優勝でした。

Jeopardy部門でも2位は取れていましたが、1位のヨーロッパチームとその時点で2600点差付けられていたため、A&Dで逆転しきることができなかった形になります。 Jeopardyの2~4位では点差が1000点しか開いていないことからも、ヨーロッパチームのJeopardyでの活躍が凄まじかったことがわかります((現在公開されているJeopardyスコア表はないため内部の情報です))。

cdi-maeda_20220623155339cdi-maeda_20220623155348
ガラス楯と表彰発表時のレター

おわりに

ICC2022の様子は伝わったでしょうか。 JeopardyとA&Dの2形式で開催されたこともあり、内容がかなり盛りだくさんになりました。

CTF以外にも国際交流という観点でも多くの経験が得られました。 私は以前DEF CON CTFの本戦に出場した経験がありますが、その時は日本人チームでの出場だったため、チーム内言語は日本語でした。 一方アジアという枠では英語でコミュニケーションを取らざるを得ません。 私は英語に自信はありませんが、メンバー全員がトッププレイヤーなこともあってニュアンスが通じれば理解してもらえたので、とりあえず何か話してみるという精神を持てました。

私は95年生まれなので代表選手になれませんが次回もアジアチームはICCに参加すると思うので、96年以降生まれの腕に自信のあるCTFプレイヤーはぜひ予選に参加してみてください。

最後に、一般社団法人セキュリティ・キャンプ協議会様からはACSCの予選の運営を始め、日本人プレイヤーの渡航費・宿泊費・食費等まで、多大なご支援を賜りました。 この場をお借りして感謝申し上げます。

© 2016 - 2024 DARK MATTER / Built with Hugo / Theme Stack designed by Jimmy