Tipy pro prevenci vkládání SQL pro webové programátory
V dnešní době je vzácné vidět skutečně statický web nebo aplikaci. Namísto pevně zakódovaných dat a rozložení tabulek, které byly dříve tak běžné, dominuje modernímu vývoji webu The Feed: zdánlivě nekonečný posuv dynamického obsahu.
Zdroje, jako jsou tyto, jsou vytvářeny vytahováním dat z databáze. To vám umožňuje filtrovat věci, jako jsou tweety nebo aktualizace stavu, aniž byste museli přepínat stránky, a znamená to, že mnohem více vašich služeb lze automatizovat než v předchozích desetiletích. Nejběžnější typy databází jsou založeny na velmi oblíbeném SQL (Structured Query Language), což je zjednodušeně programovací jazyk, který databáze používá pro ukládání a získávání dat. Každou tabulku v databázi lze považovat za podobnou tabulce, jejíž každá buňka obsahuje datové body.
SQL Injection je speciální typ útoku, který se zaměřuje na tyto typy online aplikací. Zatímco samotná databáze může být zabezpečena proti hackerům, slabým místem těchto útoků je aplikace a její úroveň přístupu k databázi. Útok přiměje aplikaci k předání dalších SQL příkazů do databáze, obvykle se záměrem buď získat zvýšená přístupová práva, nebo nechat databáze vyplivnout více informací, než by měla.
Budeme se zabývat třemi hlavními způsoby, jak může programátor posílit svůj kód proti tomuto druhu útoku. Poskytneme také ukázky kódu Java, PHP, Python a Perl, které vám pomohou pochopit, jak jsou tato řešení nasazena. Existují také způsoby, jak může správce databáze nebo serveru posílit svůj server ve spojení s programátorem. Tyto jsou:
- Použití zástupných symbolů v připravených dotazech
- Čištění vstupu uživatele
- Použití uložených procedur
A pro správce serveru/databáze:
- Nejméně oprávnění k přístupu
Zástupné symboly
Nejprve probereme použití zástupných symbolů v připravených SQL příkazech vložených do zdrojového kódu programu. Zástupný symbol je jednoduše „?“ a vloží se do dotazu SQL místo hodnoty, která má být ještě dodána. Očekává se, že program nahradí toto „?“ hodnotou, kterou zadá buď uživatel, nebo jiná část programu.
Pro ilustraci konceptu si vezměte následující příklad dotazu SQL:
|_+_|Výstupem tohoto dotazu bude každý sloupec dat v tabulce klienta týkající se klienta s clientID 1078. Nyní předpokládejme, že program požádá uživatele, aby uvedl své vlastní clientID. Nebezpečná verze stejného dotazu může vypadat nějak takto:
|_+_|V tomto příkladu se očekává, že uživatel, který přistupuje k programu, jednoduše zadá své ID do správného pole, aby získal podrobnosti o svém účtu. Pokud však uživatel poruší syntaxi příkazu (například zadáním „0 NEBO 1=1“ místo platného clientID), bude nyní databázový dotaz odeslán jako:
|_+_|Protože 1 se bude vždy rovnat 1, výše uvedený dotaz vyplivne obsah každého řádku v tabulce klienta. To zahrnuje každé clientID spolu s jakýmikoli informacemi uloženými v této tabulce týkajícími se klientské základny společnosti; není něco, k čemu chcete, aby měl přístup jen kdokoli. První způsob, jak se vyhnout tomuto typu úniku, je použití připravených příkazů se zástupnými symboly pro dynamicky generované hodnoty.
Připravené výpisy
SQL již obsahuje možnost použití zástupných symbolů v dynamicky generovaném SQL připraveném příkazu. Zástupný symbol pak lze nahradit správnou hodnotou těsně před provedením dotazu. Připravený příkaz je jednoduše SQL dotaz, který je nastaven na začátku za účelem jeho opakovaného použití, aniž by bylo nutné celý dotaz znovu odesílat do databáze. Namísto toho je připravený výpis odeslán jednou a poté jsou postupně odeslány různé hodnoty.
Ve svém programu připravíte svůj SQL dotaz nebo příkaz se zástupným symbolem na místě datového pole, které má uživatel poskytnout. Například v našem vzorovém dotazu výše program žádá uživatele o jeho ID klienta, aby mu umožnil zobrazit podrobnosti o účtu. Namísto umístění proměnné přímo do dotazu tam chcete místo toho umístit znak ‚?‘. Bude to vypadat nějak takto:
|_+_|Program má poté za úkol poskytnout hodnotu, která nahradí zástupný symbol. Může to být dříve definovaná proměnná, uživatelský vstup nebo dokonce seznam hodnot zcela z jiného programu. Jakmile je hodnota nahrazena, lze dotaz provést a zpracovat výsledky.
Zástupné symboly jsou skvělé ve své jednoduchosti. Samotná databáze odfiltruje od uživatele mnoho neočekávaných nebo potenciálně nebezpečných dat na základě toho, na co zástupný symbol ukazuje. Pokud například pole vašeho uživatelského jména přijímá pouze alfanumerický vstup, jednoduše vypustí z dotazu cokoliv navíc, aniž by to vůbec zpracovalo.
Samozřejmě, kdykoli má uživatel zadat hodnotu, program se musí ujistit, že vstup uživatele neobsahuje injekci dalšího kódu SQL. Program bude muset před provedením dotazu nejprve vyčistit vstup.
Dezinfekce uživatelského vstupu
Čištění vstupu uživatele jednoduše znamená zajistit, aby uživatel zadal pouze to, co očekáváte, že zadá. Cokoli, co uživatel zadá a neodpovídá tomu, co program očekává, je automaticky vyhozeno a vygenerována chyba, než se vůbec něco dostane do databáze. Tato metoda pouze ověřování je upřednostňována před tím, než nechat uživatele zadat, cokoli si přeje, a poté se pokusit vytvořit regulární výraz, který se vyřadí nebo chybuje, když je detekován další kód SQL.
V našem příkladu výše předpokládejme prozatím, že clientID je řetězec znaků. Program může omezit vstup uživatele na whitelist životaschopných znaků, přičemž maximální délka nesmí překročit délku nejdelšího clientID a nepovoluje žádné mezery. Pokud by ID klienta bylo číslo, omezte vstup uživatele pouze na celá čísla určité délky: ne více, nic méně.
U polí, která vyžadují kombinaci různých typů znaků, jako je e-mailová adresa, může program stále potřebovat regulární výraz, aby vyčistil vstup uživatele. Za prvé, abyste se ujistili, že programu byla přidělena skutečná e-mailová adresa, a za druhé, abyste se ujistili, že je poskytnuta pouze e-mailová adresa.
Regulární výraz je definovaný vyhledávací vzor založený na nastaveném řetězci znaků. Jeden příklad regulárního výrazu speciálně pro ověřování e-mailových adres vypadá takto:
|_+_|Je mnohem lepší přidat na seznam povolených uživatelských vstupů pouze přijetím očekávaných informací a zamítnout vše ostatní, než zakázat uživatelské vstupy prohledáváním poskytnutých informací a hledáním čehokoli, co vypadá podezřele. U černé listiny vždy existuje možnost, že regulární výraz vynechá něco, co bylo skutečně neočekávané.
Jako krátký příklad, před několika lety hacker zjistil, že může zadejte zápornou hodnotu převodem prostředků z jeho bankovního účtu na bankovní účet přítele pomocí aplikace online bankovnictví jeho banky. Výsledkem bylo, že efektivně ukradl peníze z účtu svého přítele. Jako etický člověk to nejen ukázal svému příteli pro smích, ale také dal vědět bance.
Programátoři online aplikace této banky nepředpokládali, že uživatel vloží zápornou hodnotu do vstupního pole pro převod prostředků z jednoho účtu na druhý, takže to nebylo na jejich černé listině. Toto je nejjednodušší forma útoku SQL injection, kterou jsem mohl najít na internetu a nevyžadovala žádné znalosti SQL nebo programování. Chtělo to jen trochu zvědavosti a platný uživatelský účet s dostatečnými právy k provedení transakce.
Uložené procedury
Uložené procedury mohou být velmi bezpečné, když jsou podkladové transakce SQL statické. Například, když data, ke kterým se přistupuje, nezávisí na vstupu od uživatele, z jiného programu nebo z proměnné nastavené dříve v programu, která je získávána z jiného zdroje než z místního serveru. Pokud jsou prezentovaná data založena na proměnné prostředí, jako je aktuální datum, geografická poloha uživatele nebo uživatelské jméno, jsou data považována za statická.
Pokud je však transakce SQL založena na dynamicky generovaných datech, měla by být považována za podezřelou, dokud ji nelze ověřit. Vše, co uživatel dodá během běhu programu, je automaticky považováno za nebezpečné. Stejně tak jakákoli část informace generovaná jiným programem by měla být považována za podezřelou, dokud nebude dezinfikována nebo vyřazena.
Při manipulaci s dynamicky generovaným obsahem jsou uložené procedury stejně náchylné k injekčním útokům jako jakákoli jiná interakce SQL. Mohou také těžit ze stejné taktiky používané u připravených příkazů, zejména z použití zástupného symbolu a dezinfekce vstupu před jeho předáním do databáze.
Jak název napovídá, uložená procedura je ve skutečnosti vytvořena v samotné databázi, zatímco připravený příkaz je nastaven v programu těsně před dotazem na databázi. Pokud již máte uloženou proceduru v databázi, program pouze potřebuje zavolat tuto proceduru a poskytnout hodnoty, které očekává.
Chcete-li použít předchozí ukázkový dotaz SQL, můžete vytvořit uloženou proceduru v samotné databázi pomocí příkazu CREATE PROCEDURE takto:
|_+_|Nyní má vaše databáze uloženou proceduru s názvem sp_getClientData. Aby jej mohl program využít, stačí jej zavolat a zadat hodnotu, která nahradí zástupný symbol. Podívejte se na ukázky kódu na konci článku pro volání uložené procedury v každém z pokrytých programovacích jazyků.
Tip na posílení serveru: Nejméně oprávnění k přístupu
Hlavním tipem, jak učinit samotný databázový server o něco bezpečnějším při práci s jakýmkoliv programem, je minimalizovat přístupová práva všech různých „uživatelských“ účtů v databázi. Měl by existovat pouze jeden účet DBA nebo správce a tato pověření by nikdy neměla být použita v žádném programu nebo aplikaci. Ve skutečnosti by každý program, který potřebuje přístup k databázi, měl mít různé účty na základě oprávnění, která potřebuje, když se připojuje.
Při vytváření těchto účtů vstupuje do hry staré přísloví „méně je více“. Začněte bez práv a poté přidejte pouze práva potřebná k provedení volané funkce. Současně je třeba vytvořit jakékoli pohledy nebo uložené procedury. Opět platí, že méně je více, pokud jde o bezpečnost dat.
Pokud program potřebuje pouze vyhledat informace týkající se klienta, bude potřebovat uživatelský účet s přístupem pouze pro čtení ke konkrétním tabulkám týkajícím se klientských informací. Na druhou stranu, pokud se předpokládá, že bude schopen změnit některou z informací týkajících se zaměstnanců společnosti, měl by mít uživatelský účet během segmentu připojení k databázi tohoto programu přístup pro čtení a zápis do tabulek týkajících se zaměstnanců společnosti.
Ukázky kódu
Pojďme tedy všechny tyto informace vložit do některých skutečných příkladů kódu. Nejprve budeme pracovat s databází MySQL na localhost s názvem ‚demo‘. Pro zjednodušení má tato databáze pouze dvě tabulky, klienty a profily. Všimněte si také, že operačním systémem pro tyto ukázky je Debian Linux, ale ukázky lze použít i na serverech s jinými operačními systémy.
Všechny ukázky kódu provádějí stejné úkoly, ale v různých programovacích jazycích. Jazyky jsou v žádném konkrétním pořadí Java, PHP, Python a Perl.
Jedna poznámka na závěr. V následujících ukázkách probíhá proces pro naše programy takto:
- Připojte se k databázi
- Připravte příkaz s jedním nebo více zástupnými symboly nebo zavolejte uloženou proceduru
- Získejte požadovaný vstup od uživatele
- Vyčistěte vstup uživatele
- Vložit vstup uživatele do transakce SQL nahrazující zástupné symboly
- Proveďte SQL dotaz
- Zobrazte výsledky SQL dotazu
- Po dokončení se odpojte od databáze
Jáva
Získání vstupu od uživatele je v Javě poměrně jednoduché:
|_+_|Pro připojení k databázi potřebuje program znát adresu serveru, název databáze na tomto serveru a přihlašovací údaje účtu, který má přístupová práva potřebná pro příkazy SQL, které budou provedeny. během relace připojení k databázi:
|_+_|Chcete-li připravit parametrizovaný příkaz, potřebujete zástupný symbol v příkazu SQL, příkaz předaný databázi a hodnotu zadanou uživatelem místo zástupného symbolu:
String query = 'SELECT * Z klientů WHERE clientID = ?