【问题标题】:Preventing spamming of the functionality of a php page防止 php 页面功能的垃圾邮件
【发布时间】:2009-09-24 23:13:03
【问题描述】:

背景: 好的,我在 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);
}

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

【问题讨论】:

    标签: php javascript concurrent-processing spam-prevention


    【解决方案1】:

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

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

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

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

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

    【讨论】:

    • 我并没有真正理解这本书的语言。我想这与我最终使用的概念相似,但在我寻求实现时我并不能真正破译它。我猜这意味着第一页从数据存储中擦除令牌并放入唯一令牌。第二页检查唯一令牌并删除所有令牌,成功或失败。第三、第四或第五页加载没有标记,因此它们都失败了。
    • 关于你的具体情况没有提供足够的细节,所以我非常笼统地谈到了这个问题和一个可能的解决方案style。基本思想是您需要生成一些不容易猜到的令牌。一种解决方案是将令牌附加到用户帐户,但如果您允许立即打开窗口,这可能是一个问题,因此我建议您可能需要一个此类令牌队列。无论哪种方式,我都很高兴你找到了解决方案......我从不启用会话状态(REST 是一个目标,会话失败了)但它显然是一种可接受的方法并且对你有用。
    • 啊,这确实清除了队列的点,我可能会发现这种可重复比例的级别很有用/必要,尽管我主要是想为单个节点创建一个阻塞点目前的页面,并且由于我已经在使用 Session (是的,这不一定是理想的),额外的使用可能不会有太大的不同。无论如何,谢谢,也许我能够/利用这个概念。
    【解决方案2】:

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

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

    【讨论】:

    • 正如 Godeke 所说,PRG 只会解决您眼前的一个问题,但这是一个很好的第一步。如果您有足够的用户群,您将需要更复杂的解决方案。此外,您的计时器想法有一些优点,但听起来您需要使其具有事务性,因此三个请求不能同时提交一个新计时器。
    • 嗯,这个概念我可能可以在其他地方使用提交页面,比如我的登录页面,如果我可以围绕如何实际实现 302 进行思考。 微笑
    【解决方案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
       } 
    

    【讨论】:

    • 这应该可以工作,除非有人记得查看源代码,并尝试使用随机令牌提交数据......反过来呢? - 在第一页的会话中设置令牌,然后在第二页的会话中验证并删除它。
    • 使用 SESSION 确实使构思解决方案变得更容易,并消除了所有数据库访问的麻烦,所以这就是我的队列。
    【解决方案4】:

    另一种解决方案是序列化发布数据(我自己喜欢 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 数据,但这也很容易检查。

    【讨论】:

      【解决方案5】:

      这个解决方案应该是不可能绕过的:

      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 个更新影响任何行。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2010-10-19
        • 1970-01-01
        • 1970-01-01
        • 2011-10-08
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多