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

PHP 防止遠程非法表單提交(CSRF 防禦)完整指南

     編輯:PHP 相關知識

威脅場景

攻擊者在第三方網站構造隱藏表單,誘導已登錄用戶點擊,向目標站點發起偽造請求(修改密碼、轉賬、發帖等)。瀏覽器會自動攜帶用戶的 Cookie,服務器無法區分請求來源。

方案一:HTTP Referer 檢查(不推薦)

<?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 可被瀏覽器設置、代理服務器、防火牆剝離或篡改
  • HTTPS → HTTP 請求不發送 Referer
  • 用戶可安裝插件偽造 Referer
  • cURL 等工具可輕易偽造

方案二:CSRF Token(同步令牌模式)- 行業標準

核心原理:服務器生成不可預測的隨機令牌,存入 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>

關鍵安全點

  1. 使用 random_bytes() 生成高熵令牌(PHP 7+),避免 md5(uniqid(rand())) 可預測性
  2. 使用 hash_equals() 恆時比較,防時序攻擊
  3. 每會話/每請求更新 Token:敏感操作建議一次性 Token
  4. 僅對狀態改變操作(POST/PUT/DELETE)驗證,GET 請求不應修改數據

進階防禦:SameSite Cookie 屬性(現代瀏覽器原生支持)

在設置 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**,舊版瀏覽器不支持。

雙重提交 Cookie 模式(無狀態 API 適用)

服務器不存儲 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),或表單隱藏字段傳遞。

ASP 版本參考(歷史項目維護)

<%
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 風險。

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