攻擊者在第三方網站構造隱藏表單,誘導已登錄用戶點擊,向目標站點發起偽造請求(修改密碼、轉賬、發帖等)。瀏覽器會自動攜帶用戶的 Cookie,服務器無法區分請求來源。
<?php
$servername = $_SERVER['SERVER_NAME'];
$sub_from = $_SERVER['HTTP_REFERER'] ?? '';
$sub_len = strlen($servername);
$checkfrom = substr($sub_from, 7, $sub_len); // 跳過 "https://"
if ($checkfrom !== $servername) {
exit('警告!你正在從外部提交數據!!請立即終止!!');
}
?>
致命缺陷:
HTTP_REFERER 可被瀏覽器設置、代理服務器、防火牆剝離或篡改核心原理:服務器生成不可預測的隨機令牌,存入 Session 並嵌入表單隱藏字段。提交時驗證兩者一致。
<?php
session_start();
// 1. 處理表單提交
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['submit'])) {
// 驗證 Token
if (!hash_equals($_SESSION['csrf_token'] ?? '', $_POST['csrf_token'] ?? '')) {
die('CSRF 驗證失敗:非法請求來源');
}
// 業務邏輯:輸入清洗
$name = trim($_POST['name']);
$name = substr($name, 0, 40);
$name = htmlspecialchars($name, ENT_QUOTES, 'UTF-8');
// 移除十六進制實體(防 XSS 繞過)
$name = preg_replace('!&#x([A-Fa-f0-9]{1,3});!', '', $name);
// ... 後續處理
echo "提交成功:" . $name;
// 成功後重新生成 Token,防止重放
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
exit;
}
// 2. 生成 Token(首次加載或驗證成功後)
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
$token = $_SESSION['csrf_token'];
?>
<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>CSRF 防護示例</title></head>
<body>
<form method="post">
<p><label>姓名: <input type="text" name="name" size="20" maxlength="40" required></label></p>
<input type="hidden" name="csrf_token" value="<?php echo $token; ?>">
<p><input type="submit" name="submit" value="提交"></p>
</form>
</body>
</html>
random_bytes() 生成高熵令牌(PHP 7+),避免 md5(uniqid(rand())) 可預測性hash_equals() 恆時比較,防時序攻擊在設置 Session Cookie 時添加 SameSite=Strict 或 Lax:
<?php
// php.ini 或 session_set_cookie_params()
ini_set('session.cookie_samesite', 'Strict'); // PHP 7.3+
// 或
session_set_cookie_params([
'lifetime' => 0,
'path' => '/',
'domain' => '',
'secure' => true, // 必須 HTTPS
'httponly' => true,
'samesite' => 'Strict'
]);
session_start();
?>
Strict:完全禁止跨站發送 Cookie(最安全,可能影響正常跳轉)Lax:允許鏈接跳轉、GET 表單提交攜帶 Cookie(推薦平衡選擇)None:跨站發送(需 Secure),不防禦 CSRF注意: SameSite 是纵深防禦,**不能替代 CSRF Token**,舊版瀏覽器不支持。
服務器不存儲 Token,而是將 Token 同時寫入 Cookie 和表單/請求頭,驗證兩者一致:
<?php
// 生成頁面時
$token = bin2hex(random_bytes(32));
setcookie('csrf_token', $token, ['httponly' => true, 'secure' => true, 'samesite' => 'Lax']);
// 頁面模板渲染表單隱藏字段或 JS 讀取 Cookie 發送 Header
// API 驗證時
if (!hash_equals($_COOKIE['csrf_token'] ?? '', $_POST['csrf_token'] ?? $_SERVER['HTTP_X_CSRF_TOKEN'] ?? '')) {
http_response_code(403);
exit('CSRF 驗證失敗');
}
?>
優點:無需服務端 Session 存儲,適合分布式/無狀態架構。缺點:需 JavaScript 協作(SPA),或表單隱藏字段傳遞。
<%
Server_v1 = Cstr(Request.ServerVariables("HTTP_REFERER"))
Server_v2 = Cstr(Request.ServerVariables("SERVER_NAME"))
If mid(server_v1, 8, len(server_v2)) <> server_v2 Then
Response.write "警告!你正在從外部提交數據!!請立即終止!!"
Response.End
End if
%>
同樣存在 Referer 不可靠問題,建議遷移至 Token 方案。
| 防禦層級 | 措施 | 必要性 |
|---|---|---|
| 核心 | 所有狀態改變請求驗證 CSRF Token | ✅ 必須 |
| 核心 | Token 使用 CSPRNG 生成 + hash_equals 比較 | ✅ 必須 |
| 纵深 | 設置 Session Cookie SameSite=Lax/Strict | ✅ 強烈建議 |
| 纵深 | 關鍵操作二次驗證(密碼、短信、郵箱) | ✅ 高風險業務必須 |
| 纵深 | 檢查 Origin/Referer Header(僅作輔助) | 可選 |
現代 Web 應用應以同步令牌模式為核心,配合 SameSite Cookie 形成纵深防禦,徹底消除 CSRF 風險。