问题
I am having an issue with someone who keeps exploiting my betting site with a bot. He is able to (presumably) use a bot to press the "Roll" button multiple times very quickly and get the same roll numbers.
The roll button using a function to work. Here is this function:
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();
}
This function then leads to the php file. Here is the header of it:
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);
As you can see from the beginning, I tried to create a work around by generating a random string exclusive to that one run ($setstring), storing it and then comparing it against itself. However somehow he managed to run it fast enough to get past this as-well.
$newseed is the variable which has the roll number. As you can see a new one is usually generated each run-time. I'm generally confused as to how he can do this, as I thought each php file runs separately.
Can anyone help provide some insight or solutions! I have been suggested transaction encapsulation for example, but not sure how to implement. Thanks for taking the time.
回答1:
I'm assuming this attack works because of multi-threading. Firing many requests will make your code behave erratically because of random interleaving.
A simple example is a bank:
Let's say you want to deduct $20 from your account:
$amount = q("SELECT * FROM accounts WHERE id = $account_id");
q("UPDATE accounts SET amount = $amount - 20 WHERE id = $account_id");
If you start with $100, and you run this code twice you expect to end up with $60. But, if the thread interleaving happens between the select and update call both threads will read $100 so they will both update to $80.
You're thinking in the right direction with $setstring
but you need something more powerful. You need to look at locking.
$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);
Locks can be implemented in many ways. PHP even has some special functions and extensions for it. The simplest way is to use a file lock.
function acquire_lock($name) {
return fopen($name, "rw");
}
function release_lock($lock) {
fclose($lock);
}
Disclaimer: I haven't tested this, I believe it should work in theory :p
You can test this with a script that does:
$lock = acquire_lock("foo");
sleep(30); // do nothing for 30 seconds
release_lock($lock);
And then try to run another script that also tries to acquire the foo
lock. It should have to wait 30 seconds.
回答2:
you can also implement a simple debouncer using javascript in your roll button, or an easy to implement google captcha
来源:https://stackoverflow.com/questions/29688596/stop-bot-sending-multiple-requests-quickly-php-ajax