2

背景: わかりました。ninjawars.net でレガシー BBG を実行しています。フォーム ポストを介して初期化された、プレイヤーが他のプレイヤーに対して実行できる「攻撃」があります。基本的に、状況を単純化して、ページがあるふりをすることができます。それを attack.php と呼び、別の php ページに送信する巨大な「ATTACK」フォーム投稿を持ち、それを accept_attack.php と呼び、2 番目のページが攻撃を実行します。たとえば、他のプレイヤー 1、2、または 3 を倒すとします。サーバーは PHP5、Postgresql、Apache を実行します。

問題点:

  • 大きな「ATTACK」ボタンを押して、accept_attack.php に移動すると、更新を 3 回押して、毎回再送信し、3 回続けて攻撃することができます。
  • 最初のページの 3 つのタブを開き、各ページで攻撃をヒットすると、プレイヤー 1、2、3 を一度に殺す 3 つの瞬間的な攻撃が発生し、継続的に更新して繰り返すことができます。
  • データベースに保存される「最新の攻撃」タイマーを作成しようと試みたにもかかわらず、プレイヤーはおそらく、コピーされた 3 つのタブを十分に同期された方法で更新するだけで、それを回避できるようです。タイマー (例: 10:00:00:0000 am) を取得し、結果の処理を続行します。

必要なソリューション:

では、特定のスクリプトの同じ処理が一度に 3 重に実行されるのを防ぐにはどうすればよいでしょうか。

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 応答の前の送信は複数になります)。リダイレクトはまだ行われていないため)。

代わりに、簡単に再現できない情報トークンを攻撃ページに配置することをお勧めします。攻撃を受け入れたら、攻撃をデータベース キュー テーブルにプッシュします。具体的には、キューイング時に攻撃ページに送信された情報トークンを保存し、攻撃をキューイングする前にそのトークンが既に使用されているかどうかを確認します。

トークンの単純なソースは、乱数ジェネレーターを実行してテーブルに配置した結果です。攻撃ページの読み込みごとに次の番号を取得し、その番号が最近配布されたことを確認します。攻撃ページの読み込み時にトークンを再入力し、ポリシーに基づいて、ページが「古く」なる前に使用可能な期間の「未使用」トークンを期限切れにすることができます。

このようにして、「有効な」トークンの有限セットを生成し、それらのトークンを攻撃ページ (ページごとに 1 つ) に公開し、それらのトークンがまだ攻撃処理ページで使用されていないことを確認します。繰り返し攻撃を作成するには、プレイヤーはどのトークンが有効かを判断する必要があります...トークンが消費されているため、同じ投稿を繰り返すと失敗します。BigInt とまともな疑似乱数ジェネレーターを使用すると、検索スペースにより、簡単に回避することができなくなります。(このメソッドを確実に成功させるには、トークンの検証と更新に関するトランザクションが必要になることに注意してください。)

ログインが必要なユーザー アカウントがある場合は、これらのトークンを生成してユーザー テーブルに格納できます (ここでも、これらの手順をラップするデータベース トランザクションを使用します)。次に、各ユーザーは一度に 1 つの有効なトークンを持ち、複数の送信が同様の方法でキャッチされます。

于 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

ゴデケのソリューションに似ています。「攻撃」ボタンフォームの隠しフィールドでトークンを生成し、それをセッションに保存できませんでしたか? 次に、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

この解決策は回避できないはずです。

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 が一度にテーブルに対して 1 つの UPDATE しか実行できないため機能します。まったく同時に 100 個のスパム リクエストがあったとしても、mysql による最初の UPDATE はトークンを変更し、他の 99 個の更新が行に影響を与えないようにします。 .

于 2009-09-25T00:42:30.683 に答える
1

別の解決策は、投稿データをシリアル化し (私は JSON が好きです)、それをハッシュして、結果を DB に格納することです。

ユーザーが同じ情報を 2 回送信すると、ハッシュがデータベースに存在します。

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 に答える