Databázové systémy II Přednáška č. XI Ing. Tomáš Váňa, Ing. Jiří Zechmeister Fakulta elektrotechniky a informatiky
Obsah SQL Injection Plánovač - DBMS_SCHEDULER IDAS2 - Přednáška XI
SQL Injection Databázové systémy 2 - př. 1 3
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
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.
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 AND 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 IdINT NickVARCHAR(32) VARCHAR(255)
Příklad 1 Zobrazení celého obsahu tabulky původní dotaz: SELECT nick, 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, 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, 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, FROM uziv WHERE nick = '' or 'a' = 'a' SELECT nick, 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, 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, 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, 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 FROM uziv -- Výsledný dotaz: SELECT nick, FROM uziv WHERE uid = '1' UNION SELECT heslo AS nick, nick AS 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.).mysql_query()
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
Plánovač - DBMS_SCHEDULER IDAS2 - Přednáška XI
Plánování úkolů Umožňuje vytvářet úlohy, které budou spouštěny v pravidelných intervalech. Typicky lze naplánovat spuštění úloh jako jsou: – sběry statistik, – mazaní tabulek (typicky tabulek s LOG informacemi), – spuštění libovolného anonymního bloku PL/SQL, – spuštění uložené procedury, – skript příkazové řádky. Kdysi plnil úlohu plánovače balíček DBMS_JOB. – Dnes již považován za zastaraly a dále nerozvíjený. Nyní převzal veškeré činnosti spojené s plánováním balíček DBMS_SCHEDULER. IDAS2 - Přednáška XI
Plánování úkolů - spouštění Time-based scheduling – Úkol lze naplánovat na specifický den a hodinu. – Může se periodicky opakovat nebo provést pouze jednou. – Př.: „Každé pondělí a čtvrtek ve tři ráno mimo svátky“, „každou poslední středu finančního kvartálu“ Event-based scheduling – Plánovač může naplánovat spuštění úlohy v návaznosti na specifickou systémovou nebo byznys událost. – Příklady takových událostí mohou být: Spuštění úlohy ve chvíli kdy jsou do DB uloženy specifické soubory. Počet kusů určitého produktu v inventáři klesne pod určitou mez. Selhání transkace. IDAS2 - Přednáška XI
Plánování úkolů Dependency scheduling – Plánovač může spustit určitou úlohu v návaznosti na výsledek jiné úlohy. – Lze definovat komplexní řetězec závislostí, jenž bůže zahrnovat větvení či vnořování řetězců závislostí. IDAS2 - Přednáška XI
Plánování úkolů - prioritizace Plánovač umožňuje efektivně organizovat alokaci systémových zdrojů pro běh úlohy. To je zajištěno dvěma způsoby: – Seskupení úloh s podobnými charakteristikami do tzv. tříd úloh (job classes). Pak lze efektivně prioritizovat celou třídu úloh a přiřadit ji specifické množství systémových zdrojů. Lze tak vytvořit třídu pro kritické úlohy, pro běžné úlohy a pro úlohy podpůrného charakteru. – Plánovač umožňuje dynamicky měnit prioritu úloh v závislosti na plánu. Definice kritické úlohy se může v čase měnit. Některé úlohy mohou být považovány za kritické mimo „špičku“, naopak ve špičce jsou považovány za podpůrné. IDAS2 - Přednáška XI
DBMS_SCHEDULER Hlavní plánovač databáze Oracle (od verze 10g). – Nahrazuje balíček DBMS_JOB -> ten je v databázi již jen z důvodu zpětné kompatibility -> není dále rozšiřován -> již dále nepoužívat. Balíček DBMS_SCHEDULER je vlastněn uživatelem SYS. – Je třeba přidělit příslušná systémová oprávnění. – Lze přidělit roli SCHEDULER_ADMIN, obsahující oprávnění: CREATE ANY JOB EXECUTE ANY PROGRAM EXECUTE ANY CLASS MANAGE SCHEDULER IDAS2 - Přednáška XI
DBMS_SCHEDULER.CREATE_JOB CREATE_JOB – CREATE_JOB je několikrát přetížená (4 varianty). Tato verze umožňuje vytvořit úkol bez předdefinovaného rozvrhu, IDAS2 - Přednáška XI
DBMS_SCHEDULER.CREATE_JOB JOB_NAME - Unikátní identifikátor úlohy. Nesmí kolidovat s jiným názvem databázového objektu. Lze využít generátor unikátních názvů úloh -> DBMS_SCHEDULER.GENERATE_JOB_NAME -> který zajistí jedinečnost identifikátoru úlohy. JOB_TYPE - Typ může nabývat hodnot PLSQL_BLOCK, STORED_PROCEDURE a EXECUTABLE (externí úloha). JOB_ACTION - Specifikuje akci, která se provede. Jeli JOB_TYPE = PLSQL_BLOCK, pak zde musí následovat anonymní blok PL/SQL. Pokud JOB_TYPE = STORED_PROCEDURE, pak se zde objeví volání uložené procedury. NUMBER_OF_ARGUMENTS - Počet parametrů, které úloha očekává. START_DATE - Požadované datum a čas začátku úkolu. Datový typ TIMESTAMP WITH TIMEZONE. IDAS2 - Přednáška XI
DBMS_SCHEDULER.CREATE_JOB REPEAT_INTERVAL - Pomocí kalendářního výrazu nebo data a času určuje frekvenci spouštění. END_DATE - Datum kdy bude úkol odstraněn (pokud je AUTO_DROP nastaven na TRUE). JOB_CLASS - Třída, do níž se má úkol zařadit. ENABLED - Pokud je TRUE, pak bude úkol povolen ihned po svém vytvoření, jinak je nutné úkol explicitně povolit pomocí DBMS_SCHEDULER.ENABLE. AUTO_DROP - Pokud je TRUE, po dosažení END_DATE dojde k odstranění úkolu z fronty úkolů. IDAS2 - Přednáška XI
REPEAT_INTERVAL Parametr určující frekvenci. Frekvenci lze definovat buď kalendářním výrazem nebo datem a časem. Datum a čas – Platným výrazem data a času je takový výraz, který vrací datum. Protože je ale parametr REPEAT_INTERVAL typu VARCHAR2, uzavřeme jej do uvozovek. Kalendářní výrazy – Značně odlišné. Dnes ale považovány za standard. – Standardní syntaxe výrazu: IDAS2 - Přednáška XI
Kalendářní výrazy Parametr FREQ je povinný a nabývá následujících hodnot. – YEARLY, MONTHLY, WEEKLY, DAILY, HOURLY, MINUTELY, SECONDELY Interval (INTERVAL) souvisí s frekvencí a může obsahovat libovolné celé číslo o 1 do 199. (Například FREQ=DAILY a INTERVAL=7 -> úkol poběží jednou za 7 dní). Frekvence a interval určují jak často má úkol běžet, ale neurčují kdy má běžet. A právě specifikátor obsahuje detailní pokyn, kdy má úkol běžet. Platné specifikátory: – BYMONTH, BYWEEK, BYYEARDAY, BYMONTHDAY, BYDAY, BYHOUR, BYMINUTE, BYSECOND. IDAS2 - Přednáška XI