Tvorba internetových aplikací

Jakub Klinkovský

:: České vysoké učení technické v Praze
:: Fakulta jaderná a fyzikálně inženýrská
:: Katedra softwarového inženýrství

Akademický rok 2022-2023

Obsah dnešní přednášky

  1. Security
  2. Deployment
  3. Performance and optimization

Security

Oblasti a techniky, o kterých se budeme bavit:

  1. Zabezpečený přenos dat – protokol HTTPS
  2. Autentizace a session
  3. Cross-site scripting (XSS)
  4. Cross-site request forgery (CSRF)
  5. Clickjacking
  6. SQL injection
  7. Validace uploadovaných dat
  8. Validace dat v hlavičkách požadavku
  9. Bezpečnostní hlavičky odpovědi

Více na stránce Security in Django. Znáte nějaké další techniky a oblasti bezpečnosti?

Protokol HTTPS

HTTPS je zabezpečený přenosový protokol (HTTP over TLS). Umožňuje:

  1. autentizaci navštíveného serveru (certifikát podepsaný certifikační autoritou),
  2. šifrování přenášených dat (zajištění integrity a soukromí).

Certifikáty jsou vydány vždy pro danou doménu (resp. skupinu domén) a na omezenou dobu, po které je nutné požádat o obnovení. V závislosti na certifikační autoritě lze celý proces zautomatizovat a provést zdarma (např. pomocí Let's Encrypt).

Pro aplikace nasazené v praxi je vždy lepší preferovat protokol HTTPS před HTTP, jedině tak lze zabránit odposlechům a falšování přenášených dat (ze serveru klientovi i od klienta serveru).

Konfigurace frameworku Django pro HTTPS

V souboru settings.py je možné nastavit řadu parametrů týkajících se protokolu HTTPS:

  • SECURE_SSL_REDIRECT – přesměrování HTTP požadavků na HTTPS
  • SECURE_PROXY_SSL_HEADER – pro konfiguraci při nasazení aplikace za proxy serverem, který s vnějším světem komunikuje přes HTTPS, ale s aplikací přes HTTP (parametr ovlivňuje důvěryhodnost hlavičky X-Forwarded-Proto)
  • parametry pro HTTP Strict Transport Security (HSTS) – pro aplikace, které by měly být navštěvovány pouze pomocí HTTPS (hlavička Strict-Transport-Security umožňuje eliminovat počáteční požadavek přes HTTP)
  • bezpečné cookies (viz dále)

Autentizace a session

Autentizace by vždy měla probíhat pouze s protokolem HTTPS. Protokol je v aplikaci možné ověřit pomocí request.is_secure(), ale nejjednodušší je nakonfigurovat poskytování celé aplikace striktně přes HTTPS.

Při používání cookies je třeba zajistit jejich bezpečnost:

  • parametr SESSION_COOKIE_SECURE nastavit na True (prohlížeč by je pak měl odesílat jen pomocí protokolu HTTPS – důležité, protože počáteční požadavek před přesměrováním může být přes HTTP)
  • útočník nesmí mít přístup k žádné subdoméně – viz Session security (lze nastavit cookies pro doménu vyšší úrovně, které se pak vztahují na všechny subdomény)

Cross-site scripting (XSS)

Útoky typu XSS spočívají ve vložení klientských skriptů (např. JavaScript) do HTML dokumentu cizí webové aplikace.

Příklad v kontextu aplikace Django:

<style class={{ var }}>...</style>

Pokud var obsahuje např. string class1 onmouseover=javascript:func(), máme problém... Další příklady jsou na Wikipedii.

Django poskytuje automatické escapování v šablonách HTML, ale je potřeba dávat pozor na případy, kdy je nutné tuto ochranu vypnout (např. když chceme zobrazit kus HTML uloženého v databázi). Nedůvěryhodná data můžou pocházet odkudkoliv (např. URL parametry, formuláře, databáze, cookies, uploadované soubory, ...)

Cross-site request forgery (CSRF)

Útoky typu CSRF spočívají v odesílání neautorizovaných příkazů, které pochází od uživatele, kterému serverová aplikace důvěřuje.

Příklady:

  • HTTP metoda GET: triviální zneužití (stačí odkaz s URL na stránku s parametry)
    Touto metodou by nikdy nemělo docházet ke změně stavu aplikace!
  • HTTP metoda POST: je potřeba použit formulář v HTML, možnosti zneužití závisí na způsobu kódování dat a případné ochraně v aplikaci (viz dále)
  • další HTTP metody (PUT, DELETE, ...) lze v prohlížeči použít pouze při splnění principů same-origin policy a cross-origin resource sharing, které brání útokům typu CSRF (ale aplikace je může explicitně deaktivovat)

Příklad útoku CSRF pomocí metody POST

  • aplikace A obsahuje nezabezpečený formulář: https://app1.com/form.php
  • stránka B, kde útočník umístil vlastní HTML kód, obsahuje formulář odesílající data do aplikace A (např. pomocí skrytých značek):
    <form action="https://app1.com/form.php" method="post">
        <input type="hidden" name="password" value="not-secret">
        ...
    </form>
    
  • pokud útočník může na stránku B umístit vlastní JavaScript, pro vyslání požadavku aplikaci A ani nepotřebuje formulář

Přitom nezáleží na tom, jestli aplikace A používá HTTPS, ani jestli daný formulář vyžaduje autentizaci (při odeslání požadavku totiž prohlížeč pošle všechny cookies aplikace A, tedy včetně session ID přihlášeného uživatele).

Ochrana proti CSRF ve frameworku Django

Django poskytuje ochranu proti většině variantám CSRF útoků, aplikace ji ale musí využít. Pro ochranu formulářů používá tzv. synchronizer token pattern:

  • SecurityMiddleware nastaví tzv. CSRF cookie – náhodný řetězec vygenerovaný pomocí tajného klíče, hodnota se mění na začátku každé session
  • do formulářů odesílaných metodou POST se přidá skrytá hodnota s názvem csrfmiddlewaretoken (viz tag {% csrf_token %} používaný v šablonách)
  • při doručení požadavku metodou POST proběhne validace:
    • hodnota csrfmiddlewaretoken musí odpovídat datům v CSRF cookie
    • kryptografické metody umožňují validaci na základě tajného klíče na serveru a dokonce použití mnoha různých tokenů se společnou hodnotou cookie
  • Django dále kontroluje hlavičky Origin a Referer, viz How it works.

Ochrana proti CSRF ve frameworku Django

Ochrana protit CSRF má řadu parametrů, např. CSRF_COOKIE_SECURE zajistí odesílání CSRF cookies pouze přes HTTPS.

Použitý způsob ochrany má svá omezení:

  • subdomény mohou nastavit cookies pro domény vyšší úrovně, nastavením vhodné dvojice cookie + token lze obejít ochranu
  • jediné spolehlivé řešení je zajistit, že přístup k subdoménám mají jen důvěryhodné strany (nebo alespoň, že subdomény nemohou nastavit cookies, např. statické weby)

Další podrobnosti: viz OWASP cheatsheet.

Clickjacking

Clickjacking je typ útoku, kde útočník přiměje uživatele kliknout na něco jiného, než co uživatel vnímá ve svém prohlížeči. Technicky může být proveden mnoha způsoby, např.:

  • s využitím cross-site scripting a skrytých vrstev nebo elementů v HTML
  • s využitím rámců (značky <frame> a <iframe> v HTML)

Ochrana v Django využívá hlavičku X-Frame-Options, kde hodnota DENY zabrání použití obsahu v jakémkoliv rámci.

Modernější hlavička Content-Security-Policy umožňuje větší flexibilitu (ale její nastavení zatím není k dispozici v Django).

Další podrobnosti: viz OWASP cheatsheet.

SQL injection

SQL injection je útok podobný cross-site scripting, při kterém útočník konstruuje falešná vstupní data, která mu umožní spustit libovolný kód v SQL:

center

Ochrana v Django je zajištěna použitím objektu QuerySet, který konstruuje tzv. parametrizované dotazy (vstupní data jsou oddělena od struktury požadavku a databázový systém se postará o jejich escapování).

V případě, že píšete vlastní SQL dotazy, je třeba dát pozor na správné escapování.

Validace uploadovaných dat

Bezpečnostní zásady týkající se uploadovaných souborů:

  • omezit maximální velikost (nebezpečí útoků typu DOS)
  • zabránit spouštění statických souborů (nebezpečí spuštění libovolného kódu)
  • validovat data, která se zobrazí v rámci HTML dokumentu
    • např. uploadovaný soubor, který obsahuje validní hlavičku PNG a dále kód v jazyku HTML, může po zobrazení ve značce <img> umožnit útoky typu cross-site scripting
    • neexistuje neprůstřelné řešení na úrovni frameworku
    • poskytování uploadovaných souborů z jiné domény (např. usercontent-example.com pro example.com) může využít blokování cros-site scripting útoků díky principu same-origin

Validace dat v hlavičkách požadavku

V určitých případech Django využívá data z hlavičky Host pro sestavení URL.

Django provádí validaci:

  • escapování pro zabránění cross-site scripting
  • porovnání se seznamem ALLOWED_HOSTS v souboru settings.py

Pozor: validovaná data jsou dostupná pouze pomocí metody request.get_host(). Ve slovníku request.META je původní, nevalidovaná hodnota.

Další bezpečnostní hlavičky odpovědi

V řadě případů bezpečnost využívá parametry nastavené v hlavičkách odpovědi (viz např. zmíněné Strict-Transport-Security nebo X-Frame-Options).

Další důležité hlavičky konfigurovatelné v Django security middleware:

  • Referrer-Policy – instrukce pro prohlížeč, kdy (ne)má odesílat hlavičku Referer (překlep je již ve standardu 🙃) pro odkazy na zobrazené stránce
  • Cross-Origin-Opener-Policy – umožňuje nastavit stupeň izolace v prohlížeči mezi top-level dokumenty a cross-origin dokumenty
  • X-Content-Type-Options – hodnota nosniff zakáže prohlížeči odhadnout typ obsahu dle samotných dat, a výsledek použít místo hlavičky Content-Type

Další podrobnosti: viz OWASP cheatsheet.

Shrnutí bezpečnosti v Django

Pro vývoj bezpečných aplikací Django využívá:

  • zabezpečení cookies
  • nastavení bezpečnostních hlaviček v odesílaných odpovědích
    (konfigurace chování kompatibilních prohlížečů)

A dále poskytuje:

  • nástroje pro validaci dat (modely, formuláře, hlavičky, obrázky)
  • automatické escapování znaků v šablonách (HTML) a dotazech v databázi (SQL)

Deployment

Deployment = nasazení aplikace na serveru pro použití v reálném světě.

Co je potřeba:

  • vlastní server s veřejnou IP adresou a doménou nebo web hosting
  • instalace a konfigurace web serveru
  • konfigurace vlastní aplikace pro deployment

Podrobnosti: How to deploy Django.

Poznámka: pro zápočtové projekty deployment není potřeba řešit. Můžete si vyzkoušet jen ze zvědavosti nebo pro vlastní potřeby.

Web hosting pro Python

Fakultní web hosting neumožňuje deployment aplikací v Pythonu (má jen základní podporu pro skripty v ASP a PHP).

Příklady služeb dostupných zdarma (s řadou omezení) a s podporou Pythonu:

Rozhraní mezi aplikací a web serverem

Pro Python se používají dva standardy:

  • WSGI (Web Server Gateway Interface) – tradiční rozhraní pro synchronní aplikace
  • ASGI (Asynchronous Server Gateway Interface) – moderní rozhraní využívající asynchronní operace v jazyku Python (klíčová slova async a await)

Naše aplikace ale nevyužívají asynchronní funkce frameworku Django, stačí tedy použít WSGI.

Konfigurace aplikace pro server WSGI

Příklady serverů: Gunicorn, uWSGI, Apache + mod_wsgi

Obecný postup:

  1. server spouštět v adresáři, ve kterém je skript manage.py (ten se ale pro deployment spouštět nebude)
  2. server se nakonfiguruje tak, aby našel objekt představující WSGI aplikaci
    • v defaultním projektu je to objekt application v souboru mysite/wsgi.py (tedy modul mysite.wsgi)
  3. nakonfiguruje se modul settings určený pro deployment
    • defaultní modul mysite.settings obsahuje DEBUG = True, pro deployment je vhodné vytvořit oddělený modul
    • pro konfiguraci je možné použít proměnnou prostředí
      DJANGO_SETTINGS_MODULE (viz použití v souboru mysite/wsgi.py)

Deployment checklist

Před skutečným nasazením je vhodné projít deployment checklist. Některé kontroly lze automatizovat spuštěním příkazu manage.py check --deploy.

  1. Kritické proměnné v settings: SECRET_KEY, DEBUG
  2. Parametry závisející na prostředí, ve kterém aplikace poběží, např.
    ALLOWED_HOSTS, CACHES, DATABASES
  3. Konfigurace poskytování statických souborů – proměnné STATIC_ROOT,
    STATIC_URL, MEDIA_ROOT, MEDIA_URL a odpovídající konfigurace web serveru
  4. HTTPS
  5. Optimalizace (viz dále)
  6. Logování chyb

Performance and optimization

Efektivitu aplikace nestačí zajistit ve vývojovém prostředí – skutečný svět se chová jinak (velké množství požadavků, více dat v databázi, atd.)

Nejprve je potřeba zjistit charakteristiky prostředí a dané aplikace:

  • benchmarky a profilování – např. Django Debug Toolbar
  • monitoring – např. počet návštěv a zatížení serveru

Poté může začít optimalizace nejpostiženějších částí.

V dokumentaci Django: Performance and optimization

Optimalizace přístupů do databáze

  • doba zpracování přístupů do databáze závisí na tom, kolik a jaká data jsou v ní uložena (pokud je testovací databáze malá, i neefektivní dotazy se zpracují rychle, ale v praxi způsobí velké zpomalení)
  • zpracování operace přímo v databázi je typicky rychlejší než ekvivalentní kód v Pythonu, např. some_objects.count() vs len(some_objects)
  • prakticky všechny databázové systémy využívají tzv. indexy – pomocné datové struktury, které umožňují rychlé vyhledávání a řazení dle specifikovaných atributů (sloupců v tabulce)
  • primární a unikátní klíče jsou automaticky indexované, dodatečné indexy lze přidat pomocí Meta.indexes
  • podrobnosti viz Database access optimization

Optimalizace formou cacheování

  • Django obsahuje framework pro cacheování, tj. dočasné uložení často potřebných dat na místě s rychlým přístupem
  • často se používají programy Memcached a Redis – cacheování v operační paměti
    (lze použít i pro cacheování dat uložených v databázi)
  • data, jejichž získání trvá velmi dlouho, je možné cacheovat v databázi (trvalé uložení, ale pomalejší přístup)
  • data s jednoduchou strukturou lze efektivně cacheovat přímo v souborech na disku

Úrovně cacheování:

  • celá aplikace (website) (django.middleware.cache)
  • jednotlivá view, kde cacheování dává smysl (dekorátor @cache_page)
  • části šablon (tag {% cache ... %})
  • libovolná data (využití low-level API)

Optimalizace na úrovni HTTP

Další optimalizace

  • použití ASGI místo WSGI
  • použití jiného jazyka a balíčku pro šablony (např. Jinja2)
  • optimalizace kódu aplikace (např. implementace efektivnějšího algoritmu nebo použití efektivnější knihovny)