Fetch the Flag CTF 2022 解説:Pay Attention
Assaf Ben Josef
2022年11月10日
0 分で読めますFetchに参加していただき、ありがとうございました! 参加してくださった数千人の皆さん、 Fetch the Flag CTFへのご参加、おめでとうございます。そして、チャレンジの作成、テスト、解説の執筆に携わったSnykerの皆さんにも、心から感謝します!
この記事では、Snykの2022年Fetch the Flag CTFの「Pay Attention」チャレンジにチームがどう取り組んだかを見ていきます。このチャレンジでは、広く使われているパッケージが乗っ取られ、悪意あるものに改変された可能性がある状況を想定します。私たちは、問題を調査するセキュリティ研究者の立場で取り組みます。
解説
開始すると、ダウンロード可能なファイルpytest-7.1.3.tar.gzと、「最近、ちゃんと注意を払っていましたか?」というヒントが表示されます。
まず目に留まるのはファイル名です。Pytestは非常に人気のあるPythonテストフレームワークで、この記事の執筆時点では7.1.3が最新リリースです。つまり、このファイルは最新のpytestリリースのアーカイブと思われます。
もちろん、実際のpytest 7.1.3と完全に同じものではありません。そうでなければ、手掛かりがありませんから!確かめるために、pytestのGitHubリポジトリから7.1.3リリースのソースコードをダウンロードします。アーカイブファイルのプロパティを調べると、CTF版のpytestは本物のリリースよりほんの少しサイズが大きいことがわかります。
2つのアーカイブの違いがどこにあるのかを見つけるには、フォルダを展開して、ターミナルでdiff -rqコマンドを使って比較します。このコマンドはフォルダの内容を再帰的に調べ、次のようにファイルの違いを探します。

メタデータファイルにいくつか小さな違いが見つかりました。しかし、さらに重要なのは、ソースコードファイルのfixtures.pyにも違いがあることです。これが原因に違いありません!
不審なコードに注意を払う
使い慣れたIDEでファイルを調べ、少しスクロールしていくと(あるいは、予期しないインデントがIDEに指摘されると)、次の不審なコード行が見つかります。
ぱっと見たところ、この行はライブラリをインポートし、b64decodeやexecといった興味深い関数を呼び出しているようです。明らかに不審ですが、まだ何が起きているのかはわかりません。そこで、デコードしてみましょう。
まずは、\x65\x78\x65\x63のような、短くて似たパターンの文字列から始めます。
Python 3では文字列の表現にUnicodeが使われていることをご存じの方もいるでしょう。Python 3のドキュメントを詳しく調べると、このパターンはUnicodeコードポイントを2桁の16進数で表したものにすぎないとわかります。
とはいえ、そこまで知っている必要はありません。コードを見ると、これらの文字列は__import__()などのPython組み込み関数にそのまま渡されています。つまり、Pythonインタープリターがすでに文字列を処理できるはずです。そこでPython REPLを起動し、こうした文字列をprint()関数に渡すと、人間が読めるUnicode表現が出力されます。たとえば、print(‘\x65\x78\x65\x63’)を実行すると、execという文字列が得られます。簡単ですね!
すべてを解読すると、次の結果が得られます。
このスクリプトはBase64文字列をデコードし、コンパイルしてから実行するようです。次に知りたいのは、ここで実行されるコードの内容です。オンラインのデコードツールなどを使ってBase64文字列をデコードしてみましょう。結果は次のとおりです。
今度のコードは、一時ファイルを作成し、短いスクリプトを書き込んで実行するようです。一時ファイル内のスクリプトは、オンラインリソース(Snyk CTFのドメイン)からコードをダウンロードして実行します。
手掛かりをたどり続け、この一連の処理の最後に実行されるコードを突き止めたいところですが、なかなか手ごわい相手です。ダウンロードされるコードは完全に難読化されています!理解できるのは先頭部分だけですが、そこには難読化に使われたツールの名前とGitHubリポジトリ、Hyperionが記されています。
最初は、難読化されたスクリプトと難読化ツールのソースコードがあるのだから、リバースエンジニアリングに取りかかろうと考えるかもしれません。しかし、このスクリプトは巧妙に難読化されているようです。また、難読化ツールのGitHubリポジトリのREADMEには、現時点で難読化解除ツールは存在しないと書かれています。そこで、まずはほかの選択肢をすべて試すことにしました。
そこでまず、この難読化されたスクリプトをローカルファイルにダウンロードし、デバッグ環境を立ち上げて、実行時に何が起きるかを調べることにしました。使い慣れたIDEでコードを1行ずつデバッグ・実行すれば、処理の流れを確認できます。
セキュリティに関する注意事項
これが現実の状況で、実際に被害を与えることを目的としたスクリプトを扱う場合は、すべての作業を安全なサンドボックス環境で行います。
すると、幸いにも、宣言された変数の1つに隠れていたフラグを見つけられました(バックスラッシュを取り除いた後)。

注意を払えば見つかる!
ここまでの流れをまとめると、次のとおりです。
一見無害なパッケージのファイルに、不審なコード行が挿入されていた
そのコード行は、Base64でエンコードされたスクリプトをコンパイルして実行する
そのスクリプトは一時ファイルを作成し、実行する
そのファイルはリモートリソースにアクセスして難読化されたスクリプトをダウンロードし、実行する
難読化されたスクリプトを調べてデバッグした結果、宣言部分に隠されたフラグを見つけることができました。
このような多層的な難読化は、悪意あるスクリプトの挿入でよく見られます。大変な道のりでした!
ほかのフラグをどうやって見つけたか知りたいですか?Fetch the Flagの解答ページで、解き方をご覧ください。



