停止机器人快速发送多个请求。 PHP + AJAX

Stop bot sending multiple requests quickly. PHP + AJAX

有人一直用机器人利用我的博彩网站,我遇到了问题。他能够(大概)使用机器人非常快速地多次按下 "Roll" 按钮并获得相同的卷号。

滚动按钮使用一个函数来工作。这是这个函数:

var rolling=false;
var lastBet=(Date.now()-<?php echo $settings['rolls_mintime']; ?>-1000);
function place(wager,multiplier,bot) {
  if ((rolling==false && (Date.now())>=(lastBet+<?php echo $settings['rolls_mintime']; ?>)) || bot==true) {
    rolling=true;
    lastBet=Date.now();
    $("#betBtn").html('ROLLING');
    if (bot!=true) _stats_content('my_bets');      
    $.ajax({
      'url': './content/ajax/place.php?w='+wager+'&m='+multiplier+'&hl='+under_over+'&_unique=<?php echo $unique; ?>',
      'dataType': "json",
      'success': function(data) {
        if (data['error']=='yes') {
          if (data['data']=='too_small') alert('Error: Your bet is too small.');
          if (data['data']=='invalid_bet') alert('Error: Your balance is too small for this bet.');
          if (data['data']=='invalid_m') alert('Error: Invalid multiplier.');
          if (data['data']=='invalid_hl') alert('Error: Invalid under/over specifier.');
if (data['data']=='invalid_bts') alert('Using bots, tut tut.');
          if (data['data']=='too_big_bet') alert('Error: Your bet is too big. Currently max profit is set at: '+data['under']+' this represents 1% of the invested backroll.');
        }
        else {
          var result=data['result'];
          var win_lose=data['win_lose'];
          if (win_lose==1) winCeremonial();
          else shameCeremonial();
        }

此函数随后会生成 php 文件。这是它的header:

if (empty($_GET['_unique']) || mysql_num_rows(mysql_query("SELECT `id` FROM `players` WHERE `hash`='".prot($_GET['_unique'])."' LIMIT 1"))==0) exit();

$playerinv=mysql_fetch_array(mysql_query("SELECT `id`,`playcoins`,`time`, `ex`, `server_seed` FROM `players` WHERE `hash`='".prot($_GET['_unique'])."' LIMIT 1"));
$random = base64_encode(openssl_random_pseudo_bytes(10));
$setstring = $random;
mysql_query("UPDATE `players` SET `string` = '$setstring' WHERE `id`=$playerinv[id] LIMIT 1");
$playersec=mysql_fetch_array(mysql_query("SELECT `string` FROM `players` WHERE `hash`='".prot($_GET['_unique'])."' LIMIT 1"));

if ($setstring != $playersec['string']) {
echo json_encode(array('error'=>'yes','data'=>'invalid_bts'));
exit();
}

$newSeed=generateServerSeed();
mysql_query("UPDATE `players` SET `server_seed`='$newSeed' WHERE `id`=$playerinv[id] LIMIT 1");

$settings=mysql_fetch_array(mysql_query("SELECT * FROM `system` LIMIT 1"));

$player=mysql_fetch_array(mysql_query("SELECT * FROM `players` WHERE `hash`='".prot($_GET['_unique'])."' LIMIT 1"));
$player['server_seed_']=$player['server_seed'];
$player['server_seed']=(double)substr($player['server_seed'],27);

正如您从一开始就看到的那样,我试图通过生成一个专用于 运行 ($setstring) 的随机字符串来创建一个解决方法,存储它然后将其与自身进行比较。然而不知何故,他设法 运行 足够快地通过了这个 as-well.

$newseed 是有卷号的变量。如您所见,通常每个 run-time 都会生成一个新的。我通常对他如何做到这一点感到困惑,因为我认为每个 php 文件 运行 是分开的。

谁能帮忙提供一些见解或解决方案!例如,有人建议我进行事务封装,但不确定如何实施。感谢您抽出时间。

我假设这种攻击因为多线程而起作用。由于随机交错,发出许多请求会使您的代码行为不稳定。

一个简单的例子是银行:

假设您想从您的帐户中扣除 20 美元:

$amount = q("SELECT * FROM accounts WHERE id = $account_id");
q("UPDATE accounts SET amount = $amount - 20 WHERE id = $account_id");

如果您以 100 美元开始,并且您 运行 两次执行此代码,您预计最终将获得 60 美元。但是,如果线程交错发生在 select 和更新调用之间,两个线程都将读取 $100,因此它们都将更新为 $80。

您正在考虑使用 $setstring 的正确方向,但您需要更强大的功能。你需要看看锁定。

$lock = acquire_lock("foo");
$amount = q("SELECT * FROM accounts WHERE id = $account_id");
q("UPDATE accounts SET amount = $amount - 20 WHERE id = $account_id");
release_lock($lock);

锁可以通过多种方式实现。 PHP 甚至还有一些特殊的功能和扩展。最简单的方法是使用文件锁。

function acquire_lock($name) {
    return fopen($name, "rw");
}
function release_lock($lock) {
    fclose($lock);
}

免责声明:我还没有测试过这个,我相信它在理论上应该有效:p

您可以使用执行以下操作的脚本进行测试:

$lock = acquire_lock("foo");
sleep(30); // do nothing for 30 seconds
release_lock($lock);

然后尝试 运行 另一个同样尝试获取 foo 锁的脚本。它应该必须等待 30 秒。

您还可以实施 simple debouncer using javascript in your roll button, or an easy to implement google captcha