Hned po napsání aplikace bychom měli pamatovat na její bezpečnost. Proto se podívejme, co všechno lze nezabezpečené databázi provést a jak se proti tomu bránit. Už jste využili naší rubriky „Bezpečnost“ a zabezpečili si i své aplikace? Pokud ne, najdete na konci článku potřebné odkazy.

Pokud vytváříme aplikaci, která pracuje s databází, je nutné kontrolovat vstupní údaje. Pokud tak neděláme, vystavujeme se nebezpečí, že SQL dotazy, které naše aplikace generuje, budou mít jiný význam, než jsme zamýšleli. Neošetřené vstupní parametry může útočník zneužít ke vkládání fragmentů SQL dotazů. V některých případech (záleží na typu aplikace, použitém jazyku, databázovém engine) může útočník spouštět libovolné SQL příkazy, přepnout se i do jiné databáze a tam smazat tabulky.

Neošetřený skript může vypadat třeba takto:

PHP: „SELECT * FROM table WHERE id=$id“;
ASP: „SELECT * FROM table WHERE id=“ & Request.QueryString(„id“)

Z parametru předaného uživatelem bez jakékoli kontroly vytvoříme součást SQL dotazu. V lepším případě to skončí chybovým hlášením, v horším třeba smazáním dat na serveru. Zamyslete se, co se stane, pokud uživatel do URL zadá třeba:

?id=0%20or%201=1 znaky %20 jsou uvedeny jako korektní náhrada mezery do URL)
?id=57;%20DELETE%20FROM%20table

V prvním případě se provede

SELECT * FROM table WHERE id=0 or 1=1

čímž se vyberou všechny záznamy z tabulky table. To může vypadat nevinně, ale co když jde třeba o ověření jména a hesla administrátora redakčního systému? Takto se může někdo falešně přihlásit k aplikaci, aniž by znal jméno nebo heslo. Ve druhém příkladu se může podařit (jak si ukážeme dále, závisí to na databázovém systému) podvrhnout našemu databázovému serveru příkaz pro smazání všech údajů v tabulce!

Předpokládejme, že se jedná o MySQL. Tam to sice tak jednoduché není, můžeme ale spouštět různé funkce. Zkusmo si můžeme zjistit, pro jaké číslo je generován jaký článek (obsah stránky). Můžeme třeba id generovat jako číslo odpovídající ordinální hodnotě znaků ASCII ze jména uživatele, pod kterým skript pracuje s databází. Podobných „užitečných“ funkcí existuje celá řada.

?id=ascii(mid(user(),5,1)
?id=mid(version(),3,1)

V prvním případě jako odpověď získáme ten samý výsledek jako pro id odpovídající v ASCII kódu pořadí pátému znaku ze jména uživatele. Na několik pokusů tak můžeme zjistit celé jméno uživatele. Druhý příklad vyjme třetí z čísla verze použitého databázového systému.

Některé databáze umožňují spuštění více dotazů najednou oddělených středníkem:

?id=1;%20SELECT%201
SELECT * FROM table WHERE id=1; SELECT 1

K zadávání kompletních dotazů je nutné znát názvy tabulek a sloupců. Ty mohou být vyzrazeny chybovými hlášeními po nepovedených dotazech, ale i třeba přečteny ze zapomenutých .inc souborů. Je proto také velmi vhodné po odladění našeho skriptu potlačit vypsání možného chybové hlášení při pokládání dotazu ve skriptu. V PHP se to provede přidáním @ (znak at) před možný chybující povel. I když budete mít skript ošetřený proti zde uváděným technikám, nemůžete tušit, co někdo vymyslí příště.

Dalo by se předpokládat, že pokud v dotazu následuje za místem vkládání parametru ještě nějaký kód (například LIMIT 10), je vložení druhého dotazu nemožné. Některé databázové systémy však interpretují v dotazech sekvenci — (dvě pomlčky) jako komentář, čímž můžeme nechtěný zbytek odříznout:

SELECT * FROM table WHERE id=1; SELECT 1 — LIMIT 10

Pokud je předávaná hodnota textová, hrozí podobné útoky tehdy, pokud je možno vložit do dotazu neošetřený znak ‚. Ošetřit tento znak lze v případě MySQL a Postgresu zpětným lomítkem na ‚, v případě MS SQL zdvojením na “.

?string=‚;%20SELECT%201%20
SELECT * FROM table WHERE text like ‚%‘; SELECT 1 — %‘ LIMIT 10

Pokud je apostrof ošetřen, pak je dotaz stále korektní, ale vložený kód se neinterpretuje, stane se součástí porovnávaného údaje:

SELECT id,text FROM table WHERE text like ‚%‘; SELECT 1 — %‘ LIMIT 10

Na MS-SQL serveru je navíc možné spouštění příkazů pomocí takzvané vložené procedury xp_cmdshell (pokud je k tomu databázový uživatel oprávněn).

Řešení a pravidla

V případě číselných hodnot je úprava zcela triviální – vynásobíme číslo 1x. Je možné i přičíst 0, ale lze předpokládat, že násobení bude (asi stejně jako ve strojovém kódu) rychlejší. Pokud je zadán nějaký řetězec, který začíná číslem, zbude jen hodnota počátečního čísla. Pokud je zadán řetězec nezačínající číslem, je výsledkem 0 a v tu chvíli bychom měli třeba přesměrovat na výchozí stránku a případně si nechat poslat na mail, cože nám kdo do skriptu pustil. Pokud víme, jak má vstupní parametr vypadat, zkontrolujeme si pomocí regulárních výrazů, zda tak opravdu vypadá.

V případě textových parametrů je nutné patřičným způsobem uvést lomítkem (případně zdvojit) znak ‚ ve všech proměnných které jsou vkládány do SQL řetězce. V PHP mohou být takto automaticky upraveny všechny řetězce z parametrů GET, POST a Cookies. PHP provádí potřebné vkládání lomítka při výchozím nastavení v souboru php.ini samo. Lze nastavit i automatické zdvojení apostrofu, nebo naopak jakékoli úpravy vypnout. Nechceme-li použít automatické ošetření nebo nemáme-li možnost jej aktivovat, pomůže nám funkce AddSlashes(), která přidává zpětná lomítka k zadanému řetězci. Obdobnou funkci musíme použít i v jazycích, který automaticky ošetření uvozovek a apostrofů neprovádějí (ASP, Perl).

Pro ASP by ta funkce mohla vypadat takto:

Function ReadPar(ByVal PID)
  Dim Tpar
  Tpar = Request(PID)
  Tpar = Trim(Tpar)
  Tpar = Replace(Tpar, „‚“, „““)
  ReadPar = Tpar
End Function

Není špatným tahem udávat do všech dotazů, u nichž očekáváme jako výsledek jen jeden záznam (a to třeba i updatovaný nebo smazaný), ještě parametr LIMIT 1. Takový dotaz bývá zpravidla rychleji vyřízen a dá se předpokládat, že může přinejmenším ztížit různé „vymýšlení“ záškodníků.

Starší komentáře ke článku

Pokud máte zájem o starší komentáře k tomuto článku, naleznete je zde.

Žádný příspěvek v diskuzi

Odpovědět