Podívejte se raději na online verzi přednášky, slajdy mohly být aktualizovány nebo doplněny.

Detail přednášky

V rámci přednášky si vysvětlíme, jak fungují z pohledu klienta a serveru a kde se skrývají jejich nejčastější slabiny. Cílem je pochopit fungování aplikace z pohledu klienta a serveru, rozpoznat nejčastější typy zranitelností a chápat důsledky bezpečnostních chyb pro uživatele i provozovatele webu – včetně úniku dat, narušení důvěry nebo právních následků.

Obsah:

Detail přednášky na webu akce

Datum a akce

28. ledna 2026, SIT Port eventy (délka přednášky 120 minut, 73 slajdů)

Slajdy

Nejčastější bezpečnostní chyby webů

#1 Přednáška o několika klasických chybách a útocích na webové aplikace, jejich řešení a předcházení jim. Řekneme si (a napíšeme si tu) o tom, jak případně nalezené chyby někomu hlásit a komu a jak na takové hlášení reagovat, pokud ho vám ho někdo (jako třeba já) pošle. Některé tyto chyby jsou známé přes 20 let, ale to neznamená, že už nejsou nikde k nalezení, nebo že jsou k nalezení jen ve starých aplikacích. Naposled jsem nějakou takovou chybu hlásil minulý týden a je v podstatě skoro jedno, kdy tohle budete číst.

Obsah

#2 Cross-Site Scripting, zkráceně XSS, je jedním z těch klasických útoků a stále používaný. V době psaní tohoto textu čekám na opravu dvou takových nahlášených problémů. Na XSS se hezky vysvětluje ochrana pomocí více vrstev (tzv. Castle Approach), např. i pomocí Content Security Policy (CSP). SQL Injection je útok, který může za spoustu uniklých databází. Dále se dozvíte něco o heslech, jak si (ne)stahovat cizí faktury (Insecure Direct Object Reference, IDOR) a další data a co dělat, když nějaký problém najdete, nebo když ho někdo najde u vás.

XSS aneb všechno je uživatelský vstup

#3 Cross-Site Scripting (XSS) vzniká nejčastěji tak, že do stránky chceme vypsat nějaký uživatelský vstup, např. vyhledávací dotaz. Co je to ten uživatelský vstup? Všechno je uživatelský vstup. DNS záznamy (všimněte si dat v části Answer), HTTP hlavičky („Not secure“ v řádku s adresou napoví), prostě fakt všechno, není třeba rozlišovat.

Tohle ještě není XSS

#4 Při vyhledávání něčeho normálního se nic zajímavého nestane, tohle totiž ještě není XSS.

Tohle už je XSS

#5 Ale při zadání něčeho nenormálního (ačkoliv jak pro koho, že, já „vyhledávám“ podobný JS skoro furt) může dojít k problému, pokud data nejsou na výstupu správně ošetřena.

Google Analytics v 404 na Alze

#6 Konkrétní případ na webu 👽.cz – při zadání neexistující stránky se do HTML kódu vypsala zadaná adresa do bloku <SCRIPT> ... </SCRIPT>, aby si ji mohli někam „odtrackovat“ („ga“ znamená Google Analytics, reálný kód byl složitější, ale to není podstatné).

XSS v 404 na Alze

#7 Při propašování nějakého JS do adresy neexistující stránky se ten vypsal rovnou do stránky. Plus v URL se převádí při výpisu na mezeru, v prvním řádku je modře ta neexistující URL, v druhém řádku je pak barevně zobrazeno jak to chápal browser. Adresa „skončila“ za otazníkem a pak za středníkem následoval alert(1); a zbytek nepotřebuji, dokonce by mi někdy mohl vadit, tak je za komentářem.

XSS v 404 na Alze: alert(1)

#8 A takto to také browser pochopil a ten alert(1) opravdu spustil.

XSS v 404 na Alze: kód z baz.xss.sk/js

#9 Jenže alert(1) je spíš jen takový folklor a detekce toho, že to jde. Nemá vypovídací schopnosti o závažnosti útoku XSS, takže je lepší použít něco zábavnějšího. Vytvořením této URL (na slajdu rozděleno na více řádků, aby se to tam lépe vešlo) jsem přesvědčil browser, aby vytvořil novou značku SCRIPT, nahrál do ní JS z https://baz.xss.sk/js (archív pro jistotu, blok začíná řádkem s #lblLogin) a značku pak připojil do hlavičky HEAD, aby se kód opravdu vykonal.

XSS v 404 na Alze: login formulář s vlastní obsluhou

#10 V adrese je pak sice vidět nějak moc „divných“ znaků, ale to by se dalo vyřešit pomocí History API viz můj článek. Adresa pak klidně může vypadat třeba jako alza.cz/login, takže vůbec nevadí, že to XSS je na čtyřistačtyřce. Změnu cesty v URL jsem ale vynechal, aby ten připojený JS byl kratší. Kód „kliknul“ na odkaz Přihlásit nahoře vpravo, čímž vyvolal reálné přihlašovací okénko od mimozemšťana a přidal obsluhu stisku klávesy i stisku zeleného tlačítka Přihlásit.

XSS v 404 na Alze: výsledek odeslání login formuláře

#11 Pokud k jednomu z toho došlo (uživatel by chtěl vyplnit svůj e-mail nebo heslo, nebo kliknout na Přihlásit), tak se ukázalo varování, že by to neměl dělat. Mohl jsem ale taky klidně změnit např. atribut action formuláře a tím si zadané údaje posílat někam ke mě (tj. k útočníkovi). Alza po nahlášení problém naštěstí rychle opravila, popisuju to v článku o hlášení chyb, o čemž se budeme bavit i v závěru této přednášky.

Ochrana proti XSS

#12 XSS se řeší takto: nebezpečné < > " ' znaky se převedou na jejich bezpečné varianty, tzv. entity, které nemají žádný speciální význam. Říká se tomu „escapování“ a nejlepší je to dělat nějakým šablonovacím systémem automaticky – to pak lze považovat za primární úroveň ochrany. Pro PHP existují systémy jako Latte a Twig. V některých je třeba automatiku zapnout, např. ve Smarty. Při autoescapování vývojář nemůže nic zapomenout ošetřit, šablonovací systém escapuje za něj. Ošetřovat je možné také ručně nějakou hypotetickou funkcí escape (kterou ve finále ty šablonovací systémy také používají), v PHP je to funkce htmlspecialchars.

Oprava chyby na Alza.cz

#13 Při správném escapování by se ta stránka na Alze vypsala takto viz první řádek, a prohlížeč by ji pochopil takto, viz druhý řádek. Tedy vše by chápal jako URL, protože apostrof, kvůli kterému bylo možné „vyskočit“ z řetězce a psát další JS, je převeden na entitu v tomto případě &apos; dostupnou z HTML5.

Jako když hraješ StarCraft®a

#14 Když jsme ve škole hráli StarCraft®a a bránili naše základny, tak jsme postavili hradbu z jednoho typu jednotek, za ně dali další, a do pozadí dali dalekonosné lasery, aby na ně nikdo nemohl. Když nepřátelé prorazili tu první hradbu, tak tam byla druhá a ta to zvládla zastavit. Většinou. Případně získala čas, abychom vyrobili další jednotky, které jsme do toho masakru mohli poslat. Tuhle strategii obrany jsem musel pracně znovu objevovat o mnoho později, když jsem začal navrhovat webový aplikace.

Defense in Depth

#15 Tahle strategie se jmenuje Defense in Depth, hloubková obrana, pojem pochází z válčení ačkoliv tam má trochu jiný význam, než v počítačové bezpečnosti, Ve válčení, alespoň dle Wikipedie, jde o jakousi výměnu času za dobyté území s tím, že když útok trvá déle nebo probíhá na větším území, tak se více projeví slabé stránky útočníka (např. logistika). V počítačové bezpečnosti se používá Defense in Depth pro vícevrstvé ochrany.

Castle Approach

#16 Někdy se takovému přístupu říká Castle Approach, metoda hradu. Jeden takovej krásnej hrad máme tady, jmenuje se Krak des Chevaliers, je v Sýrii a pochází z dob křižáckých výprav. Na něm je možná systém více vrstev ochrany vidět lépe: Je na kopci, rozhled do dálky, omezený vstup do hradu, jedny hradby, druhé hradby, věže, střílny. Dostat se na kopec je těžký, ale co když ho někdo vyleze? Úzký vstup a hradby! A co když je někdo překoná? Druhý hradby a střílny! A co když někdo překoná i ty? Vnitřní hradby? A co když…

Defense in Depth/Castle Approach v běžném životě

#17 Defense in Depth a Castle Approach se ale do určité míry běžně používá i v běžném životě. Dva zámky na dveřích jsou příkladem, auto zaparkovaný pod lampou, zamčenej volant a na něm navíc tyč, imobilizér a hnusná barva zajistí, že auto najdete pod lampou i ráno. Kim ze Severní Koreje se při státních návštěvách sice spoléhá na neprůstřelnou limuzínu, ale co kdyby ji někdo prostřelil? Tak si k tomu najme ještě 12 maratonských běžců a dá jim bouchačky. Možná tam jsou ale jen proto, kdyby tomu zápaďáckýmu Mercedesu vypověděl motor, tak aby ho mohli odtlačit. Více vrstev zajištění mobility diktátorů, neasi. Mimochodem, tohle bych nechtěl nacvičovat ani z pozice řidiče.

Krádež session cookies

#18 Cookies jsou důležitá věc a když někdo ukradne ty správné, tak se díky cookie se session id (PHPSESSID, JSESSIONID, apod.) může vydávat za přihlášeného uživatele – prostě si ji uloží ve svém browseru a ten ji pak při dalším požadavku pošle.

Sessions jsou vlastně takové pojmenované plechovky s daty

#19 Sessions se dají vzdáleně přirovnat k takovým plechovkám s daty na serveru. Při přihlašování uživatel odešle jméno a heslo na server, ten heslo ověří a přiřadí uživateli nějak pojmenovanou plechovku a do ní uloží, že je přihlášen uživatel Pepa. Jméno té plechovky potom odešle v cookie do prohlížeče, který to jméno pak v cookie při každém dalším požadavku posílá místo hesla. Server poté ví, že si má otevřít plechovku s daným jménem, které mu přišlo v cookie, a podívat se jestli je uživatel Pepa přihlášen, mimo jiné.

Sessions plechovky se session id

#20 Kdyby ale plechovky měly předvídatelná jména, tak by bylo jednoduché si v browseru změnit cookie z Pepy na jiné jméno a zkusit jestli v takové plechovce není přihlášen jiný uživatel. Proto se jako jména plechovek používají náhodné identifikátory, říkáme jim session id, které nejde předvídat ani uhodnout. V plechovce s názvem 31e160... je poté na serveru uloženo, že uživatel je přihlášen a že jeho uživatelské jméno je Pepa. Takové 31e160... nejde uhodnout, opravdová session id navíc bývají ještě mnohem delší, takže získat ho lze jedině ukradením.

HTTP-only cookies

#21 Cookies se kradou pomocí malware, k tomu se v krátkosti ještě také dostaneme, ale dají krást i pomocí zákeřného JavaScriptu pomocí XSS. Aby to pomocí JS nešlo, tak můžeme využít HTTP-Only Cookies.

Content Security Policy

#25 Pomocí response hlavičky Content-Security-Policy lze browseru říci, odkud má načítat zdroje i skripty (pomocí direktivy script-src). 'self' je zkratka pro aktuální origin (protokol, doména, port). Pokud v HTML dorazí nějaký zdroj, ale Content Security Policy (CSP) ho nedovolí, browser ho nenačte. Opět dle principu Defense in Depth či Castle Approach.

Content Security Policy 'unsafe-inline'

#26 'self' a domény v direktivě script-src povolují pouze načítání externích souborů pomocí značky <script> a atributu src. Pro povolení inline JS (což je kód přímo vložený mezi značkami <script> a </script>, onclick atributy apod.) je potřeba přidat 'unsafe-inline'. Ale u takového inline JS nelze poznat, kdo ho vložil, proto „unsafe“. A už dle názvu, tohle používat spíš moc nechcete.

Content Security Policy 'nonce-něco'

#27 Hodnotou 'nonce-něco' a HTML atributem nonce=něco můžeme označit externí soubory i kód mezi <script> a </script>, které má browser načítat nehledě na to, odkud pochází. To něco by mělo být min. 16 náhodných bajtů (v hexdec, nebo Base64), jiné při každém načtení stránky a můžeme ho použít pro více/všechny značky script na jedné stránce. Tím zajistíme to, že když někdo do stránky vloží nějaký zákeřný JS mezi <script> a </script>, tak ho browser stejně nespustí, protože útočník nezná to něco, protože je náhodně vytvářené a nedokáže tak vytvořit validní atribut nonce=něco.

Content Security Policy 'form-action'

#28 Vložením zákeřného HTML formuláře nebo změnou existujícího formuláře, viz Alza dříve, může útočník ukrást např. jméno a heslo tak, že si ho změnou <form action=...> pošle k sobě. Pomocí CSP můžeme browseru říci, na které adresy má formuláře odesílat, takže kdyby se nějaký formulář chtěl odeslat jinam, tak to browser neudělá. Můžeme to udělat pomocí direktivy form-action, hodnota 'self' pak opět říká, že form lze odeslat jen na aktuální origin. Direktivy můžete kombinovat, zkuste si to na https://canhas.report/csp-urls (všiměte si hlavičky a níže na stránce zkuste někam poslat formulář). CSP hlavička je „per stránka“, na každé stránce může být jiná, pro správu CSP ideálně na všech stránkách stejná, ale nijak se nekešuje, nebo tak něco.

Cross-Site Request Forgery, zranitelný formulář v aplikaci

#29 Cross-Site Request Forgery, CSRF, je útok, pomocí kterého útočník může provést akci za přihlášeného uživatele, kterou by ten uživatel normálně neudělal, pomocí „zfalšovaných“ požadavků. Představte si, že máte nějakou školní aplikaci, do které se zapisují známky. Když by vám chtěl učitel zapsat pětku z Bezpečnosti webových aplikací, tak se přihlásí do té aplikace na skola.cz, ve formuláři by vybral uživatele a vyplnil známku. Kliknutím na tlačítko by se pak odeslal formulář, který by mohl vypadat jako to na obrázku. Formulář by se odesílal metodou POST, což jsem na obrázku úmyslně vynechal, aby to bylo kratší.

Útok Cross-Site Request Forgery, automaticky odeslaný formulář

#30 Vy jste se mezitím něco doučili a rádi byste si známku změnili na jedničku. Tak učiteli pošlete odkaz na svou stránku super.stranka.zejo, že jste tam jakože našli hezký článek o koťátkách. Na tu stránku navíc přidáte skrytý formulář, který se bude odesílat na školní aplikaci, a do něj předvyplníte svoje id (123) a chtěnou známku (1). Ve formuláři na obrázku opět úmyslně chybí method=post, protože sem už by se to fakt nevešlo. Učitel na tu stránku přijde, ale na skrytý formulář by pochopitelně kliknout nechtěl a ani nemohl. Tak to uděláme za něj: pod ten skrytý formulář dáme JavaScript, který ten právě vytvořený formulář pomocí metody submit() automaticky odešle. K požadavku se zároveň přiloží i cookies pro skola.cz, čímž se to pro server bude tvářit jako normální požadavek od přihlášeného uživatele. A CSRF je na světě a vylepšená známka taky.

Token pro ochranu proti Cross-Site Request Forgery

#31 Formulář proti CSRF ochráníme tím, že do něj přidáme něco náhodného, říkáme tomu token. Server ten token vygeneruje, uloží si ho do session a ten token se poté vypíše do skrytého políčka (na obrázku je to z důvodu jednoduchosti prostě jen nějaký input). Server po odeslání formuláře porovná to co má uloženo v session s tím, co se mu vrátilo z prohlížeče. Pokud to souhlasí, tak akci vykoná, pokud to nesouhlasí, tak nazdar. Ukládání tokenu do session není jediná možnost ochrany, další možnosti jsou ukládání do cookie, kontrola hlaviček Origin nebo Sec-Fetch-Site apod. Frameworky a knihovny pro tvorbu formulářů tuto ochranu obvykle naštěstí řeší za programátory, ale je potřeba to nějak zapnout. Implementovat si tuhle ochranu celou po vlastní ose je totiž docela náročné, protože možných scénářů je zvlášť u větších aplikací vícero.

Útočník nezná Cross-Site Request Forgery token

#32 Když pak útočník vytvoří takový skrytý automaticky odesílaný formulář na nějaké jiné stránce, tak nedokáže předvyplnit políčko token, protože je náhodné a nepředvídatelné. Po odeslání formuláře bez tokenu server zjistí, že se to určitě neshoduje s tím, co má uloženo např. v session, a akci odmítne vykonat.

Same-site cookies jako ochrana proti Cross-Site Request Forgery

#33 A poněvadž vývojáři ochranu proti CSRF nezapínají až příliš často, tak se výrobci prohlížečů zamysleli, jestli s tím nemohou něco udělat na jejich straně. A trochu očekávatelně přišli na to, že ano, a vymysleli tzv. same-site cookies. Toto omezení cookies spočívá v tom, že se sušenka na server odešle jen v případě, že site, na kterém vznikl požadavek (česky řečeno tam kde uživatel kliknul) se musí rovnat site kam se má požadavek odeslat. V tuto chvíli je podstatná hlavně cookie se session id, která identifikuje přihlášeného uživatele, ale samozřejmě týká se to i jiných cookies. Náš příklad, ve kterém by učitel kliknul na super.stranka.zejo, resp. kdy by se z té stránky automaticky odeslal formulář na skola.cz metodou POST by tedy nefungoval. super.stranka.zejo a skola.cz je totiž jiný site a cookie pro skola.cz se session id přihlášeného uživatele by se s takovým požadavkem neodeslala, takže by na skola.cz přišel požadavek jakoby od nepřihlášeného uživatele. Defaultně jsou totiž cookies „same-site“ pro všechny POST požadavky (takové úrovni same-site omezení říkáme Lax), lze to rozšířit i na metodu GET (úroveň Strict) pokud by aplikace obsahovala nějaké důležité „akční“ odkazy nebo formuláře odesílané metodou GET, což samo o sobě není moc dobrý nápad. Same-site omezení lze také úplně vypnout pomocí None, ale ani to není moc dobrý nápad.

Co je origin, site, scheme, eTLD, eTLD+1, TLD

#34 To site v „same-site cookies“ ale neznamená jen tak nějaký „sajt“, jakože web, nebo tak něco. Site je přesně definovaná část URL, která znamená schéma (např. https) a tzv. registrovatelnou doménu, někdy též označovanou jako eTLD+1 (Efektivní top-level doména + 1). To je doména, kterou si můžete koupit, jako např. michalspacek.cz, nebo cam.ac.uk. Je dobré si také uvědomit, že doména druhé úrovně může v různých zemích znamenat různé věci, proto se používá spíše název registrovatelná doména nebo eTLD+1. Třeba ve Velké Británii může efektivní top-level doména být doménou druhé úrovně, registrovatelná doména pak doménou třetí úrovně. A aby to nebylo jednoduché, tak u nás registrovatelná doména může být i neco.gov.cz, protože gov.cz je také efektivní top-level doména. Seznamu takových domén se říká Public Suffix List a používá se na spoustě míst, více o tomhle všem píšu ve svém článku o eTLD a dalších věcech.

Same-site cookies a subdomény

#35 Zpátky k tématu. Říkal jsem, že same-site cookies se posílají jen pokud se site stránky, kde uživatel kliknul ať už vědomě, nebo ne, shoduje se site stránky, na kterou se má požadavek poslat. Takže automaticky odesílaný formulář ze super.stranka.zejo se sice odešle na skola.cz, ale bez cookie se session id, takže skola.cz si bude myslet, že nejde o přihlášeného uživatele. Ale kdyby se vám povedlo získat kontrolu např. nad doménou spacek.skola.cz a mohli jste si tam umístit takový nějaký hezký formulář, tak by se cookie se session id poslala, protože spacek.skola.cza skola.cz (ale i např. www.skola.cz, app.skola.cz apod.) mají stejný site, čímž by útok CSRF zafungoval. Z toho plyne, že same-site cookies zvýšila užitečnost subdomén pro útočníky, tak snad vám škola (ne)umožní dělat studentské stránky na nějaké „vysoce použitelné“ adrese. Proto je mj. dobré úplně nezatracovat ochranu pomocí tokenů zmíněnou dříve a používat ji jako další úroveň ochrany.

SQL Injection: It's not funny when you're next

#36 Útok SQL Injection je taky legrace, ale jen dokud nejste další na řadě. Tak si ho pojďme vysvětlit, ať se vám to nestane.

SQL Injection na post-it lístečku

#37 SQL Injection si můžete představit i v reálném životě. Na lísteček se seznamem co máte donést si injektnete něco dalšího, viz ten druhý post-it lístek. Do věci („dřevo“) vložíme další příkaz („ze sklepa colu“), tzv. přepínáme kontext, „vyskočíme“ z kontextu věc na kontext místo a přidáme další věc.

Zranitelný SQL dotaz

#38 U webové aplikace si to lze představit nějak takhle: mějme nějaké vyhledávání kde můžeme určit maximální cenu produktů, které si chceme zobrazit.

SQL Injection, příkazy na místu pro data

#39 Do ceny (400) vložíme další příkaz (UNION SELECT ...) a necháme si kromě knížek vrátit i jména a nějak uložená hesla. Všimněte si „vyskočení“ z čísla (400) do příkazu (UNION SELECT), tedy vlastně vložení nebo injektnutí toho příkazu na místo, kde se očekávají jen data.

SQL Injection v testovacím katalogu bonsají

#40 SQL Injection si můžete zkusit na mém testovacím webu sqlinjection.cz. Měl by vás zajímat parametr id, který jsem se sice pokusil nějak ošetřit, ale v tomto případě to není nic platné, protože aplikace očekává číslo a vstup není uzavřen do apostrofů jak můžete vidět ve zdrojovém kódu.

SQL Injection a ORDER BY

#41 Změnou parametru můžete zkusit např. zjistit počet sloupců v dotazu SELECT ... FROM:

  • id=1 ORDER BY 1
  • id=1 ORDER BY 5 (chyba, 5 sloupců v tabulce už není)
  • id=1 UNION SELECT 1,2,3,4 (přímo ukáže kam se který sloupec vypisuje)

Tímto už si ověříme, že útok SQL Injection je proveditelný.

Blind SQL Injection

#42 Nebo taky ověřit Blind SQL Injection, kdy nevidíte žádný (nový) výstup, ale jen zjišťujete jak se stránka chová:

  • id=1 AND 1=1
  • id=1 AND 1=2 (cokoliv AND nepravda je nic, bonsaj se nezobrazí)
  • id=1 AND SUBSTRING(VERSION(),1,3)=8.0 (ověření verze databáze, je-li to 8.0, něco se zobrazí)
  • id=1 AND SUBSTRING(VERSION(),1,6)=0x382e302e3435 (aktuální verze je 8.0.45, ale to už není číslo, je to řetězec, který v tomto případě musíme zapsat pomocí hexadecimálního zápisu, jinak by se apostrofy kvůli tomu pokusu o ošetření zaescapovaly do \' a SQL Injection by nefungovalo)

Někdy se Blind SQL Injection může projevit skoro až nepatrně, viděl jsem případ, kdy se buď zobrazil, nebo nezobrazil, jenom název kategorie produktu.

SQL Injection zjištění metadat

#43 Pomocí SQL Injection lze také zjistit metadata o databázi:

  • aktuální databázi a verzi serveru: id=1 UNION SELECT 1,DATABASE(),VERSION(),4 (kdyby vám nefungovalo to s hexadec zápisem výše, tak si takto ověřte, že jsem neaktualizoval server)
  • jména tabulek a sloupců, ke kterým má uživatel přístup id=1 UNION SELECT 1,table_name,column_name,4 FROM information_schema.columns

Zvláště názvy sloupců a tabulek se hodí pro další kroky.

Únik dat pomocí SQL Injection

#44 No a konečně lze také zjistit uživatelská jména a (špatně) uložená hesla:

Co víc si přát, snad jen možnost spustit nějaký kód, Remote Code Execution (RCE). To sice s využitím SQL Injection není skoro vůbec běžné, nicméně pomocí nějakého SELECT ... INTO OUTFILE 'soubor.php' a následným zavoláním toho soubor je v některých případech i RCE možné.

Ochrana proti SQL Injection pomocí oddělení kódu a dat

#45 Ochrana proti SQL Injection spočívá v oddělení kódu od dat: dotaz položíme trochu jinak, přesně určíme, co na lístku bude napsáno a zatímco „dřevo“ je předmět, tak „dřevo a ze sklepa colu“ předmět není. Pro lepší představu to přečtěte fakt rychle: „dřevo“ × „dřevoazeskle­pacolu“.

Ošetřený dotaz proti SQL Injection

#46 Podobně u posílání dotazů z webové aplikace do databáze. Když řekneme, že na místě za cena < očekáváme jen číslo, tak 400 UNION SELECT... žádné číslo není. Ty uvozovky kolem toho tučně označeného jsou spíš pro ilustraci, aby bylo jasné, že to je, nebo spíš má být, všechno jeden „kontext“ – data.

Ochrana proti SQL Injection pomocí prepared statements

#47 V aplikacích se pro takové odlišení dat od kódu používají prepared statements (předpřipravené dotazy) nebo-li vázání proměnných (variable binding). Mezi těmi pojmy je trochu drobný rozdíl, ale často je obojí vidět spolu, takže se to míchá dohromady. Prepared statements je způsob posílání dotazů pomocí příkazu PREPARE a po potvrzení serverem vykonání onoho dotazu pomocí příkazu EXECUTE. V dotazu připravovaném pomocí PREPARE ale nemusí být vůbec žádné proměnné.

Ochrana proti SQL Injection pomocí variable binding

#48 Variable binding je způsob práce s proměnnými, kdy do dotazu vložíme zástupné znaky ? (placeholdery) a v dalším kroku se na jejich místo naváže nějaká hodnota. Obvykle se dotaz se zástupným znakem nejdříve pošle na server pomocí PREPARE a v dalším kroku EXECUTE se pošlou proměnné, ale taky se PREPARE může provést jen v aplikačním serveru, při EXECUTE se mohou proměnné správně escapovat a pak to všechno poslat na databázový server najednou. To je např. případ rozšíření PDO v PHP, které pro práci s MySQL defaultně používá tzv. emulované prepared statements. Pomocí $pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false) lze používat nativní, plnohodnotné, dvoukrokové prepared statements. Pokud váš PHP framework nemá nic pro práci s databází, koukněte třeba na knihovnu Dibi a její dokumentaci, kde je používání zástupných znaků hezky vidět.

Ochrana proti SQL Injection doplněná statickou analýzou

#49 Vhodné je také přidat statickou analýzu zdrojových kódů např. pomocí nástroje PHPStan, který při správném použití může zahlásit když někde budeme řetězce lepit dohromady místo vázání proměnných, viz ilustrační příklad. Na řádku 7 říkáme, že parametr $query musí být typu literal-string, kterému PHPStan rozumí a když zjistí, že tam dáváme něco jiného než „doslova řetězec“, tak to označí jako chybu. Některé databázové knihovny literal-string využívají přímo, např. Nette Database ve třídě Explorer.

Zeď slávy (Hall of Fame) českého T-Mobile

#50 Mnoho společností neposkytuje o nalezených zranitelnostech veřejné informace, ale alespoň nějaký náznak se dá najít v tzv. Zdích slávy (anglicky Hall of Fame), což jsou seznamy lidí, kteří nějakou bezpečnostní chybu nalezli. Jednu takovou zeď má i T-Mobile, takže můžete vidět, že SQL Injection a další bezpečnostní chyby se nevyhýbají nikomu. T-Mobile ale nezveřejňuje další detaily, takže nevíme, jestli chyby byly na nějakém důležitém systému, nebo jen na něčem nepodstatném, co třeba ani neobsahuje žádná uživatelská data. Přiznám se, že já evidentně o několika chybách informace mám, ale i tak si je nechám pro sebe, nejen proto, že to vyžadují podmínky jejich bug bounty programu pro vyplácení odměn. SQL Injection jako útok byl poprvé popsán v roce 1998, ale i v tomto desetiletí jsou takové chyby stále k nalezení. Možnost získání celé databáze pomocí SQL Injection jsem naposledy někomu hlásil před půl rokem, rok předtím také, a rok předpředtím také a…

SQL Injection poslepu

#51 V některých případech nebylo na první pohled poznat, že by bylo možné SQL Injection provést, protože se jen vypsala nějaká chybová hláška o jakési výjimce. Kus SQL dotazu ve zdrojovém kódu stránky samozřejmě napověděl, takové nápovědy jsou úplně boží, ale to ještě nemusí nic znamenat. Existuje technika Time-based Blind SQL Injection, kdy se díváme jen na dobu načítání stránky a z toho můžeme pak něco usoudit. Tu techniku si také můžete vyzkoušet na mé testovací aplikaci s bonsajemi, upravte adresu tak, aby obsahovala id=1 AND SUBSTRING(VERSION(),1,1)=8 AND SLEEP(5). Ten SLEEP(5) na konci se spustí jen pokud předchozí podmínka bude pravda a prodlouží načtení stránky o 5 vteřin. Pokud by ta podmínka nebyla pravda, tak už nemá cenu spouštět zbytek, protože výsledek už je jasný – false AND cokoliv bude stejně false. Srovnej s id=1 AND SUBSTRING(VERSION(),1,1)=7 AND SLEEP(5).

Insecure Direct Object Reference

#52 Insecure Direct Object Reference, IDOR je název pro problém, kdy se například ídéčkem v URL napřímo odkazujeme na nějaký objekt, aniž bychom si dále ověřili, jestli k němu má mít uživatel přístup. Například když si v e-shopu objednáte granule pro firemního psa a chcete si stáhnout fakturu, tak odkaz může vypadat např. takhle. Když v parametru změníte číslo objednávky, třeba ho snížíte o jedničku nebo o dvě, a náhodou vám to nabídne ke stažení fakturu cizí s cizím e-mailem a telefonním číslem, tak přesně to je IDOR. Pracovně tomu proto říkám „protáčení ídéček“.

Insecure Direct Object Reference na doručovací adresu

#53 Může se to vyskytnout všude, kde se aplikace na něco odkazuje. Třeba tady, tady jsem mohl vybrat doručovací adresu, jestli to něco mám poslat sobě, nebo tátovi domů.

Cizí adresa kvůli Insecure Direct Object Reference na doručovací adresu

#54 Já jsem v tom SELECTu v elementu OPTION změnil číslo 144423 na 144420 a aplikace mi ukázala doručovací adresu a telefonní číslo někoho úplně jiného. Tímto způsobem jsem mohl získat všechny doručovací adresy všech zákazníků CZC, ale já jsem to neudělal, místo toho jsem to poslal někomu v CZC, uvedl jsem, kterou tu jedinou adresu jsem viděl a za nahlášení jsem získal finanční odměnu. Za necelý rok jsem při nákupu podobně snížil jiné číslo v jiné části aplikace a zase na mě vypadla nějaká cizí adresa. Opět jsem jim to napsal a s úsměvem slíbil, že dlouho na CZC nakupovat raději zase nebudu, což díky koupi toho e-shopu Allegrem nebude těžké dodržet.

Jak zabránit Insecure Direct Object Reference

#55 Při přístupu k nějakým objektům, fakturám, adresám atd., je potřeba kontrolovat, jestli přihlášený uživatel je vlastníkem daného objektu. Pokud potřebujete např. nabídnout fakturu ke stažení i nepřihlášenému uživateli, tak je dobré použít nějaký náhodný řetězec, např. 16+ náhodných bajtů zakódovaných do Base64 nebo převedených do hexadecimálního tvaru, nebo použít Universally Unique Identifier (UUID) verze 4, random. Pokud byste použili např. verzi 1, která se počítá z MAC adresy a času, tak to bude předvídatelné. Zkuste si to třeba na https://www.uuidgenerator.net/version1 – pokud nezmění MAC adresu např. výměnou síťové karty v serveru, tak dostanete id končící na 0e02-11f1-8de9-0242ac120002. U generování náhodných dat je pak potřeba je generovat fakt náhodně, nepoužívat funkce, které nějak pracují s časem (v PHP uniqid()) nebo s generátorem, který počítá na základě seedu (v PHP rand() a mt_rand()). V PHP použijte random_bytes() nebo random_int().

Žebříček bezpečnostních chyb OWASP Top 10 2025

#56 Bezpečnostních chyb je tolik druhů, že mají dokonce svou hitparádu, kterou jednou za pár let sestavuje sdružení Open Worldwide Application Security Project a nazývají ji OWASP Top 10. Tohle je její aktuální žebříček z roku 2025 a my jsme si některé příčky i prakticky ukázali:

  1. Broken Access Control je když přístup k něčemu je chráněn špatným způsobem, např. neměnným heslem, které se přenáší v URL
  2. Jako Security Misconfiguration označujeme nastavení, které úplně nechrání podle představ, třeba v CSP je to použití 'unsafe-inline', které jsme si také ukazovali
  3. Supply Chain, tzv. dodavatelský řetězec a jeho zranitelnosti, tedy například chyby v použitých knihovnách, jsou kapitola sama pro sebe, rozebíral jsem to v přednášce o zadních vrátkách
  4. Cryptographic Failure by bylo třeba použití anti-CSRF tokenu 123, nebo použití UUID verze 1 místo 4
  5. Do Injection spadají zmiňované útoky XSS i SQL Injection

A na ty další dojde řada až příště.

70 GB dat ukradených malwarem ke stažení

#57 Sedmá příčka v OWASP Top 10 pro rok 2025 patří Authentication Failures a když jsme u ověřování, tak si pojďme říct jak se v posledních letech také kradou hesla. Takovou klasikou je jedno heslo používané na více místech, z nichž z jednoho unikne a útočníci ho pak mohou použít pro přihlášení jinam. Takovému nehezkému jednání se říká Credential Stuffing. Další nepříjemnou možností úniku dat je malware spouštěný na počítači, který jen stáhne data a jinak se neprojevuje. Ta data pak jsou často ke stažení na Internetu, na Telegramu, Discordu apod., ani není třeba chodit na onen skoro až bájný dark web. Z množství dat se až točí hlava, tohle je jen jeden příklad. 70 GB dat stažených z napadených počítačů…

Obsah archívů ukradených dat

#58 … a to přitom jde o textová data, žádný filmy nebo empétrojky. Malware stahuje cookies, uložená a zadaná hesla i čísla platebních karet ze všech prohlížečů, informace o nainstalovaných a spuštěných programech, přístupové tokeny a klíče ze Steamu a další věci, které se dají nějak využít dále. Distribuuje se to pak nejčastěji v RAR archívech, obsah jednoho takového je na obrázku.

Infostealer malware Loader.exe v adresáři Mod Menu V5.80

#59 Takovému malwaru, který jen krade data a nic dalšího nedělá, se říká infostealer malware. Ten se často šíří jako součást různých editorů, modů a cheatů do her jako je třeba Minecraft a GTA jako třeba v tomto případě na obrázku, nebo jako cracky na Photoshop apod. Prosím, prosím, smutně koukám, nestahujte tyhle věci, fakt. Často pomáhám lidem, kterým se takový malware podařilo spustit a není to nic veselého. Jak jsem již zmiňoval, ta data jsou pak často volně ke stažení, obsahují přístupy i do spousty školních systémů (hmm, takže další způsob jak si změnit známky, najs!) Oběti si často ani neuvědomí kolik jim toho uniklo a tak přístupy fungují ještě poté docela dlouho. Provozovatelé aplikací by s tím měli počítat a třeba alespoň posílat informaci o přihlášení z neznámého prohlížeče a nabízet nebo dokonce vynucovat 2FA. Infostealery ale kradou i cookies, do kterých se ukládá session id nebo i informace o tom, že 2FA se nemusí vyžadovat např. následující měsíc, takže třeba zmíněná 2FA nestačí. Krádeží cookies ji malware umí často obejít, ani není třeba zneužívat nějakou bezpečnostní chybu v dané aplikaci.

"Tvůrci webu bych doporučil jediné: ať poslední zhasne. Vypněte to. Jděte dělat něco jiného."

#60 Co dělat když takovou nějakou chybu najdete? Já už jsem si vyzkoušel spoustu možností jak to dělat a hlavně jak to nedělat, co funguje a nefunguje, co vede k cíli a co vede spíš do háje zelenýho. Před nějakou dobou jsem byl porotcem v české soutěži WebTop100, kde jsem hledal technické i bezpečnostní chyby, a občas to bylo opravdu frustrující. Představte si, že máte na hodnocení 200 webů (v pozdějších ročnících už jen třetinu, bylo více porotců, ale zase jsme museli napsat víc textu), na jeden cca půl hodiny a i během té půl hodiny dokážete kouzlit. Z jednoho ročníku vznikl takový vtipný (vlastně spíš smutný) článek o tom, jak jsem ukradl session i objednal vodku za kvintiliardu. Do hodnocení jsem pak psal takové „perly“ jakože poslední má zhasnout. Akorát se to zcela míjelo účinkem, což mi tenkrát nedocházelo. Dnes už bych to nenapsal (a abych to opravdu nenapsal, tak jsem s hodnocením již dávno skončil, vidíte, Defense in Depth v praxi).

"Celý svět zná vaše heslo na Seznam.cz. Při zjištění jeho síly při registraci i změně se přenese nešifrované po HTTP."

#61 Začalo mi to docházet někdy v době, kdy jsem tenkrát na Twitteru, dnes na X, napsal třeba tohle. Potřeboval jsem screenshot registrace na Seznamu do nějaké přednášky a během toho screenshotování jsem objevil, že Seznam posílá heslo pro kontrolu po nešifrovaném HTTP, takže ho někdo může odposlechnout. Heslo se při samotné registraci odeslalo šifrovaně po HTTPS, ale při té kontrole ne. Neměl jsem mnoho času na zjišťování komu to nahlásit a ani mi to tenkrát nepřišlo jako nějaký extra velký problém, což se zpětně sám sobě divím, a tak jsem jen vyrobil screenshot a příspěvek a šel dodělat ty slajdy. Akorát to pak nějak explodovalo a docela dost lidí to retweetovalo, takže se to dostalo samozřejmě i do Seznamu.

"Ahoj, byl jsem v hospodě s klukem, kterej kvůli Tobě zestárnul o rok Příště je máš kontaktovat a zveřejnit to o 24 hodin později"

#62 Jenže mi nedošlo třeba to, že to heslo bude i někde v access logu, protože se poslalo metodou GET. A že k logům má určitě přístup docela dost lidí v Seznamu a že ti by taky hesla vidět neměli. O pár dní později mi napsal bývalý kolega, že mi jeho bývalý spolužák vzkazuje, že to příště mám udělat jinak. Najednou jsem za tím mým příspěvkem uviděl konkrétní osobu a její stres při řešení nějaké situace, který jsem těmi tenkrát max 140 znaky způsobil. Měl pravdu, představil jsem si sám sebe a pochopil že takhle ne. Na přednášce v Seznamu o pár měsíců či let později jsem za tím o rok starším člověkem přišel, představil se a omluvil se mu. Od té doby ho považuji za kamaráda a snad i on mě. Bohužel jsem podobný hlouposti tropil častěji, takže to nebyla náhoda, jako třeba když jsem našel zdrojáky webu Air Bank.

Mobilní aplikace Seznam - zabezpečení neznám

#63 Přibližně od té doby to dělám trochu jinak. Když jsem se podíval na zoubek mobilnímu browseru a mobilnímu e-mailu od Seznamu, tak jsem taky našel pár docela zásadních věcí. Tenkrát jsem to napsal jako článek…

"Aktualizace: Článek jsem po napsání poslal do Seznamu, jeho vývojáři si vyžádali necelé dva týdny na opravu chyb"

#64 … a poslal do Seznamu, ať dají vědět, kdy to opraví, že článek vyjde až potom. Po vyřešení jsem jen dopsal aktualizaci, že v nové verzi jsou ty věci opravené a článek mohl vyjít. Asi, spíš teda asi určitě, to byl lepší způsob pro všechny strany, ale jen já musel napsat celej článek, chjo!

Bez emocí

#65 Díky těmhle zkušenostem jsem si vytvořil „návod“, jak chyby hlásit. Hlavně bez emocí, žádné „ať poslední zhasne“, nic takového. Lidi pak vidí jak to říkám, ale už ne co říkám. To, že někteří teda nevidí ani jedno, už ale záleží na míře jakési „osvícenosti“ dané firmy.

Čas na opravu a koho kontaktovat

#66 Je fajn dát lidem čas na opravu. Googlovo Project Zero dává 90 dní, já většinou píšu do konce měsíce (někdy i do konce toho příštího, zvlášť pokud je třicátého), ale spíš je to na domluvě. Minimálně by měly být dva týdny, aby si lidi stihli dokončit svou rozdělanou práci a nemuseli hned jít něco fixovat, ale ze zkušenosti to stejně obvykle vyřeší co nejrychleji. Ono je to často trochu prekérní situace, protože když vývojář chybu neopraví, tak ji stejně nemůžete jen tak zveřejnit, protože by ji mohl někdo zneužít a odnesli by to vlastně nevinní uživatelé, a možná byste za to mohli částečně i vy. Další možnosti jsou kontaktovat třeba Úřad pro ochranu osobních údajů (ÚOOÚ), Národní úřad pro kybernetickou a informační bezpečnost (NÚKIB) nebo Národní CSIRT, záleží o co se jedná. Kdybyste nevěděli, tak klidně napište, něco vymyslíme.

Odměna za nahlášení chyby

#67 Myslím si, že dobrým hlásitelům náleží nějaká odměna, kolik je velice individuální. Koukněte na „ceník“ T-Mobile (a taky na již zmíněnou zeď slávy, ze které vám mávám), ale z vlastní zkušenosti mohu říci, že odměny se pohybují spíš v řádu tisíců a že cokoliv potěší (např. od dříve zmíněného CZC to bylo 10000 poprvé, 6000 Kč podruhé). Do výše odměny by se mělo promítnout to, kolika lidí a uživatelů by se to dotklo. Např. hlášení chybějící druhé úrovně ochrany, třeba chybějící hlavička Content-Security-Policy nebo jiná by ale na odměnu nemělo mít nárok, protože to vyžaduje nějaký jiné „kdyby“ a „aby“. Do odměny by se mělo promítnout i to, kolik by stálo hasit malér, kdyby někdo ta data třeba opravdu stáhl a „hodil“ na Ulož.to. Snažím se posílat kompletní hlášení, žádné „zaplaťte a pak vám řeknu kde je ten problém“, to je hloupost (a vydírání) a zároveň se snažím firmy učit, že poskytovat odměnu je normální. (Na obrázku je sice můj účet, jen to nejsou americký dolary, ale chilský pesos, zůstatek na účtu po přepočtu asi necelých 20 tis. Kč).

Odměna za nalezení a nahlášení SQL Injection na Alfa.cz

#68 Tohle je hezký příklad. Přemýšlel jsem o novém počítači a podařilo se mi získat uvedenou slevu, protože šlo získat celou databázi alfa.cz (dnes T.S.Bohemia) pomocí SQL Injection. To jsem ale objevil dávno předtím, než jsem zjistil, že potřebuju nový laptop. Od prvního e-mailu v prosinci 2019 do zprávy na obrázku uběhlo celkem 8 měsíců, opraveno bylo ale naštěstí hned druhý den od nahlášení.

Jsme na jedné lodi

#69 Když vám napíše někdo jako já a navíc stylem jakým to píšu teď, tak si buďte jistí, že jsme na jedný lodi, i když to tak nemusí na první pohled vypadat. Protože napsat to na Twitter je vždycky jednodušší, míň práce, míň všeho. Teda aspoň pro toho, kdo vám chce nějakou chybu nahlásit. Můj bonmot, který v této souvislosti často používám, je ten, že nahlásit vám chybu by mělo být jednodušší, než to prostě napsat na Twitter/X. Ale je nutný poznamenat, že ne všichni na tý stejný lodi jsou. „Našel jsem na webu chybu, nabízíte nějaký odměny a kolik?“ je často k viděni i v mém inboxu, většinou od zahraničních „hekerů“. Na dotaz „a na jakým webu“ (mám jich víc) a „jakou chybu“ mi nikdy nikdo neodpověděl, většinou to pak jsou nějaký chybějící hlavičky a podobný většinou nepodstatný věci, jako třeba „easter eggy“ na mým webu – mnoho lidí nerozumí výstupům ze svého skeneru a prostě to zkusí.

Když najdu chybu, tak nestahuju celou databázi nebo všechny faktury

#70 Když v nějaké aplikaci najdu bezpečnostní chybu a chci ji ověřit, tak rozhodně nestahuju celou databázi, jenom se podívám pomocí SELECT COUNT kolik záznamů obsahuje, abych měl nějaký přesvědčivý důkaz. Pokud lze stáhnout např. cizí faktury, tak je všechny nestahuji a pokud uvidím nějakou cizí, tak přesně do hlášení chyby napíšu co jsem viděl a že to dál nijak nepoužiju. Chci totiž nahlásit bezpečnostní chybu, jejíž oprava ochrání trochu sobecky i moje osobní údaje, nechci danou firmu vydírat, nechci se jim mstít a ani netoužím se stát tím, kdo třeba i nechtěně ten únik dat ve finále způsobí. Do hlášení poté napíšu i svou IP adresu, aby se v logu mohli přesvědčit, že jsem celou databázi opravdu nestáhl. Píšu všechno co vím, nic si nenechávám na později (až zaplatí), což teda často vede k tomu, že to hlášení píšu hodinu i víc. Telefon často k volání nepoužívám, ale když už musím někomu kvůli nahlášení zavolat, tak volám ze svého čísla a neběžím si do večerky koupit novou SIM kartu. Chybu totiž skoro vždy najdu při běžném používání té aplikace (no dobře, běžném pro mě), takže se pak najednou pokoušet skrývat by bylo nenápadné jak sáňky v létě.

"Státní zástupce potažmo soud by jeho jednání pravděpodobně odložil"

#71 Když jsem před několika lety měl přednášku pro publikum, ve kterém byli převážně lidé práva a zákonů více znalí, tak přesně tohle jsem jim také povídal. Padl pak dotaz jestli to co dělám nenaplňuje formální znaky trestného činu a jestli už někdo nezkoušel třeba nějakou žalobu. Na dotaz jsem ve zkratce odpověděl, že se snažím firmám nedávat k tomu příležitost, že se snažím dělat to jak tu píšu. Pod video záznamem se pak objevil komentář, který mě doplnil a trochu uklidnil, že to je (snad) ta správná cesta:

Státní zástupce potažmo soud by jeho jednání pravděpodobně odložil na subjektivní stránku trestného činu (TČ), kde prokazatelně pan Špaček má několik objektivních skutečností dokazujících […], že jeho motiv není spáchání TČ (např. jeho veřejná IP, transparentní jednání, oznámení provozovateli bez výhružek apod.). Pochopitelně pokud by např. způsobil škodu např. stažením oněch údajů, již je to TČ, protože překročil rozsah svého motivu.

Snad nikdy nebudu mít příležitost to ověřit v praxi.

Hlášení chyb na adresu uvedenou v security.txt

#72 Teď z druhé strany, co když někdo, třeba já, najde chybu na vašem webu a chce vám ji nahlásit? No jo, ale kam takovou věc ten někdo má poslat? Když nějakou chybu hlásím, tak moc možností není: e-mailový adresy ve WHOIS už nejsou, navíc to byly spíš adresy majitelů domén, které se mohou od sekuriťáků nebo dodavatelů software lišit. Na info@ nebo support@ to někdy nechcete posílat, protože tam často sedí třeba brigádníci a těm nechcete psát, že jejich chlebodárci unikla databáze např. Hlášení někdy zabere mnohem víc času, než nalezení chyby. Zrovna v době psaní tohoto textu se snažím už měsíc nahlásit nějaké chyby, píšu na různé adresy, ale je také možné, že to chytá nějaký spam filtr. Tuhle nudnou (a nutnou) část řeší soubor security.txt umístěný na předvídatelném místě (/.well-known/security.txt) s různými informacemi, kam a jak a co hlásit. Každému doporučuji takový soubor na web umístit, abych to měl jednodušší, až jim budu chtít napsat. O tom, co by v něm mělo být, se dozvíte v mém článku, který vám alespoň na začátku bude něco připomínat, teda pokud jste dávali pozor. Na adresu uvedenou v security.txt vám brzy začne chodit „security spam“, hlášení nerelevantních chyb a velmi manipulativní dotazy na to, jestli vyplácíte odměny za nálezy, ale viz dříve. Koneckonců spam může chodit a chodí i jinam.

Spolupráce, bez emocí, transparentně

#73 Když byste někdy potřebovali psát třeba nějakou bezpečnostní tiskovou zprávu, tak rozhodně není dobré psát stylem „tak jako skoro každému v této době, i nám unikla vaše data“ a „bezpečnost vašich dat bereme vážně, priorita blabla“, protože to většinou není pravda a dá se to jednoduše ověřit. Komunikujte transparentně, bez emocí, spolupracujte, a to platí pro všechny zúčastněné strany. A nejen webové aplikace budujte tak, aby vám nikdo takto psát ani nemusel. Vzpomněl jsem si na citát od kosmonauta Franka Bormana: „Vynikající pilot používá svůj vynikající úsudek, aby se vyhnul situacím, které vyžadují použití jeho vynikajících dovedností.“ Je lepší krizovým situacím předcházet, než je muset řešit. Držím palce!

Michal Špaček

Michal Špaček

Vyvíjím webové aplikace, zajímá mě jejich bezpečnost. Nebojím se o tom mluvit veřejně, hledám hranice tak, že je posouvám. Chci naučit webové vývojáře stavět bezpečnější a výkonnější weby a aplikace.

Veřejná školení

Zvu vás na následující školení, která pořádám a vedu: