Každý, kdo se někdy zajímal o bezpečnost aplikací, pravděpodobně slyšel o SQL injekci. Je to útok zodpovědný za mnoho velkých úniků dat. Princip zneužití, kdy data mohou být považována za část dotazu či příkazu, je ale velmi obecný. Existuje řada způsobů, jak takto přimět aplikaci, aby dělala něco, co programátor nezamýšlel a ani by nechtěl dopustit. V tomto seriálu nejen připomeneme injekci SQL, ale také vysvětlíme několik příbuzných, ale méně známých typů zranitelností. Ty stejně jako v případě SQL injekce mohou vést ke krádeži dat a v krajním případě až ke kompletnímu převzetí systému. Projekt OWASP ve svém Top 10 označuje injekce obecně jako nejnebezpečnější chybu webových aplikací a podobně společnost MITRE uvádí několik konkrétních typů injekcí na předních příčkách v jejím seznamu Top 25 nejnebezpečnějších chyb v software (bez omezení na web).
Začněme ale právě od té nejznámější injekce, protože i přes větší povědomí stále zůstává mezi nejčastěji zneužívanými chybami. Velkým únikům dat se kvůli ní nevyhnulo například Sony nebo Yahoo. Přestože problém byl známý už v minulém století a obrana je relativně jednoduchá, podle některých statistik dnes odpovídá dokonce za více než polovinu všech zaznamenaných útoků (ale velmi záleží na metodologii, samotné číslo moc neřekne). Vypadá to, že mnozí útočníci už mají k dispozici velmi vyspělé postupy a nástroje pro zneužití SQL injekcí a díky přetrvávajícím chybám nemají moc důvodů experimentovat s modernějšími útoky. Pokud se navíc objeví chyba třeba v populárním systému pro správu obsahu webových stránek, jak se stalo letos (2018) znovu systému Joomla a minulý rok Wordpress, útočníkovi stačí aplikovat na míru připravený exploit na jednu z mnoha stránek, které otálejí aktualizovat na novější verzi. Dokonce i pokud aplikace neobsahuje žádná užitečná data, může se stát obětí malware, který chyby automaticky vyhledává a snaží se stránku třeba přimět těžit kryptoměny. Přiznané útoky se snaží sledovat stránka SQLi Hall-Of-Shame, pravděpodobně se však jedná jen o špičku ledovce a o množství úniků se ani nedozvíme (snad pomůže ohlašovací povinnost legislativy GDPR).
Princip SQL injekce
SQL je dotazovací jazyk používaný pro správu dat v relačních databázích. Možná nejrychlejší vhled do SQL injekce poskytne známý komiks xkcd.
V něm se předpokládá použití dotazu (pro extrakci informací o žákovi) jako
SELECT * FROM Students WHERE (name='Robert')
kde ovšem namísto konstanty Robert
má být použita proměnná se jménem žáka (a pokračovat by mohl dalšími podmínkami). Výsledný dotaz je třeba zkonstruovat, což může vypadat v závislosti na jazyku např. takto ( +
značí zřetězení)
query = "SELECT * FROM Students WHERE (name='" + studentName + "')";
Pokud ovšem v místě proměnné studentName
bude použita hodnota Robert'); DROP TABLE Students;--
z komiksu, výsledný dotaz bude vypadat jako
SELECT * FROM Students WHERE (name='Robert'); DROP TABLE Students;--')
a po výběru se zavolá ještě dotaz na smazání celé tabulky. Sekce --
slouží jako komentář (v závislosti na SQL dialektu) a může být útočníkem přidána proto, aby byl výsledný dotaz validní bez ohledu na to, jak po vložení proměnné pokračuje.
Pokud nechce útočník jen bezúčelně škodit, místo mazání zkusí například nahradit administrátorské heslo (uložené v jiné tabulce) a přihlásit se do aplikace s vysokými právy. To, jestli se dotaz skutečně provede, záleží na podpoře tzv. stacked queries závislé na dialektu SQL a platformě. Technika se dá použít například pro Microsoft SQL Server či PostgreSQL. Zneužitelné nejsou samozřejmě jen dotazy typu SELECT, ale i další složené SQL příkazy (UPDATE apod.). V některých případech je možné skrz SQL i zapisovat do souborů a převzít kontrolu nejen nad aplikací, ale nad celým serverem.
Pokročilé injekce
Existují ale i univerzálnější způsoby SQL injekce. Řekněme, že aplikace přihlásí uživatele, pokud dotaz zkonstruovaný jako
query = "SELECT id FROM users WHERE username='" + formUser + "' AND password='" + formPassword + "'"
vrátí nějaký výsledek ( formUser
a formPassword
jsou neošetřená data z formuláře na přihlašovací stránce). Útočník může do pole pro heslo vyplnit řetězec jako ' OR username='admin' AND ''='
(a do uživatelského jména cokoliv) a výsledkem bude dotaz
SELECT id FROM users WHERE username='cokoliv' AND password='' OR username='admin' AND ''=''
a přihlášení za uživatele admin (bez znalosti skutečného hesla). Skutečné username
se posílá až v poli pro heslo, protože operátor OR se obvykle zpracovává s nižší prioritou než AND a bez určení konkrétního uživatele by došlo nejspíše k přihlášení za prvního uživatele. Řetězec ''='
je použit jako zástupce vždy pravdivého výrazu, který zároveň doplní poslední apostrof, aby byl celkový dotaz validní (také by bylo možné opět použít znaky pro komentář).
Co když chce ale útočník přečíst data z ostatních tabulek? Pak se hodí použít výraz UNION SELECT
, který umožní zkombinovat více dotazů do jednoho. Stačí tak například najít stránku s dynamickým výpisem dat a přes SQL injekci přidat do vypisovaných dat další řádky, ale pocházející z jiných tabulek (jen jen potřeba vybrat tolik dat, aby počet sloupců seděl s původní tabulkou). Pokud žádný takový výpis aplikace nemá a všechna data se přímo zpracovávají, pořád je tu ještě technika tzv. slepé injekce (blind SQL injection). Při ní se používá pouze binárního signálu z aplikace, jakým může být například to, zda došlo k úspěšnému přihlášení či nikoliv. Pomocí výrazu LIKE
se lze zeptat, zda určitá hodnota v databázi začíná na konkrétní písmeno. Je možné provést takovou SQL injekci, že odpovědi ano/ne bude odpovídat úspěšné/neúspěšné přihlášení. O samotné přihlášení už přitom nejde, tady se snažíme získat z SQL injekce víc. Pomocí takových dotazů lze znak po znaku nakonec vyextrahovat kompletní databázi. Názvy tabulek můžeme hádat nebo se jednoduše podívat nejprve do tabulky INFORMATION_SCHEMA
. Při slepé injekci je oproti UNION SELECT
asi jednodušší použít výraz EXISTS
a vnořené dotazy. Jak prakticky vyextrahovat data z databáze si můžete vyzkoušet online, je tu pěkný návod (anglicky). Další slepé techniky si ukážeme ještě v příštím článku.
Manuální útok je samozřejmě zdlouhavý, existují ale volně dostupné nástroje jako sqlmap, které celý proces zautomatizují. Často už útočník ani nemusí rozumět tomu, jak nástroj funguje, a stačí jej pustit třeba přes noc. Někteří mají tvorbu takových nástrojů jejím autorům za zlé, protože rozšiřují okruh útočníků na téměř každého, kdo má motivaci a základní technické dovednosti. Na druhou stranu často až demonstrace snadného útoku přiměje tvůrce aplikace k opravě nalezených chyb a média k větší pozornosti. Navíc je můžeme využít na testování, podle mnohých tak naopak úroveň bezpečnosti globálně zvyšují. Vyspělí útočníci stejně nejspíš zvolí vlastní scénář útoku a uzpůsobí dotazy a načasování tak, aby byl průnik co nejnenápadnější.
Obrana
SQL injekce nemůže být provedena, pokud se místo ruční konstrukce dotazu použijí tzv. prepared statements (též parametrizace nebo binding proměnných), které navíc můžou zlepšit výkon aplikace a zpřehlednit kód. Ty fungují tak, že databáze obdrží zvlášť dotaz s vyznačenými parametry a zvlášť samotné parametry. Logika dotazu a samotná data jsou tak jasně oddělena a pokud je dotaz pevně určen v kódu, není možnost jej skrz data pozměnit.
Řešením ale nejsou vždy, protože ne všechny části SQL dotazu lze takto parametrizovat a vyhnout se skládání dotazu. V některých implementacích a specifických dotazech také použití mechanismu naopak vede k propadu výkonu. Pak nezbývá než se ujistit, že do dotazu jsou vloženy pouze bezpečné symboly. Ideální je potom dotaz skládat jen z námi definovaných částí nebo být velmi striktní ohledně povoleného formátu, protože jsme závislí na použitém SQL dialektu a existují triky, jak nedůslednou validaci obejít. Také je potřeba zkontrolovat nejen data přímo vyžádaná od uživatele, ale i další, které může mít útočník pod kontrolou, např. cookies zasílané prohlížečem. Útočník také může využít techniku SQL injekce druhého řádu, kdy nebezpečný řetězec nepoužije přímo, ale nechá aplikací uložit do databáze a později použít v jiném dotazu. Proto bychom neměli věřit ani hodnotám z vlastní databáze.
Ještě se můžeme bránit tím, že SQL dotazy nebude aplikace přímo vůbec používat, ale pro přístup k databázi použije například objektově-relační mapování (ORM). Pokud při tom ale opustíme objektový svět použitím specifických dotazovacích jazyků (jako Hibernate Query Language), hrozí stejné riziko jako u SQL (pak se např. můžete potkat s pojmem HQL injekce).
Více o obraně si ještě řekneme obecně po seznámení s dalšími typy injekcí. Stejně tak i vysvětlíme, jak lze injekce efektivně detekovat, což je důležitý předpoklad pro obranu. Nejdřív se ale zaměříme na další nebezpečný typ injekce – injekci příkazu (OS command injection).