漏洞代碼還原
<?php
$username = $_POST['username'];
$password = $_POST['password'];
$sql = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";
$res = mysql_query($sql); // 假設舊版 mysql_* 函數
if (mysql_num_rows($res)) {
header('Location: home.php');
} else {
die('登錄失敗');
}
攻擊 Payload 構造
| 輸入字段 | Payload | 生成 SQL | 原理 |
| password | `' OR '1'='1` | `... AND password = '' OR '1'='1'` | 條件恒真繞過驗證 |
| username | `admin'--` | `... WHERE username = 'admin'--' AND ...` | `--` 註釋掉後續條件 |
| username | `' UNION SELECT 1,2,3--` | 聯合查詢讀取其他表數據 | 需列數匹配、類型兼容 |
為什麼過濾/轉義不可靠?
- `addslashes()` 繞過:多字節編碼 (GBK) 吃掉反斜線。
- `mysql_real_escape_string()` 需正確設置連接字符集,否則失效。
- 黑名單過濾 (select, union 等) 極易繞過 (大小寫、編碼、註釋)。
根治方案:PDO 預處理語句
<?php
$dsn = 'mysql:host=127.0.0.1;dbname=app;charset=utf8mb4';
$pdo = new PDO($dsn, 'user', 'pass', [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_EMULATE_PREPARES => false, // 關鍵:使用真實預處理
]);
$stmt = $pdo->prepare('SELECT * FROM users WHERE username = ? AND password = ?');
$stmt->execute([$_POST['username'], $_POST['password']]); // 密碼應為哈希值
$user = $stmt->fetch();
if ($user) {
// 驗證密碼哈希
if (password_verify($_POST['password'], $user['password_hash'])) {
// 登錄成功
}
}
深度防禦清單
- 最小權限:數據庫賬號僅擁有必要表的 SELECT/INSERT/UPDATE,無 DROP/ALTER。
- 錯誤信息不外露:生產環境 `display_errors=Off`,錯誤寫日志不顯示頁面。
- WAF/IDS:部署 ModSecurity 或雲 WAF 攔截明顯攻擊特徵。
- 代碼審計:引入 PHPStan、Psalm 靜態分析,CI 集成 SQL 注入掃描工具 (sqlmap 自動化測試)。
- ORM/Query Builder:優先使用 Eloquent、Doctrine、Medoo 等構建查詢,避免手寫 SQL。