瀏覽器自動攜帶 Cookie 發起請求的特性,使攻擊者可構造惡意頁面,誘導已登錄用戶訪問,在用戶不知情情況下發起敏感操作(轉賬、修改密碼、發帖等)。
核心思想:服務端生成不可預測的令牌,表單提交時攜帶,服務端驗證令牌有效性。
<?php
class Crumb {
const SALT = 'your-secret-salt-change-in-production'; // 必須修改為強隨機密鑰
static $ttl = 7200; // 令牌有效期 2 小時
// HMAC-MD5 簽名
static public function challenge($data) {
return hash_hmac('md5', $data, self::SALT);
}
// 生成令牌
static public function issueCrumb($uid, $action = -1) {
$i = ceil(time() / self::$ttl); // 時間窗口
return substr(self::challenge($i . $action . $uid), -12, 10);
}
// 驗證令牌(允許前一時間窗口,應對邊界情況)
static public function verifyCrumb($uid, $crumb, $action = -1) {
$i = ceil(time() / self::$ttl);
$current = substr(self::challenge($i . $action . $uid), -12, 10);
$prev = substr(self::challenge(($i - 1) . $action . $uid), -12, 10);
return ($crumb === $current || $crumb === $prev);
}
}
?>
<form method="post" action="demo.php">
<input type="hidden" name="crumb" value="<?php echo Crumb::issueCrumb($uid)?>">
<input type="text" name="content">
<input type="submit">
</form>
<?php
if (Crumb::verifyCrumb($uid, $_POST['crumb'])) {
// 令牌有效,正常處理業務邏輯
} else {
// 令牌無效,拒絕請求並提示錯誤
die('CSRF 令牌驗證失敗,請重新提交。');
}
?>
random_bytes(32) 生成,存儲在配置文件,不入庫、不輸出$uid 標識用戶,$action 區分業務(登錄、轉賬、發帖等),防止令牌跨用戶/跨操作復用$ttl 限制令牌有效期,驗證時兼容前一窗口,平衡安全與用戶體驗$_POST、$_GET,防止參數污染繞過驗證// PHP 7.3+ 原生支持
setcookie('session_id', $value, [
'expires' => time() + 3600,
'path' => '/',
'domain' => '',
'secure' => true, // 僅 HTTPS
'httponly' => true, // 禁止 JS 讀取
'samesite' => 'Strict' // 或 'Lax',瀏覽器級 CSRF 防護
]);
SameSite=Strict 完全阻止跨站請求攜帶 Cookie,Lax 允許鏈接跳轉 GET 請求。配合 Crumb 令牌構建雙重防線。
CSRF 防禦的核心是請求唯一性驗證。Crumb 令牌機制通過 HMAC 簽名、時間窗口、用戶/操作綁定,提供輕量級、高強度的防護。現代應用應結合 SameSite Cookie、雙重提交 Cookie 模式、自定義請求頭驗證等多層手段,構建纵深防禦體系。