Bezpečnost na webu – přehled útoků na webové aplikace

V článku si ukážeme časté bezpečnostní chyby webových aplikací (zej. SQL injection, Cross Site Scripting, Cross Site Request Forgery) a postupy, jak jim předcházet. Soustředíme se na webové aplikace napsané v Javě, zmíněné problémy se ale týkají všech webových aplikací nezávisle na použité platformě.

Každý systém může být napaden na mnoha úrovních: od operačního systému serveru až po prohlížeč uživatele. My se zaměříme na problémy, které jsou způsobeny interakcí webové aplikace a prohlížeče. Jinými slovy, půjde nám pouze o HTTP, HTML a JavaScript.

Než začneme

Připomeňme si nejprve některé základní poznatky o HTTP a HTML. HTTP je bezestavový protokol, jehož základem je URL, které identifikuje dokument poskytnutý uživateli.

Jak na stavovost

Ačkoliv je HTTP bezestavový, existuje několik způsobů, jak předávat stav aplikace. Aplikace si může uložit údaje do URL a ty pak přenášet ze stránky na stránku (např. index.php?JSESSIONID=abc). Další možností je použití cookie. Jde o krátkou textovou informaci přijatou ze serveru v HTTP hlavičce požadavku. Každá cookie je vázaná na doménu, ze které byla přijata. Při komunikaci se servery z této domény je cookie posílána s každým HTTP požadavkem zpět na server.

Metody HTTP

V HTTP existuje několik „metod“ komunikace. Základem jsou metody GET a POST. Metoda GET se používá pro zjištění či přečtení informací ze serveru. Požadavky metodou GET by neměly modifikovat stav na serveru; díky této vlastnosti mohou crawlery (internetové vyhledávače) libovolně generovat požadavky s touto metodou (odkazem nebo i formulářem). Naopak metoda POST se používá hlavně k modifikaci stavu a také v případech, kdy chceme nahrát na server větší množství dat (např. nahrát soubor).

V následujících odstavcích se budeme věnovat konkrétním typům útoků.

Injection a SQL Injection

První typ útoku (injection) využívá špatného ošetření vstupu od uživatele při sestavování dotazů a parametrů do jiných systémů. Nejčastějším typem tohoto útoku je SQL Injection – špatná obsluha parametrů při tvorbě SQL dotazů.

Ukázka špatného použití (proměnná param obsahuje text ze vstupu):

String query = "select * from tabulka where field = '"+param+"'"; 

Pokud však uživatel do param umístí např. hodnotu "' or 1=1 or field = '", může tak přečíst všechna data v celé databázi, spojením totiž vznikne dotaz:

select * from tabulka where field = '' or 1=1 or field = '' 

Případně díky subselectům či joinům lze přečíst i data z jiných tabulek.

Tento problém má naštěstí snadné řešení – nesestavovat SQL dotaz pomocí jednoduchého skládání řetězců. V případě Javy můžeme použít pozicových (JDBC) nebo pojmenovaných (Hibernate, JPA) parametrů. Ty se postarají o správné obalení parametrů.

String query = "select * from tabulka where field = :p_field";
Query q = session.createQuery(query);
q.setString("p_field", param); 

Pokud budete striktně dodržovat tento přístup, lze snadno identifikovat problémy již při letmém pohledu na kód.

Problém injection se netýká jen SQL, ale obecně i dalších dotazovacích systémů, jako je LDAP či funkce typu eval (spusť kód uložený v proměnné) v dynamických jazycích (PHP, JavaScript atd.).

Spuštění nebo načtení souboru

V případech, kdy se pomocí parametru načítá nebo spouští soubor z disku a není správně kontrolován vstup, lze aplikaci přesvědčit k přečtení špatného souboru. Pokud v aplikaci použijeme kód:

readfile($_GET["file"]); 

uživatel může zavolat URL index.php?file=/etc/passwd a získat obsah souboru /etc/passwd. Nejjednodušší obranou je striktní kontrola parametrů dle seznamu povolených hodnot.

Krádež session

Protože je HTTP bezestavový protokol, ale všichni potřebují alespoň určitou formu stavu přenášet, používá se mechanizmus sezení (session). Uživateli je při prvním přístupu vytvořeno sezení a je přidělena jeho identifikace pomocí cookie nebo pomocí předávání v URL (viz výše).

Mechanizmus sezení je možné zneužít. Stačí získat identifikátor sezení a následně můžeme předstírat korektně přihlášeného uživatele. Proto je nutné identifikátory sezení generovat opravdu náhodně (s rovnoměrným rozložením) a ještě lépe kontrolovat i IP adresu uživatele.

Problém nastane v případech, kdy uživatel nechce zadávat jméno a heslo každých několik hodin, ale chce být přihlášen i několik týdnů (uživatelova IP adresa se pak bude pravděpodobně měnit).

Pokud používáte cookie, je nutné správně zajistit omezení cookie na doménu, kontrolu IP adresy a také dobu platnosti. Musíte také zajistit, aby na vašem serveru nebyl možný Cross Site Scripting útok (viz níže). Zvažte také použití nové vlastnosti HttpOnly. Pokud ji nastavíte u cookie, nebude možné hodnotu cookie přečíst pomocí JavaScriptu. Toto rozšíření je podporované od IE 6 SP 1 či Firefox 2.0.

Pokud ukládáte identifikaci sezení do URL, je nutné zajistit, aby URL nebylo zasláno žádnému jinému serveru jako HTTP Referer (ze které stránky uživatel přišel). Musíte tedy provést několik kroků. Nejprve zabránit načítání obrázků (stylopisů apod.) z jiných (nedůvěryhodných) serverů a při odkazování na jiný server musíte provést přesměrování uživatele – tj. pomocí refresh v HTML stránce uživatele přesměrujete na novou stránku (v tomto případě nelze použít přesměrování pomocí protokolu HTTP, protože při něm nedojde ke změně refereru).

Cross Site Scripting (XSS)

Při zobrazování vstupů od uživatele je nutné zajistit, aby veškerý výstup nebyl do HTML stránky zapsán přímo, ale jen jako text. Jinými slovy, musí dojít k nahrazení pro HTML klíčových znaků za jejich entitní vyjádření tj. < za &lt;, > za &gt; a & za &amp;.

Nejlepším řešením je používat takové systémy, které při výpisu textu na obrazovku rovnou vše správně převedou na entitní vyjádření. Texty, které převést nechceme, pak musíme explicitně označovat. Pokud by tomu bylo naopak (označovali bychom text, který se má převést), mohli bychom snadno přehlédnout nebezpečné místo a způsobit tak chybu typu XSS.

Situace je komplikovanější v případě, kdy chceme ve stránce použít WYSIWYG editor. Tyto editory vytváří HTML kód, který je následně nahrán na server. Musíme pak intenzivně otestovat vstup od uživatele. Zde proti nám hraje fakt, že HTML je velmi odolné vůči chybám v kódu (nevalidní kód), ale způsob jakým se prohlížeče zotavují z chyby, není ve standardu zcela definován, a proto různé prohlížeče řeší tento problémem odlišně (někdy kód, na první pohled nesprávný, může být prohlížeči stejně interpretován). Naštěstí již dnes existují knihovny, které dokáží zkontrolovat a pročistit kód (odstranit nepovolené značky).

Celý proces kontroly lze tedy rozdělit na několik částí:

  • oprava HTML kódu (doplnění chybějících značek),
  • kontrola a případné odstranění nepovolených značek.

Pro opravu HTML kódu v Javě můžeme použít například knihovnu NekoHTML:

String text = "<strong>ahoj";
DOMFragmentParser parser = new DOMFragmentParser();
HTMLDocument htmlDocument = new HTMLDocumentImpl();
DocumentFragment fragment = htmlDocument.createDocumentFragment();
StringWriter sw = new StringWriter();

// zde je důležitá definice filtrů, které se mají na HTML aplikovat
XMLDocumentFilter[] filters = { new Purifier(), new Writer(sw, "utf-8") };

parser.setProperty("http://cyberneko.org/html/properties/filters", filters);
parser.setProperty("http://cyberneko.org/html/properties/names/elems", "lower");

InputSource is = new InputSource(new StringReader(text));
parser.parse(is, fragment);
System.out.println(sw.toString()); 

Výsledkem v tomto případě bude <strong>ahoj</strong>.

Dále je nutné zkontrolovat povolené značky – zde můžeme použít knihovnu AntiSamy. Jedná se o snadno použitelnou knihovnu, která na základě konfiguračního souboru dokáže HTML kód zkontrolovat:

Policy policy = new Policy(POLICY_FILE_LOCATION);
AntiSamy as = new AntiSamy();
CleanResults cr = as.scan(dirtyInput, policy);
System.out.println(cr.getCleanHTML()); 

Konfigurační soubor umožňuje definovat povolené značky, atributy a použité kaskádové styly. Protože vytvořit tento soubor je poměrně náročné, existuje sada již existujících konfigurací, které můžete použít. Jmenují se slashdot, ebay, myspace a snaží se chovat stejně jako filtry použité na stejnojmenných serverech. Můžete je použít přímo, nebo si je dále upravit pro svoje potřeby.

Poznámka: Pokud přemýšlíte nad názvem projektu AntiSamy, tak vězte, že Samy se jmenoval slavný XSS virus napadající MySpace.

Cross Site Request Forgery (CSRF)

Pokud je stránka dobře zabezpečená proti XSS útokům, ještě stále není vyhráno. Zůstává nebezpečí nazvané Cross Site Request Forgery. Představte si případ, že uživatel navštíví stránku s tímto HTML kódem:

<img src="http://www.vasebanka.cz/submitTranser/?account=1/1234&amount=1000000&confirm=1"> 

URL je vložena jako cíl obrázku, ale ve skutečnosti simuluje formulář odeslaný metodou GET (server nemusí poznat rozdíl). Pokud by bankovní systém na adrese www.vasebanka.cz nevyžadoval další parametry pro potvrzení operace (v tomto případě account obsahuje číslo účtu pro převod peněz, amount částku a confirm=1 potvrzení), mohlo by následovat skutečné odeslání peněz.

Předpokladem je, že uživatel je právě přihlášen v aplikaci a má stále platné cookie (řada webových aplikací dnes nabízí trvalé přihlášení). Obranou není ani vyžadování příjmu dat metodou POST, protože i to lze pomocí JavaScriptu snadno provést.

Bránit bychom se mohli potvrzováním operací jiným způsobem, např. pomocí CAPTCHA nebo SMS, pak nelze operace z prohlížeče nijak podvrhnout.

Naštěstí bezpečnostní mechanizmy v prohlížeči neumožní JavaScriptu přečíst obsah HTML stránky z jiného serveru (tj. přečíst výsledek XMLHTTPRequestu z jiné domény). A tohoto faktu také využijeme. Pro zabránění CSRF u ostatních systémů je tedy nutné používat tyto metody:

  • Při zobrazení formuláře uživateli vygenerovat náhodný kód (jako neviditelné pole formuláře) a očekávat jej při dalším odeslání. Tento kód musí být náhodný a pro každého uživatele jiný. Následně při odeslání formuláře musíme kód na serveru zkontrolovat. Tento kód můžeme mít klidně shodný po celou dobu platnosti session.
  • Mít co nejkratší platnost session. Pokud je uživatel odhlášen z bankovního systému, žádné takové riziko nehrozí.

Aby všechny tyto mechanizmy fungovaly, je nutné zajistit, aby nemohlo na celém serveru nikde nastat XSS. Jinak by útočník mohl pomocné kódy snadno přečíst a emulovat sekvence návštěv stránek či přečíst kontrolní kód. Proto by samotný systém (např. bankovní) měl být oddělen doménou (3. úroveň domény by měla být dostatečná – záleží ovšem na omezení cookie na doménu) od dalších okrajových služeb (např. fórum či blog). Tím lze riziko XSS výrazně snížit.

Bohužel obrana proti CSRF jde částečně proti filozofii HTTP. Uživatel nemůže přímo přistoupit na webovou stránku ze záložek. Nelze tak např. přímo do e-mailu, který informuje o přidání příspěvku, uvést odkaz na smazání bez potvrzení. Uživatel musí vždy akci na stránce potvrdit.

JavaScript Hijacking

S rozvojem aplikací používajících JavaScript se objevil i nový typ útoku nazvaný JavaScript Hijacking. Týká se aplikací používajících JavaScript pro výměnu dat, např. pomocí formátu JSON.

Pokud aplikace nabízí uživateli data ve formě JSON na adrese http://www.example.com/privatedata.json (předpokládejme nyní, že sezení je udržované formou cookie, obsah privatedata.json se bude měnit v závislosti na cookie podle uživatele), mohou se tato data snadno stát terčem útoku. V našem případě známe strukturu dat a víme, že obsahuje vlastnost „email“. K útoku dojde, pokud je uživatel právě přihlášen (má nastavené cookie) a navštíví stránku s následujícím kódem (kód je připraven pro prohlížeč Firefox podporující javascriptovou vlastnost setter):

<script>
// využijeme toho, že v JavaScriptu lze upravovat vestavěné objekty
Object.prototype.email setter = captureObject;

function captureObject(x) {
  var objString = "";
  for (fld in this) {
    objString += fld + ": " + this[fld] + ", ";
  }
  objString += "email: " + x;

  // nyní objString obsahuje data z JSON. Stačí je odeslat na náš server
  ...
}
</script>
<script src="http://www.example.com/privatedata.json"></script> 

V případě, že JSON podporuje tzv. callback, tj. že všechna data jsou předána funkci, která je zavolána, je vše mnohem jednodušší. Stačí tuto funkci definovat. Tento callback se běžně používá u MashUp aplikací, a jeho hlavním smyslem je právě sdílení dat mezi aplikacemi na různých doménách.

Clickjacking (Click Hijacking)

Pokud máme aplikaci dobře zabezpečenou proti XSS a CSRF útokům, stále ještě nemáme vyhráno. Další typ problému, clickjacking, se z webové aplikace vždy ošetřit nedá, mohou jej ošetřit hlavně prohlížeče.

Kdysi jsem viděl jednu webovou hru, jejíž cílem bylo zabít mouchu. Uživatel na obrazovce jednoduše zabíjel mouchy klikáním myší. Hra se stále zrychlovala. Při nejvyšší rychlosti se najednou otevřelo okno pro potvrzení instalace software. Protože uživatel stále klikal, potvrdil i instalaci software – a uživatelův počítač byl zavirován. (To je také důvod, proč dnes prohlížeč Firefox čeká několik vteřin, než můžete potvrdit dialog k instalaci.)

Něčeho podobného využívá i clickjacking. Uživateli je zobrazena stránka, ve které je nějaký obsah a tlačítko. Tlačítko pochází z jiné webové aplikace, ale vizuálně je umístěno (např. pomocí iframe) tak, že uživatel o tomto faktu netuší a považuje je za součást stránky, kterou vidí. Tak, jak je stránka uživateli zobrazena, dává tlačítku úplně jiný význam – např. smazání zobrazeného textového pole. Nicméně se stále jedná o původní tlačítko, a tak vykonná původní činnost. I když bude aplikace zabezpečená proti CSRF útokům, stejně bude tato činnost provedena, protože uživatel onu akci skutečně potvrdil (byť nevědomky).

Řešením je nedovolit vložení naší aplikace do iframe (pokud si to můžeme dovolit). K tomu postačí kontrola pomocí JavaScriptu.

Shrnutí

Je velmi důležité zajistit, aby celá aplikace neměla bezpečnostní chyby. K nejhorším dnes patří XSS a CSRF útoky. Musíme proto upravit aplikaci, aby útoky nebyly možné. To není snadné a vyžaduje pečlivou explicitní kontrolu celé aplikace. Pokud umožníte na stránce i byť jediné XSS, nelze se už nijak bránit proti CSRF.


Autor článku je v současné době vedoucí vývojového oddělení a technologický lídr softwarové společnosti SoftEU s.r.o. a především její divize WinStrom, kde se zabývá vývojem multiplatformního ekonomického systému FlexiBee. Pravidelně také přispívá na firemní blog.

Napsat komentář

Vaše e-mailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *