Databázové systémy II SQL Injection Přednáška č. 4 RNDr. David Žák, Ph.D. Fakulta elektrotechniky a informatiky david.zak@upce.cz Připravit prezentaci funkce triggerů Připravit prezentace funkce integritních omezení (primární klíč, cizí klíče, kaskádní delete, nulování, rozsah hodnot …)
SQL dotazy SQL je jazyk pro komunikaci s relačním databázemi. Existuje několik variant/verzí jazyka SQL - společnou vlastností všech verzí SQL je textová komunikace s databází. Typickou jednotkou jazyka je dotaz, což je soubor příkazů pro databázi, která na dotaz odpoví množinou výsledků. Příkazy SQL mohou modifikovat strukturu databáze (Data Definition Language - DDL) nebo mohou manipulovat s obsahem databáze (Data Manipulation Language - DML)…
SQL injection V historii českého Internetu najdete řadu hacknutých webů díky bezpečnostní chybě - v angličtině označované jako SQL injection - jde o souhrnné označení pro nepříjemné bezpečnostní chyby, které umožní vsouvat do SQL kódu (odtud SQL injection) internetové aplikace vlastní informace - pochopitelně takové informace, které umožní změnit smysl původních SQL příkazů. SQL injection napadnutelné weby najdete na internetu neustále. Řada tvůrců aplikací nevěnuje základní pozornost těm nejobyčejnějším návykům a vypouští na veřejnost aplikace, které neměly nikdy opustit privátní síť. Webové aplikace se málokdy podrobí testům na použitelnost, natož aby proběhl alespoň základní bezpečnostní audit. Zdroj http://digiweb.ihned.cz/c4-10122900-19732020-i00000_d-sql-injection-princip-a-ochrana
SQL injection – potenciální rizika Možná rizika SQL injection lze vyjádřit například takto: získání přístupu k datům, ke kterým přístup mít nemáte (tedy i citlivým, důvěrným, …) možnost změny dat, která měla být určena jen pro čtení možnost vstoupit do administračních částí internetové aplikace možnost vyvolat příkazy SQL serveru, které umožní ovládnout stroj, na kterém SQL server běží možnost smazat nějaké tabulky nebo celou databázi
SQL injection – ochrana SQL injection je riziková všude tam, kde autoři internetové aplikace zapomněli na základní bezpečnostní pravidlo - veškeré vstupy do aplikace je nutné kontrolovat na povolené hodnoty/typy. A je u plně jedno, jestli jde o PHP/mySQL či ASP/MS-SQL
SQL injection – ochrana Potenciálně rizikové části internetové aplikace jsou všechna místa, kde něco vstupuje zvenčí. Formuláře (POST/GET) Parametry URI HTTP/XML/SOAP komunikace (nezapomenout na Cookies) "Importy" souborů A k formulářům je vhodné dodat ještě jedno malé varování - řada webových aplikací používá "hidden" (skryté) prvky. Tvůrci aplikace mají falešný pocit bezpečí, že tenhle prvek "není" vidět a ani "není" editovatelný. Přitom stačí příslušnou stránku uložit jako HTML, libovolně si jí upravit a pak používat.
Jak ověřit bezpečnost webové aplikace Nejjednodušší způsob jak otestovat nějakou internetovou aplikaci na SQL injection: Začněte si hrát s vstupy do této aplikace. Nejdůležitější pomůcka pro ruční test na SQL Injection je řetězec ' or 1=1 -- (skutečně bez apostrofu na konci) Pokud takovýto řetězec (můžete si odpustit prozatím ty dvě "--" na konci) použijete pro testování, možná dostanete nějakou tu chybu od SQL serveru - pak víte s jistotou, že tvůrce webové aplikace něco zanedbal - má neošetřené vstupy do aplikace.
Jak ověřit bezpečnost webové aplikace Tuto konstrukci použijete v nejčastějším způsobu testování přihlašovacích informací - vložíte ji do "políčka" pro heslo! Předpokládáme, že aplikace obsahuje dotaz vzniklý zřetězením částí syntaxe dotaze a parametrů z formulářových polí či z adresního řádku: cSQL = "SELECT uniqueid FROM Users Where UserName='" & request("userid") & "' and Pwd='" & request("pwd") & "' " Z uvedeného příkladu se pak stane (například) toto: SELECT uniqueid FROM Users Where UserName='' or 1=1 --' and Pwd='heslo' Výsledkem bude likvidace nutné podmínky na shodu hesla. A pravděpodobně získání přístupu do systému. "--" se totiž postará o ignorování zbytku SQL dotazu. Pokud naznačený nejuniverzálnější řetězec nepomůže, zkuste některé další varianty: '' or 1=1 -- or 1=1 -- ' or 'a'='a případně si pohrajte se závorkami
Jak se vyhnout SQL injection? Existuje nespočet návodů jak se SQL Injection vyhnout - pomocí Google a zadání "SQL Injection" jich najdete řadu - budete si tak moci i vybrat zda potřebujete návod pro Microsoft SQL, MySQL či pro "něco" jiného. Principy jsou obdobné : Textové vstupy prohnat náhradou ['] za [''] (neboli dvojici apostrofů). Jde o nejjednodušší způsob, který znemožní použít ['] pro "ukončení" vytvářeného SQL dotazu.
Jak se vyhnout SQL injection? Textové vstupy prohnat odpovídající RegExp() transformací, která v něm ponechá pouze znaky, které v něm mají být. Tímto způsobem je pochopitelně více než vhodné ošetřovat i vstupy, které se stanou později výstupy - vyhnete se tak nebezpečí jinému, jménem XSS. Netextové vstupy prohnat odpovídající typovou konverzí - tj. například pokud ?id=XXX má být integer, tak použít odpovídající konverzí funkci - třeba int() Vstupy s pevně daným malým výčtem hodnot opravdu testovat na to, zda danému výčtu odpovídají. Příkladem může být testování na True/False, On/Off.
Jak se vyhnout SQL injection? Uvedeným způsobem se vyhnete SQL Injection v okamžiku, kdy nechcete použít nějakou jinou metodu tvorby SQL dotazů a zůstáváte u "operativní" klasiky sestavování SQL dotazů (způsobem naznačeným výše). Pokud to není nutné (a popravdě, ono to ani není příliš vhodné), tak se můžete vydat dalšími cestami, které úplně stejně zabrání SQL Injection.
Jak se vyhnout SQL injection? Parametrizovatelné SQL využívá toho, že "objekty" používané pro vyvolání SQL dotazů zpravidla umožňují předávat parametry způsoby, které samy o sobě zabezpečí potřebné zabezpečení parametrů. Sestavený dotaz v takovém případě není pouhým zřetězením částí syntaxe dotazu a parametrů, jako v předchozím případě. cSQL = "SELECT uniqueid FROM Users WHERE UserName=@username AND Pwd=@password" Následně postačí využít SqlCommand a s pomocí SQLParameter provést odpovídající "náhradu" definovaných parametrů.
Co více dělat proti SQL injection? více než vhodné je, aby účet, pod kterým přistupuje internetová aplikace do SQL serveru, zcela určitě nebyl účtem systémového administrátora a měl by mít pouze nezbytná práva! v úplně ideálním případě by měly existovat pouze uložené procedury a příslušný účet by měl mít práva pouze pro jejich spouštění - tím se dá odstranit i případné nebezpečí z "podvrhnutých" lahůdek jako "truncate table" či "delete * from" (popravdě i možnost vytvářet nové tabulky může být nebezpečná, stačí je vytvářet tak dlouho a tak velké až ...).
Co více dělat proti SQL injection? Aplikace by neměla zobrazovat detailní informace o vyskytnuvší se chybě. Webová aplikace často nemá potlačeno zobrazování detailních chyb a je tak možné vidět i části SQL kódu - což útočníkovi umožní snadno se rozhodovat, jak SQL injection použít.
Co nehrozí (v MySQL ve spojení s PHP) PHP nepovoluje vykonávání několika SQL dotazů v jednom volání funkce mysql_query(), takže tím odpadá problém s řetězcem typu '; truncate tabulka; - provádí se vše, co se nachází před prvním středníkem mimo hodnoty sloupců. V MySQL neexistuje nic, co by mohlo zavolat externí aplikaci, jako je tomu například u Microsoftí alternativy MS SQL, takže odpadá spousta dalších potenciálně nebezpečných dotazů.
Příklad Následující příklady budeme aplikovat na tabulce UZIV: Název sloupce Datový typ Id INT Nick VARCHAR(32) Email VARCHAR(255)
Příklad 1 Zobrazení celého obsahu tabulky původní dotaz: SELECT nick, email FROM uziv WHERE id = {$id} použité proměnné: $id = integer získaný od uživatele vložená hodnota: 9 OR 1=1 -- výsledný dotaz: SELECT nick, email FROM uziv WHERE id = 9 OR 1=1 --
Příklad 1 popis: principem všech operací se "všemi daty" je to, že v dotazu musí být vždy platná podmínka a tudíž dotaz (select, update, delete, to je jedno) ovlivní celý obsah tabulky. Jediné omezení při SQL injection je to, že vzniklý dotaz musí být syntakticky správný - musíte nejdřív ukončit původní podmínku a pak teprve můžete pokračovat se svým kódem. V našem případě je ukončení původního dotazu pouze první znak (9), zbytek už je naše "vždy platná podmínka" - 1 se vždy rovná 1.
Příklad 1 MySQL má komentáře ve stylu "--" a to se nám hodí pro případ, že v originálním dotazu je ještě něco za částí, kterou zneužíváme (klauzule order by, limit a jiné) POZOR: TENTO DOTAZ BUDE ZE STRANY SERVERU FUNGOVAT VŽDY, i když budete mít magic_quotes na ON - vložený řetězec neobsahuje žádné escapovatelné znaky (integery se nemusí dávat to apostrofů), je tedy jen na Vás, abyste si ohlídali vstupní data, jestli obsahují pouze povolené znaky (např. málokdy potřebujete vložit ?=? nebo ?-? ).
Příklad 1 kdy to bude fungovat: přes nastavení serveru to projde vždy, je tedy jen na vás a na vašich skriptech, jak si s tím poradíte. co s tím: pokud víte, že potřebujete vypsat právě jednu (nebo víc, záleží na Vás) hodnotu a nikdy jinak, kontrolujte počet řádků ve výsledku, když číslo nebude jakkoliv souhlasit, zalogujte si to. Když máte předem daný počet řádků, které očekáváte, nepoužívejte while($x = mysql_fetch_*()), ale obyčejnou funkci mysql_result() kde přesně stanovíte, kolik řádků výsledku chcete zobrazovat (omezený počet iterací cyklu for nebo konkrétně vámi definovaná čísla řádků, které se mají vypsat).
Příklad 2 Zobrazení celého obsahu tabulky původní dotaz: SELECT nick, email FROM uziv WHERE nick = '{$nick}' použité proměnné: $nick = řetězec získaný od uživatele třeba stejnou cestou, jako v příkladě #1 mnou vložená hodnota proměnné $nick: ' or 'a' = 'a ' or 1=1 -- výsledný dotaz: SELECT nick, email FROM uziv WHERE nick = '' or 'a' = 'a' SELECT nick, email FROM uziv WHERE nick = '' or 1=1 --'
Příklad 2 popis: toto je velmi podobný příkladu 1 s tím rozdílem, že pokud chcete pracovat s řetězci, musíte je uzavřít do apostrofů, a ty už by neprošly bez újmy skriptem, který escapuje nebezpečné proměnné. Ten by ' přeměnil na \' a výsledek by byl (ve většině případů) prázdný. co s tím: jak jsem se již předtím zmínil, pro zamezení tohoto typu SQL injection stačí escapovat vstupní hodnoty - nastavením direktivy magic_quotes_gpc na on zautomatizujete kontrolu vstupních hodnot, přesto ale doporučuji nespoléhat se na cizí opatření.
Příklad 3 Jak vyzrát na skriptem zajištěné zobrazení jediného řádku výsledku? Původní dotaz: SELECT nick, email FROM uziv WHERE nick = '{$nick}' LIMIT 1 ve skriptu navíc implementováno vypsání pouze prvního řádku výsledku Použité proměnné: $nick = řetězec získaný od uživatele Mnou vložená hodnota proměnné $nick: ' OR id = [cislo] ORDER BY [sloupec][DESC/ASC] -- ' OR id = 2 ORDER BY id DESC -- Výsledný dotaz: SELECT nick, email FROM uziv WHERE nick = '' OR id = 2 ORDER BY id DESC --' LIMIT 1
Příklad 3 Popis: zde jde o to dostat námi požadovaný záznam na první místo, nic víc. Problém je v tom, že takto zobrazíte pouze jediný záznam a navíc musíte znát jednu z hodnot, která je v požadovaném záznamu, pak musíte zvolit jednu vyšší (nebo nižší) a následně seřadit výsledek buď sestupně nebo vzestupně v závislosti na zvolené hodnotě Co s tím: téměř nic. Pokud už povolíte uživateli, aby vkládal do vašich SQL dotazů apostrofy a uvozovky, nezmůže s tím skript vůbec nic, pouze zpracuje data vrácená SQL dotazem, pokud útočník upraví skript tak, aby vracel na prvním řádku data, která se snaží získat, máte smůlu. Jediná obrana proti tomuto typu útoku je vždy kontrolovat počet řádků výsledku (když znáte předem počet řádků, které má dotaz standardně vracet.
Příklad 4 Použití klauzule UNION pro získaní dat z jiných tabulek UNION slouží pro spojení výsledků 2 různých dotazů a jejich vrácení skriptu jako 1 výsledek. Původní dotaz: SELECT nick, email FROM uziv WHERE uid = '{$uid}' LIMIT 1 Použité proměnné: $uid = řetězec získaný od uživatele, použitelný kdekoliv Mnou vložená hodnota proměnné $nick: 1' UNION SELECT heslo AS nick, nick AS email FROM uziv -- Výsledný dotaz: SELECT nick, email FROM uziv WHERE uid = '1' UNION SELECT heslo AS nick, nick AS email FROM uziv --' LIMIT 1
Příklad 4 Popis: Tímto SQL dotazem se vybere jeden uživatel a jeho jméno a druhým dotazem se vyberou všichni uživatelé a jejich hesla. Klauzule UNION toto všechno spojí do jediného výsledku, takže pokud toto útočník vloží například do seznamu uživatelů (kde zpravidla bývá cyklický výpis výsledku), tak skript poslušně vypíše i velice citlivá data. Samozřejmě můžete kombinovat jakékoliv sloupce a jakékoliv tabulky Co s tím? Nesmíte dovolit útočníkovi vložit řetězec podobný tomuto příkladu. Pokud se mu to podaří, nedá se s tím zpravidla dělat vůbec nic. Konkrétně pro případ uchovávání hesel doporučuji hashovat hesla například pomocí PHP funkce sha1(), pokud už dojde k ukradení těchto údajů, nesmírně tím znepříjemníte útočníkovi jejich lámání.
PHP a MySQL PHP má pro každou databázi jinou funkci. Například pro Oracle je to ora_bind, ora_execute. Některé databázové ovladače nemají podporu placeholderů (například MySQL) a je třeba z každé proměnné zvlášť odstranit nebezpečné znaky: $esc_jmeno = mysql_real_escape_string($jmeno); $query = "SELECT id, jmeno, prijmeni FROM lide WHERE jmeno=$esc_jmeno“; $result = mysql_query($query) Funkce mysql_real_escape_string upraví řetězec pro bezpečné použití v mysql_query. Tato funkce opatří speciální znaky v neupraveny_retezec zpětným lomítkem pro bezpečné použití v mysql_query() v závislosti na aktuální znakové sadě spojení (konce řádků jsou nahrazeny značkou \n atd.).
Jak ošetřit vstupní formuláře a z nich získaná data Kontrola použití regulárních výrazů využití konverzních funkcí nahrazení rizikových znaků kontrola na výčet hodnot omezení délky vstupního pole formuláře použití funkcí pro nahrazení nebezpečných znaků v textu
Otázky ? Děkuji za pozornost. Při tvorbě prezentace byly použity informace z WWW http://www.security-portal.cz/sql-injection.html http://cs.wikipedia.org/wiki/SQL_injection http://digiweb.ihned.cz/c4-10122900-19732020-i00000_d-sql-injection-princip-a-ochrana http://php.vrana.cz/obrana-proti-sql-injection.php http://www.youtube.com/watch?v=1ZSx2GakVWk&feature=fvst http://www.youtube.com/watch?v=jMQ2wdOmMIA&feature=related