Přednáška online

Detail přednášky

Aneb jak se bránit XSS pokud děláte nejen v PHP, protože htmlspecialchars() je tak starý, že byste to už používat snad ani neměli.

O novinkách v CSP Level 3 se dozvíte v přednášce Kladivo na JavaScript.

Pořádám školení Bezpečnost webových aplikací (13. 12. 2017 Praha), přijďte!

Datum a akce

3. 12. 2015, PHP live (33 slajdů, video)

Slajdy

Vysoký roštěnec

#1 O útoku Cross-Site Scripting, hovězím, Content Security Policy i všem ostatním.

Cross-Site Scripting (XSS)

#2 Útok Cross-Site Scripting (XSS) poprvé popsal David Ross z Microsoftu už v roce 1999. Od té doby je na webu stále k vidění, nezmizel a pořád je tu s námi. Vývojářům se ho totiž nedaří moc eliminovat, i přesto, že o XSS už alespoň jednou v životě slyšeli.

The page at https://www.t-mobile.cz says: 1

#3 Cross-Site Scripting se nevyhýbá ani známým, leč nejmenovaným firmám. Podívejte se na zeď slávy jedné takové a uvidíte, kolikrát se na jejich webu objevil. Jednička v alertu na stránce je pro spoustu lidí ten útok XSS, ale to vůbec není vše, co dokáže.

Label: [<script>alert('XSS');</script>]

#4 Názvy foo, bar, foobar a další slouží pro případ, kdy nevíte, jak něco pojmenovat. Já když nevím, tak to pojmenuju nějak takto. Můj SSH klíč je sice můj dobrý kamarád, ale přesto bych nevěděl, jak jinak ho pojmenovat.

The page at https://bitbucket.org says: XSS

#5 Trochu mě překvapilo, když na mě po uložení klíče na další stránce vyskočil náš starý známý alert. V tomto případě šel Cross-Site Scripting použít k získání uživatelovo CSRF tokenu z cookie, stačilo mu poslat odkaz na stránku, bez JavaScriptu v URL – tomu se říká Stored XSS. Za nahlášení tohoto problému mě Atlassian zařadil do Security Hall of Fame, díky!

Password Reminder: [<script>alert('XSS in PW Reminder');</script>]

#6 LastPass nepoužívám jako svůj hlavní password manager, takže náhodně vygenerované hlavní heslo mám napsané v jiném správci hesel. Není tedy nic, co by mi ho připomnělo. A když nevím co napsat, tak programuju v JavaScriptu.

The page at https://lastpass.com says: XSS in PW Reminder

#7 Po uložení na mě zase vyskočil nějaký alert, tentokrát ale JavaScript na tomto místě nešel k ničemu zneužít. I přesto mě do síně slávy LastPassu zařadili a nahlášení tenkrát ocenili 5 body, díky.

echo $row->username

#8 Z pohledu vývojáře vypadá útok Cross-Site Scripting například takhle. Dostanete za úkol něco vypsat, tak to vypíšete. Na rozdíl od například vyhledávacího políčka vás ani nenapadne ošetřovat uživatelské jméno. Asi byste se divili, kolik uživatelů si do něj schválně vloží nějaký roztomilý JavaScript. Ten se pak spouští na každé stránce, kde se jméno vypisuje, tedy například i v administraci e-shopu.

Andělé světla na Mall.cz

#9 Další až příliš častá varianta XSS vypadá třeba takto. JavaScript, nebo v tomto případě obrázek, je vložený přímo do URL a uživatel musí na web přijít přes tuto speciálně upravenou adresu. Této variantě se říká Reflected XSS. Jen pro jistotu, ten obrázek na web nepatří, ačkoliv se tam vlastně docela hodí.

The Browser Exploitation Framework (BeEF) Project

#10 The Browser Exploitation Framework usnadňuje spouštění útoků XSS. Pomocí něj je možné ovládat více browserů, které jsou připojené (hooked) na BeEF server. Browser můžeme donutit zahrát nějakou písničku, získávat z něj stisknuté klávesy nebo v něm zobrazit falešný přihlašovací formulář. Ukázku naleznete ve videu přibližně od 14. minuty.

echo htmlspecialchars($row->username);

#11 Pokud se chcete bránit útoku Cross-Site Scripting, měli byste veškerý uživatelský vstup včetně uživatelského jména ošetřovat pomocí funkce htmlspecialchar­s().

<script>var tracking = '<?php echo … ?>';</script>

#12 Pokud budeme vypisovat uživatelský vstup do JavaScriptu, tak jen samotné htmlspecialchars($vstup) nemusí stačit. V JavaScriptu se řetězce uzavírají do jednoduchých uvozovek (apostrofů) a htmlspecialchars() standardně apostrofy nepřevádí.

echo htmlspecialchars($row->name, ENT_QUOTES);

#13 Abyste htmlspecialchars() donutili převádět i apostrofy, musíte přidat příznak ENT_QUOTES. Jenže kvůli převodu 4 znaků (<, >, ", ') je tohle poměrně dlouhý zápis.

{$row->username}

#14 Nejlepší věc, co můžete udělat pro obranu proti Cross-Site Scriptingu je použít nějaký šablonovací systém, který výstup správně a sám automagicky ošetřuje. Nemůžete pak na ošetření zapomenout. Toto je ukázka správně ošetřeného výpisu uživatelského jména v Nette, resp. v šablonovacím systému Latte.

$el = Html::el()->add('Uživatel ')->add(Html::el('b')->add($row->name))->add(Html::el()->add(' přidán')); $this->flashMessage($el);

#15 Při použití frameworků a šablonovacích systémů je potřeba dávat pozor na jejich správné použití. Framework Nette obsahuje hezkou třídu pro vytváření HTML kódu, která se dá použít například pro zobrazování flash message s formátováním zprávy. Najdete v této ukázce zobrazení flash message „Uživatel Michal přidán“ chybu, která umožňuje provést XSS? Aktualizace: v Nette/Utils 2.4.0 byla metoda add() přejmenována na jasnější addHtml() a byla přidána nová metoda addText().

$el = Html::el()->setText('Uživatel ')->add(Html::el('b')->setText($row->name))->add(Html::el()->setText(' přidán')); $this->flashMessage($el);

#16 Místo metody add() byste měli použít metodu setText(), ta ošetřuje uživatelský vstup. Slova „Uživatel“ a „přidán“ bychom sice mohli připojit pomocí add(), ale pro jistotu použijeme také setText(). V budoucnu by totiž někdo mohl text nahradit za uživatelský vstup a nemusel by si všimnout, že má změnit i jméno metody. Aktualizace: v Nette/Utils 2.4.0 byla metoda add() přejmenována na přesnější addHtml() a byla přidána nová metoda addText(), tu můžete použít také.

XSS Auditor/Filter

#17 Weboví vývojáři zapomínají vstupy ošetřovat a zapomínat budou z různých důvodů i nadále. Proto se výrobci browserů už před časem rozhodli, že zavedou několik dalších úrovní obrany, které mohou problém do jisté míry zmenšit. Jedna z takových úrovní je tzv. XSS Auditor (někdy nazýváno jako XSS filtr), který je v Chrome a IE. Do Firefoxu si ho můžete přidat instalací rozšíření NoScript. XSS filtr může zabránit útoku Reflected XSS tím, že v požadavku, např. v adrese nebo formulářových polích hledá JavaScript a zkoumá, jestli se nevrátil v odpovědi serveru a pokud ano, tak jeho spuštění zablokuje, ale stránku zobrazí. Aktualizace: Režim mode=block je defaultně zapnutý v Chrome od verze 57. Na mém testovacím webu si můžete XSS auditor vyzkoušet, Chrome vám zobrazí chybu ERR_BLOCKED_BY_XSS_AUDITOR a vypíše upozornění do konzole.

X-XSS-Protection: 1; mode=block

#18 „Vyčištění“ stránky je defaultní nastavení (aktualizace: Chrome od verze 57 blokuje zobrazení). XSS auditor se někdy nemusí správně trefit a může zablokovat spuštění správného JavaScriptu nebo při pokusu o „opravu“ stránky právě může nějaký Cross-Site Scripting vyrobit. Proto je lepší ze serveru poslat hlavičku zobrazenou výše a filtr nastavit do režimu block. Nebo ho úplně vypnout pomocí hodnoty 0.

X-XSS-Protection: 1; mode=block; report=https://…

#19 Hlavička X-XSS-Protection má nestandardní rozšíření report. Hodnotou tohoto parametru je adresa, na kterou browser návštěvníka pošle report, pokud se XSS auditor aktivuje. Vy se tedy dozvíte, jestli na webu někde máte možnost Reflected XSS provést. Report podporuje WebKit a Chromium a prohlížeče na nich postavené.

request-url + request-body

#20 Browser na uvedenou adresu pošle JSON se dvěma klíči, request-url a request-body. Dozvíte se tedy na jaké adrese je možné XSS provést a jaký požadavek aktivaci XSS auditoru způsobil a problém pak můžete vyřešit. Reporty si můžete posílat do vlastní aplikace, v budoucnu by tento typ reportů mohl umět i nástroj report-uri.io.

Content Security Policy (CSP)

#21 Content Security Policy umí zabránit i ostatním typům XSS útoků, nejenom Reflected variantě. CSP podporují všechny moderní browsery (Chrome, Firefox, Edge a některé další) a spočívá v tom, že browseru pomocí whitelistu řeknete, které zdroje (např. JavaScript, obrázky a další) a odkud může do aktuálně zobrazované stránky načítat. Pokud chcete CSP používat pro weby psané v Nette, sledujte vývoj balíčku spaze/csp-config na GitHubu.

Response Headers: Content-Security-Policy

#22 Content Security Policy je v podstatě obyčejná HTTP hlavička, kterou server prohlížeči posílá. Obsah té hlavičky může být poměrně dlouhý, podle toho, co všechno chci browseru povolit. Hlavička také může být na každé stránce jiná.

Content-Security-Policy: default-src 'self'

#23 Základní nastavení, které byste mohli chtít používat je defaultně povolit načítání obrázků, JavaScriptů a dalších zdrojů pouze z domény, na které je umístěna načítaná stránka. To zajistíme použitím direktivy default-src a hodnoty 'self' v apostrofech. Browser pak například JavaScript vložený do stránky pomocí <script src=//útočník/útok.js></script> nenačte a to i přesto, že v HTML taková značka opravdu bude. Přísnější je nastavení default-src 'none', které zakáže načítání všeho odkudkoliv. Nebojte, postupně jednotlivé typy zdrojů povolíme.

Content-Security-Policy: default-src 'self'; img-src 'self' https://www.google-analytics.com

#24 CSP direktiva img-src určuje, odkud může browser načítat obrázky. Hodnotou 'self' říkám, že je mohu načítat pouze z aktuální domény a druhou hodnotou https://www.google-analytics.com říkám, že prohlížeč načte i obrázky vložené pomocí <img src=https://www.google-analytics.com/...>. Google Analytics si do stránky vkládá obrázek pomocí JavaScriptu, pro správnou funkčnost to musíme tedy povolit. Ostatní zdroje, například kaskádové styly se načtou pouze ze 'self' domény, povolí to direktiva default-src. Hlavička by samozřejmě měla být na jedné řádce, na slajdech je rozdělena z důvodu nedostatku místa.

Content-Security-Policy: default-src 'self'; img-src 'self' https://www.google-analytics.com; script-src 'self' https://www.google-analytics.com

#25 Direktivou script-src určujeme, odkud browser může stáhnout a spustit JavaScript. Hodnoty 'self' a https://www.google-analytics.com opět říkají, že do stránky se může stahovat JavaScript pouze z aktuální domény a z domény https://www.google-analytics.com. Nespustí se dokonce ani inline JavaScript, tedy kód napsaný mezi značkami <script> a </script> nebo v atributech jako onmouseover, onerror apod.

Content-Security-Policy: default-src 'self'; img-src 'self' https://www.google-analytics.com; script-src 'self' 'unsafe-inline'

#26 Pokud byste takový inline JavaScript chtěli spouštět, musíte přidat hodnotu 'unsafe-inline'. Už z názvu je vidět, že tvůrci specifikace CSP považují inline JavaScript za nebezpečný. Mohl ho do stránky totiž vložit útočník. Používat inline JavaScript bych nedoporučoval, nepříjemné ale je to, že bez něj nefungují některé nástroje jako například Google Tag Manager. Pokud ho na stránkách používáte, musíte mít 'unsafe-inline' povolené. Content Security Policy Level 2 přidává možnost k inline JavaScriptu připojit hash nebo token pro ověření, čímž se zaručí, že přímo ve stránce bude spuštěn pouze JS, který je předem známý. Podpora tohoto způsobu zatím není v browserech moc rozšířená, bohužel. Aktualizace: Content Security Policy Level 3 toto poměrně elegantně řeší pomocí 'strict-dynamic', podívejte se na moji přednášku o CSP3.

Content-Security-Policy: default-src 'self'; img-src 'self' https://www.google-analytics.com; script-src 'self' https://www.google-analytics.com; frame-src https://www.youtube-nocookie.com

#27 Když do stránky budete chtít vložit nějaký iframe, například video z YouTube, tak musíte doménu povolit pomocí direktivy frame-src. Tato direktiva musí být uvedena i když byste chtěli do stránky vložit frame z vaší domény, default-src 'self' totiž nezahrnuje frame-src. Direktiva frame-src je zastaralá, v novější verzi CSP je nahrazena direktivou child-src a ta již do default-src spadá. Tu bohužel nepodporuje třeba Safari a nejčastěji to dopadá tak, že se prostě pošle child-src a frame-src zároveň, se stejnými hodnotami.

Content-Security-Policy: default-src 'self'; img-src 'self' https://www.google-analytics.com; script-src 'self' https://www.google-analytics.com; frame-src https://www.youtube-nocookie.com; form-action 'self'

#28 CSP umí pomocí direktivy form-action omezit URI, na které se odesílají formuláře na stránce. Direktiva do default-src také nespadá. Pomocí form-action 'self' říkám, že formuláře bude možné odeslat pouze na aktuální doménu. Útočník tak nemůže vložit nějaký falešný formulář, který si odešle k sobě.

<input type=submit form="form1" formaction="https://attacker">

#29 V HTML 5 je možné do stránky vložit odesílací tlačíko i mimo samotný formulář a pomocí atributu form ho s tímto formulářem propojit. Pomocí formaction mu pak dokonce mohu změnit adresu, kam se formulář odešle. Velkým tlačítkem s nápisem „Neklikejte sem“ tak mohu uživateli ukrást třeba statický CSRF token. Tomuto typu útoku se říká Scriptless Attack a nejen kvůli němu byste měli omezit, kam vaše formuláře lze odesílat.

Content-Security-Policy: default-src 'self'; img-src 'self' https://www.google-analytics.com; script-src 'self' https://www.google-analytics.com; frame-src https://www.youtube-nocookie.com; form-action 'self'; report-uri https://….report-uri.io/r/…

#30 Content Security Policy také nabízí reportování, pokud k porušení nějaké nastavené politiky v browseru dojde. Prohlížeč na zadanou adresu opět odešle JSON, tentokrát trochu větší. Přijímání a zobrazování reportů si můžete napsat sami, ale je výhodnější použít službu report-uri.io. Uživatelé mají totiž zavirované počítače, jsou připojení na zákeřných Wi-Fi sítích, které do stránek vkládají reklamy, nebo mají jen nainstalované špatně napsané extenze prohlížečů a tohle všechno zaručí, že vám těch reportů bude chodit celkem dost.

report-uri.io Reports

#31 Napsat správně zobrazování reportů není zrovna lehké a je zbytečné to dělat znovu. Služba report-uri.io umí přehledně zobrazovat reporty, vytvářet grafy apod. a pomůže vám zjistit, kde je problém. Výše je vidět report, který mi oznamuje, že jsem udělal chybu na svém webu, pokusil jsem se totiž vložit obrázek ze špatného umístění, které není povolené. Můj admin totiž nemá povoleno vkládat obrázky z https://admin.michalspacek.cz. Bezpečnostní útok by byl hlášen podobně.

Content-Security-Policy-Report-Only: default-src 'self'; img-src 'self' https://www.google-analytics.com; script-src 'self' https://www.google-analytics.com; frame-src https://www.youtube-nocookie.com; form-action 'self'; report-uri https://….report-uri.io/r/…

#32 Zavádět CSP na existující web není jednoduché. Musíte udělat inventuru všech měřících kódů na webu, zjistit co na stránky všechno vkládáte a to zabere nějakou dobu. CSP vám může pomoci. „Report Only“ varianta se zapíná hlavičkou Content-Security-Policy-Report-Only a říká browseru, že když dojde k porušení nějaké nastavené politiky, tak má sice poslat report, ale akci (zobrazení obrázku, spuštění JS) má stejně provést. To se právě hodí pro zavádění CSP na běžící web. V logu pak najdete co ještě máte povolit, pokud to dává smysl.

alert(1) na webu tatra.cz

#33 Tatra sice nezná bratra, ale je to jednička. Začněte používat CSP a buďte lepší!

Video záznam

https://www.youtube.com/watch?v=fC8zPGBo_B8

English