iT邦幫忙

2026 iThome 鐵人賽

DAY 20
0
AI Security

Medical AI Security Lab:醫療 AI Chatbot 的攻防實驗與自動化 Red Team系列 第 20 篇

Day20|最小權限與存取控制:不是機器人該說多少,是系統該讓誰碰到什麼

  • 分享至 

  • xImage
  •  

今天要解決的問題
Day19 留了一個沒修的洞:重述職責範圍那題繞過了 Output Validation,規則被講出來了。那篇文章的結論是——靠過濾輸出文字永遠有漏網之魚,真正該做的是讓系統本身沒有東西可以洩漏。

今天做的最小權限(Principle of Least Privilege),就是從另一個角度在補這件事。但要先澄清一個很容易誤會的方向。

容易走錯的方向:不要用角色調整機器人講多少
原始規劃裡想過設計 Patient / Doctor / Admin 三種角色,直覺的做法可能是:讓 Doctor 角色能問到比較深入的臨床資訊,Patient 角色維持最基本的衛教範圍。

這個方向被我刻意避開了,理由跟 Day18 的教訓直接相關:如果角色能換來機器人願意多講一點,等於又幫系統開了一個新的攻擊面——攻擊者只要在 Header 或對話裡宣稱自己是 Doctor,就有機會套出更多東西。Day18 已經證明過,System Prompt 每加一條新規則,都會變成新的洩漏素材;同樣的邏輯放在這裡——每多一種角色能解鎖的內容深度,就多一種可以被冒充騙到的東西。

所以今天的最小權限,管的不是機器人對誰比較坦白,而是誰能呼叫系統裡的哪個功能(路由)——跟聊天內容的深淺完全脫鉤。

為什麼現在就要做,而不是等 Day21 再補
Day21 要做 Audit Logging + Dashboard。Log 這種東西天生就是高濃縮的敏感資訊——裡面會記錄使用者問了什麼、System Prompt 的內容片段、甚至是曾經被攔截下來的攻擊紀錄本身。如果這個 Dashboard 沒有存取控制,它會是全案目前為止最大的洩漏點,比任何一次 Prompt Leakage 都嚴重,因為它是把所有敏感紀錄集中擺在一個網址。

真實世界裡,這種「功能先上線,權限管控後補」的時間差,正是很多資安事件的成因——系統上線的那一刻到權限補上的那一刻之間,就是暴露窗口。所以今天的作法是:先把門禁蓋好,Day21 再往裡面裝東西,而不是先把 Log 功能做出來、裸奔幾天,之後才想起來要鎖門。

對應到 OWASP API Security Top 10 的 Broken Function Level Authorization(功能層級授權失效)——系統裡存在某些功能,只要知道網址任何人都能呼叫,因為沒有人檢查這個角色有沒有資格用這個功能。

OWASP API Security Top 10 — Broken Function Level Authorization https://owasp.org/API-Security/editions/2023/en/0xa5-broken-function-level-authorization/
NIST 最小權限原則定義 https://csrc.nist.gov/glossary/term/least_privilege

做法:簡化版的角色判斷,不做完整登入系統
這是我自己原始規劃表裡就寫的風險提醒:建議簡化版,用簡單的 if-else 判斷就好,不用做完整登入系統,把心力留給後面 Promptfoo。所以這次的實作刻意做得很薄:

  • 角色透過自訂Header x-user-role傳入(patient / doctor / admin三選一),模擬這個請求宣稱自己是什麼身分。真實系統會從登入後的 Token 解析角色,這裡省略了驗證這個角色宣稱是否為真的那一步。
  • 沒帶 Header,或帶了清單外的值,一律當作權限最低的 patient——這是存取控制裡的預設拒絕(Default Deny) 精神:不確定你是誰,就假設你是權限最小的那個,而不是最大的那個。
  • 用一個 Express middleware requireRole(allowedRoles) 掛在路由上,請求進來先檢查角色在不在允許清單裡,不在就直接 403,連路由本體的程式碼都不會執行到。
  • /chat 掛 requireRole(['patient', 'doctor', 'admin'])——三個角色都放行,等於沒有真的限制誰。這行刻意寫出來,是要讓這個路由對所有角色開放變成一個明確評估過的決定,而不是沒寫存取控制,所以預設大家都能用的隱性狀態。
  • 新增/admin/audit-log空殼路由,掛 requireRole(['admin'])——今天先把門禁架好,Day21 才會把真正的 log 資料接進去。

測試結果

測試情境 預期 實際結果
不帶角色 Header,打 /admin/audit-log 403 403 權限不足,reason: 此功能僅限 admin 角色使用 ✅
帶 x-user-role: admin,打 /admin/audit-log 200 200,拿到空殼訊息,accessedBy: admin ✅
/chat 正常對話 跟 Day19 完全一樣 正常回答,沒有因為這次改動而壞掉 ✅

三項都跟預期一致,沒有意外。這次沒有「踩到坑」的部分——比較像是把一個原本不存在的門禁裝上去,驗證它確實能擋也確實不會誤擋。

這一層防禦的真實定位,以及它沒做到的事
要老實講清楚這個簡化版本留下的缺口:角色是使用者自己宣稱的,系統完全沒有驗證這個宣稱是否為真。現在的實作裡,任何人只要在 Header 塞 x-user-role: admin,就能拿到 Admin 權限——這在教學示範上沒問題,因為重點是展示有分層、有門禁這個架構概念,但如果是真實上線的系統,這一步必須換成從已登入、已驗證身分的 Token 裡解析角色,而不是相信使用者自己說的話。這個落差,我會留在文章裡,不假裝它是完整的身分驗證系統。

把 Day19 跟 Day20 放在一起看,會看到一個分層防禦(Defense-in-Depth)逐漸成形的樣子:

  • Day19 擋的是「這句話長怎樣」——規則式比對輸入/輸出的文字特徵,天花板是「只能擋已知的講法」。
  • Day20 擋的是「這個請求有沒有資格碰這個功能」——跟文字內容完全無關,就算攻擊者講出再自然、再躲過 regex 的句子,只要它打的是沒有權限的路由,照樣進不去。

這兩層防的是不同維度的風險,互相不能取代——Day19 擋不住我是真的只是權限不夠,Day20 也擋不住我有權限,但講出了不該講的內容。這正是分層防禦的意義:每一層只負責自己能負責的部分,漏洞不會因為疊了很多層就消失,但攻擊者要同時繞過好幾層的難度,會比繞過一層高得多。

NIST Defense-in-Depth 概念參考 https://csrc.nist.gov/glossary/term/defense_in_depth

小結
今天沒有新的紅隊攻擊題目,是因為今天本來就不是在測機器人會不會講出不該講的話,而是在測系統的門禁有沒有確實擋下沒資格的請求——這是完全不同的測試標的,所以評分標準也不是 ✅/⚠️/❌ 三級,而是單純的該擋的擋住了嗎、該放行的放行了嗎二元判斷,兩邊都過關。

明天預告
明天(Day21)把 Audit Logging 真正接進 /admin/audit-log,門禁已經先立好了,不用擔心新功能上線的那一刻就是裸奔。

本系列所有病患資料皆為人工生成之虛構資料,不涉及任何真實病患。GitHub Repo:medical-ai-security-lab


上一篇
Day19|Input/Output Validation Middleware:規則擋住了一半
下一篇
Day21|Audit Logging + 陽春 Dashboard:讓「防禦有沒有生效」這件事看得見
系列文
Medical AI Security Lab:醫療 AI Chatbot 的攻防實驗與自動化 Red Team 共 22 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言