2014-05-19 134 views
0

我的問題是我有一個類似於Stack Overflow的投票系統。我的問題是,一個人可以垃圾郵件的投票按鈕,這使得小故障,並提交更多的時間比它應該。例如,如果某個帖子上有10張投票,我可以反覆點擊投票向上按鈕,它將增加兩張或三張選票而不是一張。同樣,我可以用向下按鈕來做到這一點。我如何防止這種情況?如何防止垃圾郵件發送ajax表格

的index.php:

<?php 
session_start(); 
require('db.php'); 
$pid = 2; 
$uid = $_SESSION['id']; 
$sql = mysqli_query($con, "SELECT * FROM posts WHERE pid = '$pid'"); //check to see how many likes the post has 
$r = mysqli_fetch_assoc($sql); 
$body = $r['body']; 
$likes = $r['likes']; 
$sql2 = mysqli_query($con, "SELECT * FROM likes WHERE pid = '$pid' AND uid = '$uid'");  //check to see if user has voted 
$n = mysqli_num_rows($sql2); 
if ($n == 0) { 
    //user hasn't liked or down vote anything yet 
    $liked = "no"; 
} else { 
    if ($n > 1) { 
     //like scammed 
     echo "<script>alert('Stop spamming for votes. You are banned for spam.')</script>"; 
     exit("You have been banned for spam"); 
     //This isn't fool proof though, and I don't want to ban people for this. It would be best if I could just prevent the vote scam in the first place 
    } 
$r = mysqli_fetch_assoc($sql2); 
$type = $r['like_type']; 
if ($type == '0') { 
    $liked = "liked"; 
} else { 
    $liked = "disliked"; 
} 
} 
?> 
<!DOCTYPE html> 
<html> 
<head> 
    <title>Test</title> 
    <script src="//code.jquery.com/jquery-latest.min.js"></script> 
    <style> 
     .selected { 
      color: red; 
     } 
    </style> 
</head> 
<body> 
<div class="post"> 
    <p><?php echo $body; ?></p> 
</div> 
<div class="likes"> 
    <a href="javascript:;" class="upvote <?php if ($liked == 'liked') {echo "selected";} ?>" id='up-<?php echo $pid; ?>' onclick="vote('up', '<?php echo $pid; ?>', '<?php echo $uid; ?>', 'up-<?php echo $pid; ?>', 'votes-<?php echo $pid; ?>')">Upvote</a> 
    <span id="votes-<?php echo $pid; ?>"><?php echo $likes; ?></span> 
    <a href="javascript:;" class="downvote <?php if ($liked == 'disliked') {echo "selected";} ?>" id='down-<?php echo $pid; ?>' onclick="vote('down', '<?php echo $pid; ?>', '<?php echo $uid; ?>', 'down-<?php echo $pid; ?>', 'votes-<?php echo $pid; ?>')">Downvote</a> 
</div> 
</body> 

的Javascript票()函數

function vote(type, pid, uid, id, voteId) { 
var vote = $('#'+ id); 
if (vote.hasClass('selected')) { 
    //user voted for this 
    $.post("vote.php", {pid: pid, uid: uid, type: type, vote: 'reset'}, function(d) { 
     if (d == '0' || d == '1') { 
      vote.removeClass('selected'); 
      var votes = $('#' + voteId); 
      var num = votes.text(); 
      if (d == '1') { 
       votes.text(++num); 
      } else { 
       votes.text(--num); 
      } 
     } else { 
      alert('An error occurred') 
     } 
    }); 
} else { 
    var upVoteId = $('#up-' + pid); 
    var downVoteId = $('#down-' + pid); 
    if (upVoteId.hasClass('selected') || downVoteId.hasClass('selected')) { 
     //user wants to switch votes 
     $.post('vote.php', {pid: pid, uid: uid, type: type, vote: 'switch'}, function(data) { 
      var votes = $('#' + voteId); 
      var num = votes.text(); 
      if (data == '1') { 
       //downvote successful 
       votes.text(parseInt(num) - 2); 
       vote.addClass('selected'); 
       upVoteId.removeClass('selected'); 
      } 
      if (data == '0') { 
       //upvote successful 
       votes.text(parseInt(num) + 2); 
       vote.addClass('selected'); 
       downVoteId.removeClass('selected'); 
      } 
      if (d == 'error') { 
       alert('error'); 
      } 
     }); 
    } else { 
     $.post('test2.php', {type: type, pid: pid, uid: uid}, function(d) { 
      if (d == "1") { 
       //everything good 
       $('#' + type + '-<?php echo $pid; ?>').addClass('selected'); 
       var votes = $("#" + voteId).text(); 
       if (type == 'down') { 
        //downvote 
        votes = --votes; 
        $('#' + voteId).text(votes); 
       } else { 
        votes = ++votes; 
        $('#' + voteId).text(votes); 
       } 
      } else { 
       alert('failed'); 
      } 
     }); 
    } 
} 
} 
} 

Vote.php

<?php 
session_start(); 
require('db.php'); 

if (!isset($_SESSION['id'], $_SESSION['un'])) { 
    //not logged in 
    header('Location: index.php'); 
    exit; 
} else { 
    if (!isset($_POST['uid'], $_POST['pid'], $_POST['type'], $_POST['vote'])) { 
     //form not submitted 
     header('Location: home.php'); 
     exit; 
    } else { 
     $uid = (int)$_SESSION['id']; 
     $pid = (int)$_POST['pid']; 
     $type = preg_replace('#[^a-z]#', '', $_POST['type']); 
     $vote = preg_replace('#[^a-z]#', '',$_POST['vote']); //vote type 

     if ($vote == 'reset') { 
      //initiate vote reset 
      if ($type == 'down') { 
       //downvote 
       $sql = mysqli_query($con, "DELETE FROM likes WHERE like_type = '1' AND pid = '$pid' AND uid = '$uid'"); //delete the downvote 
       $sql2 = mysqli_query($con, "UPDATE posts SET likes = likes + 1 WHERE pid = '$pid'"); 
       if ($sql) { 
        echo "1"; // 1 
        exit; 
       } else { 
        echo "error"; 
        exit; 
       } 
      } else { 
       //upvote 
       $sql = mysqli_query($con, "DELETE FROM likes WHERE like_type = '0' AND pid = '$pid' AND uid = '$uid'"); //delete upvote 
       $sql2 = mysqli_query($con, "UPDATE posts SET likes = likes - 1 WHERE pid = '$pid'"); 
       if ($sql) { 
        echo "0"; // 0 
        exit; 
       } else { 
        echo "error"; 
        exit; 
       } 
      } 
     } 

     if ($vote == 'switch') { 
      //user wanted to switch vote 
      if ($type == 'down') { 
       //user had voted up but wants to vote down now 
       $sql = mysqli_query($con, "DELETE FROM likes WHERE like_type = '0' AND pid = '$pid' AND uid = '$uid'"); //delete the previous vote 
       $sql2 = mysqli_query($con, "INSERT INTO likes (pid, uid, like_type, date_liked) VALUES ('$pid', '$uid', '1', now())"); //insert new vote 
       $sql3 = mysqli_query($con, "UPDATE posts SET likes = likes - 2 WHERE pid = '$pid'"); 
       if ($sql AND $sql2 AND $sql3) { 
        //all three queries were successful 
        echo "1"; 
        exit; 
       } else { 
        echo "error"; 
        exit; 
       } 
      } else { 
       //user had voted down but wants to vote up now 
       $sql = mysqli_query($con, "DELETE FROM likes WHERE like_type = '1' AND pid = '$pid' AND uid = '$uid'") or die(mysqli_error($con)); //delete the previous vote 
       $sql2 = mysqli_query($con, "INSERT INTO likes (pid, uid, like_type, date_liked) VALUES ('$pid', '$uid', '0', now())"); //insert new vote 
       $sql3 = mysqli_query($con, "UPDATE posts SET likes = likes + 2 WHERE pid = '$pid'"); 
       if ($sql AND $sql2 AND $sql3) { 
        //all three queries were successful 
        echo "0"; 
        exit; 
       } else { 
        echo "error"; 
        exit; 
       } 
      } 
     } 
    } 
} 

Test2.php

<?php 
require('db.php'); 

$pid = $_POST['pid']; 
$uid = $_POST['uid']; 
$type = $_POST['type']; 

if ($type == "down") { 
    //downvote 
    $type = 1; 
    $sql = mysqli_query($con, "INSERT INTO likes (uid, pid, like_type, date_liked) VALUES ('$uid', '$pid', '$type', now())"); 
    $sql2 = mysqli_query($con, "UPDATE posts SET likes = likes - 1 WHERE pid = '$pid'"); 
    if ($sql) { 
     echo '1'; 
     exit; 
    } 
} else { 
    //upvote 
    $type = 0; 
    $sql = mysqli_query($con, "INSERT INTO likes (uid, pid, like_type, date_liked) VALUES ('$uid', '$pid', '$type', now())"); 
    $sql2 = mysqli_query($con, "UPDATE posts SET likes = likes + 1 WHERE pid = '$pid'"); 
    if ($sql) { 
     echo '1'; 
     exit; 
    } 
} 

這些是我目前使用的頁面。我計劃將test2.php移動到vote.php

在我的數據庫中,我有兩張表,一張存儲所有帖子的詳細信息,包括投票數。第二張表是存儲誰投了哪個帖子,以及是否投票或停止投票。

如果我可以讓我的系統更高效,請給我提示或建議。

+0

很多方法可以做到這一點,一個快速的發送的投票數與請求。忽略來自同一會話的任何數字。 –

+0

一種方法是統計嘗試次數,然後簡單地禁用該帖子。 – Jonast92

回答

1

快速SQL黑客:就pid,uid唯一索引,以便用戶永遠只能在一個帖子投票一次。

例如:ALTER TABLE vote ADD UNIQUE INDEX pid_uid (pid, uid);

快速JS砍:設定提交,你不明朗,直到響應變量;如果變量已設置,則不提交表單。因此,垃圾郵件點擊將不會執行任何操作,因爲第一次點擊後的每次點擊都將被忽略。

例如:

var submitting = false; 
function submit_form() 
{ 
    if (!submitting) 
    { 
     submitting = true; 

     // example; insert actual arguments for it to work 
     $.post(
      url, 
      postData, 
      function (data, textStatus) 
      { 
       submitting = false; 
       // handle data here 
      }, 
      "json" 
     ); 
    } 
} 
+0

謝謝你,這是最有用的答案 – Beehive

+0

我遇到了一個問題,當我在'pid,uid'上設置了一個唯一的索引時,它並沒有讓其他人在已投票的帖子上投票,而且一個用戶可以只在一篇文章上投票而不在多篇文章中 – Beehive

+0

另外,請您詳細說明JS黑客攻擊,這對我來說不是很清楚。 – Beehive

0

那麼,有很多改進要做。 首先,您正在運行SQL注入查詢。將其移至準備好的語句。

然後,您可以在插入之前檢查用戶是否已經爲該類型投票,因爲您已經有uid,pidlike_type。這是服務器端。

客戶端JavaScript可能會禁用單擊按鈕以防止雙擊。這將阻止用戶向服務器發送很多請求。

這裏的目標是讓服務器PHP處理驗證,如果用戶已經投票到該帖子,因爲在這種情況下客戶端-ide很容易被操縱。

不要忘記,將這些SQL查詢移到安全的地方。

0

最終,您只能希望使用服務器端驗證來控制多個投票。

堆棧溢出要求用戶登錄到一個已知的帳戶進行投票,這使得多次投票更加困難(但當然不是不可能)。

如果您不需要,最好的解決方案取決於您的具體要求。

一個簡單的客戶端解決方案是設置一個cookie,指示用戶已投票。如果設置了該cookie,請禁用相應的UI元素。清除cookie或使用InPrivate風格瀏覽的人很容易繞過這一點。有人也可以編寫自己的客戶端,忽略cookie。也許這對你的要求就足夠了。

一個天真的服務器端解決方案是每個IP地址只允許一個投票。我不建議這樣做,但包括它,讓你明白爲什麼。不幸的是,一個用戶可以擁有多個IP地址(只需在移動設備上行駛,查看您獲得多少IP),或者一個IP可以代表多臺物理計算機(代理服務器)。

一個可靠的服務器端解決方案將結合IP地址,用戶代理和設備的各個方面來產生設備指紋。這是一個複雜的解決方案,超出了大多數網站的需求(但如果您需要它,那裏有幾家公司提供設備指紋識別)。退房https://panopticlick.eff.org/

摘要

如果你可以要求用戶登錄到投票(如StackOverflow上),這往往是最好的解決方案。

如果你不能要求,如果它在你的預算,使用設備指紋,否則依靠一個cookie。如果你選擇後者,可能仍然值得記錄選民的IP地址和用戶代理人,以便你可以密切關注公然的作弊行爲。

0

我會考慮在投票後將選舉人的IP地址作爲INT存儲在mysql表中。

之後,無論是向他們展示理貨,還是讓他們選擇通過投票上/下來撤消他們的投票。

退房PHP函數ip2long: http://www.php.net/manual/en/function.ip2long.php

使用的IP地址轉換成INT格式,並存儲在你的mysql參考反對。

其他資源: http://www.php.net/manual/en/function.long2ip.php http://dev.mysql.com/doc/refman/5.0/en/miscellaneous-functions.html#function_inet-aton