我的问题是我有一个类似于Stack Overflow的投票系统。我的问题是,一个人可以垃圾邮件投票按钮,这会使其出现故障,并使其提交的次数超出预期。例如,如果一个帖子上有10个投票,我可以反复点击投票按钮,它会增加两到三个投票而不是一个。同样,我可以使用向下投票按钮执行此操作。我该如何防止这种情况?
的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
。
在我的数据库中,我有两个表,一个用于存储所有帖子详细信息,包括投票数。第二个表是存储谁投票支持哪个帖子,以及是否是一个upvote或down。
如果我可以提高我的系统效率,请给我提示或建议。
答案 0 :(得分:1)
Quick SQL hack:在pid,uid
上创建一个唯一索引,以便用户只能在帖子上投票一次。
ex: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"
);
}
}
答案 1 :(得分:0)
嗯,还有很多改进要做。 首先,您正在运行SQL注入查询。将其移至准备好的陈述。
然后,您可以在插入之前检查用户是否已经为该类型投票,因为您已经拥有uid
,pid
和like_type
。这是服务器端。
客户端JavaScript可以禁用点击按钮以防止双击。这将阻止用户向服务器发送许多请求。
这里的目标是让服务器,PHP,如果用户已经投票给该帖子,则处理验证,因为在这种情况下客户端很容易被操纵。
不要忘记,将这些SQL查询移动到安全的地方。
答案 2 :(得分:0)
最终,您只能希望使用服务器端验证来控制多个投票。
Stack Overflow要求用户登录已知帐户才能投票,这使得多次投票更加困难(当然也不是不可能)。
如果您不需要,最佳解决方案取决于您的具体要求。
一个简单的,仅限客户端的解决方案是设置一个cookie,表明用户已投票。如果设置了该cookie,则禁用相应的UI元素。清除cookie或使用InPrivate样式浏览的人很容易绕过它。有人也可以编写自己的忽略cookie的客户端。也许这足以满足您的要求。
天真的服务器端解决方案是每个IP地址只允许一次投票。我不推荐这个,但包括它让你理解为什么。不幸的是,单个用户可以拥有多个IP地址(只需在移动设备上行驶,查看您获得的IP数量),或者单个IP可以代表多个物理计算机(代理服务器)。
可靠的服务器端解决方案将组合IP地址,用户代理和设备的各个方面以生成设备指纹。这是一个复杂的解决方案,超出了大多数网站的需求(但如果您需要,有一些公司提供设备指纹识别)。查看https://panopticlick.eff.org/
<强>摘要强>
如果您可以要求用户登录投票(如StackOverflow),那通常是最佳解决方案。
如果您不能要求,请使用设备指纹识别,如果它在您的预算中,否则依赖cookie。如果你做了后者,可能仍然值得记录选民的IP地址和用户代理,这样你就可以留意明显的作弊行为。
答案 3 :(得分:0)
一旦他们投票,我会考虑将选民的IP地址存储在mysql表中作为INT。
之后,或者只是向他们展示统计数据,或者通过向上/向下投票为他们提供撤消投票的选项。
查看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