資安指南
只加了登入就以為安全了嗎 — 身分驗證與授權的差別
身分驗證(你是誰)與授權(你可以做什麼)是兩回事。只加了登入卻沒有依擁有者篩選資料,就會變成「登入=看到全部資料」。本文說明為何發生與如何防禦。
「我加了登入,所以現在有保護了」——這是個危險的假設。登入(身分驗證)和權限(授權)是不同的東西,把兩者搞混就會釀成一場事故:凡是登入的人都能看到全部資料,包含別人的。 這裡不寫任何攻擊步驟——只講它是怎麼發生的,以及如何防禦。
身分驗證與授權是不同的東西
它們很容易被混為一談,但角色完全不同。
只有身分驗證(常見的誤解)
- 以為「能登入=安全」
- 把登入後的資料讀取當成對所有人都一樣
- 結果:凡是登入的人都能抵達全部資料
身分驗證+授權(應有的樣子)
- 由身分驗證確認「你是誰」
- 由授權限縮到「只有這個人可以碰的資料」
- 資料讀取永遠依擁有者做範圍限制
身分驗證是「入口的身分核對」;授權是「控制可以進入哪些房間」。登入只保證前者。「誰可以碰哪些資料」是你必須另外寫的東西。
「看到全部資料」在真實應用中是怎麼發生的
在現場,同一個漏洞在彼此獨立、各自自建的系統上一再出現。個人的待辦/筆記應用,或是小型的商品・訂單管理系統(帶有外部 API 整合)——起因都是相同的兩個缺陷的重疊。
① 註冊是開放的(而且分佈在兩條路由上)
驗證腳手架(像是 Laravel 的 Breeze/Jetstream 那類)往往預設就把註冊頁公開。而當註冊路由在 UI 函式庫和驗證套件兩邊重複時,關掉其中一條,/register 仍然留著。知道網址的陌生人就能註冊。
② 有身分驗證但沒有授權(缺少擁有者範圍限制)
資料表沒有擁有者欄位(user_id),或列表/讀取/更新/刪除的查詢沒有限縮到目前使用者。於是「登入=存取全部資料」。若應用在註冊後隨即自動登入,陌生人一註冊就會落在別人的資料列表上。
結果:authn ≠ authz 的事故
入口(登入)有了,但房間的鑰匙(授權)沒有。因為陌生人的行為就像「一個正常登入的使用者」,未授權存取幾乎不留下任何痕跡。
而牽涉的業務資料越多,波及範圍就越大(客戶記錄、訂單、整合用的 API 金鑰都可能受影響)。而且用相同方式自建的另一個系統,很可能也有同一個漏洞。
如何防禦
永遠把資料依擁有者做範圍限制
把每一次讀取與寫入都限縮到「登入使用者自己的那幾筆」。透過 ORM 的全域 scope 或授權策略(policy/gate),在讀取/更新/刪除全部強制加上擁有者條件。如果資料表沒有擁有者欄位,先從那裡重新設計。
關閉你不需要的註冊——並懷疑路由有重複
在個人/內部應用上停用註冊。實際用 HTTP 量測 /register 之類的路徑是否回傳 404,並同時檢查 UI 函式庫和驗證套件兩邊的註冊路由。
在管理介面做多層防禦
內部/管理用途不要只靠身分驗證——疊上 IP 限制、Basic 驗證之類的手段。一道牆被攻破,還有下一道能擋下。
在事故發生*之前*備好稽核與存取記錄
沒有「誰、在何時、從哪個 IP、做了什麼」,事後就無法還原「有哪些東西被看過」。至少要保留登入成功/失敗的稽核記錄,並保存與輪替 Web 存取記錄。
偵測新的註冊與異常
這次的核心教訓是「很長一段時間都沒被察覺」。加上能快速察覺的辦法——新使用者註冊時發通知、定期盤點使用者/資料。沒有偵測,漏洞就會靜靜地一直開著。
本站的觀點:函式庫只保證「你是誰」
驗證函式庫替你處理的只到「你是誰」——就到此為止。「這個人可以做什麼」除非身為設計者的你去寫,否則並不存在。而且——在一個系統中發現的結構性缺陷,很可能也存在於用相同方式打造的其他系統中。不要修好一個就收工;請橫向盤點所有同型的建置。當你無法判斷某樣東西是否被讀取過時,正確的姿態是把它當成已被讀取,並將任何外洩的機密失效/輪替掉。
接下來閱讀
- 術語:IDOR(存取控制缺陷)是什麼(經典手法:指定別人的 ID 就能碰到別人的資料)
- 實作:各框架的安全(入口)(在每個框架裡該把授權寫在哪)
- 相關:別把機密檔案放進公開目錄(檢查 token/credentials/.env 的外洩點)
- 入門:從頭學會保護機密 / 基礎:最低限度的安全檢查清單
出處
- OWASP Top 10 (2021) A01: Broken Access Control: owasp.org
- OWASP Authorization Cheat Sheet: cheatsheetseries.owasp.org
FAQ
Q身分驗證與授權有什麼不同?
身分驗證是確認「你是誰」(登入)。授權是決定「這個人可以做什麼」(權限)。打個比方,身分驗證是在大樓入口做的身分核對;授權則是控制這個人可以進入哪些房間。只加了登入,做到的僅是身分驗證——授權(誰可以碰哪些資料)是你必須另外寫的東西。把這兩者混為一談,就會落得「凡是登入的人都能看到全部資料」。
Q明明有登入,為什麼所有人都能看到全部資料?
因為取資料的查詢沒有限縮到「只有目前登入使用者自己的那幾筆」。只要在列表/讀取/更新/刪除的任何一處忘了加上擁有者(user_id)條件,一旦有人登入,全部資料列就都會回傳——包含別人的。這是與有沒有身分驗證無關的設計漏洞,正是 OWASP 排名第一的經典 Broken Access Control(存取控制缺陷)。
Q如果是個人工具或內部應用,把註冊開著不是沒關係嗎?
很危險。許多驗證腳手架預設就把註冊(例如 /register)公開出去。陌生人只要知道網址就能註冊,若沒有授權,就會直接走到裡面的資料上。而且註冊路由有時會重複(UI 函式庫和驗證套件各有一條),關掉其中一條並不代表關掉全部。停用你不需要的註冊,並在管理介面上疊加 IP 限制之類的防禦。