革新的な技法: Aviatrix Controller でのリモートコード実行
Mandiant
【Next Tokyo ’25】
【Next Tokyo】120 以上のセッションをアーカイブ公開中。話題の Gemini、生成 AI、AI エージェントなどの Google Cloud のアップデートや顧客事例をチェックしましょう。
視聴はこちら※この投稿は米国時間 2025 年 6 月 24 日に、Google Cloud blog に投稿されたものの抄訳です。
このブログ投稿では、「初期アクセス ブローカー」アプローチをシミュレートした Mandiant のレッドチームのケーススタディを紹介します。このアプローチにより、さまざまなクラウド ベンダーとリージョン間のリンクを作成できるソフトウェア定義ネットワーク(SDN)ユーティリティである Aviatrix Controller で 2 つの脆弱性が発見されました。
-
CVE-2025-2171: 管理者認証バイパス
-
CVE-2025-2172: 認証されたコマンド インジェクション
これらの脆弱性は Aviatrix Controller 7.2.5012 以前のバージョンに影響し、バージョン 8.0.0、7.2.5090、7.1.4208 でパッチが適用されました。報告されたセキュリティの問題を真剣に受け止め、迅速に修正してくれた Aviatrix のチームに感謝します。
レッドチームは、認証バイパス、安全でないファイルのアップロード、引数インジェクションを介して、完全にパッチが適用された Aviatrix Controller を不正使用することに成功しました。攻撃チェーンの詳細を図 1 に示します。


図 1: 不正使用の手順
今年に入って、Mandiant はレッドチームの活動中に、非常に小さな攻撃対象領域を発見しました。この攻撃対象領域の大部分はサードパーティの SaaS であり、取り組みの対象外と判断されました。攻撃対象となる興味深いサービスの一つに、AWS でホストされている、完全にパッチが適用された Aviatrix Controller がありました。
Aviatrix には、さまざまなクラウドとリージョンにデプロイされたゲートウェイがあり、これらはすべて Aviatrix Controller に接続されます。Controller に不正アクセスすると、これらのすべてのクラウド ゲートウェイと Cloud APIs にアクセスできる中央コンポーネントに侵入できるため、Controller は攻撃者にとって魅力的な標的となります。さらに、Aviatrix Controller が 2024 年に発覚した未認証のコマンド インジェクションの脆弱性(CVE-2024-50603 として追跡)の影響を受けていることも確認しました。Jakub Korepta 氏の優れたブログ投稿に記載されている脆弱性は、Aviatrix Controller にさらなる脆弱性がないか調べる動機となるのに十分なものでした。Aviatrix Controller のソースコードを入手するのは比較的簡単で、前述のブログ投稿に記載されている手順に沿って操作するだけでした。
レッドチームの活動において早い段階で設定した目標は、クライアントのクラウド環境に侵入することでした。これは、リモートコード実行によって行われるか、または Aviatrix Controller の認証をバイパスすることによって行われます。残念ながら、これは当初の予想よりも難しいことがわかりました。
アーキテクチャ
Aviatrix Controller は、興味深いアーキテクチャを活用しています。Controller のロジックは主に Python3.10 コードベースで記述され、PyInstaller を使用してバイナリにバンドルされます。Controller サーバーでは、この実行可能ファイルが /etc/cloudx/cloudxd
にありました。
バンドルされたバイナリは、古い PHP コードベースから呼び出されました。これを「フロントエンド」と呼びます。このフロントエンドは HTTP リクエストを解析し、パラメータを抽出して、sudo
呼び出しを介して root
として実行される cloudxd バイナリに渡します。
たとえば、Aviatrix Controller のログイン ページでログインしようとすると、ブラウザは次のようなリクエストを発行します。
このリクエストは、/var/www/
にある api.php
ファイルによって処理され、そのファイルは functions.php
の verify_login
関数を呼び出します。
PHP フロントエンドは、次のように cloudxd
バイナリを呼び出します。
最初の引数は「モジュール
」で、この場合は user_login_management
です。その後に「アクション」が続きます。この場合は get_password
です。この情報は、user_login_management
モジュールのバックエンド実装を探す際に役立ちます。
バックエンド ロジックの抽出
次のステップでは、ログイン、ユーザー登録、パスワードのリセットなど、一般的な認証フローがどのように行われるかを特定しました。まず、pyinstxtractor ツールを使用して、cloudxd
バイナリにあるコンパイル済みの Python バイトコードを抽出しました。これにより、Python バイトコードをきれいに抽出した結果、幸い難読化されていませんでした。ログイン モジュールは簡単に特定できました。Aviatrix モジュールは同じ名前のファイルに保存されていたからです(user_login_management
は user_login_management.pyc
に保存されていました)
コンパイルされた Python ファイルは Python 3.10 を使用していましたが、これはよく使われている Python 逆コンパイラではサポートされていません。つまり、先人たちと同じように、Python バイトコードを手動で読み取る必要がありました。すぐに Python 3.10 インタープリタをダウンロードし、バイトコードを stdout にダンプしました。
この手法を使用すると、ソースコードのバイトコード表現を読み取ることができます。しかし、逆アセンブルされた Python は非常に冗長で、ログイン ロジックは約 6,300 行にも及びます。レッドチームの活動中にそのような時間はないため、いくつかのショートカットが必要でした。
認証バイパス
Gemini を使用して、逆アセンブルされた Python から Python 擬似コードを取得したことで、多くの時間を節約できました。興味深い点が見つかりました。アカウントのパスワード リセットを開始すると、111,111 から 999,999 までの 6 桁の数字がパスワード リセット トークンとして生成されていました。Gemini で生成された擬似コードを図 2 に示します。


図 2: reset_password アクションの LLM 生成擬似コード
一意の候補が 999,999 - 111,111 = 888,888 個しかなく、パスワード リセット トークンのエントロピーは効果を発揮するには強さが足りませんでした。無効なトークンが多すぎる場合にパスワード リセットを無効にするロジックは、コードベース内に見つかりませんでした。ただし、Aviatrix Controller はトークンを 15 分間しか受け入れず、その後はトークンが無効になります。
これにより、アカウントを乗っ取るための制限時間は 15 分間、候補は 90 万件弱となりました。攻撃の理論化が完了したところで、パスワード ブルート フォース ツールを作成しました。入力には「seq -w 111111 999999 | sort --random-sort
」を使用し、パスワード再設定リクエストの発行には ffuf を使用しました。
次の手順を 15 分ごとに繰り返し実行します。
-
新しい候補者リストを生成する(必須ではないが、ゲン担ぎ程度(!)に変更する方がよい)
-
curl
を使用してパスワード リセットを開始する -
新しい候補を使用して
ffuf
ブルートフォースを開始する
ffuf が有効なパスワード リセット トークンのみを返すよう、ブルート フォース ツールは「invalid or expired」という文字列に一致するすべてのリクエストを無視するように構成されていました。パスワードの再設定を送信すると、構成された管理者のメールアドレスに必ずメールが送信されるため、検出のリスクが高まります。ただし、管理者アカウントにメールが構成されていない可能性もあります。アカウントの乗っ取りを続行する許可を得た後、デフォルトの Aviatrix ユーザー「admin」をターゲットとしたブルートフォースを開始しました。16 時間 23 分後、図 3 に示すように一致する結果が得られました。


図 3: 有効なパスワード リセット トークンの識別
このトークンにより、管理者ユーザーのパスワードをリセットして、Controller への認証を行うことができました。Aviatrix Controller のセキュリティ制御の最初のレイヤを突破したことで、OpenVPN 構成のデプロイ、ユーザーの作成、ユーザーのハッシュ化された認証情報の取得、ローカル MongoDB からの読み取りなど、多数のクラウド機能にアクセスできるようになりました。
脆弱性利用型不正プログラムのプリミティブの探索
Aviatrix は、Controller の認証情報の漏洩がクラウドの完全な侵害につながらないように、多くの予防措置を講じています。したがって、Controller サーバー上で基本的なコマンドを実行したり、接続できる新しいクラウド インスタンス(EC2、GCE)をスピンアップしたりすることは不可能であるように見えました。レッドチームの目標の観点から言えば、管理者アカウントへのアクセス権を取得できたことは成功ですが、期待していた成功ではありませんでした。
そこで、リモートコード実行につながる脆弱性を探すことにしました。最初から注目していた興味深いコードの一つに、次のようなフロントエンド(PHP)レベルの非常にクリエイティブなファイル アップロード処理がありました。
このルーチンは、多くのことを行います。まず、upload_file()
関数によりファイル拡張子の許可リストの使用が可能になっていますが、実際にはほとんど使用されていませんでした。たとえば、PHP フロントエンド コードベースで見つかったこの関数呼び出しは次のとおりですが、いずれも拡張子の許可リストを指定していません。
-
upload_file($action, "file", $_FILES);
-
upload_file($action, "ldap_ca_cert", $_FILES);
-
upload_file($action, "ldap_client_cert", $_FILES);
-
upload_file($action, "ldap_ca_cert", $_FILES);
-
upload_file($action, "ldap_client_cert", $_FILES);
この関数の興味深い副作用として、アップロードされたファイルはディスクに書き込まれますが、ファイルの処理後も削除されませんでした。また、ディスクに書き込まれるファイルを部分的に制御することも可能でした。具体的には、ファイル拡張子を介して制御します。
たとえば、次のファイル アップロード リクエストの場合では、
アップロードされたファイルを Aviatrix Controller ファイルシステム上に「/var/avxui/test_ldap_bind-ldap_ca_cert.foobar;baz
」として作成します。
ファイル アップロード ルーチンではスラッシュは許可されず、最初のスペースの後はすべて切り捨てられ、最後のピリオド文字(.)の前はすべて無視されます。興味深いことに、Controller はファイル名にタブ文字を使用することを許可していました。ファイル名と、ディスクに書き込まれるファイル名の例を以下に示します。
ディスクに保存されるファイル名の一部を制御できるため、Mandiant はコマンド インジェクションの脆弱性を探すことにしました。Controller バックエンドがアップロードされたファイル名をコマンドライン引数で安全でない方法で使用した場合、シェルコマンドにインジェクションしてリモートコードを実行することが可能になります。
もう 1 つ興味深いアーキテクチャ面の決定は、Controller がコマンドライン ユーティリティを使用して OS レベルの操作を行っていたことです。たとえば、Controller はファイルのコピーを処理するのに Python ライブラリを使用しないで「cp
」プログラムを実行していました。ファイル名の一部を制御できるため、これにより攻撃対象領域が大幅に広がりました。
Mandiant は、オペレーティング システムのコマンドを実行するために使用されるライブラリ コードを調査するなかで、興味深いパターンを観察しました。実行されるコマンドが文字列として構築され、後でトークン化されるというものです。次の Python バイトコードに、この例を示します。
このバイトコードは、次のように Python に変換できます。
つまり、Aviatrix Controller の個々の機能は、次のような文字列としてコマンドを構築していました。
get_system_cmd_output()
は文字列を入力として受け入れますが、基盤となる Python の subprocess.check_output()
関数はリスト(["cp", "/folder/fileA", "/folder/fileB"]
)を想定しています。これに対処するために、Aviatrix Controller は Python のサブプロセスに関するドキュメントに従い、コマンドライン文字列で shlex.split()
関数を呼び出します。この shlex.split()
関数呼び出しに脆弱性があります。
引数によるスマグリング
shlex
モジュールは、シェル インタプリタと同じ方法でユーザー入力を分割します。つまり、タブ文字などの一般的な空白文字でトークン化します。ファイル アップロードのフロントエンドではタブのサニタイズやフィルタリングが行われていなかったため、これは特に興味深いものでした。そのため、アップロードされたファイル名にタブ文字を追加することで、コマンドライン引数をシェル インタープリタに不正に渡すことが可能になります。図 4 は、shlex ライブラリがタブ文字をスペースと同様にトークン化していることを示しています。


図 4: タブ文字をトークン化する shlex
たとえば、次のような名前のファイルをアップロードしたとします。
ディスクに書き込まれるファイルは次のようになります。
後で「cp
」などのコマンドライン ユーティリティに渡されると、次のコマンドが実行されます。
これにより、呼び出される基盤となるプログラムに予期しない引数を忍び込ませることができました。
そこで、ファイルのアップロードを受け付け、部分的に制御されたファイル名をシェル プログラムに渡す機能を探すことにしました。そのような機能の一つが Proxy Admin ユーティリティで見つかりました。この機能により、カスタム CA 証明書ファイルをインストールできるようになります。この証明書は、ファイルアップロードで取得され、ディスクに保存され、「cp
」でファイル システムの別の場所にコピーされます。この機能のバイトコードは次のようになります。
これは理想的な候補でした。/usr/bin/cp
プログラムに引数をスマグリングできれば、理論的には、アップロードされたファイルをファイルシステムの別の場所にコピーできます。さらに、Controller が証明書であると想定するスマグリングされたファイルの内容は、ユーザーが完全に制御可能です。このとき、私たちの目標は、/usr/bin/cp
に引数を不正に送り込み、基盤となるファイル システムで任意のファイル書き込みプリミティブを取得することでした。成功した場合、cp
呼び出しが sudo
でラップされるため、これも root として実行されます。
認定 /usr/bin/cp ハッカー
次に、多くの不正使用の要件をシミュレートするテストベッドを作成しました。つまり、次の要件に従ってファイル名を作成する必要があります。
-
ピリオド(
.
)文字は使用できない -
スラッシュ文字(
/
または\
)は使用できない -
スペース文字は使用できない
-
ファイル名は PHP フロントエンドによって小文字に変換される
-
不正な引数は 2 番目の位置に渡される
-
現在の作業ディレクトリは
/
これは一見簡単そうですが、実際はそうではありません。では実際にどうすべきなのでしょうか。インジェクション ポイントは次のとおりです。
簡潔にするため、次のように書き換えます。
{prefix
} は、アップロードされたコンテンツを含むユーザー制御のファイル名です。{trailing
} は、最終的な証明書の保存先である
/usr/local/share/cacertificates
です。
比較的早い段階で、/etc/crontab
がファイル上書きの興味深いターゲットであることがわかりました。このファイルにはピリオド文字が含まれていないためです。これで最初の要件はクリアです。
まず、cp
を使用して、アップロードしたファイルの名前を現在の作業ディレクトリで「crontab」に変更します。次に、2 つ目の cp
コマンドを実行して、/etc にコピーします。
スラッシュなしの制限があるため、当初は、/etc
という表現を使うことなく /etc
にファイルを書き込む方法がわかりませんでした。しかし、cp
コマンドは複数の入力ファイルを渡された場合に最後の引数をディレクトリとして扱います。武器化した crontab をスラッシュ文字を使わずに /etc
にコピーする 1 つの方法として、これを悪用することを考えました。つまり、最終的なファイルが単に「etc
」となるコマンドをなんらかの方法で作成できれば、その前に渡されたすべてのファイル名が、スラッシュを使うことなく /etc
にコピーされることになります。マニュアルによると次のとおりです。
しかし、不正な引数を送り込むコマンドには、末尾にファイル名 /var/avxui/test_proxy_connectivity-server_ca_cert.txt
があります。マニュアルのページを注意深く読むと、次のような興味深い引数を見つけました。
--suffix
引数を不正に挿入することで、cp
バイナリに、末尾のファイル名がバックアップ サフィックスであると誤認させることができます。そうすれば、--backup
引数を渡していないため、ここでは無視されます。こうすることで、最終的なファイルが「etc
」になる cp
コマンドを作成できました。
ローカルで動作を確認できます。赤い部分が今回作成したファイル名です。
実際の例を図 5 に示します。


図 5: 任意のファイルへの書き込みにつながる引数の不正な取り扱い
実践
ここまでで、引数インジェクションの脆弱性について理論を立てました。次にそれが機能するかどうかを確認します。
1. 最初に、dummy.txt
という単純な crontab ファイルを含む CA 証明書ファイルをアップロードしました。このファイルは、図 6 に示すように、ファイル システム上に /var/avxui/test_proxy_connectivityserver_ca_cert.txt
として保存されます。このファイルは後で crontab
に名前を変更し、/etc に移動します。


図 6: 悪意のある crontab を含むローカル ファイルの作成
2. 次に、最初の引数インジェクション攻撃を実行し、図 7 に示すように、test_proxy_connectivityserver_ca_cert.txt
ファイルの名前を crontab
に変更しました。


図 7: ファイル拡張子に不正に埋め込まれた引数を含むローカル ファイルの作成
その HTTP リクエストに続いて実行されるリテラル コマンドは次のとおりです。
説明したように、--suffix
引数を使用すると末尾のファイル名が無視されるため、cp コマンドは次のように短縮できます。
コマンドはファイル システムのルートで実行されるため、/var/avxui/test_proxy_connectivity-server_ca_cert.txt
を /crontab
にコピーします。
3. 最後に、バグをもう一度トリガーして、図 8 に示すように /crontab
ファイルを
/etc
フォルダに移動します。


図 8: ローカル ファイルを /etc フォルダに移動する
その HTTP リクエストに続いて実行されるリテラル コマンドは次のとおりです。
cp
コマンドは次のように短縮できます。
cp
に渡されるファイルが 2 つ以上あるため、最後のファイルはディレクトリとして解釈されます。このコマンドにより
/var/avxui/test_proxy_connectivity-server_ca_cert.txt
と crontab
の両方が /etc
にコピーされ、エクスプロイト チェーンが完成します。
想定したとおり、1 分以内、そしてその後も 1 分ごとに、curl
コールバックが返ってきました。これを図 9 に示します。


図 9: curl コマンドを正常に実行する crontab
実行コンテキストも crontab から継承され、root となっていました。これは攻撃者の観点からすると非常に都合の良いことです。
これにより、認証バイパス、安全でないファイルのアップロード、引数インジェクションを介して、完全にパッチが適用された Aviatrix Controller の不正使用に成功したことになります。
クラウドへの展開
初期アクセス チームの仕事はここまででしたが、レッドチーム オペレーターの仕事はここから始まります。最後のステップは、このアクセスを利用してクラウド管理者権限を取得することでした。侵害された Aviatrix Controller から AWS IMDSv2 エンドポイントをクエリして、一時的なクラウド鍵を取得できました。
これにより、ARN「arn:aws:sts::[...]:assumed-role/Aviatrix-role-ec2
」へのアクセスが許可されます。この ARN は、設計上、基本的に何もアクセスできません。権限のある Aviatrix ロールのクラウド鍵を取得するには、Aviatrix のドキュメントに記載されているように、ロールの引き受けを実行する必要がありました。
構成済みの AWS プロファイルを使用して、次の操作を実行しました。
これにより、一時的な AWS 鍵の新しいセットが付与され、EC2 や S3 バケットなどにアクセスできるようになりました。
まとめ
攻撃対象領域が非常に限定されていたため、通常とは異なる標的、つまりソフトウェア定義ネットワーク(SDN)コントローラを攻撃せざるを得ませんでした。コードレビュー、忍耐、そして運に恵まれたこともあり、Mandiant の初期アクセス チームは、新たに発見された 2 つの脆弱性を悪用して、クライアントの Aviatrix Controller を侵害し、その後クラウド環境も侵害しました。
タイムライン
-
2025 年 3 月 10 日: Aviatrix ヘルプデスクへの最初の報告
-
2025 年 3 月 12 日: 問題を Aviatrix のリーダーにエスカレーション
-
2025 年 3 月 12 日: Aviatrix のエンジニアとリーダーとの電話会議で問題を説明
-
2025 年 3 月 31 日: お客様にパッチをリリース
-Mandiant、執筆者: Louis Dion-Marcil 氏