安全 T-SQL 非單一技巧,而是**開發、部署、執行**三階段的纏繞防禦:
-- ❌ 錯誤:字串拼接 (SQL Injection 風險)
DECLARE @SQL nvarchar(max) = N'SELECT * FROM Users WHERE UserName = ''' + @UserName + '''';
EXEC (@SQL);
-- ✅ 正確:sp_executesql 參數化 (計畫重用、型別安全、防注入)
DECLARE @SQL nvarchar(max) = N'SELECT * FROM Users WHERE UserName = @UserName';
EXEC sp_executesql @SQL, N'@UserName nvarchar(50)', @UserName = @UserName;
-- ✅ 正確:預存程序參數 (最佳封裝)
CREATE PROCEDURE dbo.usp_GetUser @UserName nvarchar(50)
AS SELECT * FROM Users WHERE UserName = @UserName;
**鐵律**:所有動態 SQL、應用程式呼叫、跨資料庫查詢,**絕對禁止**字串拼接使用者輸入。
| 層級 | 原則 | 實作範例 |
|---|---|---|
| 伺服器 | 服務帳號僅具備啟動服務、存取資料/記錄/備份目錄權限;非 sysadmin。 | 建立專用 gMSA,授予 Perform volume maintenance tasks (瞬間檔案初始化)。 |
| 資料庫 | 應用使用者 僅擁有必要物件權限,透過角色彙總。 | CREATE ROLE App_ReadOnly; GRANT SELECT ON SCHEMA::Sales TO App_ReadOnly; |
| 模組 | 預存程序/函數 簽署憑證/非對稱金鑰,授予簽署者額外權限 (如 TRUNCATE TABLE),呼叫者無需直接權限。 | CREATE CERTIFICATE Cert_Truncate ...; ADD SIGNATURE TO usp_PurgeHistory BY CERTIFICATE Cert_Truncate; |
| 資料行 | 敏感欄位 (薪資、身分證) 使用 動態資料遮罩 (SQL 2016+) 或 加密欄位** (Always Encrypted)。 | ALTER TABLE Employees ALTER COLUMN Salary ADD MASKED WITH (FUNCTION = 'partial(0, "XXXXX", 0)'); |
xp_cmdshell, xp_regread, xp_regwrite, sp_OA* 等。EXEC sp_configure 'Ole Automation Procedures', 0; RECONFIGURE;EXEC sp_configure 'clr strict security', 1; RECONFIGURE; 所有組件需簽署/信任。sql_statement_completed 含參數值)。SqlCodeGuard, TSQLLint, SonarQube (TSQL Plugin) 掃描:動態 SQL 拼接、硬編碼密碼、缺少 TRY/CATCH、未參數化。
CREATE PROCEDURE [dbo].[usp_SecureTemplate]
@Param1 int,
@Param2 nvarchar(100)
WITH EXECUTE AS CALLER -- 或指定低權限 Proxy 使用者
AS
BEGIN
SET NOCOUNT, XACT_ABORT ON; -- 錯誤即回滾、不回傳行數
BEGIN TRY
-- 1. 輸入驗證 (白名單/範圍/長度)
IF @Param1 <= 0 THROW 50001, N'Invalid Param1', 1;
IF LEN(@Param2) > 100 THROW 50002, N'Param2 too long', 1;
-- 2. 核心邏輯 (參數化、無動態 SQL)
SELECT Col1, Col2
FROM dbo.SensitiveTable
WHERE ID = @Param1 AND Code = @Param2; -- 索引求值
-- 3. 審計日誌 (非同步寫入 Service Broker / 彈性資料表)
INSERT INTO dbo.AuditLog (ProcName, ParamJSON, UserName, EventTime)
VALUES (OBJECT_NAME(@@PROCID), (SELECT @Param1 AS P1, @Param2 AS P2 FOR JSON PATH), SUSER_SNAME(), SYSDATETIME());
END TRY
BEGIN CATCH
-- 統一錯誤處理:記錄、不洩露堆疊、回傳友善代碼
DECLARE @ErrMsg nvarchar(4000) = ERROR_MESSAGE();
INSERT INTO dbo.ErrorLog (ProcName, ErrorMsg, ErrorTime) VALUES (OBJECT_NAME(@@PROCID), @ErrMsg, SYSDATETIME());
THROW; -- 重新拋出保留錯誤資訊
END CATCH
END
[ ] 所有動態 SQL 使用 sp_executesql 參數化
[ ] 應用帳號最小權限、角色管理、模組簽署提權
[ ] 敏感欄位動態遮罩 / Always Encrypted
[ ] xp_cmdshell / sp_OA* / 不必要擴展程序 已停用
[ ] CLR 嚴格安全性啟用
[ ] SQL Audit / Extended Events 記錄關鍵事件
[ ] CI/CD 整合 SAST (TSQLLint/SonarQube) + tSQLt 單元測試
[ ] 代碼審查強制檢查參數化、權限、錯誤處理、敏感資料
[ ] 錯誤訊息不回傳前端、統一友善代碼
[ ] 定期滲透測試、ATP 警示訂閱