Autor Zpráva
Tomáš123
Profil
Ahoj diskusia,
mám problém s používaním $_COOKIE. Potrebujem, aby sa užívateľovi pri hlasovaní v hitparáde uložila do počítača hodnota sušienky, aby nemohol hlasovať viackrát, než je povolené. Hitparáda, samozrejme, trvá iba obmedzený čas (začiatok a koniec je uložený v databáze), na základe ktorého nastavujem expiráciu. Problém je však v tom, že aj po nastavení platnej a presnej časovej hodnoty môžem po reštartovaní prehliadača znovu manipulovať štatistiky. Od pridania tretieho parametru funkcie setcookie(), spomínanej expirácie, som si sľuboval vyriešenie problému.

So stránkami sa posielali nejaké hlavičky:
header("Expires: ".gmdate("D, d M Y H:i:s")." GMT");
header("Cache-Control: no-store, no-cache, must-revalidate");
header("Cache-Control: post-check=0, pre-check=0", false);
header("Pragma: no-cache");

Nepomohlo ani ich zakomentovanie a následné reštartovania prehliadača a premazanie Cookies v počítači (niekedy neexistovali) v rôznom poradí, viackrát. Niekedy po vykonaní týchto úkonov stále existuje zábrana (možno musí ubehnúť nejaký čas, aby sa na hodnotu zabudlo, možno chvíľu existuje v keši, neviem). Hitparáda sa správa rôzne. Popravde, neviem, čo som rozbil zneškodnením posielaných hlavičiek, navonok sa nič neopravilo ani nepoškodilo.

Tipol by som si, že možnou opravou by mohlo byť priradenie hodnoty sušienky podľa IP adresy, ktorá sa taktiež zaznamenáva. Jeden užívateľ by tak používal stále rovnakú hodnotu, ktorej by sa nedalo pri sieti s jednou verejnou IP adresou (tak ľahko) zbaviť. Potenciálna strata bezpečnosti by nebol problém, keďže sa nejedná o citlivé údaje. Viac by sa mi ale páčilo menej fixované riešenie. Páčilo by sa mi, keby sa užívateľovi stále vygenerovala nová hodnota COOKIE. Uspokojil som sa s tým, že tak niečo stratím na kvalite výsledkov (v situácii, keď si užívateľ vymaže údaje prehliadača), ale nebudem musieť prekopávať skripty a pridávať novú funkcionalitu. Nikomu nevadí malá manipulácia.

Aby z môjho dlhého príspevku bolo jasné, čo chcem dosiahnuť, zhrniem to:
• Chcel by som vedieť, ako sa zaobchádza s COOKIES a prečo mi nefunguje nastavenie presnej expirácie.
• Chcel by som vedieť, čo mohlo ovplyvniť zakomentovanie posielaných hlavičiek.
• Zaujímalo by ma, ako sa bežne rieši práca s COOKIES, aby sa zapamätali a k ujme došlo až ich manuálnym prečistením, teda čo robím zle vo svojom postupe.

Ukážka, kde sa prejavuje problém.

Ďakujem vám vopred za pomoc, milí diskutéri.
Joker
Profil
Tomáš123:
Jak vypadá nastavení té cookie?
Tomáš123
Profil
Joker:
Pardón, nemyslel som, že to bude dôležité.

Ak prečistím celý počítač, pri pokuse o hlasovanie na mňa vyskočí upozornenie spôsobené splnením tejto podmienky:
if (!isset($_COOKIE["user_id"]) || !preg_match("/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/", $_COOKIE["user_id"])) {
    header('HTTP/1.0 400 Bad Request', true, 400);
    echo json_encode(Array("errorCode" => 2, "message" => "Nie je možné hlasovať - chybné užívateľské nastavenie."));
    exit();
}
Pribudlo to po pridaní všetkého ohľadom $expire v nasledujúcom kóde (riadok 20 a úprava riadku 22) a zmenení poradia blokov. Všeobecne je problém zrejme to, že pri prvej návšteve $_COOKIE ešte neexistuje. Reštart stránky nepomáha. Vraj sa to rieši uložením hodnoty do lokálnej premennej.

Výmenu blokov som vykonal preto, lebo na vypočítanie expirácie som potreboval údaj z databázy. Samotná premenná $user_id a $_COOKIE sa tam nepoužívali.
// chart data
// pôvodne druhý blok v poradí
$chart = null;
$previous_chart = null;
$query = "SELECT *, UNIX_TIMESTAMP(chart_datetime) AS _chart_datetime_ts, UNIX_TIMESTAMP(voting_end_datetime) AS _voting_end_datetime_ts FROM ".$SETTINGS["dbserver"]["table_prefix"]."charts WHERE active = '1' AND display = '1' AND chart_datetime <= NOW() ORDER BY chart_datetime DESC LIMIT 2;";
list($sqldata, $sqlinfo) = $db->query($query);
if (count($sqldata) == 0) {
    // no active chart
    exit();
} else {
    $chart = $sqldata[0];
    if (count($sqldata) > 1) {
        $previous_chart = $sqldata[1];
    }
}

// user id
// pôvodne prvý blok v poradí
if (!isset($_COOKIE["user_id"]) || !preg_match("/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/", $_COOKIE["user_id"])) {
    $expire = $chart["_voting_end_datetime_ts"] - time();
    $user_id = gen_uuid(); // vygeneruje niečo, čo vyhovuje ragulárnemu výrazu v podmienke
    setcookie("user_id", $user_id, $expire); // $expire pôvodne nebolo použité
} else {
    $user_id = $_COOKIE["user_id"];        
}

Tu vznikla chyba s prolémom prvého hlasovania. Nemalo by sa to ale týkať všeobecného problému hlasovať opakovane.

Dodatočne som si všimol, že sa vytvára ešte jedna sušienka, ktorej som zabudol nastaviť expiráciu:
$votes_status_cookie_name = "votes_status_".$chart["id"]; // $char[id] je id aktuálnej hitparády

if (isset($_COOKIE[$votes_status_cookie_name])) {
    $temp = json_decode(stripslashes($_COOKIE[$votes_status_cookie_name]));
    foreach ($temp as $key => $value) {
        if (!isset($votes_status[(int)$key]) || $votes_status[(int)$key] < $value) {
            $votes_status[(int)$key] = (int)$value;
        }
    }
} else {
    setcookie($votes_status_cookie_name, json_encode($votes_status), $expire); // aj po nastavení $expire skript dovolí hlasovať po zatvorení okna prehliadača
}
Z tohoto mi nie je celkom jasné, čo sa deje v bloku if.

Skripty sú trochu rozťahané. Neviem, či sa z toho dá výjsť. Ak nie, nerobte si starosti. Môžeme na to z druhej strany -- proces ukladania cookies, po zatvorení prehliadača sa zničia (nie úplne okamžite).
Joker
Profil
Tomáš123:
Ak prečistím celý počítač, pri pokuse o hlasovanie na mňa vyskočí upozornenie spôsobené splnením tejto podmienky

Ano, to zní logicky. Ta podmínka testuje existenci cookie a při první návštěvě ta cookie ještě existovat nebude.
Jelikož tímhle to skončí, jestli někde dřív není setcookie, nebude ta cookie existovat nikdy.
Keeehi
Profil
Tomáš123:
Vím že ses ptal na cookies, ale vyjádřím se k celému problému. Stoprocentní ochrana hlasování neexistuje. To bys musel hlasování schovat za registraci, každý účet nejdříve fyzicky zvalidovat, že ten daný člověk k tobě přijde a ukáže ti občanku a stále nemáš jistotu, že pak ten účet někomu nepředá.

Blokování se dá udělat na základě spousta věcí, nejčastěji těch cookies a IP adresy. Problém je, že obě se dají zmanipulovat. Cookies velmi lehko, IP adresa trochu hůře ovšem stále poměrně lehce (TOR, také na mobilním připojení se dá od operátora dostávat spousta různých adres) Navíc veškeré tyto ochrany mohou mít omylem negativní dopad na poctivé uživatele. Řekněme, že v rodině je jeden počítač a každý ze 4 členů by rád hlasoval pro svoji oblíbenou písničku. Ty to však umožníš jen prvnímu.

Abych tuto čas shrnul, ochrana přináší víc problémů než užitku. Legitimní uživatele to může omezit a protože 100% ochrana neexistuje, technicky zdatný útočník si cestičku vždy najde.

Proto bych preferoval přístup "jen ať si podvádějí, já jim to nakonec promažu". Jde o to, že hlasování necháš nezabezpečené a podle nějaké statistické analýzy z výsledků nakonec odstraníš neplatné hlasy.
Proč tak?
Celé to stojí na principu honeypotu. Protože se útočníkům provede i jednoduchý útok, provedou ten a budou s výsledkem spokojeni. Jednoduché útoky se pak dají ve výsledcích lehce nálezt a odstranit. Navíc skrýváš to, podle čeho pak určuješ platnost hlasu, takže i útočník, který očekává "podvrh - následné smazání", neví co pak použiješ a co všechno má tedy maskovat. Je tedy větší šance že na něco zapomene a ty ho tím odhalíš.
Tomáš123
Profil
Joker:
jestli někde dřív není setcookie, nebude ta cookie existovat nikdy.
Celkom mi nie je jasné ako to, že až teraz, po uvedených zmenách sa chyba prejavuje. Ja som ten web tiež raz navštívil prvýkrát a všetko mi fungovalo. Nuž, prejdem tie kódy ešte raz, snáď niečo nájdem.

Keeehi:
Vím že ses ptal na cookies, ale vyjádřím se k celému problému.
To je vždy vítané.

TOR, také na mobilním připojení se dá od operátora dostávat spousta různých adres
O TORe som si prečítal prvýkrát dnes. Čo sa týka mobilných pripojení, mal som za to, že do internetu aj tak pristupujú cez nejaký prístupový bod. Podobne ako ja vystupujem na internete s jedinou verejnou IP adresou, hoci na počítači mám nakonfigurované niečo začínajúce na 172.16.x.x. Predpokladám, že aj mobilní užívatelia to majú podobne (aspoň čo sa týka Wi-Fi pripojenia, ako to ide cez mobilné dáta neviem, ale overím si a napíšem).

Rozumiem, čo sa snažíš svojim príspevkom povedať. Bolo by nutné ošetriť číselný výstup hlasov. Z jednej IP iba tri, prvé tri. Nemuselo by to byť zložité na implementáciu. Dobre, ešte nad tým porozmýšľam a opýtam sa, či by to bolo vyhovujúce.

Predsa by som ale ešte rád pokračoval v tomto vlákne a dozvedel sa, prečo sa cookies nechcú ukladať na dlhšiu dobu. Ako sa bežne používajú? Ovplyvnili to tie hlavičky (už tri dni sú zakomentované)?

Nepredpokladajte súvislosť s kódmi vyššie, iba nejaký základný príklad. Ja som si niečo skúsil:
<?php
$datetime = "10-2-2016 15:30:00";
list($date, $time) = explode(" ", $datetime);
list($day, $month, $year) = explode("-", $date);
list($hour, $minute, $second) = explode(":", $time);

$expire = mktime($hour, $minute, $second, $month, $day, $year) - time();
setcookie("meno", "hodnota", $expire);
echo (!empty($_COOKIE['meno']) ? $_COOKIE['meno'] : "<a href=\"/scripts/test4.php\">Reštartovať stránku</a>").", ".$expire;
?>
Hodnota $_COOKIE['meno'] sa odoslala po kliknutí na odkaz až po vypršaní limitu jej platnosti. Akoto?
Tomáš123
Profil
Keeehi:
Spoľahlivo by som musel zariadenie identifikovať cez jeho MAC adresu. Tá sa na mobilných zariadeniach nemení (síce sa to možno dá, ale väčšina návštevníkov hlasovacej stránky zrejme nebude vedieť ako), ale v PHP pravdepodobne nie je nejaký mechanizmus na zistenie. Zistiť by to asi šlo z HTTP hlavičky.

IP adresa sa mení v závislosti na lokalite. Ak sa užívateľ pripája cez Wi-Fi, na internete vystupuje pod IP adresou vonkajšieho rozhrania Wi-Fi routra. Jeho IP adresa sa teda mení v závislosti na tom, kam sa pripojí. Podobne je to zrejme aj s mobilnými dátami. Neviem však, ako proces prideľovania IP adresy prebieha. Dokázať to viem iba tým, že keď som si zahlasoval v meste a doma, zmenilo sa posledných osem bitov IP adresy.

Joker:
Kódy som prešiel, a skutočne nemá čo prekážať. Na to hlásenie o chybnom nastavení ([#3] prvý kód) sa odkazuje až pri priebehu JS skriptu, ktorý sa spustí pri hlasovaní. Hlasovanie sa uskutočňuje neskôr ako načítavanie. Vinu chyby som teda pripísal zakomentovaným hlavičkám, ktoré som uviedol v [#1] príspevku.

Čo sa v tých hlavičkách píše? Naspäť som ich odkomentoval, ale chyba nezmizla.

Mne hlasovať ide, ale majiteľ stránok vravel, že jemu nie. Skúste si prosím zahlasovať, vyčerpať hlasy a zavrieť prehliadač (niekedy sa zmeny neprejavia hneď), či vám to opäť povolí. Chyba, ktorá sa vám pravdepodobne zobrazí sa dá obísť navštívením hlavnej stránky (tam hitparáda z nejakého dôvodu nefunguje) a prejdením na hitparádu. Ak by sa chyba nezobrazila, dajte mi prosím vedieť.
Kubo2
Profil
Tomáš123:

$datetime = "10-2-2016 15:30:00";
list($date, $time) = explode(" ", $datetime);
list($day, $month, $year) = explode("-", $date);
list($hour, $minute, $second) = explode(":", $time);


Navrhujem lepšiu cestu:
$expire = DateTime::createFromFormat('j-n-Y H:i:s', $datetime)->getTimestamp();

$expire = mktime($hour, $minute, $second, $month, $day, $year) - time();
Nemáš v tom $expire náhodou záporný čas? On by tam síce mal byť už uplynutý časový bod, ale určite nie záporný. time() vráti vždy väčšie číslo, ak tebou špecifikovaný dátum je minulý.
Tomáš123
Profil
Kubo2:
Navrhujem lepšiu cestu:
Je zbytočné naťahovať knižnice kvôli rozobratiu jedného testovacieho reťazca.

Nemáš v tom $expire náhodou záporný čas?
Áno. Bol záporný. Cookie sa nastavilo až keď bol aktuálny čas, time(), väčší ako čas nastavený v premennej $datetime (pri kladnom čase sa vypísalo „reštartovať stránku“, teda premenná $_COOKIE['name'] bola prázdna). Práve to mi príde divné. Očakával by som, že kým je v $expire kladná hodnota, teda ešte nenadišiel čas vypršania cookie, (nastavený čas je väčší ako čas aktuálny – ich rozdiel je kladný), zápis mi vráti hodnotu cookie. Správa sa to presne naopak.
Kubo2
Profil
Tomáš123:
Je zbytočné naťahovať knižnice kvôli rozobratiu jedného testovacieho reťazca.
Použitie triedy DateTime nemá nič spoločné s načítavaním nejakých externých knižníc. „There is no installation needed to use these functions; they are part of the PHP core.

Vaše odpověď

Mohlo by se hodit


Prosím používejte diakritiku a interpunkci.

Ochrana proti spamu. Napište prosím číslo dvě-sta čtyřicet-sedm:

0