You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PHP中批量删除条目时CSRF令牌验证失败的原因及解决

PHP CSRF令牌验证问题:批量/连续删除失败的原因与修复方案

问题现象

当尝试使用同一CSRF令牌删除多个条目,或同一会话内连续发起删除请求时,会触发“CSRF attack detected”错误,验证失败。

失败原因分析

从提供的代码来看,核心问题在于令牌更新时机与前端同步不匹配:

  1. 每次处理delete_entry请求时,无论验证成功还是失败,都会立即重新生成$_SESSION['auth_token']:
    • 验证失败分支:执行令牌更新后直接exit
    • 验证成功分支:删除操作完成后同样更新令牌
  2. 前端表单中的auth_token是页面加载时生成的静态值,请求完成后页面未刷新,表单仍持有旧令牌。下一次提交时,session中的令牌已经更新,导致前后令牌不匹配,触发验证失败。
  3. 当前表单仅支持单个target_id提交,无法直接处理批量删除请求,但这不是令牌验证失败的直接原因。

修复方案

根据需求场景,提供两种可行的修复思路:

思路1:允许令牌在同一会话内多次复用(简化方案)

如果不需要严格的一次性令牌,可以移除每次请求后更新令牌的逻辑,仅在会话初始化时生成令牌,直到会话结束或用户主动登出才更新。这种方式安全性略低,但能满足连续删除的需求,适合低风险场景:

// 移除以下两处代码:
// 1. 验证失败分支中的令牌更新
// $_SESSION['auth_token'] = bin2hex(random_bytes(20));

// 2. 验证成功分支末尾的令牌更新
// $_SESSION['auth_token'] = bin2hex(random_bytes(20));

思路2:保持一次性令牌机制,同步更新前端令牌(安全方案)

如果需要严格的一次性令牌(更安全),则需要在每次请求完成后,更新前端表单中的令牌值,确保下一次提交的令牌与session一致。推荐用AJAX处理删除请求,步骤如下:

1. 修改后端代码,返回新令牌

在删除操作完成后,返回新生成的令牌(替代直接输出HTML/JS):

// 替换验证成功分支末尾的代码:
// $_SESSION['auth_token'] = bin2hex(random_bytes(20));
// echo '<script>$(document).ready(function(){toastr.success("Entry successfully deleted");}) </script>';

// 改为:
$new_token = bin2hex(random_bytes(20));
$_SESSION['auth_token'] = $new_token;
echo json_encode([
    'status' => 'success',
    'message' => 'Entry successfully deleted',
    'new_token' => $new_token
]);
exit();

// 验证失败分支也返回新令牌:
$new_token = bin2hex(random_bytes(20));
$_SESSION['auth_token'] = $new_token;
echo json_encode([
    'status' => 'error',
    'message' => 'CSRF attack detected. Please try again.',
    'new_token' => $new_token
]);
exit();

2. 修改前端表单,用AJAX提交请求

<!-- 修改表单,移除submit按钮的默认提交行为 -->
<form id="deleteForm" method="POST" action="">
  <div class="mb-3">
    <input type="hidden" name="auth_token" id="auth_token" value="<?php echo htmlspecialchars(strip_tags($antiXss->xss_clean($_SESSION['auth_token']))); ?>" required>
    <input type="hidden" name="target_id" class="form-control" id="recipient-name">
  </div>
</form>

<!-- 修改删除按钮的事件 -->
<button type="button" id="deleteBtn" class="btn btn-danger"><i class="fa-solid fa-trash"></i> delete </button>

<script>
$(document).ready(function() {
  $('#deleteBtn').click(function() {
    var formData = $('#deleteForm').serialize();
    $.ajax({
      type: 'POST',
      url: '',
      data: formData,
      dataType: 'json',
      success: function(response) {
        // 更新表单中的令牌
        $('#auth_token').val(response.new_token);
        // 显示提示
        if (response.status === 'success') {
          toastr.success(response.message);
          $('#staticBackdrop').modal('hide');
        } else {
          toastr.error(response.message);
        }
      },
      error: function() {
        toastr.error('Request failed');
      }
    });
  });
});
</script>

3. 批量删除的适配

如果需要批量删除,可以修改表单,用复选框收集多个target_id,提交时一次性传给后端,后端循环处理,只需验证一次令牌即可:

<!-- 批量删除表单示例 -->
<form id="batchDeleteForm" method="POST" action="">
  <input type="hidden" name="auth_token" id="batch_auth_token" value="<?php echo htmlspecialchars($_SESSION['auth_token']); ?>" required>
  <!-- 用复选框选择要删除的条目 -->
  <div class="mb-3">
    <?php foreach($entries as $entry): ?>
    <input type="checkbox" name="target_ids[]" value="<?php echo $entry['jnum']; ?>"> <?php echo $entry['jnum']; ?>
    <?php endforeach; ?>
  </div>
  <button type="button" id="batchDeleteBtn" class="btn btn-danger">批量删除</button>
</form>

后端处理批量删除:

if (isset($_POST["batch_delete_entry"])) {
    // 验证令牌(仅验证一次)
    if (!isset($_POST['auth_token']) || $_SESSION['auth_token'] !== $_POST['auth_token'] || empty($_POST['target_ids'])) {
        $new_token = bin2hex(random_bytes(20));
        $_SESSION['auth_token'] = $new_token;
        echo json_encode([
            'status' => 'error',
            'message' => 'Invalid request',
            'new_token' => $new_token
        ]);
        exit();
    } else {
        // 循环处理每个target_id
        foreach($_POST['target_ids'] as $target_id) {
            // 执行删除逻辑(和之前的单个删除逻辑一致)
        }
        // 生成新令牌并返回
        $new_token = bin2hex(random_bytes(20));
        $_SESSION['auth_token'] = $new_token;
        echo json_encode([
            'status' => 'success',
            'message' => 'Entries deleted successfully',
            'new_token' => $new_token
        ]);
        exit();
    }
}

内容的提问来源于stack exchange,提问作者dezeer

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 08:12:36