Creating a Fraud-proof Voting System

Setting up polls or voting systems is a great way to get users more involved in your website, and keep them coming back for more. But with fraud hanging over the professional political elections, how do you keep your visitors from screaming for a recount–or worse, stuffing the ballot box? Ian Felton describes a simple system for setting up an online poll and preventing abuses.

John Kerry just called. He said the election was full of fraud. Votes were lost. Some were torn up. A nice little old church lady tore mine right in front of me; all for the good of the cause. Fraud plays a huge role in elections, but we programmers want better than that. A fraud-proof voting system can help ensure that the outcome of polls or contests on your site are evidence of what voting is about, determining popular demand.

At United Bands (http://unitedbands.com/), I run a monthly contest. Fans come to the site and vote for songs they would like to hear on the United Bands play list. The obvious purpose of the system is to determine which songs people like and want to hear more often. Without a fraud-proof system though, skewed results are the norm.

When a fan browses the band profiles at United Bands, they listen to songs and vote for songs they like. If the system was so basic as to let users just click a button to vote, some people would click it all day or set up a script to run and cast millions of votes for the same song. This type of fraud needs to be eliminated to make the feature enjoyable for people other than those with too much time on their hands or the know-how to run an automated script.

The process for voting at United Bands is simple enough. Fans can click a link on bands’ profile pages, then enter an email address. If the address and vote are legitimate, it counts as a vote. What is a legitimate vote? Fans are allowed to vote for an individual song once per day. However, the same person can vote for as many songs as they like. So Dick Cheney, or Dick, as I like to call him can come to the site and vote for his favorite band, Vibralux Trans-Mission (http://www.unitedbands.com/bands/Vibralux_trans-mission/), one time on Monday, one time on Tuesday and so on. He can also vote for other bands each day, but again, only one vote per day for each individual band. When Dick submits the form, an email is sent to his posted email address. When Dick checks his email, he clicks the URL in the email as verification of his vote. Then the vote is counted.

{mospagebreak title=One Person, Multiple Addresses, Still One Vote}

Sending the email is only the first step for fraud prevention because some people have a thousand email addresses. Someone could (and would) vote for the same song using all of their email addresses and skew the results in that way. Because of situations like that, the system also needs to verify that fans aren’t using multiple email addresses. The verification email also needs some sort of key so that the verification script cannot be easily hacked.

Two scripts comprise the bulk of the system. One script, vote.php, casts the vote and sends the verification email. Another script, verify.php sets the vote as valid after Dick clicks the link in his inbox. A table in the database will need to be created to store votes.

First, we’ll setup the table in the database to store the votes that are cast. We need a basic primary key called ID. We’ll store the email address of the voter. We’ll timestamp the vote. We need a column that shows whether the vote has been verified. It will be a Boolean flag. We’ll store the ID of the band receiving the vote. We’ll store the song name and finally the IP address of the voter.

CREATE TABLE `votes` (
`ID` int(10) unsigned NOT NULL auto_increment,
`email` varchar(255) NOT NULL default ’0′,
`time` timestamp(14) NOT NULL,
`pending` tinyint(1) unsigned NOT NULL default ’0′,
`bandID` int(10) unsigned NOT NULL default ’0′,
`song` varchar(255) default NULL,
`IP` varchar(15) NOT NULL default ’0′,
PRIMARY KEY (`ID`),
UNIQUE KEY `ID` (`ID`),
KEY `ID_2` (`ID`)
) TYPE=MyISAM AUTO_INCREMENT=0;

{mospagebreak title=One Vote.php, Two States}

Next, we’ll examine vote.php. Vote.php has two states: one when a user clicks on a link to vote for a song and one when they have submitted an email address and posted the form. In the first state, Vote.php has one parameter passed to it: the band’s id. The first chunk of code checks that the band’s ID is passed to the script. If it is, the script goes to the database and grabs the name of the band and the name of the song.

//Check if the band’s ID is sent as a parameter
if (isset($_GET["profile"])) {
//Select the database
mysql_select_db($database, $db);
//escape parameters to avoid tampering
$ID = (get_magic_quotes_gpc()) ? $_GET['profile'] : addslashes($_GET['profile']);

//select the band’s name and song name from the database
$query_tracks = sprintf(“SELECT t1.track, t2.band FROM songs as t1, bands as t2 WHERE t1.CCID = %s and t2.CCID=%s”, $ID, $ID);
$tracks = @mysql_query($query_tracks, $db);
$row_tracks = @mysql_fetch_assoc($tracks);
}

The second state of the script is more involved. When the form is posted, it checks for two conditions: that the same address hasn’t been used to vote for that song that day and that the same computer hasn’t been used to vote for that song that day. If both test pass, then an email is sent to the address for the user to click for verification.

//Check that the form has been posted.
if ((isset($_POST["insert"])) && ($_POST["insert"] == “form1″)) {
//select the database
mysql_select_db($database, $db);
//get today’s month and day
$date = date(“md”);
//initialize variables that were posted from the form
$email = (get_magic_quotes_gpc()) ? $_POST['email'] : addslashes($_POST['email']);
$song = (get_magic_quotes_gpc()) ? $_POST['song'] : addslashes($_POST['song']);
$ID = (get_magic_quotes_gpc()) ? $_POST['bandID'] : addslashes($_POST['bandID']);
//Setup data for test #1. Has the same email been used today to vote for this song?
$query_Votes = sprintf(“SELECT time FROM votes WHERE email = %s AND bandID = %s ORDER BY time DESC”, GetSQLValueString($email, “text”),
GetSQLValueString($ID, “int”));
$Votes = @mysql_query($query_Votes, $db);
$row_Votes = @mysql_fetch_array($Votes);
$current_time = $row_Votes['time'];
$month2 = substr($current_time, 4, 2);
$day2 = substr($current_time, 6, 2);
$date2 = $month2.$day2;
//Setup data for test#2. Has the same computer been used today to vote for this song?
$query_Votes = sprintf(“SELECT time FROM votes WHERE IP = %s AND bandID = %s ORDER BY time DESC”, GetSQLValueString($_SERVER['REMOTE_ADDR'], “text”), GetSQLValueString($ID, “int”));
$Votes = @mysql_query($query_Votes, $db);
$row_Votes = @mysql_fetch_array($Votes);
$current_time = $row_Votes['time'];
$month3 = substr($current_time, 4, 2);
$day3 = substr($current_time, 6, 2);
$date3 = $month3.$day3;

//Does the vote pass both test? If so, insert the vote into the database.
if ( ($date > $date2) && ($date > $date3) ) {
$insertSQL = sprintf(“INSERT INTO votes (email, pending, song, bandID, IP) VALUES (%s, %s, %s, %s, %s)”,
GetSQLValueString($email, “text”),
GetSQLValueString(’0′, “int”),
GetSQLValueString($song, “text”),
GetSQLValueString($ID, “int”), GetSQLValueString(strip_tags($_SERVER['REMOTE_ADDR']), “text”));
$Result1 = mysql_query($insertSQL, $db);
//retrieve the ID of the vote
$id = mysql_insert_id();
//Get the time it was entered
$selectSQL = sprintf(“SELECT time FROM votes where id = %s”, GetSQLValueString($id, “int”));
$Result2 = mysql_query($selectSQL, $db);
$row_Time = @mysql_fetch_array($Result2);

//Send an email for use for verification using the timestamp as a key
mailPending($_POST['email'], $_POST['bandID'], $row_Time['time']);
$’message = “Thanks for your vote. Come back tomorrow, vote again and keep listening to your favorite bands on United Bands.”;
}
//If the vote doesn’t pass the test notify the user that they have to wait until tomorrow.
else {
$message = “You have to wait until tomorrow to vote again for this song. You can vote for other songs you haven’t voted for today.”;
}
}
//Mails the voter a verification email.
function mailPending($to, $bandID, $time) {
$message .=’Thanks for voting. Please click this link to verify your vote. http://unitedbands.com/verify.php?band=’.$bandID.’&time=’.$time.’&email=’.$to;
$subject = “Thanks for Voting at UnitedBands.com”;
$mailResult=send_email($to, $subject, $message);
}

{mospagebreak title=Trust but Verify}

Now that there is a vote in the system, we need a simple verification script that will determine if the vote is legitimate. We’ll call this script, Verify.php. It looks like this:

//Select the database
mysql_select_db($database, $db);
//Update the record to set the Boolean flag to true if the timestamp in the verification email matches the timestamp in the database record.
$selectSQL = sprintf(“UPDATE votes set pending=1 where bandID=%s AND email=%s AND time=%s”, GetSQLValueString($_GET['band'], “int”),
GetSQLValueString($_GET['email'], “text”),
GetSQLValueString($_GET['time'], “text”));
$Result2 = mysql_query($selectSQL, $UB_DATA);
//Message for voter.
$messages = “Thanks for voting. Remember, you can only vote once a day for a band , but you can vote for as many bands a day as you want.”;

This completes the general design of a fraud-proof voting system. There are a few ways to get around this system. ‘Fraud-proof’ is used in the general sense. Dick could obviously go around from computer to computer in the White House and keep voting for his favorite band. There’s some other ways around this system too, but I’ll let you figure that out. However, with this basic design, a voting system that prevents the basest level of fraud can be setup to your specific purpose relatively simply. Happy voting

Google+ Comments

Google+ Comments