Autor Zpráva
zacatecnicek
Profil *
Zdravíčko. Snažím si napsat přihlašovací formulář pro administraci webu. Troufám si tvrdit, že tak jak to mám, by to mohlo být funkční a nikdo by se tam nemusel dostat. Mohu vás poprosit o kontrolu a postřehy co je špatně, chybí, nebo by mělo být jinak?

index.php
<?php
    header("Content-Type: text/html; charset=utf-8");

    session_start();

    if(isset($_SESSION['uzivatel']['prihlasen']) && $_SESSION['uzivatel']['prihlasen'] == true)
    {
        echo 'Uživatel přihlášen';
        
        /*
            Samotná administrace
        */
    }
    else
    {
        header('Location: prihlasovaci_formular.php');
    }

prihlasovaci_formular.php
<?php
    session_start();

    if(isset($_SESSION['uzivatel']['prihlasen']) && $_SESSION['uzivatel']['prihlasen'] == true)
    {
        header('Location: index.php');
    }
    else
    {
        echo '
            <!DOCTYPE html>
            <html>
                <head>
                    <meta charset="UTF-8">

                    <link rel="stylesheet" href="style.css">
                </head>

                <body>
                    <form action="prihlas.php" method="post">
                        <input type="text" name="un" id="un" required>
                        <label for="un">Uživatel</label>

                        <input type="password" name="pw" id="pw" required>
                        <label for="pw">Heslo</label>

                        <span>';
                            if(isset($_GET['login']) && $_GET['login'] = 'false')
                            {
                                echo 'Nesprávné přihlašovcí údaje';
                            }
                            else
                            {
                                echo '&nbsp;';
                            }

                        echo '</span>

                        <button type="submit">Přihlaš</button>
                    </form>
                </body>
            </html>
        ';
    }

prihlas.php
<?php
    session_start();

    require('pripoj_db.php');

    $dotaz = $pdo->prepare('SELECT id_uzivatel, un, pw FROM uzivatel WHERE un = :uzivatel AND pw = :heslo');
    $dotaz->execute(array(
        ':uzivatel' => $_POST['un'],
        ':heslo' => md5($_POST['pw'])
    ));
    $pocet_vysledku = $dotaz->rowCount();

    if($pocet_vysledku == 1)
    {
        $uzivatel = $dotaz->fatch();

        $dotaz = $pdo->prepare('SELECT jmeno, prijmeni FROM uzivatel WHERE id_uzivatel = :id_uzivatel');
        $dotaz->execute(array(':id_uzivatel' => $uzivatel['id_uzivatel']));
        $uzivatel = $dotaz->fatch();

        $_SESSION['uzivatel']['prihlasen'] = true;
        $_SESSION['uzivatel']['jmeno'] = $uzivatel['jmeno'];
        $_SESSION['uzivatel']['prijmeni'] = $uzivatel['prijmeni'];

        header('Location: index.php');
        exit();
    }
    else
    {
        header('Location: prihlasovaci_formular.php?login=fail');
        exit;
    }

pripoj_db.php
<?php
    define('SQL_HOST', 'localhost');
    define('SQL_DBNAME', 'tabulka');
    define('SQL_USERNAME', 'root');
    define('SQL_PASSWORD', '');

    $dsn = 'mysql:dbname=' . SQL_DBNAME . ';host=' . SQL_HOST . '';
    $user = SQL_USERNAME;
    $password = SQL_PASSWORD;

    try
    {
        $pdo = new PDO($dsn, $user, $password);
        $pdo -> setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
        $pdo -> exec('set names utf8');
    }
    catch(PDOException $e)
    {
        die('Connection failed: ' . $e -> getMessage());
    }
Jan Kozák
Profil
1. Pokud $_SESSION["uzivatel"]["prihlasen"] nemůže nabýt jiné hodnoty než true, je druhá podmínka v souboru index.php na řádku 6 a v prihlasovaci_formular.php na řádku 4 zbytečná.
2. Pro zobrazení přihlašovacího formuláře nemusíš uživatele přesměrovávat na jinou adresu, to může být matoucí.
3. Na řádku 28 souboru prihlasovaci_formular.php máš operátor přiřazení. Do té proměnné přiřazuješ hodnotu fail, ale zdánlivě testuješ false.
4. Funkce MD5 je pro hashování hesel zcela nevhodná.
5. Spoléháš na to, že uživatel či útočník používá nástroj, který zná require.
6. Nerozumím smyslu řádků 8 a 9 v souboru pripoj_db.php. K čemu je dobré přiřadit hodnoty konstant do proměnných? A ty zkratky u názvů v DB a hodnot z formuláře taky nejsou kdovíjaká výhra.
7. Nehlídáš počty pokusů chybně zadaných kombinací uživatelských údajů. Ale oprávněnému uživateli situaci velmi znesnadňuješ tím, že mu ani neprozradíš, zda trefil uživatelské jméno.
8. Proč z databáze do aplikace přetahuješ hodnotu uživatelského jména a hesla, když ji stejně nevyužíváš?
9. Výpis nezlomitelné mezery na řádku 34 je poněkud nelogický. Spíš to vypadá, že sis neuvědomil, že druhá větev podmínky není povinná. Podobný problém je připojování prázdného řetězce na řádku 7 souboru pripoj_db.php.

Je dobré, že ti někdo řekl, abys používal PDO a výjimky, ale bohužel nemám pocit, že bys věděl, co ty skripty vlastně dělají. Máš jen slepené kusy skriptů, které jsi posbíral všude možně. Ale to bohužel podle mého soudu není dobrá cesta k vytvoření bezpečného systému.
zacatecnicek
Profil *
1) beru v potaz a děkuji
2) zkusím to vymyslet jinak
3) tohoto jsem se nevšiml, opraveno
4) co radíte raději použít? SHA?
5) není to v tomto případě zcela zbytečné? stejně se to poté kontroluje vůči databázi
6) řádky 8 a 9 jsou zřejmě zbytečné - odstraním je. zkratky používám tak, jak mi to vyhovuje
7) na tomto zapracuji - když bude správné uživatelské jméno, vložím ho do inputu při novém načtení formuláře. jakým způsob nejlépe ošetřit počet pokusů o přihlášení a následný timeout?
8) dále s ní pracovat budu
9) uvědomil - je tam schválně. kvůli tomu, aby mi neodskakovalo tlačítko (přijde mi jako nejjednodušší řešení)

Kód jsem psal sám - výjimku tvoří pripoj_db.php. Kdy něco nevím, snažím se hledat na php.net. Nějaká malá základy PHP mám, takže mohu na něčem málo stavět. Do světa PDO jsem teprve vkročil, takže tam na tom ještě zapracuji. Děkuji za kritiku!
lionel messi
Profil
zacatecnicek:
4) co radíte raději použít? SHA?

SHA je krokom z kaluže do blata. Dôrazne odporúčam password_hash a password_verify.
Krakatoa
Profil
zacatecnicek:
Příklad přihlašování máš ve FAQ (ten od Jana Tvrdíka): Diskuse JPW: Nejčastější potíže s PHP (FAQ) » Registrace uživatelů
Např. díky tomu zjistíš, že do session můžeš hodit celou array z výstupu z databáze nebo uvedeno vhodné hashování.
S tím, že si tam můžeš přidělat prepared statements a to username v inputu pokud jsou přihlašovací údaje špatně zadané.

Jan Kozák:
7. Podle mě je zbytečné uživateli psát že netrefil uživatelské jméno. Podle mě je nejvhopdnější napsat, že jsou přihlašovací údaje špatně zadané a když už tak mu zobrazit v inputu jaké jméno zadal, to jestli je správné stejně zjistí během chvilky. Navíc by se mohlo stát, že by zadal jméno někoho jiného a upozornilo by ho to pak třeba na nesprávné heslo což by bylo špatně.
Keeehi
Profil
Krakatoa:
Navíc by se mohlo stát, že by zadal jméno někoho jiného a upozornilo by ho to pak třeba na nesprávné heslo což by bylo špatně.
To je logicky špatně, tedy alespoň v 99% případů. Jméno většinou znáš, příkladem budiž email. Když vlezeš na přihlašovací stránku webmailu, nakopíruješ tam adresu toho, kdo ti email poslal, vyplníš heslo a zobrazí se ti "jméno nebo heslo není správně" tak z toho logicky vyplívá, že heslo muselo být špatně. OK a co když máme systém, kde uživatelská jména nejsou večejná -> nemohu vědět, zda uživatel v systému je, nebo není. V takovém případě si to mohu jako útočník ověřit tím, že se s tím přihlašovacím jménem pokusím zaregistrovat. Když mě systém odmítne zaregistrovat, vím že takový uživatel v systému existuje a mohu se vrátit k přihlašovacímu formuláři kde hláška "jméno nebo heslo není správně" mi říká, že heslo je špatně protoe jméno jsem si ověřil.

Takže u jakých systémů by to mělo smysl? Jenom u těch, které nemají registraci a username nikde neukazují. Takové systémy existují (třeba internetové bnkovnictví některých bank) ale je jich naprostá menšina.

Ještě je tu timing attack, který je založen na předpokladu, že ač se mi vrátí stejná odpověď, dokážu rozlišit jestli bylo špatně jméno nebo heslo na základě rychlosti odezvy serveru protože ověřování hesla se typicky provádí po ověření uživatelského jména. Takže pokud je špatně heslo, přijde opověď později než kdyby bylo špatně uživatelské jméno.
Krakatoa
Profil
Keeehi:
Já to ale psal z pozice uživatele ne útočníka. A smysl jsem viděl pro všechny systémy. Jen až teď mně došlo, že to bylo nejspíš myšlěno tak, že když není jméno v databázi tak se vypíše neplatné jméno a když je trefeno nějaké jméno z databáze tak se vypíše neplatné jméno nebo heslo (původně jsem si myslel, že je myšleno vypsání pouze neplatné heslo (což by bylo zavádějící).
A to původní jsem si myslel, protože Jan Kozák psal:
Ale oprávněnému uživateli situaci velmi znesnadňuješ tím, že mu ani neprozradíš, zda trefil uživatelské jméno.
Já tam nikde to velmi znesnadňuješ nevidím, když uživatel vidí co zadal za jméno tak ví hned jestli udělal chybu ve jménu. Tak to mají řešené třeba na seznamu, kdyby v tom bylo něco velmi znesnadňujícího pro uživatele, tak by to přece řešili jinak.
Keeehi
Profil
Krakatoa:
Já jsem za pro stav, kde když neexistuje uživatelské jméno v databázi, vypíše se "uživatel neexistuje", když takové uživatelské jméno existuje, ale nesedí k nemusí heslo, tak se vypíše "špatné heslo".

Proč? Tak třeba proto, že do různých služeb mohu používat různá uživatelská jména. Když pak nějakou delší čas nepoužívám a vrátím se po čase, mohu tam zadat uživatelské jméno odjinud. Napsané bude správně, takže z chybové hlášky "špatný uživatel nebo heslo" si budu myslet, že je chyba v hesle. A přitom jde o to, že se snažím přihlásit úplně jiným uživatelským jménem. To zjistím až v okamžiku, kdy to vzdám a nechám si zresetovat heslo. Pak mi systém prozradí, že takový uživatel vlastně vůbec neexistuje. Tak proč by to nemohl udělat přímo při prvním špatném pokusu?

Proto se mi celkem líbí, jak to má vyřešené google u účtů. Nejdříve zadáš email, on ověří, zda takový email spravuje a pak ti teprve zobrazí políčko pro zadání hesla.
Jan Kozák
Profil
Krakatoa:
Uživatelé používají různé přihlašovací jména. Buď proto, že na některé službě je to jejich preferované obsazené, nebo nechtějí, aby jejich identita byla dohledatelná napříč službami na internetu. Někde se k přihlašování používá e-mail, jinam se nevejde, protože je omezená délka nebo třeba zakázaný zavináč. Na některých webech (třeba zde) se nickname používá k oslovování (proto se tu přihlašuji s diakritikou a mezerou), jinde mi diakritiku i mezeru nedovolí. Někde musím použít jiný než preferovaný e-mail, protože tam nepřijímají vlastní doménu…
Radí se, aby uživatelé napříč službami používali různá hesla, aby při zneužití jednoho systému nebyly ohroženy všechny účty. A také by ho měli v průběhu času občas obměnit.
Kombinací uživatelských jmen a hesel tedy používám velké množství, a proto bych ocenil, kdyby mi web prozradil, že dost možná mám překlep ve jméně a nemusím vyzrazovat další své heslo.

Já tam nikde to velmi znesnadňuješ nevidím, když uživatel vidí co zadal za jméno tak ví hned jestli udělal chybu ve jménu. Tak to mají řešené třeba na seznamu, kdyby v tom bylo něco velmi znesnadňujícího pro uživatele, tak by to přece řešili jinak.
Vidí, co zadal, ale neví, jestli má kontrolovat jméno, nebo zkusit jiné heslo. Mnoho vývojářů univerzální hlášku používá proto, že je to podstatně jednodušší na vývoj a na uživatelský komfort zapomínají.
Pokud v Seznamu utajují existenci uživatelského jména při přihlašování z bezpečnostních důvodů, patrně si neuvědomili, že existence jména lze ověřit nejen při registraci, ale také třeba zasláním e-mailu, která se v případě neexistence schránky vrátí s vysvětlující hláškou „User does not exist“.
Prozrazením faktu, že uživatelské jméno vůbec není registrováno, můžeš oprávněnému uživateli významně pomoci. Útočníka však příliš neomezíš.

původně jsem si myslel, že je myšleno vypsání pouze neplatné heslo (což by bylo zavádějící)
Tak jsem to nemyslel, ale i kdyby… U projektu s nižšími desítkami tisíc uživatelů je pravděpodobnost, že by se někdo pokusí přihlásit cizím existujícím uživatelským jménem, poměrně malá. Pokud loguješ chybně zadaná uživatelská jména (hesla samozřejmě ne), jistě mi potvrdíš, že drtivá většina zadaných nicků v databázi uživatelů vůbec není. Zacatecnicek v tuto chvíli nevyvíjí tak rozšířený systém, kde by ke kolizím jmen docházelo. Pokud ano, tak to MD5 je pomalu na kriminál.

zacatecnicek:
5) není to v tomto případě zcela zbytečné? stejně se to poté kontroluje vůči databázi
Snažíš se pracovat s prvkem, který v tu dobu vůbec nemusí existovat. Respektuji, že ti to nevadí.
6) […] zkratky používám tak, jak mi to vyhovuje
Nemám nic proti zkratkám. Ale vol je takové, abys jejich význam pochopil i tehdy, až se ke skriptu vrátíš s odstupem, či aby jim rozuměli všichni, od kterých chceš pomoci.
9) uvědomil - je tam schválně. kvůli tomu, aby mi neodskakovalo tlačítko (přijde mi jako nejjednodušší řešení)
Na úpravu vzhledu použij CSS, ne pevné mezery a už vůbec ne vkládané pomocí podmínek kdesi hluboko ve skriptu. Na tvorbu to možná jednodušší řešení je, ale pro následnou úpravu vzhledu a čitelnost skriptu se obávám, že horší postup jen tak nenajdeš.

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: