SOFTWARE A BEZPEČNOST
Případy testování uvedené v předchozí kapitole se primárně soustředily na ověření správné a bezchybné funkčnosti testovaného produktu. Software ale musí v dnešní době odolávat i mnohem větším hrozbám, než je špatný vstup zadaný uživatelem, který se pouze překlepl. Aplikace, které jsou volně přístupné přes Internet, kde s uživateli komunikují skrze API, nebo jim nabízí své uživatelské rozhraní v podobě webových stránek, musí čelit také mnoha cíleným útokům. Na internetu se kromě uživatelů pohybují i nevítaní útočníci, jejich cílem není totiž nic jiného, než pokusit se Vaší aplikaci předat takové vstupy, které by ji uvedly do nekonzistentního tvaru, nebo vyvolaly takové chování, které nebylo tvůrci aplikace v žádném případě zamýšleno.
Zneužití bezpečnostních hrozeb může vést například k nedostupnosti služby a tím pádem k finančním ztrátám. V horším případě ale může dojít i ke spuštění libovolného kódu útočníka na straně serveru, nebo ke smazání či změně aplikace. Neméně bolestivý by byl také únik důvěrných dat společnosti, nebo klientů.
Již jsme zmiňovali, že chybně fungující aplikace by patrně utrpěla citelnou újmu v podobě ztráty důvěry zákazníků. Mohla by ale způsobit také finanční ztráty spojené například s odškodněním, pokud by aplikace chybně započítávala úrok, odvody, apd. U bezpečnostních hrozeb by byly dopady zneužití některé z chyb pravděpodobně totožné, ne-li ještě více zdrcující. Historie je plná reálných příkladů, kdy společnost přišla po bezpečnostním incidentu o většinu svých klientů a byla tak nucena ukončit svou činnost. Ztracená důvěra se totiž obnovuje velice obtížně. Pokud uživatelé přišli díky chybě ve Vaší aplikaci o cenné informace, nebo byla odcizena a zveřejněna jejich hesla či jiná důvěrná data, pak ve Vaší aplikaci budou svou důvěru vkládat podruhé jen velmi pomalu a opatrně.
Pokud chce být společnost na trhu úspěšná, nemůže si podobné bezpečností incidenty v žádném případě dovolit. Testování softwaru proto musí být doplněno také o testování její bezpečnosti, které rizika úspěšných útoků zcela odstraní, nebo alespoň minimalizuje. Realita je ale bohužel často odlišná a mnoho společností, které se vývojem softwaru zabývají, odsouvá otázku bezpečnosti stále do pozadí.
Bezpečnostním otázkám se často začínají věnovat teprve až už je pozdě, tedy ve chvíli, kdy dojde k nějakému bezpečnostnímu incidentu. Bezpečnost je stále chápána jako něco, co stojí zbytečné peníze a je proto přidávána až dodatečně. V takovém případě říkáme, že je existující funkcionalita „obalována bezpečností“. Že je to pak drahé, se nemůžete divit.
Pro zlepšení bezpečnosti aplikace toho ale můžete v celém průběhu jejího vývojového cyklu udělat mnoho, a to s minimálními náklady. Nebude dokonce nutné vymýšlet a zavádět ani žádné nové postupy v procesech, na které jste zvyklí. Bezpečnostní testy se ve svém principu téměř nijak neliší od běžných funkčních testů, kterým jsme se věnovali v předchozích kapitolách. Liší se pouze testovací případy a scénáře, které se zaměřují právě na bezpečnostní otázky. Aby Vaše společnost přinášela na trh bezpečný a tím pádem kvalitní software, stačí, aby se držela zaběhnutých pravidel testování v každé fázi jeho životního cyklu a již v raných fázích vývoje kladla na bezpečnost patřičný důraz. S minimálními náklady tak vyřešíte většinu bezpečnostních problémů a Vaše aplikace se stane po bezpečnostní stránce velice kvalitní.
Zapojení bezpečnostních testů do SDLC
Integrace bezpečnostních testů do SDLC může být velice jednoduchá, protože můžete využít všechny postupy a procesy, které jsou v rámci organizace již zavedené. Jak již bylo uvedeno, bezpečnostní chyba se v rámci testovacího procesu téměř nijak neliší od funkčních nebo výkonových chyb. Pokud je tedy chyba zjištěna v rámci SDLC co nejdříve, může být opravena rychleji a při nižších nákladech.
S bezpečností je proto nutno počítat již ve fázi návrhu softwaru, a to definicí bezpečnostních cílů v rámci řízení rizik, které nám umožní modelovat a vyhodnocovat hrozby. Proces modelování hrozeb je pro budoucí fáze vývoje velice důležitý. Budete-li totiž mít během návrhu a následných fází SDLC k dispozici soupis všech rizik, a možných hrozeb, budete jim moci mnohem lépe čelit. Nebudete se dopouštět zbytečných chyb, nepřehlédnete některá z pohledu bezpečnosti kritická místa a neopomenete ošetřit případy užití, které nejsou na první pohled předvídatelné.
V rámci naší jednoduché kalkulačky, bychom v rámci řízení rizik mohli vydefinovat například tato rizika:
- vzdálené spuštění kódu na straně serveru
- spuštění kódu v prohlížečích uživatelů
- odepření služeb (DoS)
- únik informací o použitých technologiích
Vzhledem k tomu, že je naše kalkulačka velice jednoduchou aplikací, která neukládá žádná data do úložiště, ani nepracuje s důvěrnými daty uživatelů, zde jiná rizika téměř nehrozí. Od samého počátku je ale i u takto jednoduché aplikace nutné mít na vědomí možnosti útoků, které by mohly vyústit v problémy, které jsme vydefinovali v rámci řízení rizik.
Při modelování hrozeb si proto raději definujeme možné útoky a postupy, pomocí kterých by se útočníkům mohl úspěšný útok podařit:
- Použití neaktuálních verzí softwaru se známými zranitelnostmi by mohlo vést k jejich exploitaci a ke vzdálenému spuštění kódu útočníka na straně serveru;
- Pokud se výpočet bude provádět na straně serveru, mohl by útočník serveru podstrčit ke zpracování i jiný kód, než jen výpočet. Tento kód by se tak mohl vykonat na straně serveru;
- Pokud se bude výpočet provádět na straně klienta, mohl by útočník uživatelům podstrčit ke zpracování i jiný kód, než jen výpočet. Tento kód by se pak mohl vykonat ve webovém prohlížeči uživatele;
- Vhodně připravené řetězce odeslané útočníkem by při předvyplňování vstupních polí formuláře (kalkulačky) mohly opustit původní kontext formuláře. Začlenily by se tak do HTML kódu stránky, kde by mohly způsobit spuštění kódu na straně klienta;
- Útočník by mohl velkým množstvím požadavků z jedné nebo více IP adres vyčerpat všechny dostupné prostředky serveru;
- Útočník by mohl vložením složitých výpočtů nadměrně vytížit server;
- Vložením výpočtu „dělení nulou“ by útočník mohl vyvolat zobrazení chybové zprávy, která by mohla prozradit část zdrojového kódu aplikace, cestu s uložením skriptu na filesystému, použitý programovací jazyk, operační systém, nebo verzi webového serveru;
- Stejnou chybovou zprávu by útočník mohl vyvolat také zadáním nevalidních vstupů, jako jsou znaky abecedy, speciální a bílé znaky, prázdné vstupy, nebo přetypováním proměnné na pole;
- Špatně nakonfigurovaný server by mohl na speciálně upravené požadavky vracet přespříliš sdílná chybová hlášení.
Pokud by analytik měl během návrhu aplikace k dispozici takto vymodelované hrozby, patrně by nikdy nesáhl k původně použité funkci eval(), jež měla mít podle původního návrhu na starosti výpočet operace na serveru. Pokud by se z nějakého důvodu k tomuto řešení analytik přeci jen uchýlil, pak by jistě od vývojářů požadoval neprůstřelnou validaci vstupů, aby nebylo možné této funkci podstrčit jiné než očekávané vstupní hodnoty. Analytik by již také mohl zmínit nutnost validace výstupů při začleňování hodnot do obsahu formuláře, případně nutnost zakázání výstupu chybových hlášení.
Vývojáři by tak od analytika dostali mnohem jasnější zadání a věděli by hned, na co si dát při vývoji aplikace z bezpečnostního hlediska pozor. Po pročtení hrozeb by se vývojáři mohli mnohem lépe přizpůsobit bezpečnostním požadavkům, než kdyby sami museli všechny možné hrozby vymýšlet až během psaní kódu. Je nutné si uvědomit, že programátor je placen za množství napsaného kódu, a že cílem jeho práce je v první řadě co nejrychleji odevzdat fungující kód. Nemůžete se proto divit, když se nebude z vlastní iniciativy pídit po tom, co všechno by mohl případný útočník provést. Programátor navíc nemusí mít v otázkách bezpečnosti ani potřebné vzdělání, a o případných hrozbách tak nemusí mít vůbec žádné zdání. Ponechávat bezpečnost pouze na vývojářích, jak se to bohužel často děje, není tedy z uvedených důvodů rozhodně tím nejlepším řešením.
Soupis hrozeb poslouží v neposlední řadě také jako dobrý základ pro testery v rámci revize kódu, nebo při penetračním testování. Ti díky němu budou moci do testů zařadit i takové testovací případy, které ověří, zda aplikace skutečně všem definovaným hrozbám úspěšně odolává.
Revizi kódu je v SDLC možné začlenit již do fáze implementace, kdy může tester průběžně testovat již vytvořený kód. Není tedy nutné čekat až na finální podobu sestavené aplikace, díky čemuž se některé chyby mohou opět odstranit poměrně brzy. Revize kódu umožňuje testerům procházet kód řádek po řádku s tím, že samotnou funkčnost si tester pouze představuje ve své mysli. I tak si ale může klást otázky, jak se kód zachová, pokud mu předloží ke zpracování takové a takové vstupní hodnoty.
Během revize kódu bychom se měli snažit projít všechny větve programu a případně tak i odhalit mrtvý kód, k jehož spuštění nikdy nedojde. Toto je velký rozdíl oproti black box testování, kdy se testerovi pravděpodobně nepodaří projít úplně všechny větve programu. Mezi často používané metodiky při revizi kódu patří například pokrytí příkazů, hran, podmínek, cest, nebo pokrytí hraničních hodnot. Zájemce o detailní informace jednotlivých metodik odkáži opět na knihu Řízení kvality softwaru: Průvodce testováním. Cenné informace lze najít ale také ve veřejně přístupné příručce Code Review Guide od organizace OWASP, která se zaměřuje právě na bezpečnostní otázky.
Revize umožní najít dokonce i takové chyby, které by se během jiných typů testů daly odhalit jen velice obtížně. Analýza kódu se díky tomu stává nejúčinnější a nejspolehlivější metodou testování. Vývojáři například leckdy vkládají do svého kódu různá „zadní vrátka“, která jim umožní zobrazit po zadání „tajného“ parametru ladící informace nebo jim dokonce poskytnou administrátorský přístup. Představte si například takovýto kód:
-
IF (isset($_GET[“Wj6q-02PiU_54VsP*”])) $isAdmin = true;
Tento kód by přiřadil administrátorská práva každému, kdo by vložil do parametrů URL řetězec Wj6q-02PiU_54VsP*. Obddobně nebezpečné by bylo, kdyby se autentizační tokeny vytvářely například tímto algoritmem:
-
$token = md5(“secret”.time().$iduser)
Takovéto tokeny by byly predikovatelné, neboť každý, kdo by znal algoritmus jejich tvorby, by byl schopen je generovat. I v tomto případě by se během black box testování mohlo zdát, že jsou autentizační tokeny v pořádku a dostatečně náhodné. Byl by to ovšem chybný závěr. Pokud útočníci, respektive tester během black box penetračního testu o těchto zadních vrátkách a o způsobu tvorby autentizačních tokenů nic neví, nebudou schopni tyto skutečnosti odhalit a označit je za bezpečnostní riziko, nebo je zneužít. Problém nastane teprve ve chvíli, kdy dojde k úniku zdrojového kódu aplikace, nebo kdy firmu opustí nespokojený zaměstnanec. Z těchto původně tajných informací se může jako mávnutím kouzelného proutku stát během chvíle veřejné tajemství a všechny produkty firmy, které uvedený kód obsahují, se mohou rázem stát cílem mnoha úspěšných útoků.
Tato rizika lze ale velice snadno odhalit a odstranit právě díky revizi kódu. Pokud se vrátíme k našemu příkladu s kalkulačkou, jistě jste si v jejím kódu všimli řádku, který v něm nemá co pohledávat:
-
if (isset($_GET["debug"])) phpinfo();
Tato podmínka říká, že pokud aplikaci předáme metodou GET proměnnou debug, bude zavolána funkce phpinfo(), která zobrazí detailní informace o nastavení systému. Abychom tuto a jí podobné skutečnosti odhalili během black box testování, museli bychom zkoušet vkládat do URL nekonečné množství různých parametrů. Při revizi kódu nám ale ke zjištění této skutečnosti stačil jeden jediný letmý pohled do zdrojového kódu.
Revize kódu by z uvedených důvodů neměla v rámci SDLC niky chybět. Její nevýhodou je snad pouze to, že na ní budete potřebovat testera, který je skutečně zběhlý v daném programovacím jazyce, a snadno se v kódu zorientuje.
Pro zlepšení bezpečnosti v této fázi napomohou dokonce už jen „vtíravé“ otázky testera, který si od vývojářů nechává vysvětlit, proč kód, který vyvinuli, napsali zrovna takto, nebo co přesně dělá jeho konkrétní část. Během těchto rozprav se často narazí na bezpečnostní problémy zcela samovolně, neboť vývojářům dojdou všechny možné důsledky, které si původně neuvědomovali.
Přestože je velice nepravděpodobné, že by se chyby při znalosti rizik dostaly od samého počátku vývoje až do závěrečných fází SDLC, přesto zde taková možnost stále existuje. V tomto případě by měly bezpečnostní nedostatky odhalit penetrační testy provedené nad hotovou aplikací, tedy ve fázi testování, která má v SDLC vyčleněno své vlastní místo.
Těžko ovšem budete modelovat hrozby, psát bezpečný kód, revidovat jej a provádět penetrační testy bez odpovídajících znalostí. Klíčovým krokem při vytváření softwaru je proto vzdělávání vývojových týmů a pracovníků QA v oblasti bezpečnosti. Přestože nové knihovny, nástroje nebo jazyky mohou pomoci vytvářet kvalitnější programy (s menším výskytem bezpečnostních chyb), vede jejich použití někdy k tomu, že se vývojáři až přespříliš spoléhají na jejich automaticky prováděné validace a možným hrozbám tak nevěnují dostatečnou pozornost. Mimo to vznikají neustále nové hrozby, kterých si musí být programátoři rovněž vědomi. Vzdělávání v oblasti často se vyskytujících nebo nových zranitelností, případně v testování bezpečnosti napomáhá vývojářům také v získání odpovídajícího myšlení, kdy se na své aplikace začnou dívat z pohledu útočníka. Na trhu je dnes již poměrně široká nabídka školení, z nichž zmíním například kurzy Webhacking v praxi – Zranitelnosti webových aplikací a Testování bezpečnosti webových aplikací od počítačové školy GOPAS, nebo kurz Hacking webových aplikací pořádaný Hacker Academií. Vývojáři webových aplikací odchází z těchto kurzů často doslova šokovaní. Přestože o některých hrozbách již dříve slyšeli nebo četli, nikdy si nedovedli představit všechny dopady, jež může zneužití konkrétních zranitelností mít. Po absolvování podobných kurzů se vývojáři často dívají na vyvíjenou aplikaci zcela odlišně a dokáží si mnohem lépe představit, čemu všemu musí jejich kód čelit a odolávat. S minimálním nákladem tak dokážete díky vzdělanosti svých zaměstnanců mnohonásobně zlepšit bezpečnost Vámi vyvíjených produktů.
Cílem této příručky je ale hlavně ta část SDLC, která je vyčleněna testování. Konkrétně testování se zaměřením na bezpečnost, které se běžně označuje výrazem penetrační testování. Pojďme se tedy nyní na tuto část podívat blíže.
Penetrační testování
Penetrační testování v SDLC fázi „testování“ rozšiřuje běžné testovací případy o speciální případy a scénáře, jejich cílem je napodobit činnost útočníka. Při tvorbě testovacích případů zde budou hrát důležitou roli právě předem připravené modely hrozeb, které testerům poskytují dobrý odrazový můstek. Je ale nutné si uvědomit, že penetrační testování vyžaduje od testera jakési speciálně vycvičené myšlení „mimo meze“, a tester s mnoha zkušenostmi z praxe bude proto v této fázi neocenitelný. Tester se musí na aplikaci dívat zcela jiným pohledem než ti, kdo ji navrhovali a vyvíjeli. Musí se vžít do role útočníka a aplikaci se musí snažit použít zcela jiným způsobem, než jaký byl pro její použití zamýšlen. Ve zkratce se dá říci, že se bezpečnostní tester nebude snažit ověřit, zda aplikace funguje správně, ale bude se ji všemi možnými způsoby snažit „rozbít“.
Na trhu existuje také velké množství volně šiřitelných nebo komerčních nástrojů, které dokáží bezpečnostní testy provádět zcela automaticky. Těmto nástrojům bude později věnována celá kapitola. Nyní je ale důležité zmínit, že tyto nástroje nikdy nemohou manuální testování zkušeného testera zcela nahradit. Existuje totiž celá řada zranitelností, které tyto nástroje nemohou už z principu odhalit. A to nemluvím o tom, že je někdy nutné zapojit také notnou dávku kreativity a přizpůsobení se konkrétním podmínkám. I o těchto omezeních bude řeč později. Přestože mají automatické nástroje v procesu testování své místo, nikdy by neměly být použity jako jediný způsob bezpečnostního testování. Ve chvíli, kdy byste odstranili všechny zranitelnosti nahlášené těmito nástroji, mohli byste totiž snadno podlehnout falešnému pocitu bezpečí, přestože by aplikace obsahovala ještě celou řadu závažných bezpečnostních problémů.
Rozdělení penetračních testů
Penetrační testy se běžně dělí na následující typy:
- White box testování
- Black box testování
- Gray box testování
Za white box testování se považuje revize kódu (Code Review), o které již byla řeč. Při tomto typu testování nezkoumá tester samotné chování již hotové aplikace, ale pouze její zdrojový kód. Výhodou je při tomto typu testů bezpochyby včasné odhalení chyb ještě před složením aplikace do funkčního celku a skutečnost, že lze takto odhalit i takové chyby, které jinými způsoby testování odhalit nelze.
Black box se oproti tomu staví k testování zcela odlišným způsobem. Při tomto typu testování je penetrační tester postaven před již hotovou aplikaci s tím, že nemá žádnou představu o tom, jak je daná aplikace naprogramována a jak tedy vnitřně funguje. Aplikace se pro testera stává jakousi černou skříňkou, které předává své vstupy, a ona vrátí odpovídající výstupy.
Je zřejmé, že black box testování vyžaduje, aby byla testerovi k dispozici funkční aplikace, díky čemuž se tento typ testování odsouvá až k samému konci vývojové cyklu. Protože tester nemá žádné ponětí o tom, jak aplikace uvnitř funguje, nemůže svými testy také pokrýt všechny situace, které mohou nastat například při větvení programu. Ačkoliv není z uvedených důvodů black box testování nejúčinnější metodou, neměla by ani tato v rámci SDLC nikdy chybět. Pomůže nám totiž odhalit poslední chyby, kterým se podařilo projít celým vývojovým cyklem.
Posledním typem testování je pak jakási kombinace black box a white box testování, která se označuje výrazem gray box. Tester v tomto případě postupuje stejně, jako při black box testování. Pokud ale narazí na nějaký zajímavý testovací případ, u kterého očekává, že by mohl obsahovat chybu, je mu k dispozici veškerá dokumentace a přístup ke zdrojovým kódům. Tester tak relevantní oblast může prozkoumat do nejmenších detailů.
Gray box testování dokáže také rozšířit testovací případy o některé testy, které by jinak tester během black box testování ověřit nemohl. Například, zda se hesla ukládají do databáze zahashované pomocí pomalých a silných hashovacích funkcí, zda je nasazeno vhodné logování uživatelských akcí a podobně.
V rámci této přiručky se budeme soustředit převážně na black box testování s tím, že tam, kde to bude vhodné, uvedeme také rozšíření testovacích případů pro Gray box testy.
Vrátím-li se ještě naposledy k našemu jednoduchému příkladu kalkulačky, měly by penetrační testy v závěrečné fázi SDLC odhalit následující nedostatky:
-
Vstup bílých a speciálních znaků do jednoho z operandů vede k zobrazení chybové zprávy:
Parse error: syntax error, unexpected ';' in C:\www\calc.php(6) : eval()'d code on line 1 Call Stack: 0.0006 677704 1. {main}() C:\www\calc.php:0
-
Dělení nulou vede k zobrazení chybové zprávy:
Warning: Division by zero in C:\www\calc.php(6) : eval()'d code on line 1 Call Stack: 0.0003 679424 1. {main}() C:\www\calc.php:0 0.0004 681184 2. eval('$vysledek = 1/0;') C:\www\calc.php:6
-
Vstup téměř libovolného znaku (mimo matematické operace) do hodnoty operátoru, skrze parametr op v URL vede k zobrazení chybové zprávy:
Warning: Unexpected character in input: ' in C:\www\calc.php(6) : eval()'d code on line 1 Call Stack: 0.0005 677304 1. {main}() C:\www\calc.php:0
- Chybové zprávy uvedené výše útočníkovi poskytují dostatek informací k určení OS, Webového serveru, použitého programovacího jazyka, ale také mu sdělují, že k chybě došlo při zpracování předaných hodnot funkcí eval().
- Vstup vhodně upraveného PHP kódu do jedné z předávaných hodnot vede ke spuštění libovolného kódu na straně serveru. Příklad vstupu do operandu A: 1;phpinfo();//
- Vstup uvozovky do jednoho z operandů vede při zakomponování hodnot do obsahu HTML k opuštění atributu value, a může být zneužit například ke spuštění kódu JavaScriptu ve webovém prohlížeči koncového uživatele. Příklad takového vstupu: '><script>alert(1)</script>
- Další testy by odhalily například špatnou konfiguraci webového serveru a jeho náchylnost na útoky typu SlowLoris, které mohou způsobit DoS.
Z příkladu je patrné, že nám penetrační testy provedené na naší kalkulačce skutečně zachránily krk. Pokud by se uvedené zranitelnosti dostaly až do veřejně přístupného produkčního prostředí, mohlo by mít jejich zneužití nedozírné následky. Zvláště pak v případě špatně nastavených přístupových práv a sdílení prostředků mezi různými aplikacemi.
Leckomu by se mohlo zdát, že je penetrační testování něco imaginárního, nekontrolovatelného a neměřitelného. Ve skutečnosti má ale tato činnost, podobně jako všechny ostatní činnosti, přesně definovaný průběh, se kterým Vás během čtení následujících stránek seznámím. Začneme průzkumem prostředí, zmapováním aplikace, kontrolou konfigurace webového serveru a přes otestování validace vstupů a výstupů se dostaneme až k doporučeným postupům pro sepsání závěrečné zprávy a k metrikám pro měření bezpečnosti.
Součástí této příručky bude také mnoho checklistů (seznamů), které Vám poradí, co vše je nutné na každé webové stránce aplikace ověřit. Podobné checklisty jsou k dispozici také v rámci projektu Application Security Verification Standard (ASVS), který se stal celosvětově uznávaným standardem při penetračním testování. Uvedený standard ASVS rozděluje bezpečnostní testování do čtyř úrovní (level 0-4), které jasně specifikují, které testy musí být v rámci dané úrovně provedeny. Toto poskytuje zadavatelům testů a jejich vykonavatelům snadnou cestu ke vzájemnému porozumění si v tom, co je od bezpečnostních testů očekáváno. Uvedení konkrétní úrovně, na kterou má být aplikace otestována, by se proto mělo stát součástí každé smlouvy mezi zákazníkem a testerem.