V tomto článku budeme vyžadovat alespoň základní znalosti HTML, CSS a JavaScriptu. Pro serverově orientované příklady byl zvolen značkovací jazyk PHP pro jeho jednoduchost a rozšířenost.
Na úvod: co mohou způsobit znaky „<“ a „>“
Začneme příkladem. Mějme jednoduchou stránku s jedním vstupním polem, které je součástí formuláře. Formulář odesílá svůj obsah na stejnou stránku a zobrazí obsah odeslaného políčka.
<html>
<body>
<form>
<input type="text" name="test">
<input type="submit" value="Odeslat a zobrazit">
<br>
<?php
if (isset($_GET['test'])) {
echo 'Odeslaná hodnota je: ' . $_GET['test'];
}
?>
</form>
</body>
</html>
Co se stane, když do našeho formuláře zadáte slovo „pokus“? Na výsledné stránce se zobrazí slovo „pokus“. Co se ovšem stane, když k němu přidáte značky HTML a zadáte např. „<b>pokus</b>“? Zobrazí se tučně slovo „pokus“.
Jinými slovy – v tomto případě můžete libovolně měnit HTML stránku. Uvedený příklad je jedním z mnoha případů XSS. Kromě celkem neškodné značky pro tučné písmo můžete vložit i kód spouštějící JavaScript (odtud také pochází název Cross-Site Scripting). Podívejte se na několik ukázek možných vstupů a na jejich výsledky (možností je ale daleko víc):
uživatelský vstup | akce v prohlížeči |
---|---|
<b>pokus</b> | zobrazí se „pokus“ (tučně) |
<img src=x onerror=„alert(‚XSS‘);“> | vyskočí javascriptové okno s nápisem XSS |
<img src=„http://hacker.example.cz“> | zobrazí se obrázek z útočníkovy stránky |
Jak se útoku bránit? V našem případě postačí, když nahradíme znaky „<“ a „>“ HTML entitami, tedy „<“ a „>“.
Pro začátek předvedeme tu nejjednodušší funkci, která je k dispozici snad v každém programovacím jazyce – nahrazování výskytu řetězce za jiný – v PHP je to str_replace. Uvedený kód tedy změníme na následující:
echo 'Odeslaná hodnota je: ' . str_replace(array('<', '>'), array('<', '>'), $_GET['test']);
Tím jsem uvedený příklad ochránil proti XSS. Ale pozor, tato obrana není v některých případech dostačující. Proto čtěte dál.
Co může způsobit uvozovka nebo apostrof
Rozšíříme předchozí příklad – pro snadnou změnu během testování zobrazíme odeslanou hodnotu i v textovém poli.
<html>
<body>
<form>
<input type="text" name="test" value="<?php echo $_GET['test']; ?>">
<input type="submit" value="Odeslat a zobrazit">
<br>
<?php
if (isset($_GET['test'])) {
echo 'Odeslaná hodnota je: ' . $_GET['test'];
}
?>
</form>
</body>
</html>
Po vyzkoušení uživatelských vstupů z první tabulky se může zdát, že je vše v pořádku. Ale to je omyl, viz následující vstupy:
uživatelský vstup | akce v prohlížeči |
---|---|
„><img src=“http://hacker.example.cz/obrazek.jpg“><x x=“ | zobrazí se obrázek z útočníkova serveru |
“ onclick=„alert(‚XSS‘);“ x=“ | vyskočí javascriptové okno po kliknutí do políčka |
Kromě znaků „<“ a „>“ musíme totiž ošetřit i uvozovky („) a apostrofy (‚), podle toho, co používáme. V PHP se pro tento účel používá funkce htmlspecialchars().
<input type="text" name="test" value="<?php echo htmlspecialchars($_GET['test'], ENT_QUOTES); ?>">
Co v případě značky textarea?
V dalším příkladu předvedeme ještě jedno ošetření HTML, kde nemusí být na první pohled jasné, proč jej vůbec dělat. Na první pohled se totiž může zdát, že není nutné nic ošetřovat. Mějme následující příklad:
<html>
<body>
<form>
<textarea name="test"><?php echo $_GET['test']; ?></textarea>
<input type="submit" value="Odeslat a zobrazit">
</form>
</body>
</html>
Pokud budete zkoušet prozatím uvedené uživatelské vstupy, značku textarea neprolomíte. Zkuste ale dodat do uživatelského vstupu její vlastní uzavírací značku.
uživatelský vstup | akce v prohlížeči |
---|---|
</textarea><img src=„http://hacker.example.cz/obrazek.jpg“> | zobrazí se obrázek z útočníkova serveru |
A obrana? Jako v prvním příkladu – převádět znaky „<“ a „>“ na jejich HTML entity, například pomocí funkce htmlspecialchars().
Obrana v samotném JavaScriptu
Výše uvedené příklady měly jedno společné – uživatelský vstup se ošetřoval na straně serveru. A co když nás serverová strana zrovna nezajímá, můžeme si nezabezpečená data ošetřit i na klientské straně? Ano, i to jde, ukážeme si další příklad:
<html>
<body>
<script>
function zobraz() {
document.getElementById('vysledek').innerHTML =
'Odeslaná hodnota je: ' +
document.getElementById('test').value;
}
</script>
<input type="text" id="test">
<input type="submit" onclick="zobraz();" value="Zobrazit">
<br>
<div id="vysledek"></div>
</body>
</html>
V příkladu je použita metoda innerHTML(), ta umožňuje upravit (resp. nahradit) část HTML dokumentu. Vyzkoušejme následující vstupy:
uživatelský vstup | akce v prohlížeči |
---|---|
pokus | zobrazí se „pokus“ |
<b>pokus</b> | zobrazí se „pokus“ (tučně) |
<img src=„http://hacker.example.cz“> | zobrazí se obrázek z útočníkova serveru |
<b onmouseover=„alert(‚XSS‘);“>najeď myší přes tento text</b> | vyskočí javascriptové okno s nápisem XSS |
Obrana je stejná jako u prvního příkladu: nahradíme nebezpečné znaky jejich HTML entitami. V JavaScriptu použijeme metodu replace().
function zobraz() {
document.getElementById('vysledek').innerHTML =
'Odeslaná hodnota je: ' +
document.getElementById('test').value.replace(/</g, '<').replace(/>/g, '>');
}
Míchanice: HTML + PHP + JavaScript
PHP svou jednoduchostí přímo svádí k míchání kódu (např. v samotném HTML použijeme pro výpis hodnot PHP), a tak snad nepřekvapí ani generování JavaScriptu z PHP. Může se někdy samozřejmě hodit, ale opět je nutné si dát pozor.
<html>
<body>
<script>
window.onload = function() {
var value = 'Odeslaná hodnota je: <?php echo $_GET['test']; ?>';
value = value.replace(/</g, '<').replace(/>/g, '>');
document.getElementById('vysledek').innerHTML = value;
}
</script>
<form>
<input type="text" name="test">
<input type="submit" value="Odeslat">
<div id="vysledek"></div>
</form>
</body>
</html>
Zobrazení hodnoty je zde již ošetřeno. Ale podle předchozích zkušeností s uvozovkami a apostrofy nás hned napadne, jak apostrof prolomit.
uživatelský vstup | akce v prohlížeči |
---|---|
‚; alert(„XSS“); value = ‚není tu nic | vyskočí javascriptové okno s nápisem XSS |
Pokusíme se tedy ošetřit apostrofy a uvozovky, ale pozor, to není celá obrana. Existuje totiž další možnost, jak JavaScript ukončit. Je to značka pro ukončení skriptu: „</script>“ nebo i její kratší varianta „</script “ (mezera či jakýkoliv jiný prázdný znak na konci). Jakmile interpret JavaScriptu narazí na tento výskyt znaků, ukončí se javascript a dále se pokračuje ve zpracování HTML.
uživatelský vstup | akce v prohlížeči |
---|---|
</script><script>alert(„XSS“)</script> | vyskočí javascriptové okno s nápisem XSS |
</script><img src=„http://hacker.example.cz“> | zobrazí se obrázek z útočníkovy stránky |
A jak se bránit v tomto případě? Nejjednodušší je opět nahradit „nebezpečné znaky“: < a > za jejich HTML entity.
Další případ: uživatelským vstupem je HTML
Výše uvedené příklady pracovaly jen s prostým textem a cílem bylo jej zobrazit. Existují situace, kdy uživatelským vstupem skutečně je HTML, typickým případem může být webové rozhraní e-mailu. Zde vyvstává řada dalších otázek:
- Co s nevalidním HTML?
- Budou se zobrazovat externí obrázky?
- Povolíme kaskádové styly?
- Co s JavaScriptem?
- Co s formuláři?
- … (a řada dalších)
Na jednu stranu chceme zajistit bezpečnost a na druhou stranu uživatel chce mít „hezky vypadající e-mail“ – prostě HTML. Existuje více způsobů, jak řešit tento problém, ale nejúčinnějším řešením je použití tzv. whitelistu – seznamu HTML značek a jejich atributů, jejichž vložení povolíme (protože jsou bezpečné). Příklad takového velmi krátkého whitelistu:
povolená značka | povolený atribut |
---|---|
p | – |
a | href |
b | – |
Odpovíme na předchozí otázky s pomocí této tabulky:
- Co s nevalidním HTML?
Nevalidní HTML budeme muset nejdříve převést na validní HTML (pokud je tedy nechceme rovnou zahodit). Prohlížeče to také řeší (a bohužel každý trochu jinak), ideálně k tomu použijeme nějakou hotovou knihovnu. - Budou se zobrazovat externí obrázky?
Ne, značka „img“ není v našem whitelistu povolena. - Povolíme kaskádové styly?
Ne, značka „link“ pro načtení externího stylopisu není povolena, ani atribut „style“ není povolen. - Co s JavaScriptem?
Odfiltruje se. Značka „script“ není povolena ani žádné atributy využitelné jako ovladače událostí nejsou povoleny (příkladem může být „onclick“) ani kaskádové styly nejsou povoleny (i ty by mohly vést ke spuštění JavaScriptu, viz níže). Nesmíme ovšem zapomenout zkontrolovat i obsah povoleného atributu href (ten by mohl obsahovat javascriptový kód uvozený prefixem „javascript:“) - Co s formuláři?
Odfiltrují se. Značky „form“, „input“ ani další formulářové značky nejsou povoleny.
Z košatého HTML na vstupu se zahodí veškeré nepovolené HTML značky a atributy. Zůstanou jen ty povolené.
XSS a kaskádové styly
Kaskádové styly úzce souvisí s předchozí problematikou zobrazování uživatelského HTML. I zde existují možnosti, jak útočit a např. spustit JavaScript. Příkladem může být vlastnost expression dostupná v Internet Exploreru:
<div style="width: expression(alert('XSS'));">
Pro Firefox (a všechny prohlížeče postavené na jádru Gecko) existuje v kaskádových stylech také vlastnost, která umožňuje spuštění JavaScriptu (podrobnosti najdete v XSS Cheat Sheet), viz příklad:
<p style=-moz-binding:url(xssByCssInFirefox.xml#xss);>text</p>
Řešení? Podobně jako v předchozím příkladu: whitelist, čili povolit jen ty vlastnosti, které opravdu povolit chceme.
Napsat vlastní HTML/CSS filtr není nic jednoduchého, proto pravděpodobně použijete nějakou hotovou knihovnu, v případě PHP např. projekt HTML Purifier.
Závěr
Tvůrci webů a webových aplikací by měli myslet na bezpečnost a nezapomínat pečlivě ošetřovat veškeré vstupy od uživatelů. Veškeré možné útoky včetně jejich podpory v nejpoužívanějších prohlížečích se dají nastudovat na XSS Cheat Sheet od RSnake.
Uživatelé Firefoxu se pak mohou chránit proti mnoha typům XSS pomocí rozšíření NoScript. Má však zpočátku otravnou, ale jinak velmi účinnou, vlastnost. Musíte NoScript nejdříve naučit, jaké weby mohou používat JavaScript ve vašem prohlížeči.
Odkazy
Autorem článku je Jan Pejša, vývojář webových aplikací ve společnosti Kerio Technologies s.r.o., která je jedním z hlavních výrobců bezpečnostního internetového softwaru pro malé a středně rozsáhlé sítě, se specializací na síťové firewally a bezpečnost interní firemní komunikace.
Přehled komentářů