Fetch the Flag CTF 2022の解説:Roadrunner
Giovanni Funchal
2022年11月10日
0 分で読めますFetchに参加してくれてありがとう! 参加してくれた数千人のプレイヤーの皆さん、Fetch the Flag CTFへのご参加、おめでとうございます。そして、チャレンジを作成、テストし、解説記事を書いてくれたSnykのメンバーにも心から感謝します!
脱出ゲームが好きなら、Snykの2022 Fetch the Flag大会に登場したRoadrunnerチャレンジもきっと楽しめるはずです!この記事では、サンドボックスから脱出できるコードの入力検証の脆弱性を悪用し、どのようにチャレンジに取り組み、解いたのかを説明します。
チャレンジ
ロードランナーより速く走れる?そんなの無理だよ、ホセ!
このチャレンジは、私たちを挑発するところから始まります。フラグを見つけられるでしょうか?
まず、ウェブページにアクセスすると、オンラインのコードプレイグラウンドのようなものが見つかります。このようなオンラインプレイグラウンドは、言語ツールチェーンをインストールせずに学習や実験を行うためによく使われます。Go言語にも公式のものがあります。
Goのコードを入力して実行をクリックできます。コードはバックエンドに送信され、実行結果が返されて、ウェブページに表示されます。
このウェブページを動かすバックエンドサービスのGoソースコード(roadrunner.go)と、このサービスをパッケージ化するためのDockerfileも提供されています。
最初に、与えられたヒントを確認し、いろいろ試してみます。
解説
いろいろ試してみる
こうしたチャレンジを解くには、好奇心が大切です。そこで、まず周りを調べていくつか試してみることにしました。私はGoのプログラマーではないので、オンラインで「hello world」を検索し、ウェブページに貼り付けて実行をクリックしてみました。すると「hello world」が返ってきました。ここまでは順調です。

この「hello world」の例では、fmtライブラリを使ってメッセージを出力しています。
次に、ブラウザーでウェブページのソースを調べました。実行ボタンが、フォームのsubmitアクションに紐付けられていることに気付きました。

続いてJavaScriptのハンドラーを確認すると、予想どおりバックエンドの/runエンドポイントにPOSTリクエストを送信し、レスポンスから結果ボックスの内容を設定していました。

次に、チャレンジで提供されているバックエンドのソースコードに注目しました。まずはDockerfileから見てみましょう。

フラグを見つけました!デプロイされたイメージのルートディレクトリにflag.txtファイルがあることがわかります。あとはこれを取得するだけです。このファイルの内容を読み取る方法を見つける必要があります。
手がかりを探す
roadrunner.goを見てみましょう。main関数から始めるのが定石です。

ここには/と/runの2つのエンドポイントがあります。まず、/を見てみましょう。これはwelcome関数で処理されています。

このコードではGoのhtml/templateライブラリを使い、index.htmlファイルを読み込んで属性を埋め込み、ウェブページのHTMLを生成しています。興味深いですね。
次に、/runエンドポイントを見てみましょう。これはrunner関数で処理されています。

ここではいくつか興味深い処理が行われています。まずサンドボックスを作成し、リクエストから入力スクリプトを取得して、サンドボックス内のファイルに書き込みます。続いてスクリプトのサニタイズ処理があり、実行しても安全かどうかを確認し、入力を拒否する場合もあります。入力が許可されると、スクリプトが実行され、結果が取得されてレスポンスとして返されます。
これを見たとき、最初に気付いたのは、dirname構造体のSandboxフィールドもリクエストのJSONからデコードされるため、ユーザーが制御できるということでした。このフィールドはファイル書き込み用のパスを作るのに使われており、脆弱性となります。ただし、どう悪用すればよいのかはわからなかったので、サニタイズ処理に注目し直しました。sanitizeScriptを詳しく見てみましょう。

このコードは入力スクリプトを解析し、インポートを確認しています。ファイル操作によく使われるGoライブラリを含むブロックリストがあり、入力スクリプトがそのいずれかをインポートすると、ちょっとおかしなメッセージとともに拒否されます。
目標は/flag.txtファイルの内容を読み取ることです。
最初に考えたのは、このリストに漏れがあるのではないか、あるいはファイルを読み取る別の方法があるのではないか、ということでした。先ほど述べたように、私はGoのプログラマーではないので、数分ほどオンラインで調べました。
ひらめき
以前、別の場所でファイルを読み込んでいたのを覚えていますか?

そう、またwelcome関数です。明らかにファイルを読み込んでいて、sanitizeScriptの禁止リストに含まれていないテンプレートライブラリを使っています。これを利用できるかもしれません。
そこで、このライブラリのドキュメントを確認しました。Templateオブジェクトを作成した後、テンプレートを実行すると、その結果がwriterに書き込まれます。

io.Writerインターフェースを文字列に変換すれば、それを出力できます。さらにオンラインで調べたところ、strings.Builderを使えることがわかりました(stringsライブラリもブロックリストに含まれていません)。
最終的な解決策は次のとおりです。

このコードを実行すると、flag.txtファイルの内容全体が出力されます。やりました!
Roadrunnerのまとめ
今回の学びを振り返りましょう。細工した汚染入力を使って、オンラインコードプレイグラウンドの入力検証の脆弱性を悪用しました。スクリプトのサニタイズ処理に不備があったため、サンドボックスから脱出できました。アプリケーションの内部動作に関する知識が役立ち、アイデアをすぐにエクスプロイトへとつなげられました。これはCTFのチャレンジにすぎません。実際の環境でこの種のエクスプロイトを組み立てるには、推測を交えた試行錯誤が必要になることもあります。
無料の静的アプリケーションセキュリティテスト(SAST)ツールであるSnyk Codeは、コード内の入力検証の脆弱性の発見と修正に役立ちます。
多層防御などの手法を取り入れることで、このエクスプロイトをさらに困難にできます。たとえば、runScript関数を変更し、権限を制限した別のユーザーとしてスクリプトを実行できます。この場合、権限昇格などの別の脆弱性と組み合わせない限り、この脆弱性だけでは悪用できません。
信頼できないコードをサンドボックス内で安全に実行するのは、非常に困難です。サンドボックスは、アプリケーションが他のリソースやデータにアクセスするのを防ぐためのものです。可能であれば、信頼できないコードの実行自体を避けるのが最善です。
以上です。楽しんでいただけたでしょうか!ほかのフラグをどうやって見つけたか知りたい方は、Fetch the Flagの解答ページをご覧ください。
Capture the Flagを始めよう
オンデマンドのバーチャル入門ワークショップを見て、Capture the Flagの課題の解き方を学びましょう。



