CSRF(Cross-Site Request Forgery)利用受害者已登入身份,誘導其瀏覽器向目標站點發送偽造請求。本文解析攻擊原理、實戰案例與多層次防禦策略。
bank.com,瀏覽器持有 Session Cookieevil.comevil.com 頁面包含:<img src="https://bank.com/transfer?to=attacker&amount=10000">bank.com Cookie 發送請求| 向量 | 範例 | 特點 |
|---|---|---|
| 圖片標籤 | <img src="..."> | GET、自動載入、無需互動 |
| 表單自動提交 | JS form.submit() | POST、可攜帶複雜參數 |
| iframe 隱藏 | <iframe src="..." style="display:none"> | 可執行多步驟流程 |
| Link 預載入 | <link rel="prefetch" href="..."> | 瀏覽器預測性載入 |
<!-- 惡意頁面 -->
<img src="https://target.com/admin/delete.php?id=1">
<img src="https://target.com/admin/delete.php?id=2">
<img src="https://target.com/admin/delete.php?id=3">
管理員瀏覽惡意頁面 → 留言被批量刪除。
在表單/請求中嵌入不可預測的 Token,伺服器驗證 Token 有效性。
<?php
class CsrfToken
{
private const TTL = 7200; // 2 小時
private const LENGTH = 32;
public static function generate(string $sessionKey = 'csrf_token'): string
{
if (empty($_SESSION[$sessionKey]) || time() - $_SESSION[$sessionKey . '_time'] > self::TTL) {
$_SESSION[$sessionKey] = bin2hex(random_bytes(self::LENGTH));
$_SESSION[$sessionKey . '_time'] = time();
}
return $_SESSION[$sessionKey];
}
public static function validate(string $input, string $sessionKey = 'csrf_token'): bool
{
if (empty($_SESSION[$sessionKey]) || empty($input)) {
return false;
}
// 等時比較,防時序攻擊
return hash_equals($_SESSION[$sessionKey], $input);
}
public static function inputField(string $name = 'csrf_token'): string
{
return '<input type="hidden" name="' . $name . '" value="' . htmlspecialchars(self::generate()) . '">';
}
}
<form method="post" action="/transfer">
<?= CsrfToken::inputField() ?>
<input name="amount" type="number" required>
<button>轉帳</button>
</form>
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!CsrfToken::validate($_POST['csrf_token'] ?? '')) {
http_response_code(403);
die('CSRF Token 無效或過期');
}
// 正常處理...
}
0,
'path' => '/',
'domain' => '',
'secure' => true, // 僅 HTTPS
'httponly' => true,
'samesite' => 'Strict' // 或 'Lax'
]);
session_start();
Strict:完全阻擋跨站請求攜帶 Cookie(最安全,可能影響正常導航)Lax:允許頂層導航 GET 請求攜帶 Cookie(推薦平衡點)None:跨站攜帶,**必須搭配 Secure**前端讀取 Cookie 值放入 Header/Body,伺服器比對。適用 SPA + API 架構。
AJAX 請求加上 X-Requested-With: XMLHttpRequest,伺服器檢查。注意:fetch 預設不送,需手動加入。
| 框架 | 機制 |
|---|---|
| Laravel | @csrf Blade 指令 + VerifyCsrfToken 中介層 |
| Symfony | csrf_token() Twig 函式 + CsrfTokenManager |
| CodeIgniter 4 | csrf_field() + CSRFProtection Filter |
| Yii2 | ActiveForm 自動渲染 + Request::validateCsrfToken() |
| Slim | Slim\Csrf\Guard 中介層 |
random_bytes() 產生、hash_equals() 比對SameSite=Lax 或 Strict