Microsoft AzureのLogic AppsにあるAPI Connectionsについて、またひとつ深刻な脆弱性の話が出てきました。しかもこれは単発の欠陥ではなく、同じ設計の弱さから何度も別の抜け道が見つかった、という筋書きです。元記事の著者は、最終的に5件のcriticalな権限昇格脆弱性を報告し、合計で20万ドルを得たと書いています。単なるバグ報告ではなく、クラウドの“つなぎ方”そのものがどれだけ危ういかを示す話として読むべきだと思います。
元記事の中心は、Azure Logic AppsのAPI Connectionsがどう動いているかです。Logic Appからの呼び出しは、共有のAzure API Management(APIM)を経由し、そこでOpenAPI定義を確認したうえで、接続先サービス向けの認証情報に“交換”されてからバックエンドへ届く、と説明されています。つまり、見かけ上はLogic AppがAPIを呼んでいるようでも、実際にはAPI Connectionに設定されたトークンやキーでKey VaultやAzure SQL、さらにはJiraやSalesforceのような外部サービスまで操作できる構造です。
著者が問題視しているのは、このときAPIM側に「別の接続を見に行かせる」ことができれば、他人の接続に紐づく資格情報を使ってしまえる点です。しかもARM REST APIのDynamicInvokeや/extensions/proxy/の経路を使うと、ARM経由でAPIMに直接問い合わせられるため、攻撃面がかなり広がると述べています。最初の例では、自分の接続のDynamicInvokeを使って別テナントのKey Vaultを読むペイロードが示され、path/%2e%2e/%2e%2e/%2e%2e/%2e%2e/というパス・トラバーサルでvictimsecretというシークレットの値を取得しています。著者は、これは「ARMからAPIMに飛んだ時点で、接続の所属に関係なく広い権限が使えてしまう」ことの証拠だと見ています。
その後Microsoftが修正したように見えたものの、著者はそれが単にパスを絞っただけではないかと疑い、Logic Appsの標準ホストにSSHしてコードを取り出しました。そこでDynamicInvokeだけでなく、未文書化のDynamicListなど複数の公開関数を発見します。DynamicListでも同じ発想が通用し、パスをたどったあとにVictimのSQL Serverへ任意SQLを実行できるペイロードを組めたとしています。さらに別の抜け道として、Consumption Logic AppsとStandard Logic Appsの接続作成フローの差に着目し、Consumption側には専用のmanaged identityがないのにVersion 1のAPI Connectionに暗黙アクセスできるのではないかと考えます。そこでhost.api.RuntimeUrlという入力を見つけ、接続の検証をすり抜けて別のruntime URLを差し込む形で、また別のcross-tenant compromiseを成立させました。修正後にその値が厳密照合されるようになっても、パス正規化の前に検証しているため再び回避できた、と著者は説明しています。最後には、その修正で以前のRuntimeUrl経路まで再び有効になったとも書かれています。
読んでいてまず引っかかるのは、個別のバグというより、設計の前提が攻撃者寄りに崩れていることです。著者が何度も違う入口を見つけているのは、単に腕が立つからだけではないと思います。API Connectionが「どの接続を呼んでいるのか」を複数の層で解釈し、ARM、APIM、Logic App runtimeのそれぞれが別々に検証している。こういう構造は、ひとつのチェックを直しても別の層で意味が崩れやすい。今回の話は、その典型に見えます。
特に印象的なのは、Microsoftが最初にやった修正が“黑名单”に近かったことです。DynamicInvokeだけ塞ぐ、あるいは目に見える経路を封じるやり方は、局所的には効いても、同じ機能群の別名や別エンドポイントまで見通していないとすぐに崩れます。著者がDynamicListを見つけたのも、コードを覗いたからではありますが、未文書化の関数が同じ道筋に生えていた、という事実自体がかなり重い。クラウドの管理面APIでは、「文書化されていないから使われない」は通用しないと思います。使えれば誰かが見つけるし、見つかった瞬間に攻撃面になります。
元記事には、OAuth接続なら最初に接続を作った人のトークンになるだろうし、API keyでも最小権限に絞る人は少ないだろう、というくだりがあります。これは皮肉として読めますが、実務ではかなり現実的です。接続は便利だからこそ、権限を広めに取りがちです。Key VaultやSQL、SaaS連携を一度つないでしまうと、あとから細かく絞るのは面倒ですし、運用担当が変わればなおさらです。
だからこそ、API Connectionの横取りが成立すると被害が一気に大きくなる。単に1つのAPIを読める、では済まず、バックエンドの“その接続に許されたこと全部”が流れてしまうからです。著者がcross-tenant compromiseと言っているのは大げさではなく、他人のテナントにあるKey VaultやSQLまで触れうる、という意味でかなり直球の危険です。しかも対象がAzureサービスに限らず、JiraやSalesforceのような外部SaaSにも広がる。これはクラウドの中だけの問題ではなく、業務データ全体の境界が壊れる話です。管理者が「接続は安全な部品」と見なしているほど、事故の広がりは大きくなると思います。
今回の連鎖で面白いのは、どの修正も「ここはこの文字列でなければならない」と言っているのに、実際には通ってしまう経路が残ることです。これは入力検証の限界をかなりはっきり示しています。検証する側は、文字列として見れば正しいかもしれない。けれど、その文字列がどの段階で正規化され、どのコンポーネントがどう解釈するかまでは守れない。著者がhost.api.RuntimeUrlで突いたのは、まさにそのズレです。チェックは合格したのに、後段の解釈で別の宛先になってしまう。
この種の問題は、単なるバグ修正ではなく“信頼境界の再設計”が必要だと感じます。利用者が与えたURLやパスを、管理プレーンが別の意味で読み替えられる構造は、かなり危うい。しかもAzureのような大規模基盤では、過去の互換性を壊しにくいぶん、危険な入力を完全に消すのが難しい。元記事の著者は五つのcriticalを取れたと言いますが、裏を返せば、同じ家屋のドアや窓を違う角度から何度も開けられたということです。個々の窓を板で塞ぐだけでなく、家の鍵の設計を見直さないといけない段階に見えます。
この話を読んで救われるのは、著者が何度も報告し、Microsoft側も一定期間内に修正している点です。もちろん、最初から安全ならそれに越したことはありません。ただ、実際の巨大クラウドでは、未知の経路が後から見つかるのは避けにくい。そう考えると、攻撃者が見つけた“連鎖”を、運用側がどれだけ早く封じるかが重要になります。元記事ではMSRCとの意見の相違もあったとされていますが、それでも最終的に修正が進んだのは意味があります。
一方で、ユーザーとしては楽観しすぎないほうがいいと思います。今回のような欠陥は、ひとつ直れば終わりではなく、別の類似機能や古い経路に再発しやすいからです。Azure Logic Appsを使う組織は、接続を「設定したら終わり」のものとして扱わず、どの接続がどの権限で、どの経路から呼ばれるのかを見直したほうがいい。特に外部SaaSやKey Vaultをつないでいる場合、漏えいの影響は認証情報の枠を超えます。今回の件は、便利な連携機能ほど、裏側の信頼モデルを疑わないと危ない、というかなり基本的な教訓を突きつけていると思います。
参考: Azure’s Weakest Link - Five Full Cross-Tenant Compromises