CSRF Token 双提交 Cookie 模式

解读

国内一线互联网公司在面试 PHP 后端岗位时,常把 CSRF 防护作为“安全必考题”。双提交 Cookie 模式(Double Submit Cookie)是除同步器令牌(Synchronizer Token)外最常被落地的方案,尤其适合分布式、无状态、前后端分离的 PHP 项目。面试官问此题,核心想验证三点:

  1. 是否理解 CSRF 攻击本质——“借用浏览器自动携带的 Cookie 完成状态写操作”;
  2. 是否明白双提交 Cookie 为什么“不需要服务端会话存储”,从而天然支持水平扩展;
  3. 能否用 PHP 细节(setcookie 参数、SameSite、加密、HTTPS 防嗅探)把方案讲透,并指出国内常见坑(小程序 WebView、HTTPS 证书链不全、APP 内嵌 H5 跨域等)。

知识点

  1. CSRF 攻击原理:第三方站点冒用已登录用户的 Cookie 发起写请求。
  2. 双提交 Cookie 核心:
    a. 登陆后服务端把“随机令牌”写入 Cookie,并同时在响应体(或后续 AJAX 接口)返回同一令牌;
    b. 后续写请求必须把该令牌通过自定义头或表单字段再带回来;
    c. 服务端比对“请求参数中的令牌”与 Cookie 中的令牌”,值相同即放行;
    d. 由于第三方域无法读取或写入目标域 Cookie,也无法构造出与 Cookie 值相同的参数,因此伪造请求失败。
  3. PHP 关键技术点:
    • 使用 random_bytes(32) 生成加密安全随机数,再用 bin2hexbase64_encode 转字符串;
    • setcookie('csrf_token', $token, ['httponly'=>false, 'samesite'=>'Strict', 'secure'=>true, 'path'=>'/']);
    • 前端取 Cookie 时,如跨域需 withCredentials=true;如用 Authorization 头则无需 Cookie 模式;
    • 校验阶段:hash_equals($_COOKIE['csrf_token'], $_SERVER['HTTP_X_CSRF_TOKEN'])
    • 需全局中间件统一拦截,避免开发者漏写校验。
  4. 国内合规与性能:
    • 《网络安全法》要求“采取防范危害网络安全行为的技术措施”,双提交 Cookie 属于其中一种,但需配合 HTTPS、CSP、SRI 等综合防护;
    • 高并发场景下,该模式零会话、零 IO,适合 PHP-FPM + Nginx 多机部署,OPcache 无需额外缓存。

答案

“双提交 Cookie 模式”是指:

  1. 用户登录成功后,PHP 生成一个 32 字节随机令牌 $token = bin2hex(random_bytes(32))
  2. 服务端立即通过 setcookie 把令牌种到浏览器,属性必须包含 Secure; SameSite=StrictHttpOnly 置为 false(因为前端要读取);
  3. 同时把同一令牌返回给前端,可放在 JSON 响应体,也可放在页面 <meta name="csrf-token" content="{$token}">
  4. 后续所有写请求(POST/PUT/DELETE),前端通过 X-CSRF-Token 头或 _csrf 字段把令牌带回;
  5. PHP 入口文件或 Laravel/Symfony 中间件里统一校验:
    $cookieToken = $_COOKIE['csrf_token'] ?? '';
    $headerToken = $_SERVER['HTTP_X_CSRF_TOKEN'] ?? '';
    if (!hash_equals($cookieToken, $headerToken)) {
        http_response_code(403);
        exit('CSRF validation failed');
    }
    
  6. 退出登录或令牌过期时,把 Cookie 置空并设置 expires=time()-3600,强制浏览器删除。

该方案优点:无状态、无 Session、无 Redis,PHP 服务器可任意水平扩展;缺点:依赖 HTTPS 防中间人篡改 Cookie,且前端必须能访问 Cookie,对小程序 WebView 需开 sameSite=None; Secure 并做额外降级。

拓展思考

  1. 国内小程序生态下,WebView 默认禁止第三方 Cookie,双提交 Cookie 会失效。此时可降级为“Authorization 头 + 短时间 JWT”模式,或采用“登录后下发令牌到本地存储,再带回头”的变体。
  2. 如果站点存在 XSS,攻击者就能读 Cookie 并构造合法请求,双提交 Cookie 随之失效。因此必须配套 CSP、输入过滤、输出转义,才能构成完整防线。
  3. 大型 PHP 集群若想进一步降低风险,可“加密 Cookie”:服务端用 openssl_encrypt($token, 'AES-256-GCM', $key, 0, $iv, $tag) 把令牌加密后再写入 Cookie,前端仍明文读取,但中间人即使拿到 Cookie 也无法篡改密文,解密失败直接 403。
  4. 面试加分项:能说出“SameSite 属性在 2020 年后被 Chrome 默认启用,对 CSRF 有一定缓解,但仍需双提交做纵深防御”;并能给出“灰度发布时如何兼容旧版本 APP 内嵌 H5”的具体代码——例如通过 User-Agent 识别旧版,动态关闭 SameSite=Strict,同时缩短令牌有效期并加入 IP、UA 绑定。