最直覺的防禦:檢查 `$_SERVER['HTTP_REFERER']` 是否為本站域名。但 Referer:
結論:Referer 只能擋「手動在網址列輸入 URL」的低級嘗試,無法防禦真正的 CSRF(跨站請求偽造)攻擊。
<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。
<?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 驗證失敗,請重新整理頁面再試');
}
// 繼續業務邏輯...
}
?>
兩種常見做法:
<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 });
<?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 竄改 | 基礎規範 |
| 2 | CSRF Token(同步器模式) | 防止偽造表單提交 | 核心防禦,必須一次性 |
| 3 | SameSite Cookie | 瀏覽器層面阻擋跨站攜帶 Cookie | 現代瀏覽器支援良好 |
| 4 | 雙重提交 Cookie / 自定義 Header | API/SPA 無狀態場景 | 需前端配合 |
| 5 | 驗證碼 / 再次輸入密碼 / 簡訊 OTP | 高風險操作(轉帳、改密、刪帳) | 人機驗證,成本最高但最安心 |
建立「預設拒絕、顯式放行」的表單處理習慣,配合框架內建的 CSRF 中介軟體(Laravel `VerifyCsrfToken`、Symfony `CsrfTokenManager`、ThinkPHP `token` 驗證),就能在應用層築起堅實防線。