什麼是SQL注入,如何在C#中預防?
SQL注入是一種程式碼注入技術,允許攻擊者通過使用者輸入將惡意SQL程式碼發送到您的資料庫伺服器。 在他的影片"什麼是SQL注入以及如何在C#中防止它?"中,Tim Corey 精確演示了SQL注入漏洞如何出現在真實程式碼中,展示了幾個成功的SQL注入攻擊示例(包括基於聯合的和破壞性的),並通過您可以在C#中應用的實用SQL注入防止技術進行講解。 這篇文章遵循Tim的操作步驟,以便您可以看到他展示的確切問題和修復。
演示應用及其重要性
Tim從一個小型WPF演示應用開始,該應用連接一個本地InjectableDB(人物和秘密表)。 應用的類似Web表單的搜尋框接受使用者輸入(姓氏),並構建一個SQL查詢以返回ID、名字和姓氏。 它"運作" —— 輸入Corey即可得到Tim Corey —— 但Tim強調核心要點:"僅僅因為應用運作並不意味著它是安全的。"一個運作中的Web應用仍可能在將使用者提供的輸入直接插入到SQL語句中時,通過字串連接或動態SQL,引發SQL注入漏洞。
不安全的程式碼——字串連接和動態SQL
Tim展示了許多開發人員使用的確切不安全模式:
var sql = $"SELECT * FROM People WHERE LastName = '{searchText}'";
var results = connection.Query<Person>(sql);
var sql = $"SELECT * FROM People WHERE LastName = '{searchText}'";
var results = connection.Query<Person>(sql);
這個原始查詢使用字串連接來創建SQL語句。 Tim警告:如果您看到程式碼將使用者輸入直接注入SQL查詢,那麼停止 — 這是一個SQL注入漏洞。 攻擊者可以創建惡意輸入,改變您的SQL命令的結構,甚至運行附加的惡意SQL語句。
攻擊者如何利用它——UNION和DROP
為展示SQL注入攻擊的工作原理,Tim在SQL Server中再現查詢,然後使用UNION ALL和SQL註釋(--)來隱藏尾部字符進行注入。 Tim演示的惡意載荷示例:
-
基於聯合的SQL注入以讀取其他表:
UNION ALL SELECT ID, Username AS FirstName, Password AS LastName FROM Secrets;這將Secrets中的結果混合到原始SELECT結果集中,暴露諸如使用者名稱和密碼等敏感數據。
-
破壞性注入以刪除表:
DROP TABLE DemoTable;這通過用分號終止第一個語句,然後添加破壞性命令來運行第二個SQL語句(DROP TABLE)。 Tim展示表消失——資料庫已被惡意SQL修改。
Tim的觀點:攻擊者不需要提前知道表或列名 — 他們可以從資料庫伺服器枚舉表或列名,或只是嘗試盲目或基於時間的技術來發現行為。
修復1——參數化查詢
Tim的第一個也是主要的防禦措施是停止用使用者數據構建SQL字串。 用參數化查詢替代動態SQL:
string sql = "SELECT * FROM People WHERE LastName = @LastName";
var results = connection.Query<Person>(sql, new { LastName = searchText });
string sql = "SELECT * FROM People WHERE LastName = @LastName";
var results = connection.Query<Person>(sql, new { LastName = searchText });
Tim解釋參數化(預備語句樣式的使用)意味著資料庫將使用者提供的輸入嚴格作為數據處理——惡意的SQL僅成為字串值,無法改變SQL結構。 這可以防止許多常見的SQL注入攻擊,包括基於聯合的載荷和附加的; DROP TABLE命令。
他還建議將參數化與最小輸入驗證配對:對姓氏中不太可能出現的字符(例如分號或--註釋標記)進行清理或阻止,但允許有效字符如撇號(O'Reilly)。 參數化查詢 + 輸入清理可提供對SQL注入攻擊的重大保護。
修復2——儲存過程
Tim接下來展示兩個儲存過程:一個不安全的儲存過程在proc內連接SQL然後執行它,和一個直接使用參數的安全儲存過程。
-
不安全的儲存過程從參數構建@sql字串並執行它——仍易受注入攻擊。
- 安全儲存過程執行SELECT ... WHERE LastName = @LastName並使用參數執行——安全。
Tim澄清:如果您仍在儲存過程內構建動態SQL,它們不是自動治療的良藥。 但是當正確使用時(無動態SQL),儲存過程有助於集中化SQL語句,使其更容易參數化和審核查詢。 儲存過程也可以幫助簡化應用中的SQL注入防止。
不要信任任何數據——即使是資料庫數據
Tim提出的很重要且經常被忽視的一點:您不能盲目地信任從自己的SQL資料庫中檢索的數據。 攻擊者有時會在欄位中埋下惡意載荷(一顆"定時炸彈"),稍後將被另一過程連接到動態SQL中。 Tim堅持:始終在每一步使用參數並清理數據——無論是來自Web表單、文件上傳,還是您自己的資料庫——以便惡意輸入不能之後成為注入的途徑。
附加提示——最小特權和限制資料庫特權
除了程式碼修復外,Tim建議進行防禦性配置:限制應用帳戶的資料庫特權。 在他的演示中,連接使用透過整合安全的管理員帳戶——危險。 相反,使用最小特權原則:
-
為應用創建一個只擁有所需權利的資料庫帳戶。
-
如果您使用儲存過程,僅授予該帳戶對特定儲存過程的執行權限而無其他權限。
- 不要給應用帳戶廣泛的管理員權限來允許DROP TABLE、列出所有表或讀取其他資料庫。
這減少了成功的SQL注入攻擊的影響——即使注入是可能的,攻擊者也不能超過帳戶被允許的權限。
Tim還指出Entity Framework會使這變得更複雜:EF經常需要提升的權限(遷移,結構更改)。 如果您在生產環境中使用EF,請仔細計劃其權限和部署。
總結——停止,參數化,清理,限制
Tim在他的影片最後以防止SQL注入在C#應用中的清單結尾:
-
停止使用字串連接或包含使用者輸入的動態SQL來建立SQL語句。
-
使用參數化查詢/預備語句範式,這樣使用者數據始終作為數據處理。
-
在適當的地方清理輸入(阻止分號,SQL註釋,意外字符)。
-
偏好使用安全的儲存過程(其中沒有動態SQL)來集中查詢邏輯。
-
對資料庫帳戶應用最小權限限制——限制應用的資料庫用戶的操作範圍。
- 審查程式碼(尤其是動態構建SQL的地方)並測試是否存在SQL注入漏洞。
Tim的最後警告:對使用者輸入的不當處理、動態SQL和過多的資料庫特權可能導致嚴重的漏洞——洩露敏感數據,破壞性地刪除表或長期未被發現的滲透。 將SQL注入防止視為核心安全要求,而不是可選的完善。
