iT邦幫忙

2026 iThome 鐵人賽

DAY 17
0

前面最後提到了一個問題:登入的是同一套系統,不代表每個人可以做的事情都一樣。

一般使用者、開發者、審核人員、管理員,都有自己的角色與權限。而且昨天也留了兩個例子:看不到 /admin 的按鈕,不代表不能直接輸入 /admin;看不到別人的資料,也不代表把 id=123 改成 id=124 就拿不到。

所以從這裡繼續


Phase 3:角色與分權

登入完成之後,接下來要處理的就是:你已經登入了,但你到底可以做什麼?

→ 定義系統有哪些角色
→ 定義每個角色可以看什麼、做什麼
→ 建立角色與權限
→ 實作後端權限檢查
→ 實作不同角色的功能權限
→ 測試一般使用者能不能直接輸入網址繞過限制
→ 測試修改參數後能不能看到或操作別人的資料

前面提到:把按鈕藏起來,不叫做分權。

例如一般使用者登入之後看不到「審核」按鈕,看起來好像沒問題。但如果他直接在瀏覽器輸入 /admin/review,結果頁面還是開得起來,那這個「隱藏按鈕」其實沒有什麼安全效果。

甚至頁面真的擋住了,但他直接呼叫 POST /api/review,結果後端照樣執行,那問題就更大了。

因為前端隱藏功能只是 UI 層的呈現控制,不是真正的存取控制。真正決定**「你到底可不可以做這件事情?」**的一定是後端。

所以在這個 Phase,要做的不是做「哪些按鈕要藏起來」,而是建立後端的權限控制。
把按鈕藏起來

權限不是只有「能不能用這個功能?」

另外一個例子就是前面提到的 IDOR/document?id=123 改成 /document?id=124,結果別人的資料就跑出來了。

為什麼會這樣? 很可能是後端只做了:「收到 id=124 → 去資料庫找 124 → 回傳資料。」
但是少問了一個很重要的問題: 「現在登入的這個人,有權限看 124 嗎?」
所以真正的權限控制,我會要求每一個重要操作在後端收到 Request 時,都重新確認三件事情:

  • 認證(Authentication):你是誰?Session 或 Token 還有效嗎?
  • 授權(Authorization):你的角色可以做這個動作嗎?
  • 物件層級授權(Object-level Authorization):你可以操作「這一筆」資料嗎?

舉幾個例子來看

今天有一個人只是一般的申請人員,他原本只能看到自己的案件 id=123,但他把網址改成 /document?id=124 之後,竟然可以看到別人的案件,甚至還可以編輯、刪除、取消或審核,那就是很嚴重的權限控制問題。

但問題還不只「一般人看到別人的資料」。

假設今天這個人本來就有「審核」功能的權限,代表他所有案件都可以審嗎? 理論上應該是不一定。

例如他是某個部門的主管,可能只應該審核自己部門的申請;如果他把案件 ID 改掉,就可以審核其他部門的案件,一樣有問題。

但如果今天登入的是全企業層級的主管,依照系統原本定義的權限,他可能就真的可以審核不同部門的案件。

所以後端不能只確認:「他是不是 Reviewer?」

還要繼續確認:「這一筆案件,是不是他有權限處理的案件?」

這就是為什麼不是只檢查 「能不能審核?」,還要再檢查 「能不能審核這一筆?」

Authentication → Authorization → Object-level Authorization

三個都通過,才真的執行。任何一個沒有通過,就拒絕。

分權


從防火牆預設政策學到的 Default Deny

這裡還有一個我自己很喜歡的原則:Default Deny(預設拒絕)。

以前在設定防火牆政策的時候,其實就很熟悉這個概念:沒有明確允許的流量,一律拒絕。

而不是反過來,「只要我沒有寫規則擋你,你就可以通過」。

這兩個思維看起來只是反過來講,但實際執行的時候會差很多。套到系統權限其實也是一樣。

假設今天系統新增了一支 API,但開發的時候忘記設定權限。如果原本的設計是「沒禁止就可以用」,這支新的 API 就可能直接被放行。

反過來,如果系統採用 Default Deny,那新的功能在沒有明確設定「哪些角色可以使用」之前,本來就不能使用。

這對 Vibe Coding 我覺得尤其重要,因為 AI 寫功能的速度真的很快。今天多一個頁面,明天多三支 API,如果每增加一個功能,都要靠人記得「這支記得封起來」,遲早會有忘記的一天。

這其實跟防火牆的概念沒有差太多:不是一直想著還有什麼東西忘記封,而是先全部擋住,再把真正需要的東西一個一個打開。

與其期待每次都記得禁止,不如一開始就預設不准。


AI 很容易做到「看起來有分權」

這也是 Vibe Coding 時我覺得要特別注意的地方。

你跟 AI 說:「一般使用者不能看到審核功能,只有 Reviewer 可以。」

AI 很可能很快就幫你寫好判斷,不是 Reviewer 就不顯示「審核」按鈕,然後跟你說:

「角色權限控制已完成。」

畫面看起來也真的完成了。

但要確定的是:如果使用者不是按那個按鈕呢?

我直接輸入網址呢?直接呼叫 API 呢?自己修改 Request 呢?

如果這些方法可以繞過前端的限制,那個「看不到的按鈕」根本沒有保護任何東西。

所以前端的權限判斷可以保留,因為它可以讓不同角色看到適合自己的操作介面。但它的用途主要是改善使用體驗。

前端負責讓你「看不到不該用的功能」,後端才負責讓你「真的用不了不該用的功能」。


Phase 3 做完,開始測越權

昨天 Phase 2 已經提過,除了測正常流程,會故意去測那些「不應該成功」的操作。

到了分權這個 Phase,我測的東西就更明確了:想辦法越權。

例如:

  • 一般帳號直接輸入 /admin,能不能進去?
  • 一般帳號直接呼叫管理 API,後端會不會執行?
  • A 使用者把 id=123 改成 id=124,能不能看到 B 使用者的資料?
  • 看得到別人的案件之後,能不能進一步編輯、刪除或取消?
  • 一般使用者能不能直接呼叫審核功能?
  • 部門 Reviewer 能不能審核其他部門的案件?
  • Reviewer 能不能操作原本不屬於自己權限範圍的案件?

這時候我要驗證的,其實就是前面講的三層控制:

Authentication → Authorization → Object-level Authorization

尤其最後一層很容易漏掉。因為「這個人有這個功能的權限」,不代表「這個人對所有資料都有這個權限」。

所以 Phase 3 做完,我不是再確認一次按鈕有沒有藏好,而是要確認:

就算我知道網址、知道 API、知道資料 ID,我還是做不了超出自己權限的事情。

這個 Phase 主要就是在處理:Broken Access Control、IDOR、水平越權、垂直越權。


Phase 4:檔案上傳與儲存

分權處理完之後,下一步就是我這個平台原本規劃的一個重要功能:
讓已經登入、而且有權限的使用者上傳自己的靜態網頁。

前面有提過,我希望把原本散落在外面的內部小工具慢慢收回來管理。例如原本有人把自己做的 HTML、CSS、JavaScript 靜態網頁放在 GitHub 或其他地方,未來可以把這些檔案移轉到這個平台,由平台統一管理。

所以對我的平台來說,「檔案上傳」不是附加功能,而是整個流程裡很重要的一環。
但也因為這樣,我認為這會是整個平台攻擊面非常大的一塊。
前面的登入與分權,大部分是在處理: 「誰可以進來?進來之後可以做什麼?」
到了這裡,就會多了一個更重要的問題: 「進來的人,可以把什麼東西丟進我的系統?」

因為我允許上傳的不是單純的圖片或 PDF,而是可能包含 HTML、CSS、JavaScript 的靜態網站內容。換句話說,我不是只讓使用者「存一個檔案」,而是準備接收之後可能會被瀏覽器解析、甚至執行的內容。

檢驗盤點

所以這個 Phase 預計要處理:

→ 建立上傳功能
→ 定義允許上傳的檔案與網站結構
→ 限制檔案類型與大小
→ 處理檔名與路徑
→ 決定檔案儲存位置
→ 建立上傳檔案與版本紀錄
→ 控制誰可以上傳、下載、刪除
→ 測試能不能透過修改網址或參數取得別人的檔案

AI 做上傳功能時,很容易快速完成: 選擇檔案 → POST → 後端存起來
然後告訴你: 「上傳功能完成。」

從功能面來看,它可能真的完成了,檔案選得到、傳得上去、伺服器也真的收到了。

但對我來說,檔案成功傳進伺服器,只能證明 Upload 功能正常,但不代表這個 Upload 是安全的。

真正麻煩的事情,現在才要開始。


.jpg 不一定真的是圖片

第一個問題就是檔案型別。

如果我把 test.html 改名成 test.jpg,它不會因為副檔名變成 .jpg,就真的變成一張圖片。

所以檔案型別不能只看副檔名,瀏覽器送過來的 Content-Type 也不能完全相信,因為這些都是來自使用者端的資訊。

比較完整的做法,依照系統需求做多層檢查:

副檔名白名單 + MIME Type + Magic Bytes/實際檔案內容 + 後續安全掃描

而且只開放系統真的需要的格式。如果這個系統只需要圖片,就不要因為「以後可能會用到」,順便把 ZIP、HTML、SVG、EXE 全部開起來。

但這裡也要注意:Magic Bytes 驗證通過,不代表檔案就是安全的。

它比較像是在確認「你說你是 JPG,看起來是不是真的像 JPG」。
至於裡面有沒有惡意內容,或這個檔案到底安不安全也是必須確認的,我預計後面來處理。


檔名也是使用者輸入

第二個很容易忽略的是:檔名本身也是外部輸入。

假設後端直接把使用者提供的檔名拿去組路徑,又沒有妥善處理,就可能碰到 ../../something 這類 Path Traversal(路徑穿越) 問題。

所以比較安全的方式,是讓伺服器自己產生真正儲存使用的檔名或識別碼。至於使用者原本上傳的 我的工具.zip,可以另外記錄下來,作為畫面顯示使用。

也就是: 顯示名稱是一回事,真正存在伺服器上的名稱是另一回事。


上傳目錄不要變成執行目錄

再來是檔案到底放在哪裡。

如果使用者上傳的東西直接被放到 Web Server 可以執行程式的地方,那風險就會變得很高。

所以比較好的方式,是把上傳內容放在 Web Root 之外的獨立儲存位置
並且限制 Web Server 不要把使用者上傳的內容當成程式執行。

這樣即使前面的檔案型別檢查真的被繞過,後面至少還有另外一道限制。

應用層檢查是一層,系統層限制再一層。

提供檔案下載時,也可以依照檔案用途設定適當的 Content-TypeContent-Disposition,並搭配 X-Content-Type-Options: nosniff 等控制,降低瀏覽器自行猜測內容型別所帶來的風險。


有限制檔案大小,不代表磁碟不會被塞爆

另外一個很容易出現的做法是,前端寫著:

「 限制每個檔案的大小 10 MB」

然後就覺得大小限制完成了。

但前端是使用者可以控制的,所以真正的檔案大小限制一定要在伺服器端做;依照實際架構,也可以在反向代理或 Web Server 再限制一次。

但事情還沒結束。

假設每一個檔案都真的小於 10 MB,有人一直上傳 10 MB、再 10 MB、再 10 MB,每一個檔案都符合規定,最後還是可以把磁碟塞滿。

所以除了限制「單一檔案可以多大」,還要考慮:

「你總共可以放多少?」

上傳空間最好有適當的容量限制或 Quota,必要時再使用獨立的儲存空間。不然最後掛掉的可能不只是上傳功能,而是整個系統。


上傳要驗權限,下載也一樣

最後還有一個很容易被忘記的地方:下載。

有時候上傳功能做了一堆權限檢查,結果下載變成:

/download?id=123 只要知道 ID 就可以拿。

那前面做半天的權限控制等於又破了一個洞。

如果 A 使用者把 id=123 改成 id=124,結果就可以下載 B 使用者的檔案,那其實又回到了 Phase 3 的 IDOR

所以下載、刪除、重新上傳,一樣都要重新確認:「這個人有沒有權限操作這一個檔案?」

不是知道 URL 就可以拿。


Phase 4 做完可以這樣測試

跟前面的 Phase 一樣,不會只測「正常的檔案能不能上傳」,而是會故意開始亂搞:

把不允許的檔案改副檔名,能不能騙過?不允許的檔案格式能不能直接 POST 上去?超大檔案會怎樣?特殊檔名會怎樣?A 使用者能不能下載 B 使用者的檔案?上傳的內容會不會被伺服器當成可以執行的內容?

因為檔案上傳真正要測的不是: 「正常的東西能不能進來?」

而是: 「故意丟奇怪的東西進去,系統會怎麼處理?」

這個 Phase 主要是在處理:Unrestricted File Upload、Path Traversal、Stored XSS、DoS、IDOR。


小結

看到這裡,可能有人會問:

「人自己開發也常常沒做這些啊?現在都交給 AI 了,我只要叫它幫我做好不就好了?我就是想要快、不想一直人工處理才找 AI,為什麼還要一個一個測試、一個一個確認?」

我覺得這其實是兩個問題。

如果今天是「人」本來就沒有做這些事情,那可能不在這篇討論的範圍,因為那比較偏向人的開發習慣、能力,或是組織本身的制度與流程問題。

我這裡比較想討論的是第二個問題:

既然都交給 AI 做了,為什麼還需要人確認?差別到底在哪裡?

這個問題其實可以再講很多,我後面找一天來說清楚。

差異在哪

但還是先說一下,至少開發者應該要知道:

現在做出來的東西,跟我要的是不是同一件事?
原本要求的限制,有沒有真的做到?該擋的東西,有沒有真的擋住?
你下的prompt夠不夠精準,AI有沒有照著你的規範做?

前面幾個 Phase,其實就是都是在做這件事:

Phase 2 解決的是 「你是誰?」
Phase 3 開始處理 「你可以做什麼?」
Phase 4 又多了一個問題:「你可以把什麼東西放進我的系統?」

做到這裡,檔案已經可以進來了。

但又出現下一個問題:

「上傳成功」跟「這個檔案安全」,是兩件完全不同的事情。

所以檔案進來之後,我還不會讓它直接往下走。

下一個 Phase,我們才要開始處理:

這個被上傳進來的東西,到底該不該讓它繼續往下走?


上一篇
Day 16|六個階段怎麼套進 Vibe Coding -4
下一篇
Day 18|六個階段怎麼套進 Vibe Coding -6
系列文
只要有心,人人都是食神—做得出來,就能端給別人吃嗎?19
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言