廣為流傳的「萬能分頁預存程序」雖使用參數化輸入,但因內部動態拼接 SQL 字串再 `EXEC` 執行,導致單引號轉義失效,存在嚴重 SQL 注入風險,且無法在預存程序內修補。
CREATE PROCEDURE Pagination
@tblName varchar(255), @strGetFields varchar(1000) = '*',
@fldName varchar(255) = '', @PageSize int = 10, @PageIndex int = 1,
@doCount bit = 0, @OrderType bit = 0, @strWhere varchar(1500) = ''
AS
DECLARE @strSQL varchar(5000)
-- ... 根據參數拼接 @strSQL ...
-- 第一頁範例:
SET @strSQL = 'SELECT TOP ' + @PageSize + ' ' + @strGetFields +
' FROM [' + @tblName + '] WHERE ' + @strWhere + ' ' + @strOrder
EXEC (@strSQL)
假設呼叫端使用 `SqlParameter` 傳遞 `@strWhere`,輸入 `UserName LIKE '%Jim'' dog%'`,參數化後單引號變雙引號:
@strWhere = 'UserName LIKE ''%Jim'''' dog%'''
拼接後的動態 SQL:
SELECT TOP 20 * FROM [UserAccount] WHERE UserName LIKE '%Jim'' dog%' ORDER BY ID DESC
實際執行時,`LIKE '%Jim'` 後面的 ` dog%'` 被截斷。若攻擊者輸入 `Jim'; DELETE FROM UserAccount;--`,經雙引號轉義後仍能在 `EXEC` 時構成多語句攻擊。
public DataTable GetPaged(string table, string fields, string where, string order,
int page, int size, out int total, params SqlParameter[] pars) {
// 1. 計算總數
string cntSql = $"SELECT COUNT(*) FROM {table} WHERE 1=1 {where}";
total = (int)SqlHelper.ExecuteScalar(cntSql, pars);
// 2. 分頁查詢 (SQL Server 2012+ OFFSET/FETCH)
string sql = $@"
SELECT {fields} FROM {table}
WHERE 1=1 {where}
ORDER BY {order}
OFFSET @offset ROWS FETCH NEXT @size ROWS ONLY";
var list = new List(pars) {
new SqlParameter("@offset", (page-1)*size),
new SqlParameter("@size", size)
};
return SqlHelper.ExecuteDataTable(sql, list.ToArray());
}
呼叫端:`GetPaged("Users", "*", " AND UserName LIKE @kw", "Id DESC", 1, 20, out total, new SqlParameter("@kw", "%Jim%"))`
「通用分頁預存程序」屬反模式,動態 SQL 拼接 + EXEC = 注入溫床。現代開發應回歸參數化查詢組裝於應用層,利用資料庫新版分頁語法 (OFFSET/FETCH, ROW_NUMBER) 獲得效能與安全雙贏。