背景: 好的,我在ninjawars.net上运行了一个遗留BBG。玩家可以对通过表格帖子初始化的其他玩家进行“攻击”。本质上,我们可以简化情况假装有一个页面,让我们称之为attack.php,用一个巨大的“ATTACK”表单发送到另一个php页面,让我们称之为accept_attack.php,第二页执行攻击功能,让我们说杀死其他玩家1,2或3。 服务器运行PHP5,Postgresql,Apache
问题:
需要的解决方案:
那么如何防止某个脚本的同一处理一次性重复执行?
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);
}
如果有方法可以改进那些或可以利用的方式(除了对我来说显而易见的事情之外,回应的东西不是一个很好的逻辑分离,我很想知道。沿着这些方向,社区 - 维基编
答案 0 :(得分:7)
虽然womp的Post-Redirect-Get模式会解决一些问题,如果他们故意游戏提交过程,那么我怀疑它会阻止问题,除了懒惰(如链接文章中所述,302响应之前的提交由于重定向尚未发生,因此将是多个。)
相反,您可能最好在攻击页面上放置一些不易复制的信息令牌。当您接受攻击时,将攻击推送到数据库队列表中。具体来说,在排队时存储发送到攻击页面的信息令牌,并在排队攻击之前检查该令牌是否已被使用。
一个简单的令牌来源是运行随机数生成器并将它们放入表中的结果。为每个攻击页面加载提取下一个数字,并验证该数字最近是否已分发。您可以在攻击页面加载时重新填充令牌,并根据您的策略使任何“未使用的”令牌到期,以便在“过时”之前页面可用多长时间。
通过这种方式,您可以生成一组有限的“有效”令牌,在攻击页面上发布这些令牌(每页一个),并验证他们的令牌尚未在攻击处理页面上使用过。为了创建重复攻击,玩家必须确定哪些令牌有效...重复相同的帖子将失败,因为令牌已被消耗。使用BigInt和一个体面的伪随机数生成器,搜索空间使它不太容易规避。 (注意,您需要围绕令牌验证和更新的事务,以确保使用此方法成功。)
如果您有需要登录的用户帐户,则可以在用户表上生成并存储这些令牌(同样,使用围绕这些步骤的数据库事务)。然后每个用户一次只有一个有效的令牌,并且会以类似的方式捕获多个提交。
答案 1 :(得分:4)
您可以使用表单帖子的Post-Redirect-Get模式来避免大多数表单重新提交。
简而言之,不是从原始帖子返回attack_accept.php
,而是返回302响应以将浏览器重定向到attack_accept.php
。现在,当用户重新加载页面时,他们只是重新加载302请求,并且没有重复的表单提交。
答案 2 :(得分: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
}
echo 'no cheating!';
// or redirect to the attach page
}else{
$_SESSION['token'] = $_POST['token'];
// then perform the attack
}
答案 3 :(得分: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数据,但这也很容易检查。
答案 4 :(得分: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个更新影响任何行。