Azureの最も脆弱なリンク – 5件の完全なクロステナント侵害
Azure's Weakest Link – Five Full Cross-Tenant Compromises (binarysecurity.no)
要約
本記事では、Azure Logic AppsのAPI Connectionsにおける深刻な脆弱性について詳述しています。これらの脆弱性を悪用することで、攻撃者は他のテナントのKey VaultやAzure SQLデータベース、さらにはSalesforceやJiraのような外部サービスを含む、あらゆる接続されたバックエンドへの完全なアクセス権を取得できます。発見された複数の脆弱性は、合計で20万ドルの報奨金をもたらしました。
全文翻訳
Azureの最も脆弱なリンク - 5件の完全なクロステナント侵害
以前のブログ記事「Azureの最も脆弱なリンクか?」と「Azureの最も脆弱なリンク - 完全なクロステナント侵害」では、Azure Logic Appsの一部であるAPI Connectionsの非常に安全でないアーキテクチャの概要と、これらの固有の欠陥を使用した完全なクロステナント侵害の事例について説明しました。今回は、このシステムにおけるさらなる脆弱性を紹介します。それぞれが攻撃者に同様のプリミティブを提供します。合計で、ここで見つかった脆弱性により、私は20万ドルという大金を得ました。
また、Blue Hat Asia 2026でこの件に関する講演を行いました。録画が公開されたら、ここにリンクを追加します。
TL;DR
API Connectionsは、誰でも世界中の他の接続を完全に侵害することを可能にし、接続されたバックエンドへの完全なアクセスを提供します。これには、Key VaultやAzure SQLデータベース、さらにはJiraやSalesforceのような外部サービスへのクロステナント侵害が含まれます。悪用を阻止する唯一のものは、さまざまなシステムに bolted on された入力検証のレイヤーですが、ご覧のとおり、エッジケースをすべて捉えるのは非常に困難です。
アーキテクチャ
このシリーズの最初のパートをまだ読んでいない場合は、まずそちらを確認することをお勧めします。「Azureの最も脆弱なリンクか? - パート1」と「Azureの最も脆弱なリンク - 完全なクロステナント侵害」ですが、気にしないのであれば、アーキテクチャの最も重要な部分をまず説明します。
Microsoftのドキュメントから、API Connectionアーキテクチャがどのように構築されているかを示す非常に興味深い図を見ることができます。
[画像: Microsoft's documented infrastructure]
これは基本的に、システムがどのように動作するかを理解するために必要なすべてを物語っています。作成者には kudos です!
仕組みは以下の通りです。
- ロジックアプリ(左下のシンボル)が、共有されたAzure API Managementインスタンスにクエリを発行します。
- API Managementインスタンスは、まずコネクタタイプのswagger(OpenAPI)定義をチェックし、それが有効なアクションであることを確認します。
- 一種のキー交換が行われ、入力トークンまたはキーがバックエンドサービスの設定済みトークンと交換されます。
- API Managementインスタンスは、この新しいトークンを使用してバックエンドインスタンスへの呼び出しを完了します。
ここから、API Managementインスタンスを、私たちが所有する接続とは異なる接続で操作するように騙すことができれば、事実上、被害者の接続の設定済みトークンを彼らのバックエンドサービスで使用できると推測できます。
このサービスは、効果的に何でもあり得ます。こちらで見られるように、バックエンドサービスのリストは事実上無限であり、多数のAzureサービスが含まれています。
図は素晴らしいですが、重要な省略が1つあります。これは、このシリーズのパート2で既に効果的に使用したものです。ARM REST APIのDynamicInvokeまたは/extensions/proxy/エンドポイントを使用することで、ARM経由でAPIMサービスに直接クエリを発行できます。私は図を更新してこれに合わせるようにしました。
[画像: Updated Connector Architecture]
これを考慮すると、そしてリソースマネージャーとして、ARMは基本的にすべてにフルアクセスできるという事実から、私たちのエクスプロイトパスは明確になります。私たちは、呼び出しサービスを私たちのものとは異なる接続をクエリするように騙し、それによってバックエンドサービスを完全に制御する必要があります。
注意:もちろん、これらの接続を設定した人がアクセス トークンまたはキーをスコープ設定して、API Connectionがバックエンドで最小限の権限を持つようにすることは可能です。しかし、これはめったに行われないだろうと推測します。OAuth接続の場合、それは間違いなく最初に接続を作成した人のトークンになります。そしてAPIキーの場合、誰がそのようなものを最小限にスコープ設定するのに十分気にするでしょうか?少なくともMicrosoftは気にしません。
初期エクスプロイト
API Connectionsで、そして事実上Azure全体で達成した最初のクロステナントエクスプロイトは、パート2で既に説明されています。要約すると、これは私の接続のDynamicInvokeエンドポイントを使用して、別のテナントのKey Vaultを読み取る例のペイロードです。
POST /subscriptions/8e3ce52f-d45b-4347-8705-65892507465e/resourceGroups/token-storer/providers/Microsoft.Web/connections/custom2/DynamicInvoke?api-version=2018-07-01-preview HTTP/2
Host: management.azure.com
Authorization: Bearer <token>
Content-Type: application/json
Content-Length: 147
{
"request":{
"method":"get",
"path":"path/%2e%2e/%2e%2e/%2e%2e/%2e%2e/apim/keyvault/fd8d0f4f4069495991ccb4974f96a1ed/secrets/victimsecret/value"
},
}
HTTP/2 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Length: 1329
{
"response": {
"statusCode": "OK",
"body": {
"value": "dontreadme",
"name": "victimsecret",
"version": "7914d45aa60342809fb8cc12dc68b10e",
"contentType": null,
"isEnabled": true,
"createdTime": "2025-04-04T05:38:26Z",
"lastUpdatedTime": "2025-04-04T05:38:26Z",
"validityStartTime": null,
"validityEndTime": null
},
"headers": {
<Headers>
}
}
}
ご覧のとおり、これは pretty clear なパス トラバーサル脆弱性です。これは、私たちがすでに仮定していたことを示唆しています。ARMからAPIMインスタンスへのリクエストが送信されるとき、それが操作しているものに関係なく、すべての接続に対する権限を持っているということです。
さらなるエクスプロイト
この脆弱性を報告してから数週間後、それは修正済みとマークされ、再度試したところ、次のエラーメッセージが表示されました。
[画像: Vulnerability fixed by restricting the path]
さて、これが十分な修正かどうかを検討しました。それは根本的なトークンの問題ではなく、パスを制限しているように見えたので、回避策があるだろうと推測しました。
ARM APIに関する興味深い小さな事実は、場合によっては、あなたが想定しているほどブラックボックスではないということです。関連するコードは、場合によってはリソースタイプ自体の中に見つけることができます。このコードが実際にこれらのマシン上で実行されるのか、それとも単なるアーティファクトとして含まれているだけなのかはわかりませんが、興味深い情報の宝庫です。そこで、完全に専用のホストを提供するStandard Logic Appを作成し、それにSSHで接続してコードを抽出しました。見てください、何が見つかったか:
[画像: The validation code paths include a check for ../]
実装された修正が見えますか?
ここでもっと興味深かったのは、実際には探していたものではなかったことです。実際のDynamicInvokeコードを検索しているときに、実際にはこの1つの公開関数だけでなく、4つが見つかりました。
[画像: I found other function definitions right next to Dynamic Invoke]
これらの関数は完全に文書化されておらず、どこにも参照を見つけることができませんでした。しかし、それらは到達可能であり、同じパス上にありました。例えば、DynamicListにアクセスするには、URIのdynamicInvokeをDynamicListに変更するだけです。
彼らが実装したいい加減なブラックリスト修正がDynamicInvokeにのみ適用されたことを知っていたので、もし同じ機能を持っていれば、これらすべてが同じように脆弱であると仮定しました。必要な引数と戻り値についてあまり退屈させないように、エクスプロイト方法は実際には同じですが、ペイロードは少し複雑です。DynamicListの場合、各パスパラメータを個別に指定し、結果を得るために少し作業する必要がありますが、同じロジックが適用されます。例えば、被害者のSQLサーバーへのクロステナント書き込みを達成するために、次のようなペイロードが機能します。
POST /subscriptions/162fc6db-03cd-4fe8-ab44-dc0a947e74af/resourceGroups/api-connection/providers/Microsoft.Web/connections/custom_validator/DynamicList?api-version=2018-07-01-preview&_=1765372176638 HTTP/2
Host: management.azure.com
Authorization: Bearer <token>
Content-Length: 524
Content-Type: application/json
{
"dynamicInvocationDefinition": {
"operationID": "nine",
"parameters": {
"one": {
"value": ".."
},
"two": {
"value": ".."
},
"three": {
"value": "sql"
},
"four": {
"value": "88d28f5ff7b642cfb91df184938d074c"
},
"five": {
"value": "v2"
},
"six": {
"value": "datasets"
},
"seven": {
"value": "victimservice.database.windows.net,VictimDatabase"
},
"eigth": {
"value": "query"
},
"nine": {
"value": "sql"
},
"query": {
"value": {
"query": "Insert INTO MySecrets VALUES ('NewEvilSecrets', 3)"
}
}
},
"ItemsPath": "ResultSets/Table1",
"ItemValuePath": "secrets",
"ItemTitlePath": "value"
}
}
ここでの最初のパラメータがパス トラバーサルを実行し、その後データベースに対して任意のSQLクエリを実行することが簡単にわかります。
私はDynamicListケースのみを最初に報告しました。彼らがDynamicInvokeと同様の修正を行い、私が他の2つも報告できることを期待していました。残念ながら、彼らの修正は単にこれらすべてのエンドポイントをブロックすることでした。しかし、他に緩和策は講じられていないようです。もしこれらのエンドポイントが再びアクセス可能になった場合、それらは脆弱であると想定できるでしょう。
さらなるエクスプロイト
これらのダイナミックコールでのパス操作は、その結論に達したように見えました。DynamicInvokeのパス制限を越えることができず、他のすべてのエンドポイントはブロックされました。私が必要としていたのは、別の侵入方法でした。ある日眠ろうとしながら、Logic Apps自体を使用することを忘れていたことに気づきました。実際には、ConsumptionとStandard Logic Appsの接続作成フローの間には、かなりの違いがあることを認識しました。最も顕著な違いは、Consumption Logic Apps(ワークフローが他の多くのワークフローと共存する場所)はバージョン1の接続を作成するのに対し、専用ホストのStandard Logic Appsはバージョン2の接続を作成することです。次の図は、フロー間のより微妙な違いを明確に示しています。
[画像: The V2 Connection Creation Flow.]
[画像: The V1 Connection Creation Flow.]
違いが見えますか?もちろん、そのように説明されると、ポイントはかなり明確です。Standard Logic Appを使用すると、リソースを作成し、バックエンドサーバーに必要な認証を追加した後、そのマネージドIDを使用してそれにアクセスすることを承認します。しかし、Consumption Logic Appがある場合、それを承認することはできません。なぜなら、私たちはそれを制御できないからです。これはおそらく理にかなっています。私たちはそれを承認できないので、しません。
これは、すべてのConsumption Logic Appがすべてのバージョン1 API Connectionに暗黙的にアクセスできることを意味するに違いないと認識しました。これを悪用することを阻止する唯一のものは、何らかの入力検証です。
Logic Appワークフローのクラシックビューは、入力においてかなり制限的ですが、APIエンドポイントとGUIのCodeビューの両方があり、入力でもう少し遊ぶことができます。
[画像: The code view allows for more fine-grained control.]
これにより、入力を詳細に確認し、編集し、さらに追加することができます。参照されているコネクタを被害者の接続に変更するという古典的なトリックは失敗します。ランタイムは、あなたが接続を所有しているかどうかを広範囲に検証するためです。しかし、私たちはコードベースを持っているので、すべての有効なパラメータを見つけることができます。host.api.RuntimeUrlパラメータを発見するのにそれほど時間はかかりませんでした。
このパラメータが含まれている理由、そしてそれが今日まで受け入れられている理由を理解することはできませんが、予想どおりに機能します。host.connectionで参照されている接続に対して一連の検証を実行した後、その接続に組み込まれているruntimeUrlの代わりに、まったく異なるものを使用するように指定できます。ホストがazure-apihub.netで終わる限り、必要なAuthorizationヘッダーも含まれます。
[画像: Instead of using the connection's runtimeUrl, use a different one.]
このワークフローが実行されるとどうなるでしょうか?
[画像: The victim's secret is returned.]
鋭い読者は、エクスプロイトで使用されているruntimeUrlが単なるconnectionIdではないことに気づくかもしれません。これは、URIにいくつかの追加要素が追加されるためですが、私たちにとっては重要ではありません。エクスプロイトは正常に機能します。
天からの恵み
もちろん、私は再びMicrosoftにこれを報告し、彼らは修正に取り組むのに時間を費やしました。最終的にそれが届いたとき、エクスプロイトはエラーメッセージを返しました。
The host API runtime URL is not valid for the API connection '/subscriptions/8e3ce52f-d45b-4347-8705-65892507465e/resourceGroups/token-storer/providers/Microsoft.Web/connections/keyvault-1'. The runtime URL path must match the expected API endpoint 'https://logic-apis-norwayeast.azure-apihub.net/apim/keyvault/cbfb0f4313144117a7440368902bb871?' for the referenced connection. Either omit the 'host.api' input property, or correct the runtime URL to match the API associated with the connection.
このメッセージの過剰な長さは別として、それはかなり適切な修正のように思えます。ランタイムURLは期待されるものと正確に一致する必要があります。そうでなければ、ダメです。パラメータを完全に削除することを除けば、それはかなり健全です。もちろん、それは間違ったタイミングでその値をチェックしない限りは。
[画像: Path traversal in RuntimeUrl.]
これは同じ結果で再び機能します。ここで何が起こっているかというと、検証はパスが正規化される前に行われるため、ランタイムはURIが私の参照している接続を指していると考えていますが、APIMインスタンスはそれを異なるものとして見ています。
そして、ちょうどそのように、私は別のクリティカルな脆弱性を得ました。
楽しさはそこまでではありません。Microsoftがこのパス トラバーサルに対する修正をまとめることに成功したとき、驚くべきことが起こりました。元のRuntimeUrlパスはもはや検証されなくなり、その結果、その攻撃を再度実行することができました。いくつかの画像を含めることもできましたが、それらは上記の画像と同じになるため、想像するだけです。
報告と報酬
合計で、API Connectionsは私にAzureで5件のクリティカルな権限昇格脆弱性をもたらしました。これはおそらく予想よりも多いでしょう。ここで報告されたすべての脆弱性は、レポートを提出してから合理的な時間内に修正され、MSRCとの間にはいくつかの意見の相違がありましたが、全体的には結果に非常に満足しています。