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

Detail přednášky

Chceš zlepšit zabezpečení webu a nevíš kde začít a kdy skončit? Ukážu ti, co jsme udělali na Slevomatu, co všechno jsme museli vyřešit, čemu jsme se divili a co plánujeme. Třeba tě to trochu taky nakopne.

Chcete pomoci zabezpečit web? Napište mi, rád pomohu.

Přijďte se naučit zabezpečovat weby na školení Bezpečnost webových aplikací (nejbližší termín: termín zatím nevypsán).

Datum a akce

14. června 2014, PixDevDay

Slajdy

SlideShare

Přepis poznámek

  1. Jak jsme zlepšili zabezpečení www.slevomat.cz

    (Tyto slajdy obsahují poznámky nejen pro ty, kteří na přednášce nebyli.)

  2. $this->context

    Slevomat má ~1M zákazníků, v databázi ~1.8M uživatelských účtů a web slevomat.cz denně navštíví přes 200k návštěvníků. Slevomat v roce 2013 dosáhl miliardového obratu, má vlastní značku zboží i vlastní sklad. Ve Slevomatu pracuje 150 lidí, vývojové oddělení čítá 10 hlav, mezi nimi např. @kukulich, @vasekpurchart nebo @patrikvotocek. Nechceš se k nim přidat? Slevomat používá PHP 5.5, Nette 2.1, Elasticsearch a MySQL resp. Percona Server, který obslouží ~2.5M dotazů za hodinu. Čísla kreditních karet nejsou uložena v databázi Slevomatu, pamatuje si je platební brána. Uživatelé mohou na svých účtech mít kredity, kterými mohou platit, ale nedají se převádět na jiného uživatele. Aktualizace: všechny informace na tomto slajdu jsou z roku 2014 a mohou se od aktuálního stavu lišit.

  3. Přihlašovací formulář

    Dříve bylo možné se přihlásit z jakékoliv stránky – po kliknutí na tlačítko vpravo nahoře se rovnou objevil přihlašovací formulář a to i přesto, že stránka přišla po nezabezpečeném HTTP. Formulář se sice odesílal šifrovaně pomocí HTTPS, ale mohl být po cestě do prohlížeče změněn tak, aby se odesílal někam úplně jinam. Přihlašování jsme tedy přesunuli na samostatnou stránku, která se stahuje po HTTPS, obsah formuláře tedy do prohlížeče dorazí v původním stavu.

  4. session.cookie_secure

    Web slevomat.cz zatím neběží celý na HTTPS (update: od července 2014 již ano), takže ačkoliv se případný mizera nedostane k přihlašovacím údajům změnou formuláře po cestě, může se odposlechem dostat k session id cookie, protože ta se přenáší i po nezabezpečeném HTTP. Až bude celý web přístupný pouze po HTTPS, cookie označíme jako Secure a tím zajistíme, že se bude přenášet jen po šifrovaném HTTPS a nepůjde odposlechnout. Direktivy session.cookie_httponly a session.use_only_cookies pro obranu před útoky na session máme samozřejmě zapnuté už docela dlouho.

  5. Jenom HTTPS

    Při relativně velké zátěži webových serverů Slevomatu nechceme přechod na zabezpečené HTTPS udělat najednou, ale raději postupně přesunujeme jednotlivé části webu. Velmi brzy bude celý web přístupný jen a pouze přes HTTPS. A pak zas můžeme vrátit přihlašovací formulář do Lightboxu na každou stránku.

  6. HTTP Strict Transport Security (HSTS)

    Strict-Transport-Security: max-age=31536000; includeSubDomains

    Až bude celý web přístupný pouze pomocí HTTPS, budeme posílat hlavičku Strict-Transport-Security, která zajístí to, že se prohlížeč ani nebude pokoušet server kontaktovat po HTTP a rovnou bude po stanovenou dobu všechny odkazy sám převádět na HTTPS. Zabráníme tím man-in-the-middle útokům, které převádějí šifrované HTTPS spojení na obyčejné HTTP. Hlavičku podporuje většina moderních browserů, IE ji bude podporovat od verze 12.

  7. Zálohy

    Přenos hesel a session ids tedy bude zabezpečen docela dobře. K uživatelským údajům se ale dá dostat i jinak, než odposlechem. Třeba tak, že jednoho krásného dne najdete na serveru 12 GB záloh cizích databází, protože je tam někdo kdysi nahrál a nesmazal. Opravdu se to stává a je lepší počítat s tím, že i vaše databáze se může někde objevit.

  8. $dibi->select('jmeno')->where("id = $_GET[id]");

    Ale nemusí to být jenom zálohy na nepatřičných místech. Firemní údaje a hesla uživatelů se z databáze dají vysosat třeba úspěšným provedením útoku SQL Injection. Spousta uživatelů používá jedno heslo na více místech a když z jednoho takového místa to heslo unikne, tak … raději nedomýšlet. A ano, SQL Injection lze provést i v Nette, Dibi a PDO, pokud se tyto nástroje použijí trochu nešikovně. Žádný nástroj automagicky nevyřeší zabezpečení webu.

  9. 8b8f05049b2114ad3d330db391e78549670d6b4b

    Slevomat dříve heslo před uložením zahashoval pomocí algoritmu SHA-1, aby nebylo tak jednoduché ho získat, pokud by se k databázi nějakým způsobem dostal někdo nepovolaný. Heslo nebylo saltované, takže nebylo chráněno proti tzv. narozeninovým útokům, ani proti použití předpočítaných tabulek. Jenže ty se už dnes stejně moc nepoužívají, hesla se crackují hrubou silou, slovníkovými nebo hybridními útoky. Každý obyčejný laptop umí spočítat desítky miliónů SHA-1 hashů za vteřinu a moderní grafické karty jsou stokrát rychlejší. Cracknout takové běžné heslo hashované SHA-1 je tedy otázkou maximálně pár minut a ani salt by crackování nezpomalil. Zkuste si to sami pomocí nástroje oclHashcat.

  10. cHJvZDE=;GwEuCSSgBCLx0BHHAAb8IA==;kvmDV/DEJ3H+how7bErLa+/xapV2282MmISXqVo8xREfnBKcNmAixIaDDnYkODCu9KW3+6sbM04W7Tm3w1S+NQ==

    Nyní Slevomat ukládá hesla tak, že heslo zahashuje relativně pomalým algoritmem bcrypt, aktuálně s cost parametrem 10. Výsledný hash se navíc zašifruje pomocí AES-256-CBC a poté se uloží do databáze. Je to klacek pod nohy, pravděpodobnost, že se mizera dostane k databázi a zároveň ke konfiguračnímu souboru aplikace, ve kterém je uložen šifrovací klíč, je mnohem menší, než že se dostane jenom k databázi. A pokud získá databázi i klíč, tak po rozšifrování má před sebou pořád ty bcrypt hashe. Výše je mé aktuální heslo tak, jak je uloženo v produkční databázi Slevomatu. Všimněte si id klíče, inicializačního vektoru a šifrovaného hashe, vše odděleno středníkem a zakódováno do Base64.

  11. password_hash(…, PASSWORD_DEFAULT) nebo ircmaxell/password_compat

    Pro pouhé hashování hesel algoritmem bcrypt použijte funkci password_hash() a pro ověření password_verify(). Tyto funkce jsou dostupné až od PHP 5.5, ale pro starší PHP (od PHP 5.3.7) existuje knihovna password_compat, která tyto funkce implementuje v čistém PHP. Najdete ji na GitHubu nebo ji můžete nainstalovat pomocí Composeru. Jednoduchý příklad použití najdete u mě na GitHubu na https://github.com/…password-php v souboru example-hash.php.

  12. mcrypt_encrypt() – MCRYPT_RIJNDAEL_128

    Pro následné šifrování hashů hesel pomocí AES-256 v PHP použijte funkce mcrypt_encrypt() a mcrypt_decrypt() z rozšíření mcrypt. Aktualizace: mcrypt je depracated od PHP 7.1, použijte raději na konci odstavce zmiňovanou knihovnu defuse/php-encryption. Bloková šifra AES je variantou šifry Rijndael s velikostí bloku 128 bitů, použijeme tedy konstantu MCRYPT_RIJNDAEL_128. Hodnota 256 v názvu znamená velikost použitého šifrovacího klíče v bitech. Pro ukládání hashů je vhodný režim CBC (Cipher-block chaining), ten při volání mcrypt_encrypt() zvolíme pomocí MCRYPT_MODE_CBC. Inicializační vektor vytvoříme funkcí mcrypt_create_iv() s parametrem MCRYPT_DEV_URANDOM. Jednoduchý funkční příklad najdete opět na GitHubu: https://github.com/…password-php v souboru example-encrypthash.php. Pro obecné šifrování dat raději použijte knihovnu https://github.com/…p-encryption, která přidává autentizaci zašifrovaných dat pomocí metody Encrypt-then-MAC.

  13. aes(bcrypt(sha1(…))) & aes(bcrypt(…))

    Ve Slevomatu jsme chtěli zabezpečit i hesla uživatelů, kteří se dlouho nepřihlásili. Vzali jsme tedy stávající SHA-1 hashe, zahashovali bcryptem a pak zašifrovali AES-256. Taková hesla jsme si označili, abychom při ověřování věděli, že z hesla zadaného uživatelem máme nejdříve udělat SHA-1 hash a až pak s ním pracovat dále. Když se takový uživatel úspěšně přihlásí, tak zadané heslo v čitelné podobě zahashujeme jen bcryptem s vynecháním SHA-1, hash zašifrujeme a výsledek uložíme do databáze. To děláme i při nastavení hesla nového. Mezikrok s SHA-1 je lepší, než vynucená změna hesla všech uživatelů, je ale zbytečný a pokud můžeme, tak ho odstraníme.

  14. bcrypt(token)

    Pomocí funkce password_hash() hashujeme i tokeny pro semi-permanentní přihlášení i pro přihlášení pomocí Facebooku. V čitelné podobě je v databázi nemáme. Token z cookie pak ověřujeme stejně jako heslo při přihlašování, tedy funkcí password_verify(). Hash tokenu v databázi nalezneme podle identifikátoru uživatele z cookie. Aktualizace: tokeny jsou na rozdíl od běžných hesel náhodně generované a dlouhé minimálně 20 bajtů, pro uložení by tedy stačilo použít funkci hash('sha256', $token) a pro ověření funkci hash_equals().

  15. Žádost o změnu hesla

    Funkci Zapomenuté heslo máme vyřešenu tak, že uživateli posíláme odkaz, který obsahuje token, jehož platnost je časově omezena – momentálně na 30 minut od odeslání odkazu. Tento interval by neměl přesáhnout 60 minut. E-mail nově obsahuje i odkaz pro zneplatnění tokenu. Token hashujeme bcryptem, v databázi není uložen v čitelné podobě. Zákaznické podpoře jsme odebrali možnost nastavovat hesla uživatelům v administraci Slevomatu, ale pro některé domény jsme tuto možnost museli vrátit zpět. Centrum.cz totiž doručuje některé e-maily s mnohahodinovým zpožděním a uživatel tak prošvihne interval, během kterého je token platný a o pomoc pak žádá právě zákaznickou podporu.

  16. Vaše heslo je: •••••

    Hesla nově nikdy neposíláme e-mailem, ani při registraci. Veškeré změny logujeme a víme kdo, kdy a z jaké IP adresy změnil heslo nebo e-mailovou adresu. Uživatele po změně informujeme, pro jistotu. Brzy chceme změnit politiku vytváření hesel, současné omezení na min. 5 znaků je dnes již nevyhovující. Nová hesla by měla mít minimálně 8 znaků. Maximální povolená délka hesla je 4096 znaků, delší hesla příliš zatěžují server při hashování bcryptem. Aktualizace: implementace bcryptu v PHP delší hesla automaticky ořezává na 72 znaků, toto omezení je na Slevomatu nadbytečné.

  17. Content Security Policy

    Ve Slevomatu používáme šablonovací systém Latte, který je součástí Nette Frameworku. Latte automagicky ošetřuje vypisované proměnné, ale i přesto se může stát, že uděláte chybu, třeba při tvorbě vlastních maker, a váš web bude zranitelný pomocí útoku Cross-Site Scripting (XSS). Ke snížení dopadu případného úspěšného útoku bude Slevomat brzy posílat HTTP hlavičku Content-Security-Policy, která vyjmenovává zdroje (whitelisting), odkud může prohlížeč do stránky vkládat obrázky, kaskádové styly, JavaScript a další. Browser nebude spouštět ani inline JavaScript, pokud se pošle i hodnota 'unsafe-inline', JavaScriptový kód pak musí být v externích souborech. Na přesunu pilně pracujeme.

  18. text/javascript
    <script>
    var foo = 123;
    var color = 'purple';
    </script>

    Posílat hodnotu 'unsafe-inline' rozhodně chcete, zakázáním spouštění JavaScriptu vloženého přímo do stránky zabráníte úspěšnému provedení útoku Cross-Site Scripting i kdyby někdo nějak do stránky nějaký JavaScript vložil. Občas se přímo do stránky úmyslně vkládá nějaká konfigurace, ovšem pokud je to uděláno výše uvedeným způsobem, tak to přestane fungovat. S 'unsafe-inline' momentálně nefunguje ani kód Google Tag Manageru, a to ani s využitím experimentální podpory nonce-source z připravované specifikace CSP 1.1 v Chrome 35. Slevomat používá Google Tag Manager a 'unsafe-inline' je pro nás tedy momentálně nepoužitelné.

  19. text/json
    <script id="conf" type="text/json">
    {
      "foo" = "123",
      "color" = "purple"
    }
    </script>
    <script src="code.js"></script>

    Konfiguraci ve stránce je možné zachovat, jen je potřeba změnit typ obsahu značky SCRIPT na text/json. JSON není kód, nelze spustit a browser ho tedy při 'unsafe-inline' nemusí blokovat. V code.js se pak k hodnotám dostanete takto: var c = document.getElementById('conf'); var d = JSON.parse(c.textContent || c.innerHTML);.

  20. HTTPS v PHP
    $c = stream_context_create(array(
      'ssl' => array(
        'verify_peer' => true,
        'verify_depth' => 9,
        'cafile' => $caFile,
        'disable_compression' => true,
      )
    ));

    Pokud potřebujeme komunikovat s nějakým API, používáme k tomu šifrovaný protokol HTTPS. Pro bezpečnou komunikaci musíme také ověřovat, že opravdu komunikujeme třeba s bankou a že to není nějaký mizera, který se za banku vydává. Vypnutím TLS komprese se pak bráníme proti útoku CRIME. Takto bude defaultně nastaveno i PHP 5.6+.

  21. Jde to!

    V zabezpečování Slevomatu a uživatelských dat budeme samozřejmě pokračovat dále, ještě nás čeká spousta práce, ale když se chce, tak to jde. Snad jsem vás také trochu inspiroval a pokud byste chtěli se zabezpečením webů a aplikací pomoci, napište mi, rád pomohu. Kontakty jsou uvedeny na mém webu. Díky!

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: