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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 12:35:16