Viewing 4 posts - 1 through 4 (of 4 total)
  • Author
  • #60426

    In our case we had 4 officers running unopposed and 7 candidates for 5 trustee positions. 9 votes, 11 candidates.

    The first page assigned variables named ‘vote1’ through ‘vote9’ values ranging from ‘cand1 ‘ to ‘cand11’ for the 11 candidates. Nine radio buttons are set up. The first four (for the unopposed candidates) are set up to vote for them or for no one. The remaining five were set to vote for one of the seven trustees or for no one.

    All nine radio buttons were initially set to default to the ‘no one’ choice. This is important because different browsers handle radio buttons differently if no default is selected. The W3 standard specifies that the first choice should be selected, while many people disagree and program their browsers to not show any selection at all. Because we can’t control this, we define a default value that will not affect the outcome.

    Here’s the code for a single Trustee vote:


    [/code]The ‘write’ variable was originally intended for use with write-in votes, but the RA doesn’t use write-ins so I reused it as the default selection. As you see, this one vote named ‘vote6’ can be moved from the default value ‘write’ to any of the candidates ‘cand5’ through ‘cand11’. Note that at this point the voter can vote for a single candidate more than once; i.e. the voter can assign vote6 to cand9 and also assign vote7 to cand9.

    When the voter has reassigned all of the votes as desired, the ‘submit’ process sends the votes to the processing page along with the validation information. The processing page distills the data as follows:

    [code]($vote4==”cand4″) ? $cand4=”1″ : $cand4=”0″;
    (($vote5==”cand5″) or ($vote6==”cand5″) or ($vote7==”cand5″) or ($vote8==”cand5″) or ($vote9==”cand5″)) ? $cand5=1 : $cand5=0;
    [/code]The first line was used for the unopposed officers. It’s shorthand for “If vote4 is ‘cand4’, then set the variable $cand4 to 1, otherwise make it zero.”

    The second was used to filter out duplicate votes for trustees. In the same sort of shorthand it says, “If any of the votes are ‘cand5’, then make $cand5 1, otherwise make it zero.” That way cand5 can only receive one vote from this voter no matter what.

    Once the validation of the vote checks out, the variables $cand1 through $cand11 (each having a value 1 or 0) are written into the ‘votes’ database along with the email, IP address, member number, and a timestamp.


    Once we think that it’s actually an RA member (and not a hacker) doing the voting, and we have eliminated duplicate votes, it’s time to validate the vote and hopefully record it.

    The fundamental premise was that only an actual RA member was likely to know the combination of last name, member number, and expiration date. All of these are available on the mailing label of the OTL and on the membership card, and only that RA member is in physical possession of either.

    SO, now that it’s safe to open the connection to the database (we are convinced that this isn’t an SQL injection attack), we do so and check the validation data:

    [CODE]$votes_query = “SELECT * FROM votes WHERE memno = ‘$memno'”;
    $mem_query = “SELECT * FROM members WHERE memno = ‘$memno'”;
    if ($votes_number != 0) {
    $message = “No vote cast: you already voted.”;
    $votes_query = “INSERT INTO bad_votes (memno, email, ip, voted, message, expdate, expdate1) VALUES(‘$memno’, ‘$add’, ‘$voter_ip’, NOW(), ‘$message’, ‘$exp3’, ‘$exp’)”;


    The first query fetches any row from the ‘votes’ database which have the same member number, and the second fetches the correct row from the ‘members’ authentication table.

    If there is already a vote recorded for that member, an error message is displayed and the invalid vote is written into the ‘bad_votes’ table along with the error message. If there is no such member number in the ‘members’ table, the subsequent comparisons will fail.

    With the same process, the expiration date and last name are checked.

    [CODE]elseif ($exp3 < date("Y-m-d")) {
    $message = "No vote cast: expired membership.";
    echo($message. "You entered " .$exp. " which I translated into YYYY-MM-DD format as " .$exp3. " which is before today. Please enter exactly as shown on the label, in MM/DD/YYYY format.

    elseif ($exp3!=$memberdata[exp]) {
    $message = “Incorrect expiration date.”;

    elseif ($last!=$memberdata[lname]) {
    $message = “Wrong last name.”;


    You see we did additional error reporting for the ‘bad date’ error. We’ll get to that more in the ‘usability’ discussion, but since we needed to convert the dates through several formats, using a function that tries to guess what date the voter intended to write, we found that we needed to report that back so that we (and the voter) knew where the problem was. Initially we didn’t want to give too much feedback as that might help someone cast an unauthorized vote, but in the end we simply had to give some people some help.

    The last part of the validation is a bit backward, but it reads:

    [CODE]elseif (isset($_SESSION[‘token’]) && $_POST[‘token’] == $_SESSION[‘token’]) {
    $votes_query = “INSERT INTO votes (memno, email, cand1, cand2, cand3, cand4, cand5, cand6, cand7, cand8, cand9, cand10, cand11, ip, voted) VALUES(‘$memno’, ‘$add’, ‘$cand1’, ‘$cand2’, ‘$cand3’, ‘$cand4’, ‘$cand5’, ‘$cand6’, ‘$cand7’, ‘$cand8’, ‘$cand9’, ‘$cand10’, ‘$cand11’, ‘$voter_ip’, NOW())”;

    else {
    $message = “Invalid session.”;

    That writes the vote into the ‘votes’ table, provided that the session is valid based on the sessionID variable I mentioned in the first post. This was added later in the process, but before some additional restructuring was done.

    On rewrite, the session validity check should be moved up to be with with the very first security checks. There’s no need to wait this late in the process before failing a vote for that reason.

    Finally, the voter gets feedback about the vote.

    [CODE]if ($vote_result) {

    Thank You!

    echo (“

    Member ” .$memno. “ has voted for:

    if ($cand1) echo (“

    Debbi Harbour for President

    if ($cand2) echo (“

    Gordan Boltz for Vice President



    $vote_result is a value that is assigned based on the success of writing the vote into the ‘votes’ table, so if for any reason at all the vote was not successfully written, the voter will not get this message. Wherever the $cand variable is 1 (true), the appropriate line will be written to the screen, and where it is zero (false) the line is not written.


    If only people thought like machines, this would have been be easier.

    Some people won’t, don’t, or can’t follow instructions, even simple ones. You may decide to exclude these people rather than accommodate them, and there’s plenty of justification for that. Part of our authentication was based on the concept that the person had the necessary information in front of them on paper, and the inability to reproduce it should mean that it’s not a legitimate voter. Looking at the data, though, it doesn’t seem that we had attempts to cast fake votes.

    The biggest problem we had was with the expiration date. SQL likes dates in the formate 2008/03/30 for March 30, 2008. That format sorts nicely, even after it’s exported to a spreadsheet, and it’s parseable from the standard datecode and timestamp formate. Unfortunately, asking people to input this format didn’t work, starting with the very first test voter.

    I decided the best way was to try to get people to duplicate what was on their address label, which is in the 3/30/2008 format and is more familiar to people. That, of course, was also unreliable as testers simply wrote whatever format they liked best.

    PHP has a function called strtotime() which parses a string and tries to interpret it as a UNIX timestamp. I used this to try to guess what the voters meant to write, but unfortunately UNIX timestamps only go up to the year 2038, so a few life memberships (recorded as 2050 or similar) would not work even if entered correctly (since all dates had to be processed this way).

    All of this just to get people to read “3/30/2008” and copy it as “3/30/2008″. Sheesh. Anyway, this should give you an idea what your program needs to deal with.

    Obviously the best way to deal with that part is avoid fill-in fields for anything more complicated than a number or a single word. In this case, the answer would be to have drop-down boxes for month, day and year (physically position them in the form so as to best suit the assumptions of the users) and then have the program turn that into a SQL date. I have written this bit of code for a different app and it works pretty well:

    [CODE]Select Month




    And then later in the program:



    This is a nuts & bolts description of the e-voting process, which I’m laying out for the benefit of chartered club webmasters who wish to implement this themselves and who might improve on it. This is a work in progress.

    Please do not comment on this thread without specific input on the e-voting process and programming.

    The main concerns of security and usability conflicted slightly, as you will see. This first post is about security.

    Security includes not only authenticating the votes but also protecting the existing online information from corruption and disclosure. The latter is the largest concern, really. Although we don’t have credit card numbers in our online database, we’re still at risk from pranksters. Any time a web application uses an online database, especially if it writes into it, you need to be careful. In general, the two things you are trying to guard against are [URL=””%5DSQL injection[/URL] and [URL=””%5Dcross-site scripting[/URL] (XSS). There are excellent examples online showing how a loosely written application will let a malicious user alter or erase the database.

    Ideally, the database is on a different server and the web application has the bare minimum rights needed to do its job. This usually means select/insert but not erase/drop/alter. This way, even if a user is successful with an SQL injection attack, he can’t erase the entire database because the user he is logged in as can’t do that.

    SQL Injection relies on providing input strings that mimic programming lines when the program runs them, so to prevent it you need to validate the input. In the voting app, this is done before the connection to the database is opened. The membership number variable is confirmed to be an integer, email address is confirmed to be a string in the [EMAIL=”xxx@yyy.zzz”]xxx@yyy.zzz[/EMAIL] form, and the expiration date was checked to see if it was understandable by the strtotime() function as a date. The last name was checked with is_string() to see if it was going to be interpreted as a single string, although there is probably a more secure check to be used. If any of these failed, the program stopped before any actual use was made of those suspect data fields, and before a connection to the database was established.

    The only other variables were the ones assigned by the ballot for candidate selection, and these were considered safe since they were generated within the program.

    Another loophole to close is string length; in the forms and in the database the field lengths are limited to the bare minimum, to prevent malicious input from ‘fitting in.’

    Lastly, since the voting app was on two pages (one was the ballot and the other was the processing and confirmation) we wanted to be sure that the input for the second page was actually coming from the first page rather than from a program running on someone else’s web server. We generated a token on the first page and passed that to the second page along with the data. If the token was not recognized or was more than 30 minutes old, the input was rejected.

    Here’s the initial data verification code:
    [code]if ($add) { // if an email address is provided
    if (!ereg(“[A-Za-z0-9_-]+([\.]{1}[A-Za-z0-9_-]+)*@[A-Za-z0-9-]+([\.]{1}[A-Za-z0-9-]+)+”, $add)) {
    echo (““);
    if (!is_string($last)) { // if the last name doesn’t match the format for a string, or is missing
    echo(“You must enter your last name.
    if (!is_numeric($memno)) { //if it’s not recognizeable as a number
    echo(“Member number ” .$memno. ” is not actually a number. Enter only the membership number.
    if (!$exp) { //if an expiration date isn’t provided
    echo (“You must enter your membership expiration date.
    if (!strtotime($exp)) { //if date is not parseable
    echo(“Cannot understand the expiration date you entered. Enter exactly as shown on the label.

    $last = strtoupper($last); // make last name all caps like the database
    $token_age = time() – $_SESSION[‘token_time’]; // figure out token age
    $exp2 = strtotime($exp); //convert date into UNIX date format
    $exp3 = date(“Y-m-d”,$exp2); // convert UNIX date into SQL format[/code]Now we have the ‘cleaned’ data.

Viewing 4 posts - 1 through 4 (of 4 total)
  • You must be logged in to reply to this topic.