Zabezpečení webových aplikací II. - databáze

Autor: J. Malý, J. Kacálek <xmalyj05(at)stud.feec.vutbr.cz>, Pracoviště: Vysoké učení technické v Brně, Téma: Bezpečnost, Vydáno dne: 15. 08. 2007

Zabezpečení webových aplikací je jednou z nejdůležitějších položek jejich vývoje. Tento článek popisuje možné útoky na webovou aplikaci prostřednictvím zneužití databázového dotazu a řeší možná zabezpečení a techniky správného ukládání citlivých dat v prostředích PHP a ASP.NET.


Security of web applications II. - database
Abstract
Security of web applications is one of the most important aspects of their development. This work describes possible attacks on web application with use of database query exploitation and shows possible way of security and techniques of proper saving of sensitive data in PHP and ASP.NET environments.


Úvod

Moderní web je založen na modelu relačních databází a přístupovém systému klient-server. Protože pro manipulaci s daty existuje jazyk (ve většině případů na bázi SQL), může se projevit snaha o změnu dotazu, který klademe databázi. Tímto způsobem pak můžeme docílit získání citlivých dat, v případě nejhoršího scénáře i přístup k databázovému serveru a jeho ovládnutí. Citlivá data v databázi je vhodné i chránit správným zabezpečováním při ukládání (např. správně zakódovaná hesla jsou pro útočníka téměř bezecenná). Tato část článku analyzuje problematické body při přístupu k databázi a navrhuje několik postupů, které vedou ke zvýšení bezpečnosti.

Zneužití databázového dotazu

Cílem útočníka je v tomto případě nalezení místa, ve kterém aplikace posílá SQL dotaz formulovaný na základě vstupních dat, a zmanipulovat tyto vstupní data tak, abychom pozměnili dotaz pro naše účely. Tato skupina útoků se často označuje jako SQL injection. Útoky jsou založeny na dvou častých prohřešcích návrhářů aplikace. Prvním z nich je nedostatečné filtrování kontrolních (escape) sekvencí, druhý spočívá ve špatné manipulaci s datovými typy v databázi.

Pokud klademe databázi dotaz, který je ovlivněn vstupními parametry, nejčastěji tyto parametry reflektujeme jako hodnoty (ať už při ukládání, nebo čtení). Hodnoty se v SQL jazyku ohraničují pomocí znaku '. Pokud se v tomto bodě nefiltruje vkládaná hodnota na obsah tohoto znaku a neošetří se pomocí patřičné escape sekvence – v tomto případě \', SQL jazyk pochopí výskyt tohoto řetězce jako konec hodnoty a dál pracuje se zbývajícími daty jako s pokračováním dotazu. Tím se tedy z úrovně hodnoty dostáváme na úroveň vyšší, ve které můžeme ovlivnit tvar celého příkazu. Typický příklad zneužití tohoto faktu je znázorněn na obr.1.

safeweb_03

Obr. 1 Typický příklad SQL injection útoku

Situace se může znatelně zkomplikovat, pokud útočník použije při formulaci dotazu klíčové slovo UNION, které spojuje více dotazů typu SELECT. Nejvážnější problém ale tento útok představuje, pokud je veden proti databázovým systémům jako SQLite nebo postgreSQL, které umožňují odeslat více SQL příkazů v jednom dotazu na databázi. Pak je možné pomocí několika příkazů zcela získat kontrolu nad databázovým systémem.

Chybná manipulace s datovými typy představuje velmi podobné riziko. Využívá faktu, že číselné hodnoty v SQL nemusíme ohraničovat pomocí znaků '. Potom můžeme místo číselné hodnoty podstrčit řetězec a situace je úplně stejná, jako v předchozím případě. Navíc zde nemusíme ošetřovat správné ukončení escape sekvencí a tím zajistit korektní syntaxi dotazu.

Metody zabezpečení

Zabezpečení spočívá v převedení všech kontrolních značek v jejich datovou reprezentaci pomocí patřičných escape sekvencí – tj. doplněním značek ', ", \ a NULL o zpětné lomítko \. Co se týče datových typů, je vhodné explicitně hodnoty uzavírat do uvozovek a velmi pečlivě si ošetřovat obsah ukládaných proměnných (např. pomocí regulárních výrazů).

V PHP se tento problém vyřeší pomocí funkce mysql_real_escape_string(), která se o převod znaků stará. Tato funkce je binárně bezpečná a funguje u všech znakových sad. Jiné řešení je použít funkci addslashes(), která provádí nahrazování také, je však citlivá na některá nastavení PHP – zejména magic_quotes_sybase, které nahrazuje znaky ' na ''.

Podstatný problém také představuje direktiva magic_quotes_gpc, která automaticky nahrazuje speciální znaky ve všech příchozích (POST, GET, COOKIE) datech. V případě, že je toto nastavení zapnuto (a velmi často se s touto situací v praxi setkáme), dalším ošetřováním dat pomocí výše zmíněných funkcí docílíme dvojitého nahrazování – tedy např. znak \' bude převeden na \\\', což se ve finále zobrazí jako \'. Proto je nutné, pokud je tato direktiva zapnutá, ošetřování neprovádět nebo jej odstranit pomocí funkce stripslashes(). Nastavení direktivy zjistíme přímo v PHP pomocí funkce get_magic_quotes_gpc() .

Jiný způsob ochrany je založen na použití databázové vrstvy, která umí oddělit v SQL dotazu data od zbytku dotazu. O samotné správné ošetření dat se stará tato vrstva sama. Jde o řešení komplexnější, které v první řadě vyžaduje, abychom přistupovali k databázi prostřednictvím určitého systému. Typickým příkladem je MySQLi nebo PearDB, můžeme si však pochopitelně vytvořit vrstvu vlastní.

V ASP.NET existuje více možností, jak se proti tomuto útoku bránit. Jeden způsob ochrany je ověřování dat z potenciálně nebezpečných vstupů na základě očekávaného typu, délky, formátu či rozsahu těchto dat. V ASP.NET se používají ověřovací prvky RegularExpressionValidator a RangeValidator a také funkce Regex.IsMatch(String vstup, String vzor). Ověřovací prvek RegularExpressionValidator kontroluje data z formulářového prvku podle předloženého vzoru ve formě regulárního výrazu a ověřovací prvek RangeValidator podle nastaveného číselného rozsahu. Funkce Regex.IsMatch srovnává vstupní řetězec podle vzoru ve formě regulárního výrazu.

Dalším a asi nejlepším způsobem je použití parametrizovaných SQL dotazů a parametrizovaných uložených procedur. V tomto přídě se programátor o ověřování vstupních dat nemusí starat. Tuto práci za něj vykoná objekt SqlCommand(). Ten vstupy před vykonáním příkazu ošetří tak, aby neobsahovaly žádné nebezpečné sekvence znaků. Příklad použití parametrizovaného SQL dotazu můžete vidět níže:

  SqlCommand sc = new SqlCommand(); 
  sc.Parameters.Add("@parid", SqlDbType.Int, 7);
  sc.CommandText = "SELECT * FROM Users WHERE id = @parid";
  sc.SelectCommand.Parameters["@parid"].Value = 25;

Je důležité poznamenat, že prostředí .NET neobsahuje žádnou funkci podobnou funkci addslashes() nebo mysql_real_escape_string(). V případě použití parametrizovaných SQL dotazů a parametrizovaných uložených procedur totiž prakticky není potřeba.

Proti následkům útoků se dá bránit také nastavením práv v samotné databázi. Obecně platí, že webová aplikace by neměla mít větší úroveň práv, než potřebuje, a měla by být omezena pouze na tabulky, se kterými skutečně pracuje. Dále můžeme využít mechanismů moderních SQL systémů, jako jsou vložené procedury a filtrování pomocí parametrizace vstupu. Poslední bod zabezpečení spočívá v zákazu vykonávání více příkazů v jednom dotazu (pokud to tedy situace dovoluje) .

Zabezpečení citlivých údajů v databázi

Pokud ukládáme v databázi hesla, přístupové údaje, platební informace a jiná citlivá data, je vhodné (zejména kvůli výše zmíněným útokům) zabezpečovat tyto údaje proti přímému čtení. Z hlediska opětovného získání informace rozlišujeme jednocestné a vratné zabezpečení.

Jednocestné zabezpečení

Typickým příkladem dat, která se zabezpečují jednocestně, jsou hesla, jejichž původní podoba v textové formě je velmi zranitelná. Každou aplikaci můžeme ošetřit do té míry, aby hesla ukládala v zakódované formě a nikdy nemusela pracovat s původní textovou podobou. Výhodou je, že ani útočník, ani administrátor databáze nikdy neuvidí heslo v původní podobě. Příklad komplexního řešení, které využívá jednocestného kódování při ukládání v databázi a pamatuje i na možnost ztráty hesla je na obr.2.

safeweb_04

Obr. 2 Komplexní systém zabezpečení přístupových údajů

Jednocestné zabezpečení je ve většině případů realizováno pomocí hashovacích algoritmů. Vstupem kryptografické hashovací funkce je jakýkoliv řetězec jakékoliv délky, výstupem je řetězec fixní délky, který nazýváme hash (můžeme se setkat i s pojmem otisk) [1]. Hashovacích algoritmů je celá řada (např. MD2, MD4, MD5, RIPEMD, SHA-0, SHA-1, SHA-256/224, SHA-512/384) .

Na hashovací algoritmy klademe určité požadavky:

  1. bezkoliznost - vždy se snažíme docílit, aby dva různé řetězce neměly stejný hash. Bohužel, tento požadavek nemůže nikdy platit dokonale, což pramení z faktu, že převádíme libovolně dlouhý řetězec na řetezec omezené délky (obsahující znaky pouze z určité množiny).
  2. lavinovitý efekt - v originále nazýván jako avalanche effect – jde o vlastnost algoritmu, kdy malá změna na vstupu způsobí nesrovnatelně větší změnu na výstupu (typicky se při převrácení hodnoty jednoho bitu ve vstupním řetězci změní více než polovina bitů ve výsledném otisku).

Jednocestnost hashovací funkce představuje pro útočníka podstatný problém. Prakticky neexistuje jiné řešení než využití metody síly (brute force) odhalování kódu – tedy, generujeme všechna možná hesla a srovnáváme vzniklý hash s tím, který chceme prolomit. V případě, že heslo je kombinací několika znaků, např. malých písmen, je velmi vysoká pravděpodobnost, že útočník heslo odhadne. K tomu mu slouží i online databáze otisků, obsahující milióny zjištěných hashů, ve kterých se jednoduše dohledá správné heslo. Velmi dobře je tímto způsobem podchyceno kódování MD5, které se na webu používalo zejména v posledních letech. Dnes se od MD5 ustupuje kvůli jeho náchylnosti ke kolizím [2] a doporučuje se využívat SHA-1 (nebo lépe SHA-256/224 a vyšší).

Bezpečnost samotné hashovací funkce je ale pouze jeden prvek zabezpečení, který vstupuje do kódovacího řetězce. Záleží i na uživateli a tzv. síle hesla – krátká hesla z omezené množiny znaků (malá písmena, nebo v nejhorším případě čísla) jsou relativně snadno odhalitelná pomocí silových metod. Poslední záležitost, kterou je nutné ošetřit, je možnost, že dva uživatelé systému mají stejné heslo – pak i jejich hash bude stejný a útočník má práci zjednodušenou.

Proto je vhodné bezpečnost nezanedbat použitím obyčejného otisku, a doplnit ho o tzv. sůl (salt). To je náhodně vygenerovaný řetězec, se kterým zadané heslo uživatele provážeme a až poté vygenerujeme hash. Sůl musíme pochopitelně uchovávat u ostatních údajů uživatele, což je jediná (relativní) nevýhoda této metody. Na obr. 3 je návrh bezpečné ukládací metody. Tato metoda je ošetřena proti problému krátkého hesla a problému existence dvou stejných hesel. Je vhodné nepoužívat obyčejné spojení řetězců (z toho důvodu, že salt se ukládá v databázi nekódovaně), ale zkombinovat je zabezpečeným způsobem, např. pomocí algoritmu keyed hash (HMAC) [3]. Tímto způsobem zabezpečená data jsou prakticky neprolomitelná v rozumném časovém intervalu.

safeweb_05

Obr. 3 Bezpečné uložení hesla pomocí hashování a soli

Někdy se ke zvýšení bezpečnosti používá dvojitého i několikanásobného hashování jednoho údaje. Toto však obecně nelze doporučit jako bezpečnou metodu, neboť hashovací funkce, použitá na získaný hash, vede po několika průchodech ke stejným, periodicky se opakujícím výsledkům.

V PHP využijeme při tvorbě hesla hashovacích funkcí md5() a sha1(), které přímo generují výsledný hash na základě parametru. Pro metodu HMAC lze použít např. funkci hash_hmac(), jejímž prvním parametrem je typ hashovací funkce, druhým a třetím parametrem vstup a klíč (v našem případě půjde o heslo a sůl). Tato funkce byla přidána od PHP 5.1.2, ve starších systémech je třeba použít rozšíření PECL hash 1.1 – 1.3.

Pokud při tvorbě ASP.NET stránek používáte jazyk C#, hashovací třídy naleznete v prostoru jmen System.Security.Cryptography. Tento prostor jmen obsahuje třídy MD5(), SHA1() a kompletní rodinu hashovací algoritmů SHA2 (SHA256(), SHA384(), SHA512()). Samotný hash je vytvářen metodou ComputeHash([]byte b). Této metodě je vstupní parametr předáván jako pole bytů, proto je nutné každý řetězec převést na pole bytů např. statickou metodou ASCIIEncoding.Default.GetBytes(string s). Pro metodu HMAC lze použít např. třídy HMACMD5(), HMACSH1(), HMACSHA256() apod., které také naleznete v prostoru jmen System.Security.Cryptography [5].

Vratné metody zabezpečení

Krátce se zmíníme ještě o vratných metodách zabezpečení. Ty se typicky aplikují na citlivé údaje, které potřebujeme získat v původní podobě (např. čísla kreditních karet). Využíváme nejčastěji některou z metod symetrického, lépe však asymetrického šifrování. To má tu výhodu, že obsahuje dva klíče, veřejný a privátní. Veřejný klíč, ke kterému může mít přístup většina uživatelů systému včetně případného útočníka, slouží k zašifrování údaje do zašifrované podoby. K jejímu dešifrování je třeba privátní klíč, který vlastní pouze odpovědný manipulátor s údaji.

V PHP lze pro asymetrické šifrování využít vrstvu SSL, konkrétně rozšíření OpenSSL, ve kterém lze velmi snadno vytvářet vlastní klíče a údaje šifrovat. Zájemce o podrobnější informace můžeme odkázat např. na [6]. Podobně jednoduché řešení v prostředí ASP.NET nám bohužel není známo (využívá se standardní podpory SSL v operačním systému).

Závěrem

Zajistit bezpečnost při přístupu k databázi by mělo být jednou z prvních priorit při vývoji webové aplikace. Uživatel za žádnou cenu nesmí získat přímý přístup k jakémukoliv SQL dotazu. Nejlepší metoda je dokonalá filtrace vstupních dat na základě očekávaných parametrů. Důležitou pozornost je také třeba věnovat nastavení databáze a zabezpečování citlivých údajů. Správným uložením přístupových údajů můžeme oddálit dobu nutnou ke zjištění hesla z jednotek sekund na desítky let.

Literatura

[1] Burda, K.: Bezpečnost informačních systémů (výukové materiály k předmětu), ÚTKO FEKT VUT Brno 2006, online, dostupné z http://adela.utko.feec.vutbr.cz/index.php?option=com_wrapper&Itemid=49
[2] Klíma, V.: Tunely v hašovacích funkcích: kolize MD5 do minuty, IACR ePrint archive Report 2006/105
[3] Krawczyk H., Bellare M., Canetti R.: HMAC: Keyed-Hashing for Message Authentication, IETF RFC 2104, Feb. 1997

Další zdroje
[4] Jakub Vrána - PHP triky (webová stránka), dostupné z: http://php.vrana.cz/
[5] How To: Prevent SQL Injection in ASP.NET (webová stránka), dostupné z: http://msdn2.microsoft.com/en-us/library/ms998271.aspx
[6] PHP Group: OpenSSL Functions (webová stránka), dostupné z: http://cz2.php.net/manual/en/ref.openssl.php