參數過濾屬於第二道防線(第一道是預處理語句/參數綁定)。本類別示範如何以正則攔截常見 SQL 注入特徵,並記錄攻擊日誌。
<?php
class SqlSafe {
// GET 參數規則(較嚴格,含 script 標籤)
private $getFilter = "'|(and|or)\b.+?(>|<|=|in|like)|/\*.+?\*/|<\s*script\b|\bEXEC\b|UNION.+?SELECT|UPDATE.+?SET|INSERT\s+INTO.+?VALUES|(SELECT|DELETE).+?FROM|(CREATE|ALTER|DROP|TRUNCATE)\s+(TABLE|DATABASE)";
// POST/COOKIE 規則(稍寬鬆)
private $postFilter = "\b(and|or)\b.{1,6}?(=|>|<|\bin\b|\blike\b)|/\*.+?\*/|<\s*script\b|\bEXEC\b|UNION.+?SELECT|UPDATE.+?SET|INSERT\s+INTO.+?VALUES|(SELECT|DELETE).+?FROM|(CREATE|ALTER|DROP|TRUNCATE)\s+(TABLE|DATABASE)";
private $cookieFilter = "\b(and|or)\b.{1,6}?(=|>|<|\bin\b|\blike\b)|/\*.+?\*/|<\s*script\b|\bEXEC\b|UNION.+?SELECT|UPDATE.+?SET|INSERT\s+INTO.+?VALUES|(SELECT|DELETE).+?FROM|(CREATE|ALTER|DROP|TRUNCATE)\s+(TABLE|DATABASE)";
public function __construct() {
foreach ($_GET as $k => $v) $this->check($k, $v, $this->getFilter);
foreach ($_POST as $k => $v) $this->check($k, $v, $this->postFilter);
foreach ($_COOKIE as $k => $v) $this->check($k, $v, $this->cookieFilter);
}
private function check($key, $value, $pattern) {
if (is_array($value)) $value = implode('', $value);
if (preg_match("/$pattern/is", $value)) {
$this->logAttack($key, $value);
exit('非法參數,操作已記錄!'); // 或拋出異常
}
}
private function logAttack($key, $value) {
$log = sprintf(
"%s %s %s %s %s %s\n",
$_SERVER['REMOTE_ADDR'],
date('Y-m-d H:i:s'),
$_SERVER['PHP_SELF'],
$_SERVER['REQUEST_METHOD'],
$key,
$value
);
$path = __DIR__ . '/logs/sql_inject.log';
@file_put_contents($path, $log, FILE_APPEND | LOCK_EX);
}
}
// 在入口檔案最早期實例化
new SqlSafe();
| 片段 | 攔截目標 |
|---|---|
' | 單引號閉合 |
(and|or)\b.+?(>|<|=|in|like) | 布林注入 1 or 1=1 |
/\*.+?\*/ | 註釋截斷 /* |
<\s*script\b | XSS 腳本標籤 |
\bEXEC\b | 存儲過程執行 |
UNION.+?SELECT | 聯合查詢注入 |
UPDATE.+?SET|INSERT\s+INTO.+?VALUES | 資料竄改 |
(SELECT|DELETE).+?FROM | 資料竊取/刪除 |
(CREATE|ALTER|DROP|TRUNCATE)\s+(TABLE|DATABASE) | DDL 破壞 |
$pdo = new PDO($dsn, $user, $pass, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_EMULATE_PREPARES => false, // 使用真實預處理
]);
$stmt = $pdo->prepare('SELECT * FROM users WHERE email = ? AND status = ?');
$stmt->execute([$_GET['email'], 1]);
$user = $stmt->fetch();