Administrácia našich portálov je dostupná z internetu, lebo redakcia nepracuje z jednej kancelárie. Prihlasovací formulár tak vidí každý robot, ktorý skúša uniknuté heslá alebo hľadá, čím server zahltiť. Návrh, ktorý opisujeme, vznikol na jeseň 2026 po bezpečnostnom audite a niekoľkých kolách nezávislých kontrol. Žiadna jeho časť nie je originálna, všetko nájdete v odporúčaniach OWASP, v NIST SP 800-63B-4 a v špecifikácii WebAuthn. Hodnota je v poradí krokov a v tom, aby si jednotlivé obrany navzájom neprekážali. Článok je pre vývojárov a architektov interných systémov. Konkrétne limity a konfiguráciu neuvádzame. Systém v tejto podobe beží len od októbra 2026, preto píšeme o návrhu, a nie o štatistikách.

Pred čím sa bránime

Z kontrol vyplynulo, že samotné overenie identity býva vyriešené skôr ako všetko, čo sa deje okolo neho. Návrh preto pokrýva päť situácií:

  • Skúšanie uniknutých dvojíc e-mail a heslo z iných služieb (credential stuffing), často rozložené na mnoho IP adries.
  • Útok na dostupnosť: zámerne pomalé overovanie hesla sa dá zneužiť na zahltenie servera.
  • Zamknutie majiteľa: kto pozná e-mail, môže opakovanými zlými pokusmi spustiť zámok účtu.
  • Ukradnutá alebo neodhlásená relácia, z ktorej by sa dali zmeniť bezpečnostné údaje.
  • Podvrhnutá požiadavka z cudzej stránky (CSRF), ktorá zneužije prihláseného administrátora.

Lacné kontroly pred drahými

Bcrypt je zámerne pomalý, aby sa uniknuté odtlačky hesiel ťažko lámali. Tá istá vlastnosť z neho robí cieľ, lebo každá požiadavka, ktorá sa k nemu dostane, stojí server citeľný čas procesora. Prihlásenie má preto pevné poradie a každý krok je lacnejší ako nasledujúci. Najprv sa kontroluje tvar vstupu, potom kvóta požiadaviek na adresu klienta, ktorá sa odčíta pri každom pokuse ešte predtým, než server rieši, čo požiadavka chce. Nasleduje test proti robotom, potom kvóty neúspechov na adresu aj na účet a až na konci overenie hesla. Pri neexistujúcom účte server overuje heslo proti fiktívnemu odtlačku, aby odpoveď trvala porovnateľne dlho ako pri existujúcom.

Do kvót neúspechov sa rátajú len pokusy, ktoré prešli testom proti robotom. Inak by lacný robot vyčerpal limit účtu bez toho, aby vôbec skúšal heslo. Samotný test nepreceňujeme: OWASP ho označuje za ďalšiu vrstvu, ktorá útok spomalí a predraží, nie za samostatnú ochranu.

Kvóty atomicky, pri výpadku zatvorené

Kvóta je počítadlo s časovým oknom a stropom. Kontrola a odčítanie musia byť jeden atomický príkaz v databáze. Ak aplikácia najprv číta a potom zapisuje, desať súbežných požiadaviek uvidí rovnaký stav a prejdú všetky. Rovnako atomicky sa mení počítadlo neúspechov účtu spolu so zámkom, takže súbežné pokusy prah nepreskočia a upozornenie o zamknutí odíde práve raz.

Kvóta neúspechov sa odčítava vopred a pri úspešnom prihlásení sa vráti. Útočník tak nemôže poslať sto paralelných pokusov v čase, keď ešte žiadny nedobehol. Pri adresách IPv6 počítame po blokoch, ktoré typicky patria jednému pripojeniu, nie po jednotlivých adresách.

Keď databáza neodpovie, bezpečnostné kvóty prihlásenie odmietnu. Otvorené dvere počas výpadku sú neprijateľné, lebo práve vtedy by ich niekto skúšal. Auditný záznam pokusov je od kvót oddelený. Slúži ako dôkaz, nie ako počítadlo, a pri záplave robotov sa smie obmedziť bez toho, aby sa oslabila obrana. Hranicu celého prístupu pomenúva OWASP v odporúčaniach ku credential stuffingu: limity na IP adresu nemajú byť hlavnou obranou, lebo útok rozložený na tisíce adries ich obíde. U nás sú preto len prvou vrstvou.

Zámok, ktorý nezamkne majiteľa

Klasický zámok účtu po niekoľkých neúspechoch má slabinu, na ktorú upozorňuje aj OWASP: kto pozná e-mail, zamkne kohokoľvek. NIST SP 800-63B-4 síce vyžaduje strop na po sebe idúce neúspechy, ale zároveň odporúča techniky, ktoré znižujú riziko, že útočník zamkne oprávneného používateľa.

Rozlišujeme preto známe a neznáme prehliadače. Po úspešnom prihlásení dostane prehliadač podpísaný identifikátor viazaný na aktuálnu verziu účtu, ktorý sa pri každom overení faktora porovná so záznamom na serveri. Záznam má expiráciu, majiteľ ho vidí v profile a môže ho odvolať. Zmena hesla zneplatní všetky naraz. Známy prehliadač nepodlieha zámku účtu, má však vlastnú kvótu. Keď ju vyčerpá, správa sa ako neznámy. Zámok platí pre neznámych klientov, takže majiteľ sa dostane dnu aj počas útoku na svoj e-mail.

Známy prehliadač pritom nepreskakuje heslo ani druhý faktor. Mení len to, koho môže zámok zasiahnuť. Každé prihlásenie z nového prehliadača a každé zamknutie účtu pošle majiteľovi e-mail. Upozornenia majú vlastný limit, aby sa nezmenili na záplavu, a nikdy neblokujú prihlásenie, lebo nedostupný poštový server nesmie zastaviť prácu redakcie.

Druhý faktor: WebAuthn s overením používateľa

Mimo lokálneho vývoja je druhý faktor povinný. Uprednostňovaným druhým faktorom je kľúč WebAuthn (v bežnej reči passkey). Pri registrácii aj pri prihlásení vyžadujeme overenie používateľa: autentifikátor musí potvrdiť nielen prítomnosť používateľa, ale aj jeho lokálne overenie, napríklad odtlačkom prsta alebo PIN-om. Potvrdzuje tým lokálne odomknutie autentifikátora, nie občiansku totožnosť osoby. Ak v podpísanej odpovedi chýba príznak overenia, server ju odmietne, ako to pri požiadavke „required“ opisuje špecifikácia WebAuthn Level 3, od augusta 2026 odporúčanie W3C.

Pôvod požiadavky (origin) v podpísaných údajoch klienta porovnávame s presným zoznamom povolených adries ešte pred spracovaním knižnicou. Väzba na konkrétnu službu a kontrola pôvodu robia z WebAuthn faktor odolný voči phishingu. NIST SP 800-63B-4 vyžaduje, aby systém na úrovni AAL2 aspoň jednu takú možnosť ponúkal. Takzvaný discoverable credential (rezidentný kľúč) nevynucujeme, lebo WebAuthn používame až po e-maile a hesle, keď už server vie, o ktorý účet ide. Podľa terminológie FIDO a WebAuthn je passkey práve discoverable credential. Prijímame aj nerezidentné kľúče WebAuthn, ktoré v tomto užšom zmysle passkey nie sú.

Záložným faktorom je jednorazový kód e-mailom s krátkou platnosťou, s odstupom medzi odoslaniami a s kvótami, ktoré sa odčítajú pred odoslaním. NIST SP 800-63B-4 e-mail ako mimopásmový autentifikátor nepripúšťa. K schránke sa často dá dostať len heslom a správu možno zachytiť alebo presmerovať, takže nedokazuje držbu konkrétneho zariadenia. Kód e-mailom je kompromis pre prípad straty zariadenia. Systém ho vie pre účty s passkey vypnúť a odporúčame to, keď má majiteľ zaregistrovaných viac zariadení.

Relácie a zmeny bezpečnostných údajov

Registrácia aj mazanie passkey a zmena hesla vyžadujú opätovné zadanie hesla, viazané na konkrétnu operáciu, a aj toto overenie má vlastnú kvótu pred výpočtom bcrypt. Ukradnutá relácia teda nestačí na to, aby si útočník pridal vlastný passkey. Je to priame uplatnenie odporúčania OWASP vyžadovať aktuálne prihlasovacie údaje pred zmenou citlivých údajov účtu.

Účet má číslo verzie. Zmena hesla alebo roly ho zvýši a tým sa zneplatnia ostatné bežiace relácie aj všetky známe prehliadače, bez hľadania a mazania jednotlivých relácií. Pri zmene hesla ostane platná len relácia, z ktorej sa heslo menilo. Rola sa číta z databázy pri každej požiadavke, nie z relácie, takže odobratie práv platí okamžite. Zamknutie účtu bežiace relácie neukončuje. Zámok má brániť hádaniu, nie vyhodiť majiteľa, ktorý už pracuje.

Relácia má limit nečinnosti aj absolútnu dĺžku, cookie má príznaky Secure, HttpOnly a SameSite a cestu zúženú na administráciu. Každá požiadavka POST prechádza centrálnou kontrolou CSRF tokenu a hlavičiek, ktorými prehliadač oznamuje pôvod požiadavky. SameSite je podľa OWASP len doplnková vrstva, nie náhrada tokenu. Prístup je predvolene zakázaný: neprihlásený návštevník nedostane nič okrem prihlasovacej stránky a výnimky sú vymenované v kóde. Zápisy do živých dát a bezpečnostné udalosti idú do auditného logu.

Čo sme zámerne vynechali

  • Umelé spomalenie odpovede (sleep). Drží proces servera, ktorý medzitým neobslúži nikoho iného, a útočníkovi s paralelnými požiadavkami neprekáža. Čakanie po neúspechu, ktoré pripúšťa aj NIST, má byť stav na serveri (pokus sa do určitého času odmietne), nie zablokované vlákno.
  • Rozlišovanie hlášok „neexistujúci účet“, „zlé heslo“ a „zamknutý účet“. Jedna hláška a porovnateľný čas odpovede.
  • Heslá v logoch, chybových hláškach a HTML, aj vo chvíľach, keď sa niečo pokazí.
  • Tiché skracovanie hesiel. Bcrypt spracuje najviac 72 bajtov a PHP zvyšok bez upozornenia zahodí, preto dlhšie nové heslo odmietneme namiesto skrátenia.
  • Povinnú skladbu znakov a pravidelnú zmenu hesla bez dôvodu. NIST SP 800-63B-4 ich nepripúšťa. Vyžaduje však dostatočnú dĺžku, kontrolu proti zoznamu bežných a kompromitovaných hesiel, obmedzenie počtu pokusov a zmenu pri dôkaze kompromitácie.
  • Honeypot ako bezpečnostné opatrenie. Skryté pole pre roboty je nanajvýš lacný filter šumu. Do návrhu obrany sa nepočíta a nič v ňom od neho nezávisí.

Kontrolný zoznam a odporúčanie

Keď posudzujeme prihlásenie do interného systému, či už vlastného, alebo klientovho, prechádzame tieto otázky:

  • Započíta sa každá požiadavka na prihlásenie skôr, než sa začne overovať heslo?
  • Sú kontrola a odčítanie kvóty jeden atomický príkaz a čo sa stane, keď databáza neodpovie?
  • Môže cudzí človek zamknúť administrátora?
  • Je k dispozícii faktor odolný voči phishingu a vyžaduje overenie používateľa?
  • Dá sa z ukradnutej relácie pridať faktor alebo zmeniť heslo?
  • Ukončí zmena hesla alebo roly ostatné relácie účtu?
  • Dozvie sa majiteľ o prihlásení z nového prehliadača a o zamknutí účtu?

Pri novom systéme odporúčame zvážiť Argon2id. OWASP dnes bcrypt odporúča len tam, kde Argon2id ani scrypt nie sú k dispozícii. Limit 72 bajtov navyše znamená, že pri diakritike (dva bajty na znak v UTF-8) sa do hesla nezmestí ani 64 znakov, ktoré NIST odporúča prinajmenšom povoliť. My zatiaľ používame bcrypt a prechod je možný postupne, pri ďalšom prihlásení každého používateľa. Najväčší rozdiel však robí poradie krokov, viac než algoritmus alebo jednotlivá technika. Lacné kontroly idú pred drahými, pri výpadku ostávajú dvere zatvorené a zámok mieri na útočníka, a nie na majiteľa.