Přejít k navigační liště

Zdroják » JavaScript » Bezpečnost na webu – přehled útoků na webové aplikace

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

Články JavaScript, Různé

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 WinStrom 10. Pravidelně také přispívá na firemní blog.

Obsahovala vaše aplikace někdy bezpečnostní chybu?

Komentáře

Subscribe
Upozornit na
guest
15 Komentářů
Nejstarší
Nejnovější Most Voted
Inline Feedbacks
View all comments
xurpha

Jsem jedinej paranoik, který na svých stránkách při každém načtení zabezpečené stránky měním session cookie (jak v DB, tak v prohlížeči)?

Martin Hassman

Jak se to vyrovná s tím, když uživatel v rámci session otevře odkaz do nového panelu a bude v prohlížení pokračovat současně z dvou panelů? V takovém případě by dle vašeho popisu nejspíš stránka v jednom panelu dostala novou session a druhá měla ještě session původní. Bude to uživateli fungovat?

Maverick

Vzhledem k tomu, že session cookies bývají sdílené a pouze jako non-permanent, pak se nová cookie nastaví pro všechny okna (a taby) v daném procesu (tj. i pro všechny okna otevřené z původního). Díky tomu pak dojde ke změně cookie ve všech oknech naprosto bez problému.

jam

…ale flag HttpOnly stále nefunguje ve všech prohlížečích – např. Safari – viz. https://www.owasp.org/index.php/HTTPOnly#Browsers_Supporting_HTTPOnly

Cookie s nastaveným HttpOnly flagem se pak chová jako cookie bez HttpOnly flagu.

Maverick

Flag "HttpOnly" nemá žádný vliv na to, co jsem popsal výše:)

čistéřešení

session_regenerate_id() v PHP

xurpha

Ty nesmysly, co jsou v PHP, nepoužívám vůbec. Napsat si vlastní kód je na pár řádků…

Filip Jirsák

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).

IP adresa uživatele se může měnit i v řádu hodin nebo minut – stačí třeba být připojen přes proxy server s vyrovnáváním zátěže. Pokud už má někdo v moci třeba router na cestě od uživatele k serveru, aby dokázal odchytit session ID, nebude pro něj problém ani posílat a odchytávat dotazy s tou správnou IP adresou. Takže kontrola IP adresy session mi připadá jako takový efektní způsob, jak zdánlivě něco udělat pro bezpečnost, a ve skutečnosti neudělat nic – maximálně znesnadnit použití uživatelům, kteří opravdu mění IP adresu relativně často.

petr.steinbauer

Viděl jsem řešení, kde bylo na výber. Vedle přihlášení bylo předškrnuté tlačítko kontrolovat IP adresu.

beer

Byl tam spravny default. Kdyz uzivatel nerozumi, tak kontrolu IP adresy nevypne. Priste mozna bude nastvany, ze se mu heslo neulozilo, ale aspon ho nikdo nehackne.

//R

Man in the middle je ale úplně jiná kategorie útoků. Webové útoky typicky pocházejí od osob, které nemají s obětí nic společného.

toby

Pokud má někdo v moci router, nemusí se obtěžovat s odchytáváním sessionid, může si rovnou odchytit celou komunikaci včetně hesel.

jam

to ale platí jen pro HTTP komunikaci – pokud webová aplikace vyžaduje jen zabezpečené připojení (HTTPS), tak to samozřejmě není možné odposlechnout, aniž by nedošlo ke změně certifikátu (uživatel by na to měl být prohlížečem upozorněn)

Jakub Vrána

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í.

Odkaz pro smazání příspěvku bez potvrzení do e-mailu samozřejmě vložit lze. Stačí se oprostit od toho, že token (náhodný kód) musí být uložen právě do session a právě do formuláře. Token totiž může být na jedné straně součástí odkazu posílaného e-mailem a na druhé straně může být uložen v databázi vedle příspěvku. Aplikace tak zůstane odolná proti CSRF a zároveň pohodlná.

Enum a statická analýza kódu

Mám jednu univerzální radu pro začínající programátorty. V učení sice neexistují rychlé zkratky, ovšem tuhle radu můžete snadno začít používat a zrychlit tak tempo učení. Tou tajemnou ingrediencí je statická analýza kódu. Ukážeme si to na příkladu enum.