Kildekoden til afstemningssystemet

af Thomas Huulbæk Titanium den 15. Februar 2013 Thomas Huulbæk Titanium

Hvis du kender lidt til PHP, så kan du nu læse kildekoden til afstemningssystemet - jeg modtager selvfølgelig meget gerne forslag til forbedringer!

Som udvikler ved jeg, at vores afstemningssystem ikke er 100 % sikkert - det bliver det aldrig, men målet er, at det bliver så sikkert som muligt. Sikkerhed opnås bedst ved at holde alle kort åbne, så i dette indlæg kan du selv kigge lidt på kildekoden bag afstemningsssystemet.

Websitet bygger på CMS'et ExpressionEngine, som ikke er helt gratis - men dog open source.

Afstemningen starter med en FORM, der indeholder valgmulighederne. Den vises kun, hvis bruger er logget ind og endnu ikke har afgivet sin stemme.







Formen poster til submit_vote_secure via et AJAX kald, og den indeholder følgende:

<?php
    function crypto_rand($min, $max) {
        $range = $max - $min;
        $length = (int) (log($range,2) / 8) + 1;
        $num = hexdec(bin2hex(openssl_random_pseudo_bytes($length,$s))) % $range;
        return $num + $min;
    }
    // str_makerand is used for creating random ticket, that user can use to verify vote later (note mt_rand is not cryptographically secure)
    function str_makerand ($minlength, $maxlength, $useupper, $usespecial, $usenumbers) {
        $charset = "ACDEFGHJKLMNPQRTUVWXY";
        if ($useupper) $charset .= "ACDEFGHJKLMNPQSTUVWXY";
        if ($usenumbers) $charset .= "34679";
        if ($usespecial) $charset .= "~@#$%^*()_+-={}|][";
        for ($i=0; $i<$maxlength; $i++) $key .= $charset[(crypto_rand(0,(strlen($charset)-1)))];
        return $key;
    }
    // Check that members group are allowed to vote
    if (($this->EE->session->userdata('group_id') == 1) || ($this->EE->session->userdata('group_id') == 5) || ($this->EE->session->userdata('group_id') == 6) || ($this->EE->session->userdata('group_id') == 7) || ($this->EE->session->userdata('group_id') == 8) || ($this->EE->session->userdata('group_id') == 9) || ($this->EE->session->userdata('group_id') == 10) || ($this->EE->session->userdata('group_id') == 11)) {
        // Function for logging
        function logvote ($text) {
            $handle = fopen("/dana/data/www.parlamentet.dk/votelog/logtest.txt", "a+");
            fwrite($handle, $text);
            fwrite($handle, "\r\n");
            fclose($handle);
        }
        // Get password_compat for bcrypt.
        require('/dana/data/www.parlamentet.dk/scripts/password.php');
        // Get DB connection
        require('/dana/data/www.parlamentet.dk/scripts/db.php');
        // Check of form XID token
        $member_id = (int)$this->EE->session->userdata('member_id'); // Users ID from ExpressionEngine session data
        $ip_address = $this->EE->session->userdata('ip_address'); // Users IP address, only used for blocking check and error handling
        $XID = $_POST['XID'];
        $this->EE =& get_instance();
        $this->EE->load->library('functions');
        if (ee()->security->secure_forms_check($XID) === FALSE) {
            echo '
            

AFSTEMNING

Fejl: Sikkerhedscheck fejlede. Forsøg igen eller kontakt os.

Fejlkode: 001

'; $log = $member_id." failed securitycheck 001 (from $ip_address)"; logvote($log); exit(); } $all_query_ok = TRUE; // Control variable for queries // $time_now = time() - 3600; $time_now = time(); $entry_id = (int)$_POST['entry_id']; // Unique entry ID $vote = (int)$_POST['stemme']; // What user voted - 1, 2 or 3 if ($vote == '') { echo '

AFSTEMNING

Din stemme er IKKE registreret, da du ikke valgte noget at stemme. Genindlæs siden for at stemme korrekt.

Fejlkode: 002

'; $log = $member_id." did not choose voteoptions 002"; logvote($log); exit(); } if (($vote > 3) || ($vote < 1)) { // Allowed range of votes echo '

AFSTEMNING

Fejl: Sikkerhedscheck fejlede. Forsøg igen eller kontakt os.

Fejlkode: 003

'; $log = $member_id." somehow voted for something strange 003"; logvote($log); exit(); } $anonymous_vote = (int)$_POST['anonymous_vote']; // Is it an anonymous vote? $change_vote = (int)$_POST['change_vote']; // Should user be allowed to change vote later? $changing_vote = (int)$_POST['changing_vote']; // Is this vote a change of a previous vote? $password = $_POST['password']; // One time password to change the vote later if ($change_vote === 1) { // Blowfish hash of password (bcrypt) $hash = password_hash($password, PASSWORD_BCRYPT, array("cost" => 10)); // Kan sættes til PASSWORD_DEFAULT (cost 4 - 31) // Verify hash if (password_verify($password, $hash) === FALSE) { echo '

AFSTEMNING

Din stemme er IKKE registreret, da der opstod en fejl. Forsøg venligst igen!

Fejlkode: 004

'; $log = $member_id." failed password check 004"; logvote($log); exit(); } // Create key from password $key_from_pw = hash('sha256', $password); // Get IV $iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CBC); $iv = mcrypt_create_iv($iv_size, MCRYPT_DEV_URANDOM); $iv_base64 = base64_encode($iv); // Data about vote $data = json_encode(array('member_id'=>$member_id, 'entry_id'=>$entry_id, 'hash'=>$key_from_pw)); // Encrypt data $enc = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $key_from_pw, json_encode($data), MCRYPT_MODE_CBC, $iv); $enc_base64 = base64_encode($enc); } else { $change_vote = 0; $enc_base64 = ''; $iv = ''; $hash = ''; } // Check if user already voted $result = mysqli_query($link, "SELECT voters_id FROM pt_voters WHERE member_id = '$member_id' AND entry_id='$entry_id'"); $num_results = mysqli_num_rows($result); // Check if poll is still open $result = mysqli_query($link, "SELECT field_id_18,field_id_44,status FROM exp_channel_data,exp_channel_titles WHERE exp_channel_titles.entry_id='$entry_id' AND exp_channel_titles.entry_id=exp_channel_data.entry_id"); $row = mysqli_fetch_array($result); $slut_ft = (int)$row["field_id_18"]; // Endtime for the vote in Danish Parliament if ($slut_ft === 0) { // Change this logic! $slut_ft = 9378133526; } $slut_pt = $row["field_id_44"]; // Sluttidspunkt for Parlamentets egne forslag $status = $row["status"]; if ((($num_results === 0) || ($changing_vote === 1)) && (($time_now <= $slut_ft) || ($time_now <= $slut_pt)) && ($status == 'Fremsat')) { // User has not yet voted or is allowed to change vote if ($changing_vote === 1) { // Verify password if user is changing vote $result = mysqli_query($link, "SELECT iv,hash,voters_id FROM pt_voters WHERE member_id = '$member_id' AND entry_id = '$entry_id'"); $row = mysqli_fetch_array($result); $hash_check = $row['hash']; $voters_id = $row['voters_id']; // $hash = password_hash($password, PASSWORD_BCRYPT, array("cost" => 10, "salt" => $salt_check)); if (password_verify($password, $hash_check) === FALSE) { print '

AFSTEMNING

Forkert kodeord! Du kan genindlæse siden og forsøge igen!

Fejlkode: 005

'; $log = $member_id." failed password check 005"; logvote($log); exit(); } $key_from_pw = hash('sha256', $password); $iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CBC); $old_iv_base64 = $row['iv']; $old_iv = base64_decode($old_iv_base64); $data = json_encode(array('member_id'=>$member_id, 'entry_id'=>$entry_id, 'hash'=>$key_from_pw)); $enc_test = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $key_from_pw, json_encode($data), MCRYPT_MODE_CBC, $old_iv); $enc_test_base64 = base64_encode($enc_test); $result = mysqli_query($link, "SELECT votes_id FROM pt_votes WHERE enc='$enc_test_base64'"); $num_results = mysqli_num_rows($result); if ($num_results === 1) { // OK - data checked out // Removing all data from previous vote $row = mysqli_fetch_array($result); $votes_id = $row['votes_id']; mysqli_query($link, "DELETE FROM pt_votes WHERE votes_id='$votes_id'") ? null : $all_query_ok=FALSE; mysqli_query($link, "DELETE FROM pt_voters WHERE entry_id='$entry_id' AND member_id='$member_id'") ? null : $all_query_ok=FALSE; } else { print '

AFSTEMNING

Forkert kodeord! Du kan genindlæse siden og forsøge igen!

Fejlkode: 007

'; $log = $member_id." did not enter correct password 007"; logvote($log); exit(); } } $num_results_2 = 1; while ($num_results_2 != 0) { $votes_id = str_makerand(10,10,1,0,1); // Random ID for vote $result_2 = mysqli_query($link, "SELECT * FROM pt_votes WHERE votes_id='$votes_id'"); // Check if ID exists $num_results_2 = mysqli_num_rows($result_2); if ($num_results_2 === 0) { // ID OK - casting vote if ($anonymous_vote === 1) { // $query = "INSERT INTO pt_votes (votes_id,entry_id,vote,vote_time,enc) VALUES ('$votes_id','$entry_id','$vote','$time_now','$enc_base64')"; $query = "INSERT INTO pt_votes (votes_id,entry_id,vote,enc) VALUES ('$votes_id','$entry_id','$vote','$enc_base64')"; } else { // $query = "INSERT INTO pt_votes (votes_id,entry_id,vote,vote_time,enc,member_id) VALUES ('$votes_id','$entry_id','$vote','$time_now','$enc_base64','$member_id')"; $query = "INSERT INTO pt_votes (votes_id,entry_id,vote,enc,member_id) VALUES ('$votes_id','$entry_id','$vote','$enc_base64','$member_id')"; } mysqli_query($link, $query) ? null : $all_query_ok=FALSE; // Shuffle table $query = "ALTER TABLE pt_votes ORDER BY votes_id ASC"; mysqli_query($link, $query) ? null : $all_query_ok=FALSE; // Getting ID for voters_id $num_results_3 = 1; while ($num_results_3 != 0) { $voters_id = crypto_rand(100,1000000000000); $result_3 = mysqli_query($link, "SELECT * FROM pt_voters WHERE voters_id='$voters_id'"); // Check for existance of voters_id $num_results_3 = mysqli_num_rows($result_3); if ($num_results_3 == 0) { // $query = "INSERT INTO pt_voters (voters_id,entry_id,member_id,ip_address,change_vote,iv,salt) VALUES ('$voters_id','$entry_id','$member_id','$ip_address','$change_vote','$iv_base64','$salt')"; $query = "INSERT INTO pt_voters (voters_id,entry_id,member_id,change_vote,iv,hash) VALUES ('$voters_id','$entry_id','$member_id','$change_vote','$iv_base64','$hash')"; mysqli_query($link, $query) ? null : $all_query_ok=FALSE; // Shuffle table $query = "ALTER TABLE pt_voters ORDER BY voters_id ASC"; mysqli_query($link, $query) ? null : $all_query_ok=FALSE; } } if ($all_query_ok) { mysqli_commit($link); } else { mysqli_rollback($link); echo '

AFSTEMNING

Der opstod en fejl! Du kan genindlæse siden og forsøge igen!

Fejlkode: 008

'; mysqli_close($link); $log = $member_id." made the SQL fail! 008"; logvote($log); exit(); } // Everything OK. Inform user and write to logfile, print and/or insert posts in S3 $action = ' voted'; if ($changing_vote === 1) { $action = ' changed vote'; } if ($anonymous_vote === 1) { $action .= ' anonymously'; } if ($change_vote === 1) { $action .= ' and is allowed to change vote'; } $log = $member_id.$action; logvote($log); if ($anonymous_vote === 1) { echo '

AFSTEMNING

Din stemme er registreret anonymt. Du kan bruge nedenstående ID til at verificere stemmens korrekthed, når afstemningen er slut. Bemærk at ID ikke er tilknyttet dig i vores system, så du skal selv huske det, hvis du ønsker at verificere. Det vises ikke igen, når du forlader denne side.

'.$votes_id.'

UDSKRIV

'; $QRcode = 'https://www.parlamentet.dk/parlamentet/detaljer/'.$entry_id.'#v'.$votes_id; echo " "; } else { echo '

AFSTEMNING

Din stemme er registreret uden anonymitet. Når afstemningen er slut, vil dit navn vises med din stemme, så du kan verificere stemmen. Du kan også bruge nedenstående ID til at verificere stemmen.

'.$votes_id.'

UDSKRIV

'; $QRcode = 'https://www.parlamentet.dk/parlamentet/detaljer/'.$entry_id.'#v'.$votes_id; echo " "; } } } } else { echo '

AFSTEMNING

Fejl: Du har allerede stemt i denne afstemning eller afstemningen er ikke åben.

Fejlkode: 009

'; $log = $member_id." tried to vote but failed 009"; logvote($log); exit(); } } else { echo '

AFSTEMNING

Fejl: Du har ikke rettigheder til at stemme.

Fejlkode: 010

'; $log = $member_id." does not have rights to vote 010"; logvote($log); exit(); } ?>

Og det er i virkeligheden alt. Hvis du har forslag til forbedringer eller finder graverende fejl (meget muligt!), så skriv til mig eller i kommentarerne herunder.

Læst 1410 gange

Emneord: sikkerhed afstemningssystemer

Relaterede artikler:

21.08.2013
 Ændringer i teknikken

22.05.2013
 To tabloid or not to tabloid

23.02.2012
 Nu kan NemID benyttes

22.11.2011
 Nye sikkerhedsforanstaltninger på websitet

Der er endnu ingen debatindlæg...

Du burde være logget ind for at kommentere...



Informér om nye indlæg i denne debat?