Noční můry webového vývojáře

59
NOČNÍ MŮRY NOČNÍ MŮRY WEBOVÉHO WEBOVÉHO VÝVOJÁŘE VÝVOJÁŘE Michal Špaček Michal Špaček www.michalspacek.cz www.michalspacek.cz @spazef0rze @spazef0rze Na fotce je Mike Rogers, šéf americké NSA. Tato verze slajdů obsahuje navíc poznámky nejen pro ty, kteří na přednášce nebyli.

Transcript of Noční můry webového vývojáře

Page 1: Noční můry webového vývojáře

NOČNÍ MŮRYNOČNÍ MŮRYWEBOVÉHO WEBOVÉHO VÝVOJÁŘEVÝVOJÁŘE

Michal ŠpačekMichal Špačekwww.michalspacek.czwww.michalspacek.cz @spazef0rze@spazef0rze

Na fotce je Mike Rogers, šéf americké NSA.Tato verze slajdů obsahuje navíc poznámky nejen pro ty, kteří na přednášce nebyli.

Page 2: Noční můry webového vývojáře

POUZE PROSTUDIJNÍ ÚČELY

NENABÁDÁMK TRESTNÉ ČINNOSTI

Webová bezpečnost je tak trochu jako matematika nebo sekera nebo kladivo. Když s těmito znalostmi nebo nástroji uděláte něco nepěkného, půjdete bručet. Můj anděl strážný mi doporučil vás upozornit.

Page 3: Noční můry webového vývojáře

Trestní zákoník – č. 40/2009 Sb. § 182

Porušení tajemství dopravovaných zpráv

(1) Kdo úmyslně poruší tajemství

a) …

b) datové, textové, hlasové, zvukové či obrazové zprávy posílané prostřednictvím sítě elektronických komunikací a přiřaditelné k identifikovanému účastníku nebo uživateli, který zprávu přijímá, nebo

c) neveřejného přenosu počítačových dat do počítačového systému, z něj nebo v jeho rámci, včetně elektromagnetického vyzařování z počítačového systému, přenášejícího taková počítačová data,

bude potrestán odnětím svobody až na dvě léta nebo zákazem činnosti.

Page 4: Noční můry webového vývojáře

Trestní zákoník – č. 40/2009 Sb. § 230

Neoprávněný přístup k počítačovému systémua nosiči informací

(1) Kdo překoná bezpečnostní opatření, a tím neoprávněně získá přístup k počítačovému systému nebo k jeho části, bude potrestán odnětím svobody až na jeden rok, zákazem činnosti nebo propadnutím věci nebo jiné majetkové hodnoty.

V § 230, odstavci (1), se vůbec nehovoří o úmyslu. Jakákoliv bezpečnostní opatření na webu ale bohužel často chybí.

Page 5: Noční můry webového vývojáře

Chyby tedy raději hledat nebudeme, jen si o nich budeme povídat. Chyby často hledají jiní a píšou o nich pak na webu. Když budete nějaký popis hledat, raději použijte Tor. Uber nechal přístupový klíč k databázi veřejně na GitHubu, někdo ho zneužil a Uber teď chce po GitHubu IP adresy všech návštěvníků té stránky. Tor funguje takto.

Page 6: Noční můry webového vývojáře

Jedním z webů, kde se občas zveřejňují chyby je český SOOM. Nezáleží na tom, jak velký máte web nebo kolik máte uživatelů, vždycky jste pro někoho dost dobrý cíl.

Page 7: Noční můry webového vývojáře

SQL INJECTIONSQL INJECTION

Velmi často je zmiňována útok SQL Injection. Ten vypadá přesně takto. To znáte, ne? Někteří úplně přesně takhle, že? Fajn, pro ty, kteří jste to nezažili nebo to s radostí zapomněli, tak opakování: SQL Injection spočívá v úpravě dotazu odesílaného do databáze.

Page 8: Noční můry webového vývojáře

SELECT jmeno, adresa

FROM vozidla

WHERE rz = '$prectena';

Představte si aplikaci pro kameru, která měří rychlost. Pod kamerou projede auto, kamera přečte jeho registrační značku a když pojede rychle, tak z databáze vytáhne adresu a pošle pokutu. Zjednodušeně.

Page 9: Noční můry webového vývojáře

SELECT jmeno, adresa

FROM vozidla

WHERE rz = '1AM 1337';

1AM 1337

Když pod kamerou projede nějaký elitní pražan, tak obslužný software kamery pošle do databáze tento dotaz, najde se jméno a adresa a už se tiskne milostný dopis.

Page 10: Noční můry webového vývojáře

' OR 0='0

Když pod kamerou projede třeba tohle auto ze Severní Karolíny, tak… to nebude fungovat, protože nejspíš nebude mít značku vpředu. Ale kdyby to fungovalo, tak se stane tohle:

Page 11: Noční můry webového vývojáře

SELECT jmeno, adresa

FROM vozidla

WHERE rz = '' OR 0='0';

' OR 0='0

Do databáze se pošle tento dotaz, to červené a podtržené je přečtená značka. OR 0='0' je vždy pravda, takže pokuta se pošle všem. HA HA HA.

Page 12: Noční můry webového vývojáře

' OR 1=1; --

Někdy se používá tenhle minitrik s komentářem, protože nevíme, co všechno na konci dotazu je, tak to prostě zakomentujeme.

Page 13: Noční můry webového vývojáře

SELECT jmeno, adresa

FROM vozidla

WHERE rz = '' OR 1=1; --';

' OR 1=1; --

Dotaz pak bude vypadat třeba takto, všimněte si, že vše za středníkem je zakomentováno. Středník se dá vynechat a v MySQL by bylo potřeba ještě doplnit mezeru za pomlčka pomlčka. Výsledek je ale stejný.

Page 14: Noční můry webového vývojáře

Mno, tak když by pod takovou kamerou projel tenhle frajer, tak… Ale tohle by fungovalo jen v případě, že by šlo najednou poslat více dotazů oddělených středníkem. Za určitých okolností to lze i v MySQL, třeba při SQL Injection v emulovaných prepared statements v PDO.

Page 15: Noční můry webového vývojáře

Software kamer na silnici ale není to, s čím se často setkáváme, zvlášť pokud nemáme řidičák. Pojďme si to ukázat spíš na nějaké webové aplikaci. Tady jsme chtěli zobrazit produkty značky 30'abc – to je známá čínská značka, něco jako McDonald's – v obojím je apostrof a to se hodí.

Page 16: Noční můry webového vývojáře

V téhle webové aplikaci došlo k chybě, protože dotaz není syntakticky správně, ale vývojáři nám k tomu naštěstí vypsali spoustu dalších informací, jako třeba celý dotaz, takže přesně víme, jak vypadá a co máme dělat. Tohle nikdy nedělejte, když dojde k chybě, tak tohle uživatele vůbec nezajímá. Zajímá to jen mizery.

Page 17: Noční můry webového vývojáře

"… WHERE znacka = '{$_GET['znacka']}'"

Dotaz, který aplikace odesílala, mohl vypadat třeba nějak takto, parametr je sice ohraničen do apostrofů, ale bez jakéhokoliv ošetření. Stačí pak apostrofem z toho ohraničení vyskočit a zbytek dotazu upravit dle přání.

Page 18: Noční můry webového vývojáře

Někdy není apostrof potřeba, protože není z čeho vyskakovat, to je třeba tento případ. Aplikace očekává číslo a my pošleme řetězec.

Page 19: Noční můry webového vývojáře

'… WHERE id = ' . $_GET['id']

Kód v tomto případě mohl vypadat takto, parametr je do dotazu vložen bez ohraničení apostrofy, takže i kdyby náhodou ochrana spočívala v nějakém ošetření apostrofů, aby nešlo vyskočit, tak je to jedno, apostrof zde vůbec není třeba psát.

Page 20: Noční můry webového vývojáře

STOP! DEMO TIME!SQL Injection si můžete beztrestně vyzkoušet třeba u mě, na adrese http://exploited.cz/sql/products.php

Page 21: Noční můry webového vývojáře

BLINDBLINDSQL INJECTIONSQL INJECTION

Při útoku SQL Injection je běžné, že aplikace zobrazuje nějaké chybové hlášky nebo něco vypisuje do stránky. Často ale nic takového nevidíte. Blind SQL Injection spočívá v tom, že jen pozorujete chování stránky.

Page 22: Noční můry webového vývojáře

Prepared statements (PDO)

Řešením mohou být třeba prepared statements, které jsou v PHP v PDO extenzi. Fungují tak, že si v dotazu pojmenujete zástupné místo (:id) a pak na něj navážete proměnnou. Někdy se místo těchto pojmenovaných míst mohou použít jen otazníky. Ale pozor, v PHP tohle neumí třeba vložit pole, takže si musíte spoustu věcí dopsat a můžete to podělat. Jako třeba Drupal, ten si právě takto zavlekl do kódu SQL Injection.

Page 23: Noční můry webového vývojáře

Je tedy lepší použít něco, co už tyto věci umí, třeba dibi, nebo aspoň dávejte velký pozor při implementaci, v tom případě vám přeji hodně štěstí.

Page 24: Noční můry webového vývojáře

$query where(→'p.product = (%sql)','SELECT id FROM products

WHERE code = \'' . $code . '\'');

No a nakonec, ani s dibi nesmíte udělat třeba tohle. $code je tam prostě nalepené, což je špatně, protože i toto je zranitelnost SQL Injection. Do dotazu raději nic takto nevkládejte, vždy využijte vázání proměnných.

Page 25: Noční můry webového vývojáře

Mark Burnett https://xato.net/passwords/password-stock-photos/

Může se stát a asi se i stane, že někde přece jen uděláte chybu a někdo získá přístup k databázi. Nebo se dostane k zálohám. V databázi je spousta věcí, údaje o dodavatelích apod. jsou sice jen vaše, ale hesla uživatelů vaše nejsou. Hesla uživatelů se pak dají použít na páchání zla přímo těm uživatelům, třeba na přístup k jejich e-mailovým schránkám. Hodně uživatelů používá jedno heslo na více místech.

Page 26: Noční můry webového vývojáře

Plaintext

MD5(heslo)

SHA-1(heslo)

SHA-2(heslo)

Takto hesla neukládejte. MD5, ani SHA-1 nebo SHA-2 nepoužívejte. Pokud dva uživatelé budou mít stejné heslo, budou mít i stejný hash. Takto zahashovaná hesla lze najít i v tzv. předpočítaných tabulkách (rainbow tables). Lidé, co to myslí vážně s lámáním hesel už rainbow tables nepoužívají, zabírají moc místa a dlouho se vytváří.

Page 27: Noční můry webového vývojáře

Jenže Google takových tabulek pár indexuje, takže pro rychlé vyzkoušení stačí vzít hash a dát ho do Google. Profi práce se ale takto nedělá. Profíci používají grafické karty a velké slovníky (které obsahují i sousloví a celé věty, nejenom jednotlivá slova) pro slovníkové a hybridní útoky.

Page 28: Noční můry webového vývojáře

MD5(heslo + salt)SHA-1(heslo + salt)

Pro zamezení použití rainbow tables a narozeninových útoků se používá salt. Salt je unikátní a náhodný pro každý účet, není tajný a je v db uložen v čitelné podobě, neslouží ke zpomalení lámání hesel. Takže takhle to také nedělejte. Tohle je sice lepší, než jen MD5, ale…

Page 29: Noční můry webového vývojáře

Salt nezpomaluje lámání hesel, takže hrubou silou 8 grafických karet se pořád dá lámat hodně rychle. Tenhle stroj generuje 80 miliard MD5 hashů/sec. I můj laptop dělá 30 milionů MD5 hashů/sec a asi 20M SHA-1/sec. Podobné stroje pro profíky staví Jeremi Gosney.

Page 30: Noční můry webového vývojáře

bcrypt!

Blowfish hashingTakže jak na to? Pro ukládání hesel použijte bcrypt, někdy nazýván „Blowfish hashing“ (samotný název Blowfish představuje symetrickou šifru, tedy něco jiného a nevhodného pro ukládání hesel). Bcrypt podporuje salt a dá se řídit jeho rychlost. Doporučuje se nastavit cost parametr na 10 (nebo více), což znamená, že se jedno heslo zahashuje v cyklu 2×1010. Jedno hashování trvá zhruba 80 ms, uživatel to ani nepostřehne, ale útočníka to podstatně zpomalí.

Page 31: Noční můry webového vývojáře

crypt() salt=$2y$…password_hash() password_verify()

Bcrypt je v PHP implementován ve funkci crypt() od 5.3.7. Nastavuje se saltem, který začíná na $2y$. Salt si musíte vymyslet sami, jenže to je masakr. Saltem to můžete pokazit, třeba když použijete všude stejný. Jakákoliv funkce, která po vás chce salt je na nic. Od PHP 5.5 jsou dostupné funkce, kterým jen dáte heslo a ony ho zahashují nebo ověří, nic víc neřešíte. Pro starší PHP raději použijte password_compat. Od Nette 2.2 můžete použít Nette\Security\Passwords.

Page 32: Noční můry webového vývojáře

scrypt

PBKDF2

Argon2Další vhodné algoritmy jsou scrypt a PBKDF2. Scrypt je pro PHP dostupný pouze jako PECL extenze. PBKDF2 je v PHP od 5.5. V létě 2015 bychom měli znát výherce Password Hashing Competition, tedy nový a ještě lepší algoritmus pro ukládání hesel. Snad bude brzy dostupný i v PHP a dalších jazycích. (Aktualizace: novou hashovací funkcí je Argon2, až bude dostupná ve vašem oblíbeném jazyce, tak ji používejte.)

Page 33: Noční můry webového vývojáře

Zdroj: http://www.flickr.com/photos/40852961@N04/5439723004/

Neposílejte hesla e-mailem, ani po registraci. Ne všechna spojení mezi servery jsou šifrovaná a někdo tedy e-mail s heslem může odposlechnout. Navíc když si uživatel zprávu s heslem stáhne do počítače, tak to heslo tam může někdo přečíst. Uživatel si navíc heslo zaslané e-mailem spíš nikdy nezmění.

Page 34: Noční můry webového vývojáře

Zdroj: http://www.flickr.com/photos/reidrac/4696900602/

Reset zapomenutého hesla provádějte tak, že uživateli pošlete odkaz, který bude mít náhodný token, který bude za hodinu expirovat a bude platit jen jednou. Po kliknutí si bude moci uživatel nastavit heslo. Negenerujte mu nové heslo a neposílejte ho ani e-mailem. Po změně hesla uživatele informujte, inspirujte se třeba u Facebooku.

Page 35: Noční můry webového vývojáře

Jeden uživatel=

Jedna databáze

Nebuďte líní a pro každou aplikaci vytvářejte nové databázové uživatele a nová hesla. Nastavte jim právo pro přístup pouze do jedné databáze. Jeden uživatel s možností přístupu k databázím více aplikací může být problém ve chvíli, kdy jedna z těch aplikací bude mít chybu SQL Injection.

Page 36: Noční můry webového vývojáře

Žádný kód v databázi

eval(), HTML

V databází nemějte ani žádný PHP kód, který budete chtít spouštět pomocí eval(), viz Drupal 7 Remote Code Execution, ani HTML na formátování, protože to HTML může útočník s přístupem do databáze upravit a dát tam třeba svoji reklamu. Použijte třeba Texy s vypnutým HTML, aby nešlo propašovat ani vlastní Javascript.

Page 37: Noční můry webového vývojáře

Cross-Site Scripting

XSS

Další častou chybou je XSS, tedy útok, během kterého útočník vloží nějaký kód na stránku, JavaScript, obrázek, HTML formulář apod.

Page 38: Noční můry webového vývojáře

XSS vypadá třeba takto, viz parametr zb v URL. Tím se do stránky propašoval JavaScript, který zobrazil tohle alert okno.

Page 39: Noční můry webového vývojáře

V kódu napadené stránky to vypadá takto. Modře označený text je to, co se do HTML dostalo z parametru zb v URL.

Page 40: Noční můry webového vývojáře

JavaScript od útočníka může vypadat třeba takto. Tohle konkrétně krade přihlašovací jméno a heslo z přihlašovacího formuláře po jeho odeslání. JavaScriptem lze někdy získat i session id z cookies, pak není třeba krást hesla. Ačkoliv heslo se vždycky hodí.

Page 41: Noční můry webového vývojáře

htmlspecialchars($string, ENT_QUOTES)

Výstupy ošetřujte pomocí htmlspecialchars() s parametrem ENT_QUOTES aby se převáděly i apostrofy. Ty se standardně nepřevádí. Lepší je ale použít nějaký šablonovací systém, který toto řeší za vás automaticky. Nikdy si nezkracujte cestu a automatické ošetřování nevypínejte.

Page 42: Noční můry webového vývojáře

Nepoužívat

strip_tags()

proti XSS

Funkce strip_tags() není ta správná ochrana pro XSS. Odstraňuje pouze celé značky, ale to útočníkovi nezabrání vložit třeba nový atribut onclick do existujícího formulářového políčka.

Page 43: Noční můry webového vývojáře

X-XSS-Protection: 0X-XSS-Protection: 1

X-XSS-Protection: 1; mode=block

A když náhodou zapomenete něco ošetřit (nebo když vypnete automagické ošetřování), tak tahle HTTP hlavička může vaše návštěvníky zachránit. Aktivuje XSS Auditor v prohlížečích a v pokud se na stránku dostane nějaký JavaScript přes adresu nebo z formuláře, tak jej nespustí, případně celou stránku vůbec nezobrazí, pokud použijete mode=block. A to byste měli, protože některé prohlížeče se stránku pokusí opravit, ale opraví ji tak dobře, že vložený JavaScript se stejně spustí.

Page 44: Noční můry webového vývojáře

default-src 'none'

script-src 'unsafe-inline'

script-src ajax.googleapis.com

Content-Security-Policy

Hlavička X-XSS-Protection neřeší případy, kdy útočník uloží JavaScript do databáze, například do článku. Dopad takového problému můžete zmírnit třeba pomocí Content Security Policy (CSP). Tato hlavička explicitně povoluje zdroje, odkud se mohou načítat skripty, obrázky, styly aj. Hodnota unsafe-inline povoluje inline JS, tedy JavaScript vložený přímo do HTML kódu. To byste neměli dělat, veškerý kód byste měli dát do externích souborů a unsafe-inline nepoužívat. Podle mé zkušenosti s tím ale nefunguje třeba Google Tag Manager, bohužel.

Page 45: Noční můry webového vývojáře

script-src 'strict-dynamic' 'nonce-<random>''unsafe-inline' http: https:;

object-src 'none';

report-uri https://….report-uri.io/…

Lepší Content-Security-Policy

Zavést CSP na běžící web není nic jednoduchého, právě třeba kvůli Google Tag Manageru. Proto je jednodušší použít tuto hlavičku. strict-dynamic je vlastnost z CSP3, která dovolí skriptům již vloženým do stránky vkládat další skripty, tedy přesně to, co dělá právě např Tag Manager. HTML značky script pro vkládání JavaScriptu do stránky je nutné označit pomocí atributu nonce a jeho hodnotu pak přidat do direktivy script-src. Hodnota by měla být náhodná a měla by se měnit při každém načtení stránky. Zbytek, včetně 'unsafe-inline', je pak pro starší prohlížeče, které CSP3 neumí. object-src zakáže vložit Flash, který také může způsobit XSS. Díky direktivě report-uri bude prohlížeč odesílat reporty o porušení nastavení politiky, posílejte si je na službu report-uri.io. Kvalitu nastavení CSP si můžete zkontrolovat na csp-evaluator.withgoogle.com, na stejném místě můžete stáhnout i extenzi do Chrome.

Page 46: Noční můry webového vývojáře

session.cookie_httponly: true

session.cookie_secure: true

HTTP-Only cookies

Pomocí XSS se často kradou sessions - JS načte z document.cookie session cookie a pošle ji útočníkovi, ten si ji vloží do svého browseru a vydává se za uživatele, který útočníkův kód spustil. Parametr httponly zabrání JS ve čtení dané cookie, v document.cookie pak není dostupná. Tuhle direktivu si nastavte. HNED. TEĎ. A pokud máte web pouze na HTTPS, což byste měli mít, tak nastavte i secure parametr. A tím se pomalu dostáváme k…

Page 47: Noční můry webového vývojáře

HTTPS=

How To Transfer Private Shit

Nikomu tedy není nic do toho, jaké články si zrovna čtu, jaká odesílám hesla, co si zrovna prohlížím a nikdo nemá právo modifikovat data, která stahuji.

Page 48: Noční můry webového vývojáře

STOP! DEMO TIME!Umím donutit vaše zařízení, aby se připojila k mojí Wi-Fi a HTTPS potom docela oceníte. Ukázku najdete v druhé půlce mojí přednášky z konference PHP live 2014.

Page 49: Noční můry webového vývojáře

TLSTransport Layer Security

To S v názvu HTTPS neznamená SSL, ale Secure. SSL je protokol z minulého tisíciletí a už by se neměl dnes používat. Nahradil ho protokol TLS, aktuálně ve verzi 1.2, v březnu 2015 je aktuálně k dispozici návrh TLS 1.3.

Page 50: Noční můry webového vývojáře

Pro web na HTTPS (celý web, jinak to nemá smysl) vlastní IP adresu potřebovat NE-BU-DE-TE.

Page 51: Noční můry webového vývojáře

SNIServer Name Indication

Od roku 2003 existuje rozšíření TLS s názvem SNI, které zajistí, že název serveru pošle prohlížeč nešifrovaně a server tak bude moci poskytnout prohlížeči správný certifikát. SNI umí IE od verze 7 na WinVista a pozdějších. Na WinXP to nefunguje v žádném IE, ve Firefoxu a Chrome ano. Firefox umí SNI od verze 2, Chrome od verze 6 na WinXP (na WinVista v jakékoliv verzi), Safari od verze 3, Android od 3 dále. Pokud potřebujete podporovat starší verze, tak budete pořád vlastní IP adresu potřebovat.

Page 52: Noční můry webového vývojáře

HSTSHTTP Strict Transport Security

Až budete mít celý web na HTTPS, tak přidejte hlavičku Strict-Transport-Security. HSTS, zajistí, že browser vnitřně přesměruje na HTTPS a nebude vůbec nebude posílat požadavek po HTTP. Ten by stejně jenom vygeneroval přesměrování na HTTPS verzi. Požadavek na HTTP tedy nepůjde zachytit a odstranit to přesměrování a tím efektivně HTTPS odstranit a převést na HTTP.

Page 53: Noční můry webového vývojáře

MIXED CONTENTMIXED CONTENTPo převodu na HTTPS si dávejte pozor na tzv. mixed content, tedy abyste do stránek na HTTPS nenačítali HTTP obsah, browser by tento obsah mohl zablokovat. Typicky se jedná třeba o videa, styly a obrázky.

Page 54: Noční můry webového vývojáře

REFERRER<meta name="referrer" content="origin">

Pozor na referrer, ten se ze stránek na HTTPS nepředává do stránek na HTTP, takže je potřeba si správně odkazy z HTTPS nějak označit, jinak nebudete v Google Analytics vědět, odkud návštěvník přišel. Můžete také použít meta referrer (tady už správně s dvěma „r“), ten zajistí, že se referrer bude předávat i z HTTPS na HTTP, pokud použijete hodnotu origin (předá se pouze doména) nebo unsafe-url (předá se celé URL včetně parametrů), podpora v Chrome od konce roku 2011, Firefox od verze 36, IE od verze 12/Edge.

Page 55: Noční můry webového vývojáře

HTTPONLY&

SECUREHTTP-only cookie jsem zmiňoval jako obranu proti krádeži session pomocí XSS. Secure parametr jsem také už nakousl. Ten zajistí, že browser nepošle cookie (třeba se session id) po nešifrovaném spojení, i kdyby ho útočník přesvědčoval, že to má udělat, takže nepůjde odposlechnout. Bez toho je HTTPS jakoby bez toho S.

Page 56: Noční můry webového vývojáře

NepotřebnýNebezpečnýNepořádekNene

Nene… chávejte nepotřebné soubory na webu.

Page 57: Noční můry webového vývojáře

http://www.example.com/app/config.neon

Konfigurace Nette v čitelné podobě. Přibližně jedno procento webů napsaných v Nette má konfiguraci volně a veřejně přístupnou. Tyhle soubory nesmí být přístupné pomocí prohlížeče, patří mimo document root nebo k nim musí být zakázán přístup pomocí souboru .htaccess.

Page 58: Noční můry webového vývojáře

http://www.example.com/.git

git clone http://www.example.com/.git

Pokud pracujete s Gitem, tak si zkontrolujte, jestli na webu nemáte adresář .git a pokud máte, tak ho smažte a zajistěte, aby se tam zase nedostal. Pokud se dá do toho adresáře dostat přes browser, tak je možné získat interní databázi Gitu, ve které je třeba seznam souborů. Často lze také naklonovat repozitář a získat tak kompletní zdrojové kódy.

Page 59: Noční můry webového vývojáře

Michal ŠpačekMichal Špačekwww.michalspacek.czwww.michalspacek.cz @spazef0rze@spazef0rze

https://www.michalspacek.cz/skoleni

Followujte mě na Twitteru a přijďte na školení bezpečnosti.Mějte se rádi! \V/