Článek vyšel na serveru Root.cz.
Čas od času je potřeba zajistit, aby každý návštěvník mohl nějakou akci provést jenom jednou, např. hlasovat v anketě. Akci je možné podmínit registrací, která může být znepříjemněna pomocí CAPTCHA nebo kontroly e-mailové adresy. Pokud má návštěvník na e-maily doménový koš, tak si registrací může vytvořit libovolné množství, tento podvod lze ale obvykle snadno odhalit.
Pokud se bez registrace chceme obejít, lze kontrolovat cookie:
<?php if (isset($_COOKIE["hlasoval"])) { echo "Váš hlas byl již dříve započten.\n"; } else { setcookie("hlasoval", $_POST["hlas"], strtotime("+1 month")); mysql_query("UPDATE anketa_odpovedi SET hlasy = hlasy + 1 WHERE id = " . intval($_POST["hlas"])); } ?>
Pokud má návštěvník cookies povolené, jedná se asi o nejspolehlivější způsob. Pokud si ale cookies zakáže, je tato kontrola k ničemu. V tom případě je možné kontrolovat ještě IP adresu. Ze dvou důvodů se nevyplatí spoléhat jenom na ni – jednak se adresa jednoho návštěvníka může v čase měnit (často je dynamicky přidělovaná) a jednak může jednu adresu používat víc lidí (při NAT zároveň, u dynamicky přidělovaných adres postupně). Doplňkové pravidlo je tedy vhodné nastavit např. tak, že z jedné IP adresy bude možné poslat pouze jeden hlas za hodinu:
<?php if (isset($_COOKIE["hlasoval"])) { echo "Váš hlas byl již dříve započten.\n"; } else { setcookie("hlasoval", $_POST["hlas"], strtotime("+1 month")); if (mysql_result(mysql_query("SELECT COUNT(*) FROM anketa_hlasy WHERE ip = '$_SERVER[REMOTE_ADDR]' AND datum + INTERVAL 1 HOUR > NOW()"), 0)) { echo "Hlas z této adresy byl již dříve započten.\n"; } else { mysql_query("INSERT INTO anketa_hlasy (hlas, ip, datum) VALUES (" . intval($_POST["hlas"]) . ", '$_SERVER[REMOTE_ADDR]', NOW())"); } } ?>
Pokud provoz prochází přes proxy server, bývá doplněna hlavička X-Forwarded-For, kterou by mohlo svádět kontrolovat. Protože jde ale tato hlavička snadno podvrhnout, tak se na ni nevyplatí spoléhat a je možné ji použít jen jako doplněk, např. takto:
<?php if (isset($_COOKIE["hlasoval"])) { echo "Váš hlas byl již dříve započten.\n"; } else { setcookie("hlasoval", $_POST["hlas"], strtotime("+1 month")); $forwarded_for = mysql_real_escape_string(preg_replace('~.*,\\s*~', '', $_SERVER["HTTP_X_FORWARDED_FOR"])); if (mysql_result(mysql_query("SELECT COUNT(*) FROM anketa_hlasy WHERE ip = '$_SERVER[REMOTE_ADDR]' AND forwarded_for = '$forwarded_for' AND datum + INTERVAL 1 HOUR > NOW()"), 0) || mysql_result(mysql_query("SELECT COUNT(*) FROM anketa_hlasy WHERE ip = '$_SERVER[REMOTE_ADDR]' AND datum + INTERVAL 1 HOUR > NOW()"), 0) >= 5) { echo "Hlas z této adresy byl již dříve započten.\n"; } else { mysql_query("INSERT INTO anketa_hlasy (hlas, ip, forwarded_for, datum) VALUES (" . intval($_POST["hlas"]) . ", '$_SERVER[REMOTE_ADDR]', '$forwarded_for', NOW())"); } } ?>
Takto nastavená ochrana je již poměrně spolehlivá a v omezené míře je zranitelná pouze proti lidem, kterým se mění IP adresa. U obvyklých kontrol jde ale hlavně o to možnost opakovaného provádění akce co nejvíc znepříjemnit, pokud uživatel může s vyvinutím nemalého úsilí provést akci párkrát navíc, tak se nejedná o velký problém.
Anketa by měla být zabezpečena také proti CSRF.
Diskuse je zrušena z důvodu spamu.