Autor: .cCuMiNn. | 3.9.2004 |
Z originálu " SQL Injection Attacks - Are You Safe?" od Mitchella Harpera přeložil .cCuMiNn.
Databáze je srdcem mnoha webových aplikací: ukládají se v ní data potřebná pro webové a aplikační "přežití". Obsahují uživatelské osobní údaje a citlivé finanční informace. Jsou zde skladovány data z účetnictví, platby, inventurní data apd. Díky kombinaci databáze a webového scriptovacího jazyka můžeme jako tvůrci produkovat weby které mají šťastné klienty, platí účty a více méně na nich stojí náš obchod.
Co se však stane, pokud realizuješ web, ve kterém tvá data nejsou v bezpečí? Co se stane, když bude objevena nová bezpečnostní chyba? Zřejmě aplikuješ patch nebo upgraduješ databázový server na verzi, která je již bez této chyby. Bezpečnostní díry a patche se vždy objeví pro každý databázový a programovací jazyk, ale vsadím se, že jsi nikdy neslyšel o útocích SQL injection...
V tomto dokumentu se pokusím vrhnout světlo na tento nezdokumentovaný útok. Vysvětlím co to útok SQL injection je, a jak můžeš předejít jeho výskytu uvnitř firmy. V závěru dokumentu budeš schopen identifikovat situaci kde útok SQL injection může povolit neautorizovaným uživatelům vstup do systému a naučíš se cesty k zabezpečení existujících kódů pro zabránění SQL injection útokům.
Můžeš vědět, že SQL znamená Structured Query Language. Existují v mnoha rozdílných dialectech z nichž mnoho je založeno na bázi SQL-92 ANSI standartu. SQL dotaz obsahuje jeden z mnoha SQL příkazů, jako SELECT, UPDATE nebo INSERT. Pro dotazy SELECT je typické, že obsahují klausuli na jejímž základě vrátí data. Například:
Klausule ve výše uvedeném SQL dotazu WHERE userName = 'Justin', znamená, že si přejeme vrátit pouze ty záznamy, z tabulky users, kde userName odpovídá hodnotě Justin.
Právě díky těmto typům dotazů jsou SQL jazyky tak populární a flexibilní... jsou ovšem také podstatou útoků SQL injection. Jak název napovídá, útok SQL inject nakazí nebo zmanipuluje SQL kód. Vložením neočekávaného SQL do dotazu můžeme zmanipulovat databázi mnoha různými cestami.
Jadna z mnoha populárních cest k ověření uživatele na Webu je opatřit web HTML formulářem, skrz který můžeme vložit uživatelské jméno a heslo. Předpokládejme, že máme následující jednoduchý HTML formulář:
Když je formulář potvrzen, obsahy polí userName a password jsou odeslány scriptu login.asp, a jsou tomuto scriptu dostupné skrz Request.Form kolekci. Nejjednodušší cestou k ověření uživatele bude vytvoření SQL dotazu, který zkontroluje dotaz proti databázi a zjistí jestli daný uživatel existuje. Můžeme vytvořit script login.asp, který bude vypadat takto:
Ve výše uvedeném příkladu bude uživateli zobrazeno "Logged In" pokud bude nalezen odpovídající záznam v databázi nebo "Bad Credimentials", pokud záznam v databázi nalezen nebude. Před tím než budeme pokračovat si vytvoříme databázi myDB, které se budeme v našich příkladech dotazovat. Dále vytvoříme tabulku users s nějakým záznamem:
Pokud teď vložím jméno john a heslo doe, bude mi zobrazeno "Logget In". Dotaz pro vyhledání v databázi bude vypadat takto:
V tomto dotazu není nic nechráněného ani nebezpečného...nebo je? Možná na první pohled není, ale co se stane jestliže zadáme jméno john a heslo
Výsledný dotaz pak bude vypadat takto:
Co se stane? Dotaz nyní jednoduše vyhledá uživatele se jménem john a místo zkontrolování hesla je nyní heslo porovnáno s prázdným řetězcem nebo ověřena podmínka rovnice 1=1. Je tím tedy myšleno: jestliže je heslo prázdné nebo 1 se rovná 1, pak je odpovídající záznam v tabulce users nalezen. -- (2x minus) vkládáme abychom zbytek příkazu označili za poznámku a zabránili tím chybovému hlášení , které by vzniklo v důsledku nepárovosti uvozovek.
Dříve vytvořený script login.asp nám tedy vrátí jeden záznam a bude zobrazen text "Logged In". Můžeme také zaútočit na pole username nějak takto:
Toto zadání provede následující dotaz proti tabulce users:
Dotaz uvedený výše nyní vrátí množství všech záznamů v tabulce users. Toto je perfektní příklad útoku SQL injection: vložení kódu ke zmanipulování dotazu vedoucí k provedení nesprávných výsledků.
Jiná populární cesta k ověření uživatele proti tabulce loginů je v porovnáví svých detailů proti tabulce a vyselectování platného username z databáze, podobně jako:
Pokud nyní vložíme uživatelské jméno john a heslo doe bude nám zobrazeno: Logged In As john. Avšak, jestliže užijeme následující login pověření:
pak budeme taktéž přihlášeni jako john protože záznam jehož obsah je john se nachází jako první v seznamu. Naší tabulku users si doplníme o další záznamy:
Protlačit login skrz HTML formulář podobně jako jsme právě viděli, je typický příklad SQL injection útoku. Trochu později se pak podíváme na cesty k nápravě těchto typů útoků.
Nejdříve se ale chci podívat na nějaké příklady SQL injection útoků, abychom je zcela pochopili. Pro začátek si ponecháme náš vzorový přihlašovací formulář obsahující pole username a password.
Microsoft SQL server má dialect SQL nazývaný Translact SQL nebo TSQL pro zkrácení. Můžeme napadnout zdroj TSQL s použitím čísel abychom si ukázali jak SQL injection útoky pracují. Vezmeme následující dotaz založený na tabulce users, kterou jsme vytvořili dříve.
Jestliže jsi v SQL zběhlý, pak určitě nepochybuješ o tom, že tento dotaz vyvolá nějaké chybové hlášení. Můžeme jednoduše vytvořit script login.asp dotazující se naší databáze tímto dotazem pokud použijeme tyto vstupní údaje:
Když kliknu na tlačítko submit k zahájení přihlašovacího procesu, vyhodí SQL dotaz tuto chybu na náš webový prohlížeč:
No dobře. Zdá se, že nyní tato chybová zpráva sděluje neautorizovanému uživateli název jednoho pole z databáze. Ověření našeho loginu bylo zkoušeno proti: users.userName. Použitím jména tohoto pole můžeme užít příkaz LIKE SQL serveru k loginu s následujícím prověřením:
Ještě jednou provedeme vložení SQL dotazu proti naší tabulce users:
Když vytvoříme tabulku users, vytvoříme si také uživatele jehož userName bude admin a userPass bude wwz04ff. Výše uvedený script nám po přihlášení s tímto jménem a heslem zobrazí tento výsedek: Logged In As admin
SQL server mezi různými databázemi vymezuje dotazy použitím středníku. Užití středníku umožňuje zadávat složené dotazy k vytvoření jedné série a sekvenčnímu použití, například takto:
...budou nám vráceny tři záznamové sety. První obsahuje hodnotu 1, druhý hodnotu 2 a třetí hodnotu 4, atd. Takže pokud se zalogujeme s těmito údaji:
pak se dotaz provede ve dvou krocích. Zaprvé bude vyhledáno userName v záznamech tabulky users. Zadruhé bude tabulka users vymazána. Když se poté budeme chtít znovu přihlásit bude nám pouze zobrazeno toto chybové hlášení:
Poslední příklad, spojený s naším přihlašovacím formulářem, budeme používat ke spuštění TSQL specifických příkazů. Mnoho webů užívá defaultní systémové konto (sa) uživatele, když se přihlašuje na SQL Server z ASP scriptů nebo aplikací. Čili, takovýto uživatel má přístup ke všem příkazům a může mazat, přejmenovávat a přidávat databáze, tabulky a mnoho dalšího.
Jedním z velmi silných příkazů SQL serverů je SHUTDOWN WITH NOWAIT, pomocí kterého ihned vypneme SQL server jako službu windows. Pro restartování SQL serveru po vydání tohoto příkazu musíš použít SQL service manager nebo nějaký jiný způsob restartování SQL serveru.
Tímto vstupem tedy můžeme shodit SQL server skrz naší vzorovou přihlašovací stránku:
Zadání těchto informací bude mít za následek vytvoření takovéhoto dotazu:
Pokud má uživatel nastaveno defaultní (sa) konto nebo má potřebná práva, pak bude SQL server ukončen a bude potřeba ho restartovat aby byl znovu provozuschopný.
SQL server obsahuje také rozsáhlé úložiště procedur, uvnitř jsou základní speciální C++ DLL knihovny, které můžou obsahovat silné C/C++ programy k manipulaci se serverem. Čtení adresářů a registrů, mazání souborů, spouštění příkazů, apd. Všechna úložiště procedur existují pod hlavní databází a začínají na "xp_".
Pomocí několika procedur můžeme trvale poškodit systém. Můžeme toto úložiště procedur použít třeba prostřednictvím našeho přihlašovacího formuláře s vloženými příkazy u uživatelského jména například takto:
Všechno co musíme udělat je vybrat vhodnou vzdálenou proceduru a nahradit xp_xxx, v uvedeném příkladu, jejím názvem. Například, pokud bylo IIS nainstalováno na nějakém stroji jako SQL server, můžeme jej restartovat užitím procedury xp_cmdshell. Všechno co potřebujeme zadat do našeho formuláře tedy je:
což odešle tento dotaz SQL serveru:
Jsem si jistý, že budeš souhlasit s tím, že to může být příčinou vážných problémů a se správnými příkazy můžeš zničit celý web.
Nastal čas odhlédnout od našeho scriptu login.asp a podívat se na jinou metodu provedení útoku SQL injection. Častokrát je na webu vidět URL podobné tomuto:
Očividně je dvojka ID produktu a část sítí stojí na jednoduchém vytváření dotazů kolem id a proměnném dotazování, podobně jako:
Předtím, než budeme pokračovat si vytvoříme tabulku se záznamy v našem SQL serveru:
Musíme si také vytvořit ASP script s názvem products.asp:
Pokud nyní navštívíme products.asp webovým prohlížečem s tímto URL:
... pak nám bude ve webovém prohlížeči zobrazena tato textová řádka: Got product Pink Hoola Hoop
Poznamenávám, že product.asp vrátí pole recordsetu založeného na bázy názvů polí:
Ačkoliv se to může zdát bezpečné, opravdu není a stále můžeme zmanipulovat databázi stejně jako v předešlých příkladech. Také poznamenávám, že klausule WHERE je zde opět založena na numerické hodnotě:
Proto, aby stránka products.asp fungovala správně, je potřeba ji předat požadované id produktu v proměnném dotazovacím řetězci. Není to příliš problémové. Zvážíme-li však následující URL na products.asp:
Kde každý %20 v této URL představuje zakódovaný znak mezery, takže URL ve skutečnosti vypadá takto:
Pokud použiješ toto kombinaci dotazu v products.asp dotaz bude vypadat takto:
Užitím několika našich znalostí a URL kódování můžeme nyní lehce získat jméno pole produktů z tabulky products:
Zadání tohoto URL bude mít za následek výstup takovéhoto chybového hlášení:
Teď tedy známe název pole produktů (products.prodName) a vyvoláme si následující URL:
Zde je uveden tento dotaz bez zástupných znaků:
Prvně je nám vráceno "No product found", avšak také je spuštěno INSERT na tabulku products, které bude mít za následek přidání nového záznamu s prvními 50-ti znaky verze SQL serveru (proměnná @@version obsahuje detaily o verzi SQL serveru)
K získání této verze teď jednoduše vyvoláme stránku products.asp s hodnotou posledního vstupu takto:
Tento dotaz si nejprve získá ID naposled vloženého záznamu do tabulky products použitím funkce SQL serveru MAX a zobrazí nám detaily tohoto ID, čili verzi SQL servru, kterou jsme do tabulky vložili:
Tato metoda SQL injection útoku může být použita k provedení číselných úkolů a v jednom z bodů tohoto dokumentu dám tip jak předejít tomuto SQL injection útoku.
Jestliže navrhneš své scripty a aplikace pozorně, můžeš se SQL injection útokům často vyhnout. Nyní uvedu pár bodů, ve kterých ukážu, jak můžeme redukovat místa choulostivá na útok v našich sítích.
Defaultní systémové konto (sa) pro SQL server 2000 bys neměl nikdy používat. Měl bys vždy nastavit specifická konta pro specifické účely. Například, pokud tvá databáze běží tak, že dovoluje uživatelům sítě zobrazovat a třídit produkty, pak bys měl nastavit uživatelské volání webUser_public. Nastavíš tak práva SELECT s tabulkou products a práva INSERT jen v tříděné tabulce.
Pokud nepoužíváš procedury nebo uživatelské funkce z rozsáhlého úložiště procedur, měl bys je odstranit nebo přesunout na izolovaný server. Extrémně nebezpečné procedury, jako jsou xp_cmdshell a xp_grantlogin také odstraň, čímž zablokuješ útok ještě dříve než k němu dojde.
Jak můžeme vidět v uvedených příkladech, požaduje většina injection útoků zadání jednoduché uvozovky k ukončení výrazu. Použitím jednoduché nahrazovací funkce a konverzí vstupu všech jednoduchých uvozovek na dvě jednoduché uvozovky velmi zredukujeme šanci na zdařené provedení SQL injection útoků. Pomocí ASP vytvoříme velice jednoduše základní nahrazovací funkci, která bude tuto záměnu provádět automaticky:
Pokud nyní použijeme funkci stripQuotes v kombinaci s naším prvním dotazem pak se například z dotazu:
... stane toto:
Následkem této záměny dojde k zastavení injection útoku, protože klausule pro dotaz WHERE nyní požaduje pro ověření pole userName i userPass.
Jak v tomto dokumentu vidíme, jsou určité znaky nebo znakové sekvence, jako ; , -- , select , insert , xp_ , používány k provedení SQL injection útoků. Jejich odstraněním z uživatelova vstupu před vytvořením dotazu značně zredukujeme možnost provedení takovýchto útoků.
Společně s jednoduchým řešením na záměnu uvozovek potřebujeme ještě základní funkci k náhradě všech těchto znaků:
Použitím funkce stripQuotes v kombinaci s funkcí killChars velice zúžíme šance ke zdařilému SQL inject útoku. Pokud tedy máme tento dotaz:
A protlačíme ho přes funkci stripQuotes a poté skrz funkci killChar získáme nakonec:
Obsah je od základu nepoužitelný a nebude nám vrácen žádný záznam.
Není dobré mít na formuláři textové pole akceptující 50 znaků, když pole v tabulce může obsahovat znaků pouze 10. Pokud omezíš velikost textových polí na formuláři jen na nutně potřebnou velikost, můžeš tím zamezit vložení škodlivých údajů k provedení útoku.
Pokud přijímáš číselné hodnoty pro product ID nebo podobné číselné hodnoty, používej vždy funkci pro zkontrolování tohoto vstupu, zda je hodnota číselná. Takováto funkce pro ASP je například IsNumeric(). Pokud hodnota není číselná pak odkaž uživatele na jinou stránku, ze které si může daný produkt vybrat.
Také vždy odesílej data z tvého formuláře metodou POST, čímž zabráníš dopsání škodlivých dat do URL.
V tomto dokumentu jsme viděli co to útok SQL inject je a také jak napadnout URL k provedení útoku. Není bohužel vždy možné ohlídat všechny typy těchto útoků, ale doufejme, že nyní víš jaké typy SQL injection útoků existují a víš i jak se proti nim bránit.
Ačkoliv jsem v tomto článku nahlédnul jen na Microsoft SQL server, víme již, že databáze nejsou tak bezpečné jak by se mohlo zdát. Útoky SQL injection mohou napadnout také MySQL a Oracle databazové servey - a všechny ostatní.