Unikátnost návštěvníka

Školení, která pořádám

Č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:

  1. Pokud je přítomna cookie, hlas nezapočíst.
  2. Pokud z této kombinace IP adresa + X-Forwarded-For bylo již tuto hodinu hlasováno, hlas nezapočíst.
  3. Pokud již z této IP adresy v uplynulé hodině přišlo alespoň 5 hlasů, hlas nezapočíst.
<?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.

Jakub Vrána, Řešení problému, 19.5.2006, diskuse: 15 (nové: 0)

Diskuse

Šaman:

Sorry, ale nějak jsem nepochopil, jestli je tohle ten článek, nebo jen úvod k němu..

if($this == 'Unikátnost návštěvníka') {
  print ('To si děláš srandu?')
} else {
  print ('Sorry, su slepejš..');
}

Arcao:

Článek vyšel na serveru Root.cz.

Šaman:

:p

Tak jo, jsem asi trochu opožděnej. Ten odkaz pod článkem jsem nějak minul.. :))

MiK:

Tak se koukni do zdrojáku. ;)

Jhz:

rofl

omar:

a ROHLEJK mas taky!??

radim:

Neresi to pravidelne spousteni napr. prikazu lynx s prislusnym URL pokud mu povoleno akceptovat cookies. S kazdym dalsim spustenim se totiz cookies smaze a lze vesele hlasovat treba skrze cron :-). Co vy na to?

ikona Jakub Vrána OpenID:

Proto tam je i limit na IP adresu.

papundeklová paní:

Poznámka ke druhé ukázce kódu - pokud bych mezi těma dvěma dotazy mysql_result(mysql_query(..)) místo || použila "or" (a ten první by byl splněn), pak by se ten druhý nemusel spouštět?

Remmidemmi:

celé to je nesmyslné, protože se nekontroluje identita člověka sedícího u počítače, nýbrž počítač. Krom toho Cookies lze smazat, IP adresu lze zamaskovat. Pořád to neřeší problém identifikace obsluhy počítače.

Používejte diakritiku. Vstup se chápe jako čistý text, ale URL budou převedeny na odkazy a PHP kód uzavřený do <?php ?> bude zvýrazněn. Pokud máte dotaz, který nesouvisí s článkem, zkuste raději diskusi o PHP, zde se odpovědi pravděpodobně nedočkáte.

Jméno: URL: Reakce na: Remmidemmi

Keeehi:

"Krom toho Cookies lze smazat, IP adresu lze zamaskovat."
Ty asi pořádně nečteš. V článku je toto: "jde ale hlavně o to možnost opakovaného provádění akce co nejvíc znepříjemnit"

Pokud chceš "jedinečnost" uživatele, pak to můžeš řešit třeba registrací. Stejně problém "identifikace obsluhy počítače" pořádně nevyřešíš do té doby, než se bude k připojení do internetu vyžadovat scan sítnice a test DNA.

ikona crysman:

Mankote, to doufám není to, co bychom si jako společnost přáli. Raději volím omezení přesnosti anket než omezení svobody.

Seky:

function UniqueBrowser() {
    return md5($_SERVER['HTTP_USER_AGENT']);
}
Tiez pomaha k identifikacii uzivatela...
Ale aj tak to nieje uplne efektivne.

ikona pa3k:

USER_AGENT sa dá generovať, preto je nespoľahlivý. (#9984)

ikona pa3k:

Resp. je nespoľahlivý, petože ho generuje klient.
avatar © 2005-2021 Jakub Vrána. Publikované texty můžete přetiskovat pouze se svolením autora. Ukázky kódu smíte používat s uvedením autora a URL tohoto webu bez dalších omezení Creative Commons. Můžeme si tykat. Skripty předpokládají nastavení: magic_quotes_gpc=Off, magic_quotes_runtime=Off, error_reporting=E_ALL & ~E_NOTICE a očekávají předchozí zavolání mysql_set_charset. Skripty by měly být funkční v PHP >= 4.3 a PHP >= 5.0.