iT邦幫忙

2026 iThome 鐵人賽

DAY 17
0
AI Security

Microsoft Foundry 的 AI Agent 攻防實戰:建立高資安與可治理的 Agent 系統系列 第 17 篇

Day 17|Microsoft Foundry 的 AI Agent 攻防實戰:三張識別證都是真的,Agent 為什麼仍替 Alice 越權?

  • 分享至 

  • xImage
  •  

昨天分開了使用者與 workload 身分。今天假設兩者都能通過認證,再看下一跳是否仍然使用 Alice 的權限。

Alice 只能讀自己的費用,卻要求讀 Bob 的 exp-bamboo-002。本機 workload agent-workload-01 另有背景查詢能力。如果授權時忽略 Alice 的 delegated context,就可能改用 workload 較大的權限放行。

修補前後都傳入同一份 Alice context,差別只在 executor 有沒有採用它。這個測試直接操作合成身分與費用資料,不會呼叫模型、MSAL 或 Entra。

Actor、Subject 與 Delegated Authority

  • Actor:實際送出這一跳 request 的服務或 Agent。
  • Subject:這一跳所代表的使用者,例如 Alice。actor 和 subject 相同並非必要,但兩者都必須可驗證。
  • Delegated authority(委派權限):服務只在使用者已獲授權的範圍內替他做事,不可改用服務自己的較大權限補洞。
  • OBO(On-Behalf-Of):middle tier 用收到的使用者權限向身分平台換取「給下游 API 使用」的新 token;不是把原 token 原封不動往下傳。
  • Scope:token 被授予的能力範圍。它必須來自已簽章 claims 與伺服器政策的交集,不能由 request 自填。

Context 有傳入,授權時卻被忽略

先看授權只採用 application 身分時,原本的使用者去哪裡了。

Identity workspace 顯示 app actor、空 subject 與 Bob object 的 read Receipt。

actor 是 application,subject=null,Bob 的費用卻留下 1 張唯讀 Receipt。這次問題在於授權沒有採用使用者的限制。測試沒有執行 MSAL OBO、Foundry Agent identity 或真實的下游服務。

先看 vulnerable path:

agent-workload-01(application principal 的本機替身)
  + Alice delegated_context(有傳入)
    → before profile 忽略 delegated_context
      → policy 改以 workload principal 評估
        → exp-bamboo-002(owner=bob)被讀出,Receipt=1

這條本機路徑沒有任何 Entra token;可驗證的只有 synthetic fixtures:

  • Alice 的 token 只通過 SyntheticTokenIssuer 的本機驗證。
  • workload 是 auth_type=synthetic-application 的 Principal,不是 FastAPI 驗過的 Entra workload identity。
  • 同一 synthetic workload 具有本機 service-reader role;程式沒有建立或驗證 Foundry Agent identity。

原本要確認 Alice 能否讀 Bob 的費用,流程卻只檢查 workload 能否讀取。這是 confused deputy:服務有能力存取資源,但應該把這次使用者的限制一起帶入決策。

cumulative_stage_replay() 兩次都傳入 Alice 的 delegated context。修補前沒有啟用 delegated_user_context,executor 因此忽略使用者,改用 workload principal 做授權。這個問題比漏傳參數更容易被忽略:資料明明有帶到,授權時卻沒用。輸出如下:

{
  "action": "read_user_expense",
  "actor": "agent-workload-01",
  "subject": null,
  "resource_id": "exp-bamboo-002",
  "reason_code": "APP_AUTHORITY_CONFUSED_DEPUTY",
  "receipt_count": 1,
  "side_effect_count": 0,
  "details": {
    "delegated_context_enforced": false,
    "expense_owner": "bob",
    "policy_reason": "POLICY_ALLOW"
  }
}

觀察結果時,對照授權使用的 subject、讀取的物件與 Receipt。這次 subject 消失後,Bob 的物件確實被讀取,因此已構成資料存取越權。

OBO 如何取得下游 API 的 Token

再檢查 delegated context 保存了哪些欄位,以及偽造 subject 為什麼被拒絕。

Synthetic delegated-context inspector 顯示 serialized field names 與偽造 provenance 被拒。

serialized_context_fields 列出 actor、subject、tenant_id、audience、scopes、expires_at 等欄位;偽造資料得到 forged_result=DELEGATION_PROVENANCE_REQUIRED。光填齊欄位還不夠,subject 必須有 server 核發的來源依據。畫面只列出本機合成資料的欄位名稱與拒絕原因,沒有 Entra token、MSAL client 或 OBO 交換。

這張 inspector 只讀取本機 helper 的 disclosure-safe JSON,不建立或交換 token:

PYTHONPATH=src:. uv run python -c 'import json; from magic_panda_agent.stages.day17 import delegated_context_evidence; print(json.dumps(delegated_context_evidence(), ensure_ascii=False, sort_keys=True))'

On-Behalf-Of(OBO)用來讓 middle-tier API 延續使用者的 delegated authority:

User
  └─ token A:aud = Magic Panda API
       └─ FastAPI 驗 signature/issuer/audience/tenant/subject
            └─ FastAPI 以 confidential-client 身分提出 OBO exchange
                 └─ Microsoft identity platform
                      └─ token B:aud = downstream Expenses API

Microsoft 的 OBO 文件要求 assertion token 的 aud 必須是提出交換要求的 middle-tier application。拿一顆原本發給 Microsoft Graph 或其他 API 的 token 來兌換,middle tier 應拒絕;token A 也不應直接 relay 到下游。直接轉送會破壞 intended audience、Conditional Access claims challenge 與 token binding 的邊界。Microsoft identity platform OBO flow

OBO 使用 delegated scopes,不是把 application role 假裝成某位使用者。夜間批次工作沒有互動使用者,應走 app-only/Managed Identity/Agent identity 路徑,再由 application policy 限制;不能發明一位永遠在線的 system-user 來填欄位。

先決定「替誰做」,再選 token 流程

大魔術熊貓工程司把工作分成兩條入口:

工作 authority mode 下游授權鍵 失敗時
Alice 查自己的費用 delegated/OBO actor + subject + tenant + scope + object fail closed,不改用 Agent privilege
Alice 觸發需使用者權限的工具 delegated 或該工具支援的 OAuth passthrough 使用者 consent 與下游 scope 回傳需要 consent/授權,不 fallback
夜間費用彙整 application/autonomous workload identity + application capability 走獨立 background policy
Agent 讀共用政策 application/Agent identity Agent role + tenant/corpus policy 不產生使用者身分

DownstreamIdentityContext 刻意把 actor 與 subject 分開。更重要的是,它不能由 request body 直接建出一份「看起來像 Alice」的資料就取得 authority。當日 lab 先驗證一顆受信 synthetic delegated token,再由 server-side DelegationContextAuthority 建立 opaque provenance;actor 是這一跳真正送 request 的 service/Agent,subject 才是它所代表的使用者:

from magic_panda_agent.auth import (
    DelegationContextAuthority,
    SyntheticTokenIssuer,
)
from magic_panda_agent.models import Principal

issuer = SyntheticTokenIssuer(key=b"SYNTH_MAGIC_PANDA_LOCAL_SIGNING_KEY_32B")
authority = DelegationContextAuthority(issuer)

delegated_token = issuer.issue(
    Principal(
        subject="alice",
        tenant_id="bamboo-hq",
        roles=frozenset({"employee"}),
        auth_type="synthetic-delegated",
    ),
    audience="api://expense-data",
    client_id="agent-workload-01",
    scopes=frozenset({"Expenses.Read"}),
)
context = authority.from_token(
    delegated_token,
    actor="agent-workload-01",
    audience="api://expense-data",
    scopes=frozenset({"Expenses.Read"}),
)

context.require_user_context(
    authority=authority,
    expected_actor="agent-workload-01",
    expected_tenant_id="bamboo-hq",
    expected_audience="api://expense-data",
    required_scopes=frozenset({"Expenses.Read"}),
)

這段使用 SyntheticTokenIssuer 作為本機替身。from_token() 要求 actor 與已簽署的 client_id 相同,requested scopes 也只能是 signed scopes 的子集,呼叫端不能自行加大權限。

Server registry 將不對外序列化的 provenance ID,綁到 actor、subject、tenant、audience、scopes、issuer、synthetic token ID、nbf、exp 與 authority epoch 的 digest,每次使用再檢查時效與 epoch。直接建立一份自稱 Alice 的 context,會得到 DELEGATION_PROVENANCE_REQUIRED;複製已核發資料再改 subject,則得到 DELEGATION_PROVENANCE_INVALID。

本機 token 使用 token_id,真正 Entra token 的識別 claim 則依 issuer 而定,不能固定假設為 jti。實際 OBO 交換交給 Microsoft identity library 或支援的 SDK,接收 API 再驗證取得的 token。

如果 mode="application"、subject=None,user-scoped route 必須回 USER_CONTEXT_REQUIRED。如果 actor、tenant、audience 或 scope 只有一項不符,則分別回:

DOWNSTREAM_ACTOR_DENIED
DOWNSTREAM_TENANT_DENIED
DOWNSTREAM_AUDIENCE_DENIED
DOWNSTREAM_SCOPE_DENIED

不同 reason code 對應不同的補救位置:缺少使用者脈絡、audience 錯誤與 scope 不足,不能都靠提高 workload 權限處理。保留原因,才能讓失敗停在正確的授權邊界。

Foundry 裡有不只一種「代表使用者」

在 Alice 查自己費用的路徑,先問下游到底收到誰的 token。若 Search 或 MCP 只看見 Agent 的 application identity,還要由工程司明確限制這條 app-only 查詢能讀哪些物件;若要替 Alice 查個人資料,則需走支援 delegated authority 的路徑,並在下游看到 Alice 的 subject。session_id 相同或 Agent 回答正確,都不能代替這項檢查。

Microsoft Foundry 的 Agent identity 文件區分 attended 與 unattended 情境:attended path 可使用 delegated/OBO authority;unattended path 則以 Agent 自己的 application authority 執行。這兩種模式都可能使用合法 Entra 身分,但授權語意不同,不能互相 fallback。Agent identity concepts

特定工具還有自己的支援矩陣。以 MCP 為例,官方目前列出 key、Agent identity、project managed identity、OAuth identity passthrough 與 unauthenticated 等選項。只有 OAuth identity passthrough 會保留個別 user context;Agent identity 與 project MI 代表的都是應用程式自己的權限,不帶 Alice 的身分。MCP OAuth passthrough 目前要求使用者與 Foundry project 位於同一 Entra tenant,不支援 cross-tenant token exchange;首次使用還可能回傳 consent request。MCP server authentication

每個 connection 或 tool 都要記錄實際 authentication mode、audience、scope、consent 結果與下游 principal。沒有 passthrough 支援的工具,就得用明確的 app-only policy 限縮操作,或不提供該功能,不能默認會自動延續 user authority。

x-ms-user-identity、session isolation key 或應用自己的 partition key,也不能拿來代替 OBO。它們回答的是狀態應放進哪個使用者 partition,不會自動授予 Search、Key Vault 或 MCP 的 delegated data permission。

為使用者操作與背景工作建立不同入口

接著比較替使用者操作與自主背景工作,各自使用哪一套授權條件。

Route comparison 顯示 delegated user policy 與 app-only background policy。

user route 要求 subject;background route 可以沒有 subject,但只能執行明確允許的背景動作。這裡的 background allow 只涵蓋固定的合成案例,還不能延伸到所有背景工作。

  1. FastAPI 驗證 Alice 的 inbound token,取得 server-derived (tid, oid)。
  2. route 宣告這次工作是 user_scoped 或 autonomous;不由 prompt 決定。
  3. user_scoped 工作要求 delegated context,且 actor、subject、tenant、audience、scope 全部符合。
  4. object PEP 再檢查 Alice 是否能讀指定費用;Alice 讀 Bob 物件得到 OBJECT_OWNER_DENIED。
  5. autonomous 工作走獨立 background policy,沒有 subject,也不能呼叫 user-scoped action。
  6. 任一 delegated 欄位消失時,流程停止;不改用 Agent 或 project MI 的較大權限。

背景工作本來就沒有互動使用者,所以還需要 app-only 的正常案例。測試應確認它只能走 background action,同時確認 Alice 的 user-scoped 請求不能轉用這條權限;直接拒絕所有 application token 會破壞原有工作。

Replay 的回傳欄位如下,可以直接對照程式與畫面:

from magic_panda_agent.stages.day17 import cumulative_stage_replay

evidence = cumulative_stage_replay()

assert evidence["declared_delta"] == (("delegated_user_context", True),)
assert evidence["confused_deputy"]["reason_code"] == (
    "APP_AUTHORITY_CONFUSED_DEPUTY"
)
assert evidence["object_denied"]["reason_code"] == "OBJECT_OWNER_DENIED"
assert evidence["own_object_allowed"]["reason_code"] == (
    "DELEGATED_OBJECT_POLICY_ALLOW"
)
assert evidence["app_only_user_denied"]["reason_code"] == (
    "USER_CONTEXT_REQUIRED"
)
assert evidence["background_allowed"]["reason_code"] == (
    "APP_ONLY_BACKGROUND_ALLOWED"
)
assert evidence["before_tool_receipts"] == 1
assert evidence["after_tool_receipts"] == 2
assert evidence["day16_identity_preserved"] is True
assert evidence["future_controls_active"] == []

before_tool_receipts=1 是 Bob 物件真的被 read-only tool 讀取;after 的兩筆則來自 Alice 自己的物件與合法 background policy query。兩邊的 side_effect_count 都是 0,所以本篇證明的是資料越權,不是寫入副作用。

同一個物件,分別用 app-only 與 delegated context 重播

先重送讀取 Bob 物件的請求,再用 Alice 自己的物件與合法背景工作作對照。

Same-object replay 顯示 Bob deny、Alice own object allow 與 background allow。

讀取 Bob 物件得到 OBJECT_OWNER_DENIED;Alice 讀自己的物件與合法背景工作則能通過,各自保留 Receipt 次數。這裡沒有測真實的 claims challenge、consent、跨租戶 token 交換或 Hosted session。

進入 day17/ 後執行:

uv run pytest tests/stages/day17/test_acceptance.py -q

這組驗收要確認:

  • before profile 讓 app-only Agent authority 讀到 Bob 的費用並產生 receipt。
  • after profile 綁回 Alice subject,同一物件得到 OBJECT_OWNER_DENIED,receipt 為 0。
  • Alice 讀自己的物件仍可通過,證明不是全面封鎖。
  • app-only background job 只能走明確的 background action。
  • actor、tenant、audience、scope 各自錯一項,都有不同 deny reason。
  • actor 或 requested scope 超出 signed claims、原 token 過期、尚未生效或 authority epoch
    被撤銷時,既有 context 也會 fail closed。
  • 直接建構一份已知 subject 的 context 仍因缺少 opaque provenance 被拒絕;竄改
    server-issued context 也無法沿用原 provenance。
  • moon-rabbit-lab 的 Mallory context 不能與 bamboo-hq 的 Alice 混用。

本機測試記錄為 2 passed。負向結果另列 actor、tenant、audience 與 scope 的對照,並保留 MICROSOFT_ENTRA_OBO_EXCHANGE_NOT_EXERCISED,表示這次沒有完成真實 OBO 交換。

先看修補前的 subject=null、Bob 的物件與 1 張唯讀 Receipt,再看修補後的 OBJECT_OWNER_DENIED,以及 app-only 走使用者路徑時的 USER_CONTEXT_REQUIRED。這些都是本機 policy/executor 的結果,沒有觀測雲端呼叫:

PYTHONPATH=src:. uv run python -c 'import json; from magic_panda_agent.stages.day17 import cumulative_stage_replay; print(json.dumps(cumulative_stage_replay(), ensure_ascii=False, sort_keys=True))'

同一份 cumulative_stage_replay() 也保留兩筆合法操作:Alice 讀自己的物件,得到 DELEGATED_OBJECT_POLICY_ALLOW;背景查詢得到 APP_ONLY_BACKGROUND_ALLOWED。合計 2 張 Receipt、0 次副作用,確認我們沒有把正常的背景工作一起封鎖。

委派流程還要管理 Token Cache 與撤銷

OBO 保留 user authority,也帶來 confidential-client credential、consent、token cache、refresh 與 claims challenge。access/refresh token 若進入 prompt、trace、browser storage 或 exception,新的資料外洩面就跟著出現。使用者撤權後,既有 token 與下游 cache 也未必在同一瞬間全部消失,正式系統要測 revocation 與 bounded cache lifetime。

NIST SP 800-207A 的逐請求決策,在這裡落成每一跳綁定 actor、subject、tenant、audience、scope、resource 與 action。它沒有要求所有工作都假裝成使用者;反而應把 delegated 與 autonomous authority 清楚分開。

下一篇把這些 principal 放進 Foundry、Search 與 Key Vault 的角色和 scope,看看管理資源、呼叫 Agent 與讀取資料,各自需要什麼權限。

第一方參考資料


上一篇
Day 16|Microsoft Foundry 的 AI Agent 攻防實戰:換掉共享 API Key
下一篇
Day 18|Microsoft Foundry 的 AI Agent 攻防實戰:Azure Owner 不是宇宙萬能管理員,Foundry RBAC 到底管哪一層?
系列文
Microsoft Foundry 的 AI Agent 攻防實戰:建立高資安與可治理的 Agent 系統 共 31 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言