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.

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.

Diskuse je zrušena z důvodu spamu.

avatar © 2005-2024 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.