首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 綜合知識

PHP SQL 注入防禦實戰:正則檢測、參數化查詢、WAF 規則與最佳實踐全景圖

     編輯:PHP 綜合知識

前言

SQL 注入仍是 Web 應用頭號安全風險。本文系統梳理 PHP 防禦 SQL 注入的完整體系:從根本上的參數化查詢,到輔助的輸入過濾、正則檢測、WAF 規則、ORM 安全用法,構建多層防護體系。

核心防禦原則:參數化查詢(Prepared Statements)

這是唯一根本解決方案。將數據與代碼分離,數據庫引擎在解析 SQL 結構時不會將參數視為代碼執行。

PDO 參數化查詢

<?php
$pdo = new PDO('mysql:host=localhost;dbname=test;charset=utf8mb4', 'user', 'pass');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); // 關鍵:使用真實預處理

// 佔位符綁定
$stmt = $pdo->prepare('SELECT * FROM users WHERE email = ? AND status = ?');
$stmt->execute([$email, 1]);
$user = $stmt->fetch();

// 命名佔位符
$stmt = $pdo->prepare('INSERT INTO logs (user_id, action, ip) VALUES (:uid, :action, :ip)');
$stmt->execute([
    'uid' => $userId,
    'action' => $action,
    'ip' => $clientIp,
]);
?>

MySQLi 參數化查詢

<?php
$mysqli = new mysqli('localhost', 'user', 'pass', 'test');
$stmt = $mysqli->prepare('SELECT * FROM users WHERE email = ?');
$stmt->bind_param('s', $email); // s=string, i=integer, d=double, b=blob
$stmt->execute();
$result = $stmt->get_result();
$user = $result->fetch_assoc();
?>

❌ 絕對禁止的拼接寫法

<?php
// 永遠不要這樣寫!
$sql = "SELECT * FROM users WHERE id = " . $_GET['id'];
$sql = "SELECT * FROM users WHERE name = '" . $_POST['name'] . "'";
$sql = "UPDATE users SET score = $score WHERE id = $id";
?>

輔助防線:輸入驗證與過濾

參數化查詢解決 99% 注入,但輸入驗證仍是纏深防禦必需:業務邏輯約束、XSS 預防、數據質量保證。

白名單驗證(最推薦)

<?php
function validateOrderBy(string $input, array $allowed): string {
    $field = strtolower(trim($input));
    return in_array($field, $allowed) ? $field : $allowed[0];
}

$orderBy = validateOrderBy($_GET['sort'] ?? '', ['id', 'name', 'created_at', 'score']);
$sql = "SELECT * FROM users ORDER BY $orderBy"; // 安全:值來自白名單
?>

型別強制轉換

<?php
$id = (int)($_GET['id'] ?? 0); // 強制整數
$limit = max(1, min(100, (int)($_GET['limit'] ?? 20))); // 範圍限制
$email = filter_var($_POST['email'] ?? '', FILTER_VALIDATE_EMAIL); // 格式驗證
?>

正則檢測疑似注入模式(僅作輔助監控/日誌)

警告:正則無法完美攔截所有注入(編碼繞過、語法變體),不可作為唯一防禦。適用於 WAF 規則、異常日誌告警、蜜罐檢測。

常見注入特徵正則

<?php
class SqlInjectionDetector
{
    private const PATTERNS = [
        // 聯合查詢注入
        '/\bUNION\s+ALL\s+SELECT\b/i',
        '/\bUNION\s+SELECT\b/i',
        // 子查詢注入
        '/\bSELECT\s+.*\bFROM\s+.*\bWHERE\b/i',
        // 條件永真
        '/\bOR\s+[\d\'\"]\s*=\s*[\d\'\"]/i',
        '/\bAND\s+[\d\'\"]\s*=\s*[\d\'\"]/i',
        // 註釋截斷
        '/--\s*$/',
        '/\/\*.*\*\//s',
        // 危險函數
        '/\b(LOAD_FILE|INTO\s+OUTFILE|INTO\s+DUMPFILE)\b/i',
        '/\b(BENCHMARK|SLEEP)\s*\(/i',
        // 信息收集
        '/\b(INFORMATION_SCHEMA|SYSOBJECTS|SYSCOLUMNS)\b/i',
        // 盲注特徵
        '/\bIF\s*\(/i',
        '/\bCASE\s+WHEN\b/i',
        // 編碼繞過
        '/(0x[0-9a-f]+|CHAR\(\d+\))/i',
    ];

    public static function detect(string $input): array
    {
        $matches = [];
        foreach (self::PATTERNS as $pattern) {
            if (preg_match($pattern, $input)) {
                $matches[] = $pattern;
            }
        }
        return $matches;
    }

    public static function logIfSuspicious(string $input, string $context = ''): void
    {
        $matches = self::detect($input);
        if ($matches) {
            error_log("[SQLi Alert] Context: $context | Input: $input | Patterns: " . implode(', ', $matches));
        }
    }
}

// 使用:中間件中對 $_GET/$_POST/$_COOKIE 遍歷檢測
foreach (array_merge($_GET, $_POST, $_COOKIE) as $key => $val) {
    if (is_string($val)) {
        SqlInjectionDetector::logIfSuspicious($val, $key);
    }
}
?>

WAF / ModSecurity 核心規則集(CRS)關鍵規則

# ModSecurity CRS 3.x 關鍵 SQLi 規則 ID
# 942100 - SQL Injection Attack Detected via libinjection
# 942110 - SQL Injection Attack: Common Injection Testing Detected
# 942120 - SQL Injection Attack: Union Query
# 942130 - SQL Injection Attack: Select From
# 942140 - SQL Injection Attack: Insert Into
# 942150 - SQL Injection Attack: Update Set
# 942160 - SQL Injection Attack: Delete From
# 942170 - SQL Injection Attack: Drop Table
# 942180 - SQL Injection Attack: Create Table
# 942190 - SQL Injection Attack: Alter Table

# Nginx + ModSecurity 配置片段
SecRuleEngine On
Include /etc/modsecurity/crs-setup.conf
Include /etc/modsecurity/rules/*.conf

# 調整敏感度(預設 paranoia level 1)
SecAction "id:900000,phase:1,pass,nolog,setvar:tx.paranoia_level=2"

ORM/查詢構建器安全用法

Laravel Eloquent

<?php
// ✅ 安全:參數綁定
User::where('email', $email)->first();
User::whereRaw('created_at > ?', [$date])->get();

// ❌ 危險:Raw 未綁定
User::whereRaw("email = '$email'")->first(); // 注入風險

// ✅ 安全:selectRaw 綁定
DB::table('users')->selectRaw('COUNT(*) as cnt WHERE status = ?', [1])->first();
?>

ThinkPHP / 通用查詢構建器

<?php
// ✅ 安全
Db::name('users')->where('email', $email)->find();
Db::name('users')->where('id', 'in', $ids)->select(); // 陣列自動參數化

// ❌ 危險
Db::query("SELECT * FROM users WHERE email = '$email'");
?>

進階防禦技術

1. 僅讀數據庫用戶分離

<?php
// 配置兩個數據庫連接
$pdoRead = new PDO('mysql:host=read-replica;dbname=test', 'readonly_user', 'pass');
$pdoWrite = new PDO('mysql:host=master;dbname=test', 'readwrite_user', 'pass');

// 查詢操作用只讀連接(即使注入也無法寫入/刪除)
$stmt = $pdoRead->prepare('SELECT * FROM users WHERE id = ?');
?>

2. 存儲過程封裝核心邏輯

-- MySQL 存儲過程
CREATE PROCEDURE GetUserByEmail(IN p_email VARCHAR(255))
BEGIN
    SELECT * FROM users WHERE email = p_email AND status = 1;
END;

// PHP 調用
$stmt = $pdo->prepare('CALL GetUserByEmail(?)');
$stmt->execute([$email]);
?>

3. 最小權限原則:表級/列級權限

GRANT SELECT (id, name, email) ON app.users TO 'api_user'@'%';
GRANT INSERT (user_id, action) ON app.logs TO 'api_user'@'%';
-- 拒絕 DROP、ALTER、TRUNCATE、GRANT 等管理權限
?>

應急響應:發現注入如何排查

  1. 訪問日誌分析:grep 可疑參數、長請求、異常 User-Agent
  2. 數據庫審計日誌:開啟 general_log 或 audit_log 記錄所有執行 SQL
  3. 錯誤日誌關聯:SQL 語法錯誤往往暴露注入嘗試
  4. 蜜罐字段:在表單隱藏字段 honeypot,有值即為機器人/掃描器

總結:分層防禦清單

層級措施防禦力度維護成本
1. 代碼層(核心)全量參數化查詢、PDO::ATTR_EMULATE_PREPARES=false★★★★★低(編碼規範)
2. 輸入層白名單驗證、型別強制、長度限制、格式校驗★★★★☆低
3. ORM/框架層使用安全 API、禁用 Raw SQL、代碼審計規則★★★★☆低
4. 數據庫層只讀/讀寫分離用戶、最小權限、存儲過程★★★☆☆中
5. 網絡層WAF/ModSecurity CRS、IP 封禁、請求頻率限制★★★☆☆中
6. 監控層注入特徵日誌告警、蜜罐、審計日誌分析★★★☆☆中

金科玉律:參數化查詢是唯一銀彈,其他都是輔助彈藥。沒有參數化查詢,任何過濾都不可靠;有了參數化查詢,其他層級讓防禦更穩健。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved