2

背景:好的,我在 ninjawars.net 上运行了一个传统的 BBG。玩家可以对其他玩家进行“攻击”,该“攻击”是通过表单发布初始化的。本质上,我们可以简化情况,假设有一个页面,我们称之为attack.php,有一个巨大的“ATTACK”表单提交到另一个php页面,我们称之为accept_attack.php,第二个页面执行攻击功能,可以说杀死其他玩家 1、2 或 3。服务器运行 PHP5、Postgresql、Apache

问题:

  • 如果我点击那个大的“ATTACK”按钮,它会把我带到accept_attack.php,然后我可以点击刷新三次,每次重新提交,连续攻击三次。
  • 如果我打开第一页的三个标签,并在每一页上点击攻击,我最终会得到三个瞬时攻击,同时杀死玩家 1、2 和 3,我可以不断刷新重复。
  • 尽管我尝试将“最近的攻击”计时器保存到数据库中,但玩家似乎能够解决它,也许只需以足够同步的方式刷新三个复制的选项卡,以便他们都可以检索到相同的计时器(例如上午 10:00:00:0000),然后继续进行结果处理。

需要的解决方案:

那么如何防止对某个脚本的相同处理一式三份地同时执行呢?

首选 PHP、社会工程和/或 javascript/jQuery 解决方案(可能按此顺序)。

编辑:根据答案,这是我所做的(可能是在压力测试之前)解决它:会话答案似乎最简单/最容易实现,所以我使用了那个数据存储。我对其进行了测试,它似乎可以工作,但可能有一些我不知道的方法。

$recent_attack = null;
$start_of_attack = microtime(true);
$attack_spacing = 0.2; // fraction of a second
if(SESSION::is_set('recent_attack')){
    $recent_attack = SESSION::get('recent_attack');
}

if($recent_attack && $recent_attack>($start_of_attack-$attack_spacing)){
    echo "<p>Even the best of ninjas cannot attack that quickly.</p>";
    echo "<a href='attack_player.php'>Return to combat</a>";
    SESSION::set('recent_attack', $start_of_attack);
    die();
} else {
    SESSION::set('recent_attack', $start_of_attack);
}

如果有改进的方法或者可以利用的方法(除了对我来说很明显的那个,回声的东西不是一个很好的逻辑分离,我很想知道。沿着这些思路,社区维基编辑.

4

5 回答 5

7

虽然 womp 的 Post-Redirect-Get 模式会解决一些问题,但如果他们故意玩弄提交过程,那么我怀疑它会阻止问题,除了针对懒惰的人(如链接文章中所述,302 响应之前的提交将是多个因为重定向尚未发生)。

相反,您最好将一些信息令牌放在不容易复制的攻击页面上。当您接受攻击时,将攻击推送到数据库队列表中。具体来说,存储排队时发送到攻击页面的信息令牌,并在排队攻击之前检查该令牌是否已被使用。

一个简单的令牌来源是运行随机数生成器并将它们放入表中的结果。为每个攻击页面加载提取下一个数字,并验证该数字最近是否已分发。您可以在攻击页面加载时重新填充令牌,并根据您的策略使任何“未使用”令牌过期,以了解页面在“陈旧”之前应该可用多长时间。

通过这种方式,您生成一组有限的“有效”令牌,在攻击页面上发布这些令牌(每页一个),并验证它们的令牌尚未在攻击处理页面上使用。要创建重复攻击,玩家必须确定哪些令牌是有效的......重复相同的帖子将失败,因为令牌已被消耗。使用 BigInt 和像样的伪随机数生成器,搜索空间使其不太容易规避。(注意,您需要围绕令牌验证和更新进行交易,以确保使用此方法成功。)

如果您有需要登录的用户帐户,您可以生成这些令牌并将其存储在用户表中(同样,使用围绕这些步骤的数据库事务)。然后每个用户一次将拥有一个有效的令牌,并且将以类似的方式捕获多个提交。

于 2009-09-24T23:29:46.317 回答
4

您可以通过对表单发布使用Post-Redirect-Get模式来避免大多数表单重新提交。

简而言之,不是attack_accept.php从原始帖子返回,而是返回 302 响应以将浏览器重定向到attack_accept.php. 现在当用户重新加载页面时,他们只是重新加载了 302 请求,并且没有重复的表单提交。

于 2009-09-24T23:17:14.300 回答
3

类似于 Godeke 的解决方案。您不能在“攻击”按钮表单上生成一个带有隐藏字段的令牌并将其存储在会话中吗?然后在 accept-attack.php 页面上检查 $_POST['token'] == $_SESSION['token']。

所以你会在 accept-attack.php 页面上有类似的东西

if($_POST['token'] == $_SESSION['token']){

       echo 'no cheating!';
            // or redirect to the attach page
   }else{
         $_SESSION['token'] = $_POST['token'];
         // then perform the attack
   } 

于 2009-09-24T23:46:24.367 回答
1

另一种解决方案是序列化发布数据(我自己喜欢 JSON),然后对其进行哈希处理,将结果存储在数据库中。

如果用户两次提交相同的信息,则哈希将存在于数据库中。

您还应该在同一张表中添加一个时间戳,以便在 X 小时后可以删除/更新哈希

示例 php 伪代码:

$hash = sha1(json_encode($_POST));
$results = $db->exec('SELECT timestamp FROM user_posts WHERE user_id=? AND hash=?', $user_id, $hash);

if ($results != null) {
    // check timestamp, allow if over 24 hours ago
    $ok = ($results['timestamp']+3600*24) < now();
} else {
    // no results, allow
    $ok = true;
}

if ($ok) {
    $db->exec('INSERT INTO user_posts (hash, timestamp) VALUES (?, ?)', $hash, now() );
} else {
    // show error page
    echo "your request has been denied!";
}

注意:这仍然允许在短时间内提交不同的 POST 数据,但这也很容易检查。

于 2009-09-24T23:39:37.367 回答
1

这个解决方案应该是不可能规避的:

1)NextAttackToken CHAR(32)在你的玩家表中添加一个''列,并给每个玩家一个随机生成的MD5值。

2) 在attack.php页面上,添加一个隐藏字段“current_token”,其中包含玩家当前的令牌。

3)在accept_attack.php页面中,使用如下逻辑判断是否真的允许玩家攻击:

// generate a new random token
$newToken = md5(microtime(true).rand());

// player is spamming if he has attacked less than 30 seconds ago
$maxTimer = date('Y-m-d H:i:s', strtotime('-30 seconds'));

// this update will only work if the player is allowed to attack
$query = "UPDATE player SET NextAttackToken = '$newToken'
               WHERE PlayerID = $_SESSION[PlayerID]
               AND PlayerLastAttack < '$maxTimer'
               AND NextAttackToken = '$_GET[current_token])'
         ";
$result = mysql_query($query);
if(mysql_affected_rows($result)) {
    echo "Player is allowed to attack\n";
}
else {
    echo "Player is spamming! Invalid token or submitted too soon.\n";
}

此解决方案之所以有效,是因为 mysql 一次只能对表执行一个 UPDATE,即使同时有 100 个垃圾邮件请求,mysql 的第一个 UPDATE 也会更改令牌并阻止其他 99 个更新影响任何行.

于 2009-09-25T00:42:30.683 回答