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

PHP 防偽造表單提交:從 Referer 檢查到 CSRF Token 的完整防禦體系

     編輯:PHP 相關知識

為什麼單靠 `HTTP_REFERER` 不夠?

最直覺的防禦:檢查 `$_SERVER['HTTP_REFERER']` 是否為本站域名。但 Referer:

  • 可被瀏覽器設定/外掛攔截或偽造
  • HTTPS → HTTP 請求時不會發送
  • 攻擊者可在自家網站放超連結誘騙點擊,Referer 就會是攻擊者網域,繞過檢查

結論:Referer 只能擋「手動在網址列輸入 URL」的低級嘗試,無法防禦真正的 CSRF(跨站請求偽造)攻擊。

第一道防線:強制關鍵操作使用 POST

<form method="POST" action="/transfer">
  <input type="hidden" name="csrf_token" value="<?= generate_csrf_token() ?>">
  <input type="hidden" name="amount" value="1000">
  <button type="submit">確認轉帳</button>
</form>

GET 請求參數暴露在 URL、瀏覽器歷史、Proxy 日誌中,極易被盜用。所有改寫狀態的操作(增刪改、付款、變更密碼)必須用 POST。

核心防禦:同步器令牌模式(CSRF Token)

原理

  1. 用戶打開表單頁 → Server 產生隨機 Token,存入 Session,嵌入表單隱藏欄位
  2. 用戶送出表單 → Server 比對 `$_POST['csrf_token']` 與 `$_SESSION['csrf_token']`
  3. 一致 → 合法請求,執行邏輯並銷毀/更新 Token(一次性使用)
  4. 不一致/缺失 → 拒絕,記錄安全日誌

極簡實作(可封裝為 Trait/Helper)

<?php
// 產生 Token
function generate_csrf_token(): string {
    if (empty($_SESSION['csrf_token'])) {
        $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
    }
    return $_SESSION['csrf_token'];
}

// 驗證 Token(一次性)
function verify_csrf_token(string $token): bool {
    if (empty($token) || empty($_SESSION['csrf_token'])) return false;
    $valid = hash_equals($_SESSION['csrf_token'], $token); // 時序安全比較
    if ($valid) {
        // 用過即焚,防重放
        $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
    }
    return $valid;
}

// 在處理表單的 Controller 最前面
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    if (!verify_csrf_token($_POST['csrf_token'] ?? '')) {
        http_response_code(403);
        exit('CSRF 驗證失敗,請重新整理頁面再試');
    }
    // 繼續業務邏輯...
}
?>

進階:AJAX/Fetch 請求怎麼帶 Token?

兩種常見做法:

  1. Meta 標籤 + JS 讀取:
    <meta name="csrf-token" content="<?= generate_csrf_token() ?>">
    // JS
    const token = document.querySelector('meta[name="csrf-token"]').content;
    fetch('/api/action', { method: 'POST', headers: { 'X-CSRF-Token': token }, body: formData });
  2. 雙重提交 Cookie 模式(Stateless API 適用): Server 設定 `Set-Cookie: csrf_token=xxx; SameSite=Strict`,前端讀取 Cookie 值放入 Header `X-CSRF-Token`,Server 比對 Cookie 與 Header 是否一致。

輔助防禦:SameSite Cookie 屬性

<?php
// PHP 7.3+ 可直接用 setcookie 選項
setcookie('session_id', $sid, [
    'expires' => time() + 3600,
    'path' => '/',
    'domain' => 'example.com',
    'secure' => true,      // 僅 HTTPS
    'httponly' => true,    // JS 讀不到
    'samesite' => 'Lax',   // 或 'Strict':跨站請求不攜帶 Cookie
]);
?>

`SameSite=Lax` 允許從外部連結進站時攜帶 Cookie(適合登入態),`Strict` 完全阻擋跨站攜帶(最安全但可能影響體驗)。建議關鍵業務 Cookie 用 `Strict`,一般 Session 用 `Lax`。

防禦矩陣總結

層級手段防禦目標備註
1關鍵操作強制 POST防止簡單 URL 竄改基礎規範
2CSRF Token(同步器模式)防止偽造表單提交核心防禦,必須一次性
3SameSite Cookie瀏覽器層面阻擋跨站攜帶 Cookie現代瀏覽器支援良好
4雙重提交 Cookie / 自定義 HeaderAPI/SPA 無狀態場景需前端配合
5驗證碼 / 再次輸入密碼 / 簡訊 OTP高風險操作(轉帳、改密、刪帳)人機驗證,成本最高但最安心

避坑提醒

  • ❌ 不要把 Token 放在 URL 參數(`/delete?id=1&token=xxx`)→ 會洩漏在 Referer、Proxy 日誌
  • ❌ 不要用固定 Token 或可預測算法(時間戳、MD5)
  • ✅ Token 必須加密隨機產生(`random_bytes`)、存 Session、時序安全比較(`hash_equals`)
  • ✅ 登入、註冊、登出等表單同樣需要 CSRF 保護(防登入 CSRF、註冊灌水)

建立「預設拒絕、顯式放行」的表單處理習慣,配合框架內建的 CSRF 中介軟體(Laravel `VerifyCsrfToken`、Symfony `CsrfTokenManager`、ThinkPHP `token` 驗證),就能在應用層築起堅實防線。

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