初めて外部のCTFで作問をした話

この記事はIPFactory Advent Calendar 2020の16日目の記事です。

今回初めて仙台CTF 2020という外部のCTFでWeb問題の作問をさせていただいたので、作問者視点でのWriteup含め色々書きます。

仙台CTFについて

仙台CTFとは、仙台CTF推進プロジェクトという団体が主催するCTF大会です。 で、この仙台CTF推進プロジェクトはどういう団体かというと、公式には以下のように記載があります。

仙台CTF推進プロジェクト(以下、本会という)は、実務に活かせるサイバーセキュリティ分野の技術をテーマとしたスキルアップ・イベントを開催し、産学連携により、地域におけるサイバーセキュリティ人材の育成と相互交流を活性化させることで、サイバー攻撃による被害低減を図り、地域社会への貢献を目指すことを目的に設立された任意団体です。

詳細は以下に。
https://www.sendai-ctf.org/about-us/about-sendai-ctf/

仙台を中心に東北地域のサイバーセキュリティ人材の育成や、地域での界隈を盛り上げようと社会貢献をしている団体ですね。 仙台CTF推進プロジェクトが運営している大会としては3回目の開催であり、全身の仙台CTF実行委員会が運営していた仙台CTFを含めると4回目の開催です。

この仙台CTFは、他のCTFではほとんど見たことの無い制御セキュリティの問題があり、前回は大会会場で、今回はオンラインで実際に動いている制御機器を用いるというような珍しい問題が出るのも特徴的です。

作問をするにあたって

の前に、なぜ今回作問をするに至ったかについて。

そもそもなぜ私が作問をさせていただいたかというと、仙台CTF推進プロジェクトの前身である仙台CTF実行委員会が運営を行っていた仙台CTFから3年連続で大会に出ていて、そこで知りあったスタッフの方からお話をいただいたという経緯がありました。

作問をするにあたって、何のジャンルでどの程度の難易度を想定して作問するのかを悩みましたが、昨年までの仙台CTFではWeb問題が無く、私自身がWeb問題が欲しかったなという思いがあったので、Web問題を担当させていただくことになりました。
難易度については、connpassに記載されていた、「情報セキュリティ技術に興味があり、これから学習を始めてみたい社会人や学生など」という対象を参考に難易度を決めました。

問題について

Web2のEasy Injectionですが、想定解から言うと、UNION句を用いたSQL Injectionを行うというような問題でした。

問題文はこちら

[背景] ・株式会社仙台シーテーエフでは、新人セキュリティ担当の教育のため、先輩社員が脆弱性のあるウェブサイトを構築し、セキュリティ診断させることとしました。 ・さて、本日の教育メニューは・・・?
[問題] ・練習用ウェブサイトでは、管理者のパスワードが脆弱な状態で保存されているようです。管理者ユーザーのパスワードを窃取できる脆弱性を探してください。

以下のWHERE is_hide = 0 AND (title LIKE '%%%s%%' OR body LIKE '%%%s%%')という部分がユーザ入力を直接受け取っており、ここにSQL文を注入していくことになります。

func (r *repository) SearchContents(q string) ([]*model.Content, error) {
    c := make([]*model.Content, 0)
    query := fmt.Sprintf("SELECT c.id, user.name, c.title, c.body, c.created_at FROM content AS c "+
        "INNER JOIN user ON c.user_id = user.id "+
        "WHERE is_hide = 0 AND (title LIKE '%%%s%%' OR body LIKE '%%%s%%') ORDER BY created_at DESC", q, q)
    err := r.db.Select(
        &c,
        query)

    return c, err
}

クライアントサイドでの注入部分は以下のコンテンツ検索部分。 f:id:somebodyN:20201216004211p:plain

シングルクォートを含む文字列を入れて検索すると、以下のようなエラーが出ます。

Error 1064: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'test%' OR body LIKE '%'test%') ORDER BY created_at DESC' at line 1

ここでは'ユーザ入力%' OR body LIKE '%ユーザ入力%')というようなSQL文が動いていると推測できます。 つまり、SQL文を壊さないようにSQL文を実行するには、') #といったように、)で閉じてあげることが必要になる。

次に、管理者のパスワードが格納されていそうなテーブルを探すために、スキーマが見れるか確認してみる。') UNION SELECT "" FROM information_schema.columns; #このようなSQL文を入力すると、Error 1222: The used SELECT statements have a different number of columnsというエラーが返され、カラム数を揃えれば見れそうだとわかる。カラム数を5つにすると、エラーが変わり、次のようなエラー文になる。sql: Scan error on column index 0, name "id": converting NULL to uint64 is unsupportedなので、エラー文の通りカラム1に数値を入力し、次のようなSQL文でテーブル名を確認すると、データベース内のテーブル名が出力される。') union select 0, "", "", "", table_name from information_schema.columns; #

後は、以下のようにして判明したuserテーブル内のカラム名を出す。 f:id:somebodyN:20201216011448p:plain

最後は、') union select id, name, password_hash, is_admin, created_at from user; #といった具合で管理者のパスワードハッシュを取得できる。 f:id:somebodyN:20201216011941p:plain

パスワードハッシュはぱっと見明らかに脆弱そうで、これはそのままgoogleに投げるとデハッシュされたflagが出てくる。

この問題では、アプリケーションがどのような動きをするのか、どこに脆弱性がありそうか、どんなSQL文が動いているのかを想像してほしいという意図がありました。

「現場で役立つ技術」を題材としており、CTFの競技途中で作問者による解説&ヒント提供タイムを設けるなど、初心者も気軽にご参加できるイベントとなっております。

そのため、単にシングルクォートを入力しただけでは動かないような文にしたり、色々機能をつけたりしてみました。 ただ、そこが問題としてわかりずらくなってしまった感もあります。

反省点

競技時間中に数分間サーバがダウンしてしまったことに気が付かなかったのが、やらかしポイントその1でした。 一応ログ等は取っており、サーバがダウンしたことに気付ける状態にはなっていましたが、私自身がスタッフ側の人間としてCTFに参加してしまっていたため、気づかず、他の参加者の方の報告によって判明したというような形になってしまいました。
なお、アプリケーションのエラーの原因となった箇所は、なぜかアプリケーションがめんどくさくて開発段階のままホットリロードになっていたので、その場でエクストリーム修正をしてしまいました。これも反省点ですね。 また、サーバを貸していただいていたものだったので、スケーリングその他諸々もしておらず、そこもやりたかったなという思いがあります。 今後もCTFの作問者・運営として参加させていただく機会があれば、そこら辺の死活監視やエラーの対処、スケーリング等をきちんとやりたいなぁと思っています。

やらかしポイントその2としては、私がスタッフとして競技に参加する都合上、他の方の問題や難易度感を事前に知ることが出来ず、問題の配点を盛大にミスしたことです。 自分の中では、初心者向けの100点問題かなと思っていたのですが、競技が始まると、似ている問題のWeb1の最終問題が300点になっていて、ちょっとやらかした感がありました。 また、問題に挑戦してくれた人はそれなりの人数いたのですが、最終的なSolveが1人だったので、相対的なポイントの旨味がなくてSolveまでは行ってくれなかったのかなという印象でした。
この辺は運営側の方に見ていただいて判断してもらったほうが良かったかなという感じです。

まとめ

作問をさせていただいて、かなり得られたものがあったかなという気持ちです。 作問者側の観点を学べたり社会貢献もできたので。 またこのような機会があればぜひ挑戦させていただきたいですね。

あと、今年もちょっと仙台観光をしたかったな。残念。