跳到正文
Hacker News (official RSS)(binarysecurity.no)·· 4 小時前精選品質評分85

Azure Logic Apps API Connections 內部設計缺陷暴露跨租戶攻擊路徑

Azure's Weakest Link – Five Full Cross-Tenant Compromises

重點導讀

本文揭示 Azure Logic Apps API Connections 內部設計缺陷,說明跨租戶攻擊路徑與修補進展,對安全研究者與雲端運維人員具參考價值。

推薦理由

揭示 Azure Logic Apps API Connections 內部設計缺陷,說明跨租戶攻擊路徑與修補進展,對安全研究者與雲端運維人員具參考價值。

正文 · 中文翻譯

在我之前的部落格文章中,Azure 的最弱環節? 和 Azure 的最弱環節 - 完整跨租戶攻擊,我概述了 Azure API Connections(Azure Logic Apps 的一部分)背後極度不安全的架構,以及利用這些固有缺陷實現完整跨租戶攻擊的案例。現在我又回來,揭示這個系統中的更多漏洞,每一個都為攻擊者提供相同的原始工具。總計,這些漏洞為我帶來了 20 萬美元的收益。

我也在 2026 年 Blue Hat Asia 會議上詳細說明瞭此事,錄影上線後,我會在此處加入連結。

TL;DR

API Connections 允許任何人完全掌控全球任何其他連線,進而完全存取其後端。這包括對 Key Vault、Azure SQL 資料庫的跨租戶攻擊,以及任何其他外部連結服務,例如 Jira 或 Salesforce。阻止惡意利用的唯一因素實際上只是附加在各系統上的輸入驗證層,但正如你將看到的,捕捉所有邊緣案例實在很困難。

架構

如果你還沒閱讀本系列的前幾篇,我建議先參考 Azure 的最弱環節? - 第一部分 與 Azure 的最弱環節 - 完整跨租戶攻擊,但如果你不在乎,我會先帶你瀏覽架構中最重要的部分。

從 Microsoft 的檔案 我們看到一張相當有趣的圖示,說明 API Connection 架構的構造方式。

Microsoft's documented infrastructure

這基本上說明瞭理解系統運作所需的一切,向建立此圖的人致敬!

其運作方式如下

  1. 一個邏輯應用程式(左下角的圖示)會查詢共用的 Azure API Management 例項
  2. API Management 例項首先檢查聯結器型別的 swagger(OpenAPI)定義,並確認其為有效動作
  3. 發生一種類似鑰匙交換的過程,將輸入的 token(或鑰匙)交換為後端服務所設定的 token
  4. API Management 例項以此新 token 完成對後端例項的呼叫

由此可推斷,若我們能欺騙 API Management 例項在非自有連線上操作,我們就能有效利用受害者連線的已設定 token 存取其後端服務。

實際上此服務可以是任何東西。正如你在 此處 所見,後端服務列表實際上是無限的,且包含多個 Azure 服務。

儘管圖示很棒,但有一個關鍵遺漏,我們已在本系列第二篇中充分利用。透過使用 ARM REST API DynamicInvoke 或 /extensions/proxy/ 端點,我們可以直接透過 ARM 查詢 APIM 服務。我已自行更新圖示以配合此情況。

Updated Connector Architecture

考量到這一點,以及作為資源管理員的 ARM 基本上擁有對一切的完整存取權,我們的利用路徑變得明朗。我們必須欺騙呼叫服務去查詢非自有連線,從而取得對後端服務的完全控制。

注意:當然,設定這些連線的人可以將他們的存取權杖或金鑰範圍限定,讓 API 連線在後端具有最小許可權。然而,我猜這很少發生。在 OAuth 連線的情況下,肯定永遠是最初建立連線的人的權杖。至於 API 金鑰的情況,誰會足夠關心去最小化範圍?至少 Microsoft 沒有。

初次利用

我在 API Connections 以及整個 Azure 中首次完成的跨租戶利用已在第二部分描述。作為回顧,以下是一個使用我自己的連線 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>
        }
    }
}

如你所見,這是一個相當明顯的路徑遍歷漏洞。這意味著我們已經假設的情況:當請求從 ARM 發往 APIM 例項時,該請求擁有所有連線的許可權,無論其操作的是哪一個。

進一步利用

在我報告此漏洞後的幾週,該漏洞被標記為已修復,當我再次嘗試時,收到以下錯誤訊息:

Vulnerability fixed by restricting the path

現在,我考慮這是否足夠修復。它似乎限制了允許的路徑,而非底層的權杖問題,因此我猜可能還有繞過的方法。

關於 ARM API 的一個有趣小事是,在某些情況下,它可能並不像你想像的那麼黑盒。相關程式碼有時會出現在資源型別本身中。我不確定這段程式碼是否真的在這些機器上執行,或只是作為一個 artefact 包含,但它是一座充滿有趣資訊的金礦。因此,我建立了一個 Standard Logic App,提供完全專用的主機,SSH 進入,並提取了程式碼。驚訝地發現,我找到了什麼:

The validation code paths include a check for `../`

你看見他們實作的修復嗎?

更有趣的是,我在這裡發現了實際上並未尋找的東西。搜尋實際的 DynamicInvoke 程式碼時,我看到的並非只有這一個公開函式,而是四個:

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 進行相似的修復,並讓我能報告另外兩個。遺憾的是,他們的修復僅是阻擋所有這些端點。看起來沒有其他緩解措施,因此如果這些端點再次可存取,我會假設它們仍然易受攻擊。

進一步利用

在這些動態呼叫中調整路徑似乎已經結束:我無法突破 DynamicInvoke 的路徑限制,其他所有端點也被封鎖。我需要的是另一種進入方式。當我有一天想睡覺時,我意識到我忽略了使用 Logic Apps 本身。我發現事實上 Consumption 與 Standard Logic Apps 的連線建立流程存在顯著差異。最明顯的差別是,Consumption Logic Apps(工作流程與其他多個共存)會建立 Version 1 連線,而專用主機的 Standard Logic Apps 則建立 Version 2 連線。以下圖表更清楚地說明瞭這兩個流程之間更細微的差別:

The V2 Connection Creation Flow.

The V1 Connection Creation Flow.

你看得到差別嗎?顯而易見,說明清楚後,重點就很明顯。使用 Standard Logic App 時,在我們建立資源並加入所需的後端伺服器認證後,我們會授權該特定 Logic App 以其受管身分存取。相反地,若使用 Consumption Logic App,則沒有任何此類身份可授權,因為我們無法控制它。

我意識到這必定意味著每個 Consumption Logic App 都隱式存取 每一個 Version 1 API 連線;唯一阻止我們利用這點的應該是某種輸入驗證。

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 中會加入一些額外元素,但對我們並無影響;利用仍能正常運作。

天降甘露

我當然再次向微軟報告此問題,他們花了一段時間開發修正。最終修正推出後,利用會回傳錯誤訊息:

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 的關鍵提權漏洞,這或許超出預期。所有在此報告的漏洞在我提交報告後均在合理時間內得到修復,儘管在過程中與 MSRC 有些分歧,但總體而言,我對結果相當滿意。

來源: Hacker News (official RSS) (binarysecurity.no)
開啟原文