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

PHP 表單驗證完整指南:安全處理、資料過濾、CSRF防護與最佳實踐

     編輯:PHP 入門

PHP 表單驗證完整指南

表單是 Web 應用與用戶互動的主要介面,也是攻擊面最廣的入口。完整的表單驗證包含:資料清理、格式驗證、業務規則、安全防護(XSS、CSRF、SQL Injection)、用戶體驗。本文建構系統性防護體系。

威脅模型

威脅向量後果防護層級
XSS輸入含腳本,輸出未編碼竊取 Session、劫持帳號、惡意轉向輸出編碼、CSP
CSRF偽造請求、用戶已登入未授權操作(轉帳、改密、刪除)Token 驗證、SameSite Cookie
SQL Injection輸入拼接 SQL資料洩露、竄改、刪除、提權預處理語句
Header Injection換行符注入 mail()、header()郵件濫發、回應分割、快取污染移除換行、專用庫
檔案上傳攻擊惡意檔案、雙重副檔名遠端代碼執行、服務器佔領類型驗證、重命名、隔離執行

第一道防線:資料清理

<?php
function sanitizeInput(string $data, int $flags = ENT_QUOTES): string {
    $data = trim($data);
    $data = stripslashes($data); // 相容舊版 magic_quotes
    $data = htmlspecialchars($data, $flags, 'UTF-8');
    return $data;
}

// 針對不同輸出情境的清理
function sanitizeForHtml(string $data): string {
    return htmlspecialchars($data, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}

function sanitizeForUrl(string $data): string {
    return rawurlencode($data);
}

function sanitizeForJs(string $data): string {
    return json_encode($data, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT);
}

function sanitizeForShell(string $data): string {
    return escapeshellarg($data);
}

第二道防線:格式驗證

<?php
class Validator {
    public static function email(string $value): bool {
        return filter_var($value, FILTER_VALIDATE_EMAIL) !== false;
    }
    
    public static function url(string $value): bool {
        return filter_var($value, FILTER_VALIDATE_URL, 
            FILTER_FLAG_SCHEME_REQUIRED | FILTER_FLAG_HOST_REQUIRED) !== false;
    }
    
    public static function ip(string $value): bool {
        return filter_var($value, FILTER_VALIDATE_IP) !== false;
    }
    
    public static function int(string $value, int $min = PHP_INT_MIN, int $max = PHP_INT_MAX): bool {
        $v = filter_var($value, FILTER_VALIDATE_INT, [
            'options' => ['min_range' => $min, 'max_range' => $max]
        ]);
        return $v !== false;
    }
    
    public static function regex(string $value, string $pattern): bool {
        return preg_match($pattern, $value) === 1;
    }
    
    public static function inArray(string $value, array $allowed): bool {
        return in_array($value, $allowed, true);
    }
    
    public static function length(string $value, int $min = 0, int $max = PHP_INT_MAX): bool {
        $len = mb_strlen($value);
        return $len >= $min && $len <= $max;
    }
    
    public static function notEmpty(string $value): bool {
        return $value !== '' && $value !== null;
    }
}

第三道防線:CSRF 保護

雙重 Token 模式(傳統表單 + AJAX)

<?php
// csrf.php - 共用函數
function generateCsrfToken(): string {
    if (empty($_SESSION['csrf_token'])) {
        $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
    }
    return $_SESSION['csrf_token'];
}

function validateCsrfToken(string $token): bool {
    return isset($_SESSION['csrf_token']) && 
           hash_equals($_SESSION['csrf_token'], $token);
}

// 針對 AJAX 的雙重提交 Cookie 模式
function generateCsrfCookie(): string {
    $token = bin2hex(random_bytes(32));
    setcookie('csrf_token', $token, [
        'expires' => time() + 3600,
        'path' => '/',
        'secure' => true,
        'httponly' => false, // JS 需讀取
        'samesite' => 'Strict'
    ]);
    $_SESSION['csrf_token'] = $token;
    return $token;
}

function validateCsrfHeader(): bool {
    $headerToken = $_SERVER['HTTP_X_CSRF_TOKEN'] ?? '';
    $cookieToken = $_COOKIE['csrf_token'] ?? '';
    $sessionToken = $_SESSION['csrf_token'] ?? '';
    return $headerToken !== '' && 
           hash_equals($sessionToken, $headerToken) && 
           hash_equals($sessionToken, $cookieToken);
}
?>

表單植入

<form method="post" action="">
    <input type="hidden" name="csrf_token" value="<?php echo generateCsrfToken(); ?>">
    <!-- 欄位... -->
</form>

AJAX 發送

// JavaScript
const csrfToken = document.querySelector('meta[name="csrf-token"]').content;
// 或從 Cookie 讀取
fetch('/api/submit', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
        'X-CSRF-Token': csrfToken
    },
    body: JSON.stringify(data)
});

驗證中介層

<?php
function requireCsrf(): void {
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        if (!validateCsrfToken($_POST['csrf_token'] ?? '')) {
            http_response_code(403);
            die('CSRF Token 驗證失敗');
        }
    }
}

// API 路由
if (str_starts_with($_SERVER['REQUEST_URI'], '/api/')) {
    if (!validateCsrfHeader()) {
        http_response_code(403);
        echo json_encode(['error' => 'CSRF 驗證失敗']);
        exit;
    }
}
?>

第四道防線:SQL Injection 防護

<?php
// PDO 預處理語句(唯一正確方式)
$stmt = $pdo->prepare('INSERT INTO users (name, email, role) VALUES (?, ?, ?)');
$stmt->execute([$name, $email, $role]);

// 命名參數
$stmt = $pdo->prepare('SELECT * FROM users WHERE email = :email AND status = :status');
$stmt->execute(['email' => $email, 'status' => 'active']);

// 禁止:字串拼接
// $sql = "SELECT * FROM users WHERE name = '$name'"; // 危險!

// LIKE 查詢參數化
$stmt = $pdo->prepare('SELECT * FROM products WHERE name LIKE ?');
$stmt->execute(["%{$keyword}%"]);

完整表單處理流程

<?php
// process_form.php
require_once 'csrf.php';
require_once 'Validator.php';

session_start();
generateCsrfToken(); // 確保 Token 存在

$errors = [];
$clean = [];
$success = false;

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    // 1. CSRF 驗證
    if (!validateCsrfToken($_POST['csrf_token'] ?? '')) {
        $errors['_form'] = '請求無效,請重新整理頁面再試';
    } else {
        // 2. 輸入清理與驗證
        $name = sanitizeInput($_POST['name'] ?? '');
        $email = sanitizeInput($_POST['email'] ?? '');
        $age = (int)($_POST['age'] ?? 0);
        
        if (!Validator::notEmpty($name)) $errors['name'] = '姓名必填';
        elseif (!Validator::length($name, 1, 50)) $errors['name'] = '姓名長度 1-50 字';
        
        if (!Validator::notEmpty($email)) $errors['email'] = 'Email 必填';
        elseif (!Validator::email($email)) $errors['email'] = 'Email 格式錯誤';
        
        if (!Validator::int((string)$age, 1, 120)) $errors['age'] = '年齡 1-120';
        
        // 3. 業務規則(如 Email 唯一性)
        if (empty($errors)) {
            $stmt = $pdo->prepare('SELECT id FROM users WHERE email = ?');
            $stmt->execute([$email]);
            if ($stmt->fetch()) {
                $errors['email'] = '此 Email 已註冊';
            }
        }
        
        // 4. 成功處理
        if (empty($errors)) {
            $stmt = $pdo->prepare('INSERT INTO users (name, email, age, created_at) VALUES (?, ?, ?, NOW())');
            $stmt->execute([$name, $email, $age]);
            $success = true;
            
            // PRG 重導向
            header('Location: success.php');
            exit;
        }
    }
}

// 5. 輸出表單(含錯誤、資料保持)
include 'form_template.php';
?>

速率限制防暴力提交

<?php
function checkRateLimit(string $key, int $maxAttempts = 5, int $window = 300): bool {
    $now = time();
    $attempts = $_SESSION['rate_limit'][$key] ?? [];
    
    // 清理過期記錄
    $attempts = array_filter($attempts, fn($t) => $now - $t < $window);
    
    if (count($attempts) >= $maxAttempts) {
        return false;
    }
    
    $attempts[] = $now;
    $_SESSION['rate_limit'][$key] = $attempts;
    return true;
}

// 使用
if (!checkRateLimit('form_submit_' . $_SERVER['REMOTE_ADDR'], 10, 60)) {
    $errors['_form'] = '提交過於頻繁,請稍後再試';
}

記錄與稽核

<?php
function logFormSubmission(array $data, array $errors, bool $success): void {
    $log = [
        'timestamp' => date('c'),
        'ip' => $_SERVER['REMOTE_ADDR'],
        'ua' => $_SERVER['HTTP_USER_AGENT'],
        'url' => $_SERVER['REQUEST_URI'],
        'method' => $_SERVER['REQUEST_METHOD'],
        'success' => $success,
        'errors' => $errors,
        'data_keys' => array_keys($data), // 僅記錄欄位名,不記錄敏感值
    ];
    // 移除敏感欄位
    unset($log['data_keys'][array_search('password', $log['data_keys']) ?? '']);
    unset($log['data_keys'][array_search('csrf_token', $log['data_keys']) ?? '']);
    
    error_log('FORM_SUBMIT: ' . json_encode($log, JSON_UNESCAPED_UNICODE));
}
?>

最佳實踐總結清單

  • [ ] 所有輸入經過 sanitizeInput() 清理
  • [ ] 格式驗證使用 filter_var() / Validator 類別
  • [ ] 所有 POST 表單植入 CSRF Token
  • [ ] AJAX 端點驗證雙重 Token / Header
  • [ ] 資料庫操作 100% 使用預處理語句
  • [ ] 成功提交後 PRG 重導向
  • [ ] 敏感操作(刪除、轉帳)二次確認
  • [ ] 速率限制保護提交端點
  • [ ] 錯誤訊息不洩露系統資訊(SQL 錯誤、路徑、堆疊)
  • [ ] 記錄提交日誌(稽核、異常偵測)
  • [ ] 定期滲透測試、代碼審查

表單驗證不是單一函數,而是分層防護體系。從輸入清理、格式驗證、業務規則、CSRF/SQL/XSS 防護、速率限制、稽核日誌,每一層都不可或缺。系統性建構,才能在持續演進的威脅下守護應用安全。

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