SQL.16 SQL 的權限模型:角色、GRANT 的授權單位與最小權限
GRANT 與 REVOKE 是 SQL 的一部分,與 SELECT 一樣由標準定義。它們管的是誰被允許對哪個物件做哪件事,而預設是什麼都不允許——權限是一項一項加上去的,不是先給再收回來。這個預設之下,一個帳號能做什麼等於它被授予過什麼,兩者之間沒有隱含的推導。
以下的輸出來自 PostgreSQL 18。SQLite 沒有使用者的概念,整個檔案的存取由檔案系統決定,所以本篇談的授權這一層在 SQLite 上不存在。
新建角色的預設權限
書店的資料裡有三位顧客與兩張訂單,現在要開一個只跑報表的帳號。PostgreSQL 用「角色」這一個概念同時涵蓋使用者與群組——帶 LOGIN 屬性的角色可以連線,不帶的當群組用,兩者授權的方式相同。先建一個能登入的角色,什麼權限都還沒給:
1CREATE ROLE 報表 LOGIN PASSWORD '...';
2SET ROLE 報表;
3SELECT * FROM 顧客;
4-- ERROR: permission denied for table 顧客這個角色存在、登得進來、而它讀不到任何一張表。能連線與能做事是兩件分開的事:能連線由角色的 LOGIN 屬性決定,能做事由授權決定。
給了讀取權之後才讀得到:
1RESET ROLE; -- 切回表的擁有者;以報表的身分執行這條 GRANT 會回 permission denied
2GRANT SELECT ON 顧客 TO 報表;
3SET ROLE 報表;
4SELECT 姓名 FROM 顧客;
5-- 佳穎、宗翰、雅文授權的單位:角色、表與動作
上面只給了 SELECT。同一個角色去寫:
1INSERT INTO 顧客 VALUES (4,'柏宇'); -- permission denied for table 顧客
2DELETE FROM 顧客 WHERE 顧客編號=1; -- permission denied for table 顧客兩個都被擋下來,錯誤訊息與完全沒授權時一模一樣。權限的單位是「哪個角色、對哪張表、做哪一種動作」,SELECT、INSERT、UPDATE、DELETE 各自獨立,給一項不會順帶給另一項。「哪張表」由識別字指認,而那個名字送進引擎之前會先被改寫一次——GRANT SELECT ON Orders 在 PostgreSQL 上授權的對象是 orders,這個改寫的規則在 SQL.14 識別字的規則:各家的大小寫摺疊、引號與保留字。
目前有哪些授權查得到,收回之後那個角色又回到 permission denied:
1RESET ROLE; -- 查詢與收回都由表的擁有者執行
2SELECT grantee, privilege_type FROM information_schema.role_table_grants
3WHERE table_name = '顧客';
4-- 表的擁有者:INSERT、SELECT、UPDATE、DELETE、TRUNCATE、REFERENCES、TRIGGER 各一列
5-- 報表 | SELECT
6
7REVOKE SELECT ON 顧客 FROM 報表;
8SET ROLE 報表;
9SELECT * FROM 顧客;
10-- ERROR: permission denied for table 顧客建立物件的權限與 schema
GRANT SELECT 給的是對既有表的存取,與「能不能建新的表」無關。報表角色去建表:
1CREATE TABLE 偷建的 (x INT);
2-- ERROR: permission denied for schema public被擋的層次不同——這次擋在 schema 而不是 table。PostgreSQL 15 之後 public schema 預設不再開放給所有人建立物件,在那之前這個操作會成功。同一段 SQL 在不同版本上的結果不同,而差別在預設值而不在語法。
最小權限:應用程式帳號的授權範圍
應用程式連資料庫用的帳號,開的權限涵蓋它實際會執行的動作就夠。一個只跑報表的程式從來不寫入,所以它的帳號有沒有 INSERT 對它的功能沒有差別;處理訂單的程式不會刪顧客,所以它的帳號有沒有顧客表的 DELETE 對它的功能也沒有差別。遷移是唯一需要 DDL 的場合,而它不是常態運行的一部分——那些權限跟著它自己的帳號走,平常沒有任何一條應用程式的連線用它登入。遷移本身的流程(雙寫、回填、切流與回滾)在 資料庫轉換實作。
每一種用途各用一個帳號、只開它用得到的權限,這樣分的效益在事故發生時才顯示出來。應用程式如果用超級使用者連線,一個注入漏洞——攻擊者讓自己寫的片段被當成 SQL 的一部分送出去——的可觸及範圍就是整個資料庫,包含刪表與讀取其他應用的資料;同一個漏洞在只有 SELECT 權限的帳號上,可觸及範圍縮到被授予 SELECT 的那幾張表的讀取。漏洞一樣,可觸及的範圍差很多,而差別在事發前就決定好了。 授權只是資料層攻擊面的其中一層,backend 模組一的攻擊者視角 走注入、授權繞過、資料外洩、競態與資源耗盡五類,前三類各附真實事件對照。這個推論的前提是應用程式的每一種用途各用一個資料庫帳號;所有用途共用一個帳號、由應用層自己做授權的架構下,資料庫這一層的最小權限就使不上力,防線要落在別處。
授權查得到、也審計得到,所以「這個帳號能做什麼」是一個有明確答案的問題,不必靠讀應用程式的程式碼推測。