以縱深防禦的層次(網路 \(\to\) 認證 \(\to\) 授權 \(\to\) 列/欄層級 \(\to\) 加密 \(\to\) 稽核)組織資料庫安全。
讀寫 ,選擇正確的認證方法(SCRAM-SHA-256、憑證、Kerberos、OAuth)與 TLS 模式。
以角色、權限、預設權限、 與 的注意事項設計最小權限模型。
用 Row-Level Security 實作多租戶隔離,並知道其效能與繞過條件。
解釋 SQL injection 的機制與各種形式,並以參數化查詢、、最小權限徹底防範。
區分磁碟加密、TDE、欄位加密與應用層加密各自防禦的威脅,以及金鑰管理的角色。
設定稽核日誌(、pgaudit),並列出一份可執行的強化檢查清單。
威脅模型與縱深防禦
資料庫是攻擊者的終極目標:它存著一切。常見攻擊面包括:暴露在網際網路的連接埠、弱或外洩的密碼、應用程式的 SQL injection、 權限過大的帳號(應用程式用 superuser)、未加密的備份與日誌、內部人員、以及未修補的已知漏洞。 沒有單一機制能擋住全部,縱深防禦讓每一層的失守都被下一層限制損害範圍:
兩個貫穿全章的原則:最小權限(每個主體只擁有完成工作所需的最少權限)與預設拒絕(沒有明確允許的都禁止)。
認證:pg_hba.conf
PostgreSQL 的主機式認證檔 決定「誰、從哪裡、連哪個資料庫、用什麼方法」,由上而下第一個符合的規則生效,順序至關重要:
# 本機維運:以 OS 使用者對應資料庫角色,不需密碼
local all postgres peer
# 應用程式:只允許來自應用子網、強制 TLS、SCRAM 密碼
hostssl appdb app_rw 10.0.1.0/24 scram-sha-256
# 報表唯讀帳號:以客戶端憑證認證(CN 必須等於角色名)
hostssl appdb reporting 10.0.2.0/24 cert clientcert=verify-full
# 複寫連線
hostssl replication replicator 10.0.0.0/16 scram-sha-256
# 其他一律拒絕(明確寫出比依賴「沒有符合規則」更清楚)
host all all 0.0.0.0/0 reject
lP10.6cm 方法 & 說明
& 不驗證。只能用在完全隔離的開發環境;許多入侵事件源於生產環境留下的 。
/ & 以 OS 使用者身分對應角色( 用於 Unix socket)。
& PostgreSQL 10+,14 起為 預設。鹽化、迭代雜湊、挑戰回應,密碼不經網路傳送,伺服器只存驗證子(verifier)。
& 舊方法,已不建議(可被重放、雜湊可離線破解);僅為相容保留。
& 客戶端 TLS 憑證; 要求 CN 符合角色名(或以 對應)。適合服務對服務。
/ & Kerberos/Active Directory 單一登入,企業環境常見。
/ / & 把密碼轉交外部系統驗證;注意 simple bind 會把明文密碼送到 LDAP 伺服器,必須走 TLS。
& PostgreSQL 18 新增:OAuth 2.0 device authorization flow,由 指定的驗證模組檢查 token,可對接 Okta、Entra ID 等 IdP。
密碼管理:PostgreSQL 沒有內建複雜度規則( 模組可加一點),到期以 設定; 的明文會出現在伺服器日誌與 歷史——改用 (客戶端先雜湊)或直接提供 SCRAM verifier。 雲端服務另提供 IAM 認證(短效 token 取代密碼,如 RDS IAM)。
傳輸加密:TLS
伺服器端 加上 、(可選 以驗證客戶端憑證); 用 強制加密、 拒絕未加密連線。 客戶端的 決定實際安全程度:
P2.6cmP2.4cmP2.4cmP5.6cm & 竊聽防護 & 中間人防護 & 說明
/ / & 否/視情況 & 否 & 是 libpq 預設:伺服器不支援就退回明文,攻擊者可降級
& 是 & 否 & 加密但不驗證伺服器身分
& 是 & 部分 & 驗證憑證由信任的 CA 簽發,不比對主機名
& 是 & 是 & 驗證 CA 與主機名;生產環境應使用
PostgreSQL 17 的 省去明文的 往返; 建議 以上。 GSSAPI 也可提供加密()。別忘了複寫連線與 PgBouncer 到後端的連線也要加密。
授權:角色與權限
PostgreSQL 統一以角色(role)表示使用者與群組( 屬性區分)。角色可成為其他角色的成員並繼承權限(,PostgreSQL 16 起可逐成員設定),或以 切換。
-- 群組角色(NOLOGIN)承載權限;登入角色只是成員
CREATE ROLE app_readonly NOLOGIN;
CREATE ROLE app_readwrite NOLOGIN;
GRANT USAGE ON SCHEMA app TO app_readonly, app_readwrite;
GRANT SELECT ON ALL TABLES IN SCHEMA app TO app_readonly;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA app TO app_readwrite;
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA app TO app_readwrite;
-- 未來建立的物件也套用(由執行遷移的角色 migrator 建立)
ALTER DEFAULT PRIVILEGES FOR ROLE migrator IN SCHEMA app
GRANT SELECT ON TABLES TO app_readonly;
ALTER DEFAULT PRIVILEGES FOR ROLE migrator IN SCHEMA app
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO app_readwrite;
-- 登入角色:應用程式執行期、遷移、報表各自分開
CREATE ROLE app_rw LOGIN PASSWORD '...' IN ROLE app_readwrite CONNECTION LIMIT 200;
CREATE ROLE reporting LOGIN IN ROLE app_readonly;
-- 欄位層級:薪資欄只給 HR
GRANT SELECT (id, name, dept) ON app.employees TO app_readonly;
GRANT SELECT (salary) ON app.employees TO hr;
應用程式用 superuser 或物件擁有者連線:一旦 injection 成功,攻擊者就能 、讀取檔案、改任何表。執行期帳號應只擁有 DML;DDL 由獨立的遷移角色執行。
schema:PostgreSQL 15 之前所有人都能在 建物件,攻擊者可建同名函數劫持(CVE-2018-1058)。15 起預設撤銷 ;舊叢集手動 。
與 :以定義者權限執行的函數若未固定 ,呼叫者可用自己 schema 的同名物件替換——務必 ,並限制 權限(函數預設 可執行!)。
預定義角色:、、、、、 等——後兩者等同 OS 層級的存取,授予要極謹慎。
View 不是完美的安全邊界:最佳化器可能把使用者的函數推到過濾條件之前執行而洩漏資料;需要 view 與 函數的概念(RLS 內建處理)。
Row-Level Security
RLS 讓資料庫強制「哪個角色能看到/修改哪些列」,常見於多租戶 SaaS:所有租戶共用表,但每個查詢自動加上租戶條件。
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
ALTER TABLE orders FORCE ROW LEVEL SECURITY; -- 連表的擁有者也受限制
CREATE POLICY tenant_isolation ON orders
USING (tenant_id = current_setting('app.tenant_id')::int) -- 讀取/既有列的可見性
WITH CHECK (tenant_id = current_setting('app.tenant_id')::int); -- 寫入的新列必須符合
-- 應用程式在每筆交易開始時設定(transaction-scoped,連線池安全)
BEGIN;
SET LOCAL app.tenant_id = '42';
SELECT * FROM orders; -- 自動只回傳 tenant 42 的列
COMMIT;
Policy 可指定命令()與角色();多個 permissive policy 以 OR 結合,restrictive policy()以 AND 疊加。
沒有任何 policy 符合時預設拒絕(看到空表)。Superuser 與具 的角色不受限制;表擁有者除非 也不受限。
USING 條件會被加進每個查詢計畫,確保 有索引(通常是複合索引的前導欄);policy 中的子查詢要留意效能。
比在應用程式每個查詢手寫 可靠得多——漏寫一次就是跨租戶資料外洩。Supabase 等平台的 API 授權即建立在 RLS 上。
SQL Injection
機制:程式把使用者輸入拼接進 SQL 字串,使資料被當成程式碼執行。
# Python(錯誤示範)
query = f"SELECT * FROM users WHERE name = '{name}' AND password = '{pw}'"
# 輸入 name = "admin' --" → SELECT * FROM users WHERE name = 'admin' --' AND password = ''
# 輸入 pw = "' OR '1'='1" → 條件恆真
# 輸入 name = "' UNION SELECT usename, passwd FROM pg_shadow --" → 讀出密碼雜湊(若權限足夠)
# Blind / time-based: name = "' AND (SELECT pg_sleep(5) WHERE substr(current_user,1,1)='p') --"
防禦(依重要性):
參數化查詢(prepared statement / bind parameters):SQL 結構與資料走協定的不同通道(第 17 章的 Parse/Bind),資料永遠不會被解析成 SQL。 所有主流驅動與 ORM 都支援;識別字(表名、欄名)與 方向不能參數化,必須白名單。
動態 SQL 必須正確引用:PL/pgSQL 用 (識別字)、(字面值)與 ; 應用層用 / 或驅動的 identifier 類別(如 psycopg 的 )。
最小權限:即使注入成功,一個只有 DML 權限、受 RLS 限制、不能讀檔案的帳號能造成的損害有限。
輸入驗證與輸出編碼是輔助(防止邏輯錯誤與 XSS),不能取代前三項;WAF 只能攔截已知樣式。
不要把資料庫錯誤訊息原樣回給使用者(洩漏 schema),並對 等延時函數的呼叫保持警覺。
CREATE FUNCTION count_rows(tbl text, col text, val text) RETURNS bigint
LANGUAGE plpgsql SECURITY INVOKER SET search_path = pg_catalog, pg_temp AS $$
DECLARE n bigint;
BEGIN
EXECUTE format('SELECT count(*) FROM %I WHERE %I = $1', tbl, col) INTO n USING val;
RETURN n;
END $$;
靜態加密
P2.8cmP4.2cmP3.6cmP3.4cm 層級 & 做法 & 防禦的威脅 & 不防禦的威脅
磁碟/卷加密 & LUKS、雲端磁碟 KMS 加密、自加密硬碟 & 磁碟被偷、報廢硬碟、快照外流 & 任何能登入 OS 或資料庫的人;對執行中的系統透明
TDE(透明資料加密) & 資料庫在寫頁面/WAL 時加密;PostgreSQL 核心尚無 TDE(Percona 、EDB、Cybertec 提供) & 直接讀取資料檔與備份、不經資料庫的存取 & 透過 SQL 的存取、記憶體傾印;合規需求(PCI-DSS)常明文要求
欄位加密() & ;金鑰由應用程式傳入 & 有資料庫讀取權但沒有金鑰的人 & 金鑰若存在資料庫或日誌中則無效;加密欄無法索引/比較(確定性加密可等值查詢但洩漏重複)
應用層/信封加密 & 應用程式以資料金鑰加密、資料金鑰再由 KMS 主金鑰加密(envelope) & DBA、資料庫被完整拿走 & 應用程式伺服器被攻破;查詢能力大幅受限
密碼永遠不儲存明文,也不儲存可逆加密:以 bcrypt/scrypt/Argon2 等慢雜湊加鹽(應用層,或 的 )。
金鑰管理是加密的核心難題:金鑰與資料分離、定期輪替、以 KMS/HSM 保管、稽核金鑰使用。Crypto-shredding(刪除金鑰即等同刪除資料)是實作 GDPR「被遺忘權」於備份中的常用技巧。
備份、WAL 封存、日誌、 輸出與正式資料同等敏感,必須同樣加密並控管存取。
開發/測試環境應使用去識別化的資料(PostgreSQL Anonymizer 擴充套件可做靜態或動態遮罩)。
稽核與監控
內建日誌:、、(或 ; 的量與敏感資料風險很高)、、 含使用者、資料庫、來源位址、應用程式名稱。
pgaudit:符合稽核需求的結構化記錄——session audit(依類別 READ/WRITE/DDL/ROLE/FUNCTION/MISC)與 object audit(針對特定表,透過授權給稽核角色來標記);記錄的是實際執行的語句(含 prepared statement 的參數,若開啟)。
日誌要送出資料庫主機(SIEM/集中式日誌),本機日誌可被同一個攻擊者刪除;設定保留期限與完整性保護。
監控異常:非上班時間的 superuser 登入、大量 的匯出行為、失敗登入暴增、來自新 IP 的連線、 中突然出現的新查詢樣板。
只綁必要介面;防火牆/安全群組只開給應用子網;絕不直接暴露 5432 到網際網路。
:沒有 、沒有 的允許規則、全面 或憑證、 強制 TLS;客戶端 。
應用程式帳號非 superuser、非擁有者、只有 DML;遷移、報表、監控、備份各用獨立角色;。
;函數明確 ; 函數固定 。
全面參數化查詢;程式碼審查與靜態分析禁止字串拼接 SQL。
多租戶啟用 RLS;敏感欄位以欄位權限或 view 限制。
磁碟加密 + 必要時 TDE/欄位加密;金鑰在 KMS;備份加密並定期演練還原。
開啟連線日誌與 pgaudit,集中保存;設定告警。
追蹤 PostgreSQL 安全公告,定期套用 minor 版本更新(含 CVE 修補);擴充套件白名單。
、 等資源限制,降低 DoS 影響。
為 MiniDB 的 wire protocol 加入:(1) 使用者表與鹽化慢雜湊的密碼驗證(或實作 SCRAM-SHA-256 的子集合,直接相容 psql); (2) 與執行器中的權限檢查;(3) 擴充查詢協定的參數綁定,證明參數值永遠不經過 parser。 寫一個測試:以 作為參數值查詢,確認它被當成純字串。
下列 有什麼問題? 放在第一行、之後才是 。
為什麼 不能防止中間人攻擊?攻擊者具體怎麼做?
一個 函數內呼叫了未加 schema 的 。說明攻擊者如何利用它取得定義者權限,以及兩種修法。
設計一個 RLS 方案:客服人員可以看到自己負責區域的所有客戶、客戶只能看到自己、主管可以看到全部但不能修改。寫出 policy。
以下 PL/pgSQL 是否安全? 若 來自使用者呢?
比較「整個資料庫用 TDE」與「只對信用卡號做應用層加密」在合規、效能、查詢能力、金鑰外洩影響上的差異。
(動手)安裝 pgaudit,設定 ,執行一組操作後閱讀日誌格式;再試 object audit 只稽核一張表的 SELECT。
PostgreSQL 文件 Ch. 20 Client Authentication( 與所有認證方法):https://www.postgresql.org/docs/current/client-authentication.html; Ch. 21 Database Roles;§5.9 Row Security Policies:https://www.postgresql.org/docs/current/ddl-rowsecurity.html; §18.9 Secure TCP/IP Connections with SSL:https://www.postgresql.org/docs/current/ssl-tcp.html;§33.1.2 libpq SSL Support(sslmode 表)。
PostgreSQL 安全公告與 CVE 清單:https://www.postgresql.org/support/security/
pgaudit:https://www.pgaudit.org/;pgcrypto 文件:https://www.postgresql.org/docs/current/pgcrypto.html;Percona :https://docs.percona.com/pg-tde/;PostgreSQL Anonymizer:https://postgresql-anonymizer.readthedocs.io/
OWASP SQL Injection Prevention Cheat Sheet:https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html;OWASP Top 10。
CIS PostgreSQL Benchmark(逐項的強化設定與檢查方法):https://www.cisecurity.org/benchmark/postgresql
PostgreSQL Wiki A Guide to CVE-2018-1058: Protect Your Search Path:https://wiki.postgresql.org/wiki/A_Guide_to_CVE-2018-1058:_Protect_Your_Search_Path
R. Anderson, Security Engineering, 3rd ed.(免費線上版),威脅模型與系統性安全思維的基礎讀物。