Autor: RubberDuck | 17.2.2016 |
SQL Injection je bezpečnostní chyba založená na možnosti manipulovat s daty v databázi bez nutnosti mít k nim legitimní přístup. Na první pohled by se mohlo zdát, že tato chyba je problémem webových technologií. Opak je pravdou. SQL Injection je problémem všech aplikací pracujících s databází. Zneužití může vést k získaní citlivých údajů, jakými jsou přihlašovací údaje, osobní údaje (rodná čísla, čísla bankovních účtu..) a v některých případech může vést k vykonání systémového příkazu, případně k ovládnutí celého serveru/počítače. Principem je vkládání nových/rozšiřujících SQL dotazů do již existujících SQL dotazů.
SQL je zkratka pro Structured Query Language, tedy strukturovaný dotazovací jazyk využívaný v relačních databázích pro práci s daty. První myšlenka návrhu a vzniku SQL spatřila světlo světa v laboratořích firmy IBM při výzkumu a návrhu relačních databází. Cílem bylo vytvořit jazyk co nejvíce blízký běžné mluvené angličtině, což se nakonec víceméně podařilo. V dnešní době existuje celá řada databázových mutací jazyka SQL – MySQL, MSSQL, Postgre, Oracle, SQLite, MSQL atd.
Protože se chystáme pracovat s databázovým systémem, bylo by nanejvýš vhodné nainstalovat si ho k sobě na počítač, případně využít služeb některého z freehostingů. Pokud zvolíte druhou možnost, na konec připojuji testovací skript připravený pouze a jen pro použití. Server s podporou PHP, protože většina věcí bude prezentována právě na kombinaci PHP – MySQL. Tyto požadavky plně pokrývá například EasyPHP, jakožto ideální „server“ pro začátečníky.
Pokud jste v některém jiném článku viděli věci jako například update záznamů v tabulce, případně smazání/dropnutí celé databáze, pak Vás musím zklamat. MySQL neumožňuje (z bezpečnostních důvodů) kombinovat rozdílné typy dotazů: pokud primární dotaz obsahuje SELECT, pak můžete použít zase jen SELECT (žádný DROP, UPDATE, INSERT atd). Druhou bezpečnostní pojistku v rukách třímá samotné PHP (pokud budeme mluvit jen a zásadně o něm). To neumožňuje zpracovat najednou více než jeden jediný dotaz. Takže:
opravdu fungovat nebude (v případě ASP je situace jiná).
Dosti bylo teorie, nakoukněme pod pokličku. Všude, kam se na internetu podíváme, jsou nějaké přihlašovací formuláře. User Accounty, Admin Menu, nejrůznější formuláře k soutěžním účtům. Všechny tyto formuláře spojuje jedna věc (pokud nebudeme uvažovat extrémní případy použití hesla v PHP kódu nebo v nějakém externím souboru s koncovkou txt a jemu podobných) a tou je právě skutečnost, že využívají databází k archivaci nejrůznějších informací, včetně výše zmiňovaných přihlašovacích údajů. Klasický MySQL dotaz v kombinaci s PHP ověřující, zda je daný uživatel přítomný v databázi, a tudíž legitimní, by mohl vypadat asi takhle:
V tomto případě se jedná o vůbec nejhorší možné řešení, protože heslo není nijak šifrováno a data jsou uložena v čísté textové podobě (plain text) v databázi. Co se stane, když se pokusíme do kolonky pro nick vložit znak ‚ (apostrof)? Při vyhodnocování dotazu dojde k chybě. Ptáte se proč? Důvod je na pohled zřejmý. Dotaz bude totiž vypadat následovně:
Tři apostrofy napovídají, že se někde stala chyba a jeden apostrof nám někde chybí (nebo spíše naopak přebývá). No nevadí. My nyní využijeme síly komentářů (v MySQL máme na výběr hned ze tří možných). Dotaz bude vypadat následovně:
Dotaz bude vykonán správně ale my přihlášeni nebudeme. Proč? Tím, že vložíme do dotazu jednořádkový komentář bude zbytek dotazu vypuštěn a bude tedy vypadat následovně:
Tak to už vypadá lépe. Nyní chceme vybrat z tabulky jen data, která mají login stejný s tím naším. Jenže ten náš je prázdný řetězec. Takže si najdeme (nebo uhodneme) legitimní login a použijeme ho v dotazu:
Et voila! Jsme zalogováni pod administrátorským účtem :)
Co se ale stane, pokud nebudeme schopni dopídit se k nějakému korektnímu loginu? Co třeba vyzkoušet logické operátory AND (logické a) a OR (logické nebo)? Řekněme,že login admin neexistuje, ale my ho klidně úspěšně použijeme v kombinaci s logickým operátorem:
Důvod, proč se příhlášení zdaří je úzce spjat právě s logickým operátorem OR, který říká, že dvě hodnoty, jež jsou nulové mají výsledek logická nula, jinak je to vždy logická 1. Jinými slovy: admin‘ OR 1=1– bude vždy vyhodnoceno jako logická 1 a tudíž budeme přihlášeni. Ba co víc: budeme přihlášeni na první účet v tabulce, který bývá většinou administrátorský nebo testovaci s admin právy :) Co dokáže pár znaků za divy 😀 Bypassů existuje celá řada:
Ono je sice hezké, že jsme zalogovaní jako administrátor, ale bylo by hezčí moci si stáhnout všechna zajímavá data v databázi/databázích. Alespoň tak jistě reaguje většina útočníků. Proto si nyní ukážeme, jak dále postupovat v případě, že chceme číst data z databáze. Mějme stránku, která zpracovává požadavky na články a má URL ve tvaru:
kde id=15 znamená, že požadujeme patnáctý článek v tabulce s články. Dotaz by pak mohl vypadat například takhle:
Pokud se i nadále budeme držet myšlenky použití logických operátorů, pak již víme, jak zjistit, zda je daná stránka na SQL Injection náchylná:
Pokud se stránka zobrazí s nezměněným obsahem máme nakročeno k úspěchu. Pokud totiž použijeme:
a obdržíme chybovou hlášku, případně bude obsah chybět úplně, je s nejvyšší pravděpodobností stránka na SQL Injection skutečně náchylná (využít můžeme i například služeb dělení nulou, které by mělo vést k chybě, nebo například sčítání a podobně; zde záleží na kreativitě útočníka).
Nyní, když máme jistotu, že je stránka náchylná na SQL Injection, využijeme klauzuli ORDER BY (udává podle jakého klíče/sloupce se mají záznamy seřadit) s jejíž pomocí zjistíme počet sloupců v aktuální tabulce (v našem případě v tabulce articles).
Pokud nyní dostaneme jako výsledek stránku v nezměněném stavu, pak má daná tabulka určitě jeden sloupec (k čemu by nám byla tabulka bez sloupců že? 😉 ) Zkusíme, zda má tabulka určitě dva sloupce:
a opět platí, co výše: stránka v nezměněném stavu znamená, že má tabulka určitě dva sloupce, v opačném případě má právě jeden sloupec. Naše tabulka bude mít třeba 8 sloupců, pak:
Nyní, když známe počet sloupců, použijeme příkazu UNION, který slouží pro spojování více dotazu do jednoho celistvého.
Pokud je vše tak, jak má, pak bysme měli vidět místo článku některé z čísel v rozsahu 1 až 8 (v našem případě). Najdeme si toto číslo v posloupnosti a budeme ho používat jako prostředek pro zobrazení výsledku dotazu. SQL poskytuje celou řadu užitečných funkcí. Některé z nich si ukážeme:
Prozatím si vystačíme s těmito funkcemi a průběžně si budeme představovat další. Nyní za výše zjištěné číslo doplníme některou z těchto funkcí a prohlédneme si výsledek. Například:
Někdy by se hodilo mít možnost zobrazit si více takových funkcí v jednom sloupečku v jednom dotazu. K tomu se dá využít funkce concat(), která právě spojuje více výsledků do jednoho:
Dostali jsme jako výsledek jednolitý řetězec, u kterého není patrné, kde jednotlivé části začínají a končí. Proto použijeme funkci char(), která přebírá jako argument ASCII hodnotu znaku, který chceme zobrazit:
char(58,58) bude přeloženo jako :: a jasně ohraničí, kde končí a kde začíná hranice mezi jednotlivými výsledky.
Lepší a pro mě přijatelnější je sestřička concat_ws(), která navíc bere jako první argument oddělovač:
Klíčovým v případě funkce verion() je, zda se jedná o verzi nižší než 5 nebo rovnu a vyšší než 5. V případě, že se jedná o verzi nižší než pět budeme nuceni jednotlivé tabulky a sloupce v nich hádat nebo je získávat pomocí bruteforce techniky, což je zdlouhavé a v lozích nápadné.
Ve verzi 5 a vyšší je defaultně obsažena a povolena databáze information_schema. Tato tabulka se tajně stává útočníkovým spojencem a pomocníkem nejcennějsím. Obsahuje celou řadu zajímavých tabulek jako tables, columns, user_privileges nebo schemata. My se nyní podíváme, jak se dostat k názvům tabulek v napadené databázi.
Pokud se nám zobrazí stránka v nezměněné podobě, budeme moci s touto tabulkou operovat. Náš stávající dotaz rozšíříme na oblast databáze webové aplikace, která je náchylná:
Nyní máme zajištěno, že budeme pracovat pouze a jen s „naší“ databází. Vypíšeme si první název tabulky v databázi:
K druhému názvu tabulky se dostaneme tak, že si vypíšeme všechny tabulky kromě první, nebo-li:
Pro třetí tabulku je situace analogická:
Ovšem tento způsob je poněkud neohrabaný a vede k příliš obrovským dotazům. Ty se stávají nepřehledné a náchylné na chyby a překlepy. A protože umíme používat hlavu, budeme řešit problém od lesa a použijeme klauzuli LIMIT :)
To už vypadá, myslím si, mnohem lépe :) Ale co si to ještě více zjednodušit? 😉 MySQL obsahuje jednu pro tyto účely velice užitečnou funkci. A to group_concat(). Díky této funkci budeme mít seznam všech tabulek prakticky díky jedinému dotazu (POZOR! setkal jsem se s tím, že má funkce group_concat() omezenou délku výstupního řetězce a proto nemusíme dostat nutně celý seznam tabulek!)
Nyní si ze seznamu tabulek vybereme ty, které jsou pro nás nějak zajímavé. Dejme tomu, že jsme našli tabulku s názvem users, která pravděpodobně obsahuje seznam všech registrovaných uživatelů. Pro zjištění všech názvů sloupců opět můžeme použít identické postupy jako v případě seznamu tabulek (hádání/bruteforce, využití vylučovací techniky, klauzule LIMIT nebo funkce group_concat()). Jediné, co se změní je skutečnost, že se budeme dotazovat na information_schema tabulku s názvem columns. Ta obsahuje column_name, jež uchovává názvy sloupců tabulky.
Puf!! A máme seznam všech sloupců. Z nich si nyní můžeme vybrat jen ty zajímavé – řekněme: nick, passwd, mail, priv a opět si je vypíšeme analogicky jako výše:
Právě jsme se úspěšně dostali k uživatelským účtům :) Ale ne vždy je situace tak růžová jako tady. Stále větší počet webových aplikací využívá skripty/systémy IDS (Intrusion Detection Systems). Paradoxem je, že tyto jsou většinou navrženy dost odfláknutým způsobem. Spoléhají se na detekci apostrofů, mezer, konkrétních řetězců (UNION, SELECT).
Problém detekce apostrofů může být způsoben buď nastavením serveru (magic_quotes_gpc) nebo přítomností IDS. MySQL má tu krásnou vlastnost (jako asi všechny databáze), že obsahuje funkci char(), která bere jako argumenty decimální ASCII hodnoty znaků oddelených čárkami, takže apostrof není potřeba. Stejná situace je i v případě hexadecimálních ASCII hodnot znaků. Tyto jsou uvozeny pomocí znaků 0x a samotnými hodnotami, které jsou řazeny přímo (bez mezer) za sebe.
Navíc samotné MySQL obsahuje funkce, jež je možné k šifrování využít. Například:
vunhex(hex(user))Detekce mezer je, z mého pohledu, účinná spíše jen na nO_oby a lamy. Zbytek totiž ví, že mezery je možné nahradit pomocí znaku +, který slouží pro spojování výrazů, případně pomocí víceřádkového komentáře. Opět zápeží především na zkušenostech a kreativitě útočníka:
Velké množství IDS skriptů je postaveno na detekci řetězců jako jsou UNION nebo SELECT. Problémem ale je, že berou v potaz buď pouze malé znaky, nebo naopak jen velké, ty chytřejší pak malé i velké. Ale co řetězce složené z kombinací velkých a malých znaků?
Další zajímavou tabulkou je mysql.user, ze které se útočník může dočíst, jaká má práva pro čtení nebo zápis souborů. Ve většině případů nebude mít útočník práva pro přístup do této tabulky, což ovšem neznamená, že by se sám nemohl pokusit soubory přečíst. K tomu slouží funkce load_file() a její použití je snadné:
V některých situacích je možné s pomocí fce load_file() načíst i obsah adresáře (pro rejpaly – neměl jsem možnost vlastnoručně vyzkoušet, ale byl jsem svědkem toho, že to skutečně funguje).
Když už si nejaký soubor přečteme, bylo by vhodné mít možnost i nějak soubory vytvářet. K tomu slouží klauzule INTO OUTFILE případně INTO DUMPFILE. Bohužel jsme omezeni právy na daný adresář, kam se pokoušíme zapisovat. Nejčastěji je proto zvykem hledat adresáře, kde jsou uloženy obrázky (img, images..), případně se používá /tmp adresář, který by měl být téměř vždy přístupný (používá se při kombinovaných technikách útoku).
Paradoxem je, že ač se můžeme ve všech předešlých případech vyhnout použití apostrofů díky konverzi nebo použití funkcí, zde to neplatí. Z neznámého (možná bezpečnostního? 😉 ) důvodu není možné absolutní adresu souboru konvertovat/využít funkcí.
Tento typ SQL Injection je spíš takový gurmánský :) Je možné ho provádět ručně, to je ale značně namáhavé. Jde totiž o skutečnost, že celý útok probíhá na úrovni detekce správnosti nebo naopak špatnosti dotazu. Protože nemá útočník visuální kontrolu nad tím, jak útok probíhá, zvyšuje se zákonitě počet požadavků na server. Pokud vezmeme v potaz, že kazdé SQL spojení může být „drženo“ až po dobu jedné hodiny, je jednoduché dojít k závěru, že zabrat všechna možná spojení je velice jednoduchá záležitost. Existuje sice možnost minimalizovat počet požadavků vedoucích k cíli pomocí algoritmu půlení intervalů, ale i tak se jedná o vysoká čísla.
Samotný průběh útoku se tedy zaměřuje na jednotlivé znaky. Jednoduchá detekce verze MySQL by například mohla vypadat následovně:
Funkce ascii vrací číselnou hodnotu znaku, který je v řetězci nejvíc vlevo. Funkce substring vrací počet znaků od zadané pozice v zadaném řetězci.
Podobným postupem se pokračuje i v případě zjišťování názvů tabulek, sloupců, a nakonec i u záznamů v tabulkách. Celý postup lze samozřejmě vyladit za použití podmínek a dalších funkcí, tak že je potenciální útok mnohem hůře detekovatelný.
MySQL defaultně nepodporuje spouštění systémových příkazů. Ovšem existuje balíček umožňující systémové příkazy spouštět (http://bernardodamele.blogspot.com/2009/01/command-execution-with-mysql-udf.html), a proto je celkem možné, že dříve nebo později na podobné vychytávky začneme narážet i na serverech.
Několikrát jsem se setkal s tvrzením, že pokud funguje webová aplikace pod mod_rewrite, pak nehrozí žádné nebezpečí ze strany případného SQL Injection útoku. Opak je ale pravdou. Mod_rewrite do jisté míry dělá situaci težší kvůli zjištění, co je parametrem a jak ho použit, ale tím to končí. Samotný průběh útoku se pak již nijak neliší od běžné SQL Injection.
Jak jsem zmínil již výše, MySQL má tu vlastnost, že si drží požadavky na přístup do databáze až po dobu jedné hodiny. Toho lze samozřejmě celkem efektivně využít a jednoduchým skriptem, který zašle velké množství požadavků, server buď vytížit nebo vypotřebovat počet možných SQL připojení pro server. K tomu lze využít například funkci BENCHMARK(). Tato funkce vykonává daný výraz opakovaně po daný čas:
SQL Injection umožňuje nejrůznější a nejzajímavější věci. Namátkou můžu jmenovat například využití pro scanování portů, nebo kombinování s XSS (tzv. SIXSS), jenž v budoucnu jistě budou hojně využívat phisheři. Vše závisí jen a pouze na kreativitě útočníků. Rozhodně se jedná o velmi kritickou chybu, jež může mít globální dopad na bezpečnost celého serveru, a proto není záhodno tuto chybu podceňovat.
¨¨°o©o.,.o©o°¨¨°o©[ END_OF_FILE ]©o°¨¨°o©o.,.o©o°¨¨
THANKZ:
abc, Lodus, Vrtule, Krpec, Emkei
================================================
Článek převzat z Binary Flow.