PHP网页游戏重复攻击请求致军队复制漏洞的解决方案咨询
解决PHP网页游戏重复请求致军队复制的漏洞方案
针对你遇到的问题——用户通过并发请求或离线缓存请求重复触发军队生成逻辑,同时要保留双浏览器/双账号登录的游玩权限,以下是几个落地性强的解决方案:
1. 操作令牌校验(最直接的防重复提交方案)
给每个可触发军队生成的操作页面分配唯一会话令牌,令牌与用户会话、操作类型绑定,确保同一操作请求只能执行一次:
前端生成令牌
页面加载时从后端获取令牌,提交请求时携带:
// 页面加载时生成令牌(存在当前会话中) $_SESSION['attack_token'] = uniqid('atk_', true); // 页面输出隐藏域 echo '<input type="hidden" name="attack_token" value="'.$_SESSION['attack_token'].'">';
后端校验逻辑
$token = $_POST['attack_token'] ?? ''; $userId = $_SESSION['user_id']; // 校验令牌有效性 if ($_SESSION['attack_token'] !== $token) { die('请求无效,请刷新页面重试'); } // 校验通过后立即销毁令牌,防止重复使用 unset($_SESSION['attack_token']); // 执行生成军队的核心逻辑 generateArmy($userId);
优势:每个浏览器会话拥有独立令牌,双浏览器登录时各自操作不受影响;重复请求会因令牌已被销毁而被拦截。
2. 同一操作时间窗口限制
针对用户的同一操作类型(比如攻击造兵)设置最短执行间隔(比如1秒),避免同一操作被并发触发:
$userId = $_SESSION['user_id']; $operationType = 'generate_army'; // 标记操作类型 $redis = new Redis(); $redis->connect('127.0.0.1', 6379); // 检查上次执行时间 $lastExecKey = "user:{$userId}:last_{$operationType}"; $lastExecTime = $redis->get($lastExecKey) ?? 0; if (time() - $lastExecTime < 1) { die('操作过于频繁,请稍后再试'); } // 先更新执行时间,再处理请求(防止并发请求绕过) $redis->set($lastExecKey, time(), 5); // 设置5秒过期,避免冗余键 // 执行军队生成逻辑 generateArmy($userId);
优势:无需修改前端,直接在后端拦截并发请求;不同操作类型(如造兵、升级建筑)互不影响,双浏览器可同时进行不同操作。
3. 数据库乐观锁(底层数据层面防重复)
在用户军队表中添加version版本号字段,执行操作时校验版本号,确保同一数据更新只能生效一次:
$userId = $_SESSION['user_id']; $pdo = new PDO('mysql:host=localhost;dbname=game_db', 'db_user', 'db_pass'); // 先获取当前军队数据及版本号 $stmt = $pdo->prepare("SELECT version, army_count FROM user_army WHERE user_id = ?"); $stmt->execute([$userId]); $armyData = $stmt->fetch(PDO::FETCH_ASSOC); if (!$armyData) { // 初始化用户军队记录 $stmt = $pdo->prepare("INSERT INTO user_army (user_id, version, army_count) VALUES (?, 0, 0)"); $stmt->execute([$userId]); $currentVersion = 0; } else { $currentVersion = $armyData['version']; } // 执行更新:只有版本号匹配时才生效 $stmt = $pdo->prepare("UPDATE user_army SET army_count = army_count + 100, version = version + 1 WHERE user_id = ? AND version = ?"); $stmt->execute([$userId, $currentVersion]); if ($stmt->rowCount() === 0) { // 版本号不匹配,说明已有并发请求执行过 die('操作失败,请重试'); }
优势:从数据库层面彻底杜绝重复更新,即使请求绕过前端和后端校验,也无法篡改数据;双浏览器的不同操作可正常并行。
4. 异步请求队列(高并发场景适配)
将军队生成请求放入异步队列,同一用户的同类型请求若存在未处理任务,则直接丢弃重复请求:
后端入队逻辑
$userId = $_SESSION['user_id']; $operationType = 'generate_army'; $redis = new Redis(); $redis->connect('127.0.0.1', 6379); // 检查是否有未处理的同类型请求 $pendingKey = "user:{$userId}:pending_{$operationType}"; if ($redis->get($pendingKey)) { die('操作正在处理中,请稍候'); } // 标记请求为待处理 $redis->set($pendingKey, 1, 30); // 30秒过期,避免死锁 // 加入队列 $queueData = json_encode(['user_id' => $userId, 'op_type' => $operationType]); $redis->rPush('game:operation_queue', $queueData); echo '操作已提交,正在处理';
消费者脚本(CLI执行)
$redis = new Redis(); $redis->connect('127.0.0.1', 6379); $queueKey = 'game:operation_queue'; while (true) { $requestData = $redis->lPop($queueKey); if ($requestData) { $request = json_decode($requestData, true); $userId = $request['user_id']; $opType = $request['op_type']; $pendingKey = "user:{$userId}:pending_{$opType}"; // 执行军队生成逻辑 generateArmy($userId); // 清除待处理标记 $redis->del($pendingKey); } usleep(100000); // 休眠0.1秒,降低资源占用 }
优势:适合高并发场景,避免请求堆积;同一用户的同类型请求不会重复执行,双浏览器的不同操作可并行处理。
内容的提问来源于stack exchange,提问作者morgi tmr dasdas
相关产品推荐
相关产品推荐

