🌟 什么是 SQL 注入?
SQL 注入(SQL Injection)是指攻擊者利用特殊輸入,讓數據庫執行它本來不應該執行的代碼,從而獲取或篡改數據。
就像在考試的時候偷偷改題目,讓老師改成你想要的內容! 😱
🌟 先來看一個正常的 SQL 查詢
假設你的網站有一個登錄功能,代碼是這樣的:
🔹 登錄代碼
$username = $_GET['username']; ?
$password = $_GET['password']; ?$query = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";
? 這個代碼的意思是:
👉 從 users
表里查找 用戶名 = $username
且 密碼 = $password
的用戶。
如果數據庫里有這條數據,用戶就可以登錄!
🌟 用戶輸入正常數據(正確方式)
假設用戶輸入:
username = admin
password = 123456
最終SQL語句:
SELECT * FROM users WHERE username = 'admin' AND password = '123456';
? 如果 admin
這個用戶的密碼是 123456
,那么數據庫會返回這個用戶的信息,表示登錄成功!
🚨 但如果攻擊者輸入特殊字符呢?
攻擊者可能會這樣輸入:
username = admin' --
password = (隨便填)
那么最終SQL語句變成:
SELECT * FROM users WHERE username = 'admin' -- ' AND password = 'xxxxx';
這里發生了什么?
-
--
是SQL中的注釋符號,它會讓后面代碼(AND password='xxxxx'
)變成無效的注釋。 -
結果就變成:
SELECT * FROM users WHERE username = 'admin'
相當于直接查詢
admin
用戶,而不檢查密碼! 😱 -
這樣攻擊者就可以繞過密碼驗證,直接登錄
admin
賬號!? -
🌟 現實世界的比喻
-
💡 想象你在銀行辦理取款業務,柜員要核對你的身份:
-
正常情況:
柜員問你:請告訴我你的用戶名和密碼,如果正確,我就給你取錢。-
你說:“我是
admin
,密碼是123456
” -
銀行查了一下,發現你的用戶名和密碼匹配,于是讓你取錢。?
-
-
被攻擊的情況(SQL注入):
攻擊者說:“我是admin
,但是密碼這部分不重要,你不用檢查。”-
銀行糊涂了,以為你就是
admin
,直接給你取錢! -
🌟 關鍵點總結
-
1?? SQL 注入的本質:利用特殊字符破壞 SQL 語句結構,讓數據庫執行本不應該執行的代碼。
2?? 常見注入方式: -
單引號
'
(破壞 SQL 結構) -
--
(注釋掉后續代碼) -
OR 1=1
(讓查詢條件永遠成立)? -
3?? 為什么 SQL 注入危險?
-
可以繞過密碼驗證,直接登錄
-
可以獲取數據庫里的所有數據(用戶信息、密碼、銀行卡號)
-
甚至可以刪除數據,破壞整個系統! 😱
-
🌟 2. 什么是預編譯(Prepared Statement)?
🔹 預編譯的作用
預編譯(Prepared Statement)的核心思想是: 👉 把 SQL 語句和用戶輸入的參數分開處理,防止用戶輸入的數據被當成 SQL 代碼執行。
就像去餐館點菜時,菜譜是固定的,顧客只能填菜名,不能改動菜單的內容,這樣就不會有欺騙行為。
🔹 普通的 SQL 語句(容易被 SQL 注入攻擊)
🚨 下面是一個 危險的 SQL 代碼
-
$name = $_GET['name']; ?
$query = "SELECT * FROM users WHERE name = '$name'";
$result = $pdo->query($query);👆 這里的問題是:
-
$name
直接拼接到 SQL 語句中,如果用戶輸入了惡意代碼,就會被數據庫當成 SQL 代碼執行,導致 SQL 注入! - ? 使用預編譯(防止 SQL 注入)
$stmt = $pdo->prepare('SELECT * FROM users WHERE name = ?');
$stmt->execute([$name]);execute([$name]);
個代碼是安全的! 為什么?
??? prepare('SELECT * FROM users WHERE name = ?') 這一步先定義好 SQL 語句,但不插入具體的值。
??? execute([$name]) 這一步才把 $name 作為普通字符串插入,不會被當成 SQL 代碼。
💡 無論用戶輸入什么,它都會被當成普通文本,不會影響 SQL 語句本身。
-
🌟 3. 為什么預編譯不能 100% 防 SQL 注入?
雖然預編譯能防止大部分 SQL 注入,但在某些特殊情況下,仍然可能被繞過。
下面是三個真實案例,讓你徹底理解!😱 -
🚨 案例一:寬字節注入
🌍 發生場景
某些數據庫(比如 MySQL)在處理GBK 編碼時,可能會錯誤地解析特殊字符,從而導致 SQL 注入。💻 代碼示例
-
$pdo->query('SET NAMES gbk');? // 設定 GBK 編碼
$var = "\xbf\x27 OR 1=1 /*";??? // 特殊構造的字符串
$query = 'SELECT * FROM test WHERE name = ? LIMIT 1';
$stmt = $pdo->prepare($query);
$stmt->execute([$var]);? 為什么會有問題?
1??
\xbf\x27
在 GBK 編碼下,可能會被誤解析為一個單獨的字符。
2?? 剩下的部分 "OR 1=1 /*" 會被數據庫當成 SQL 代碼執行!
3?? 這樣就變成: -
SELECT * FROM test WHERE name = '某些字符' OR 1=1 /*
OR 1=1
永遠為真,所以查詢會返回所有數據,導致信息泄露! 😱 -
🚨 案例二:表名注入
🌍 發生場景
預編譯只能防止SQL參數注入,但如果動態拼接SQL,還是會有風險!😨💻 代碼示例
-
$dbh = new PDO("mysql:host=localhost;dbname=test", "root", "password");
$name = $_GET['name'];? // 例如輸入:"users; DROP TABLE users;"
$stmt = $dbh->prepare('SELECT * FROM ' . $name . ' WHERE username = :username');
$stmt->execute([':username' => $_REQUEST['username']]);? 為什么會有問題?
1??
$name
直接拼接進 SQL 語句,而不是作為參數傳入prepare()
。
2?? 攻擊者可以輸入惡意表名,比如: -
users; DROP TABLE users;
3?? 這樣 SQL 語句變成:
-
SELECT * FROM users; DROP TABLE users; WHERE username = 'admin'
🔴 結果:
users
表被刪除!數據丟失! 😱 -
案例三:ORDER BY 注入
🌍 發生場景
有些 SQL 語句的排序字段不能使用預編譯參數,這會導致 SQL 注入。💻 代碼示例
-
$stmt = $dbh->prepare('SELECT * FROM foo ORDER BY :userSuppliedData');
$stmt->execute([':userSuppliedData' => $_GET['sort']]);為什么會有問題?
1??
ORDER BY
后面的字段名 不能使用預編譯參數。
2?? 攻擊者可以輸入: -
id DESC; DROP TABLE foo;
3?? 這樣 SQL 語句變成:
-
SELECT * FROM foo ORDER BY id DESC; DROP TABLE foo;
4?? 🔴 結果:
foo
表被刪除!數據庫數據被破壞! 😱🌟 4. 如何完全防止 SQL 注入?
雖然預編譯是一個很好的安全措施,但它并不能 100% 防止 SQL 注入。
所以,我們還需要額外的安全措施!👇
? 方法 1:嚴格限制輸入格式
💡 不要讓用戶輸入影響 SQL 結構的內容!
🔹 例如,限制表名、列名、排序字段只能從“白名單”中選擇! -
$allowedSortFields = ['id', 'name', 'date']; ?
if (!in_array($_GET['sort'], $allowedSortFields)) { ?
??? die("Invalid input");? // 直接終止,防止 SQL 注入 ?
}? 方法 2:使用 ORM 框架
🔹 ORM(Object-Relational Mapping) 框架(比如 Laravel 的 Eloquent、Doctrine)會自動處理 SQL 語句,默認帶有安全檢查。
示例:
-
User::where('name', $name)->first();
ORM 會自動處理 SQL 注入問題,不需要手動寫 SQL 語句。
? 方法 3:最小權限原則
🔹 不要使用數據庫管理員賬戶(如
root
)運行 SQL 語句! 🔹 為應用創建權限受限的數據庫賬戶,這樣即使有 SQL 注入,也不會破壞整個數據庫。CREATE USER 'webapp'@'localhost' IDENTIFIED BY 'securepassword';
GRANT SELECT, INSERT, UPDATE, DELETE ON testdb.* TO 'webapp'@'localhost';這樣,即使黑客注入了
DROP TABLE users;
,也無法執行,因為webapp
賬號沒有刪除表的權限!😃
🌟 總結
? 預編譯是 SQL 防注入的核心方法,但并不 100% 安全。
? SQL 注入繞過的三種方式: -
寬字節注入(特殊字符編碼問題)
-
表名注入(拼接動態 SQL )
-
ORDER BY 注入(排序字段不能預編譯)
? 想要 100% 防 SQL 注入,還需要額外措施!
? 最佳安全做法: -
嚴格限制用戶輸入
-
使用 ORM 框架
-
遵循最小權限原則
-
?
-