KSES odstraňuje zlé skripty

WordPress je distribuován s knihovnou KSES Švédského autora Ulf Harnhammar. Knihovna je HTML/XHTML filtrem implementovaným v PHP. Odstraňuje nežádoucí HTML elementy a atributy a provádí kontrolu jejich hodnot.

Knihovna slouží k zamezení Cross-Site Scripting (XSS) útokům. KSES přitom zdaleka není jediným, ani jediným dostačujícím, způsobem jak takovým útokům bránit. Nicméně poslouží velmi dobře v případech, kdy chceme povolit uživatelům či vzdáleným serverům poskytovat našemu kódu text s podporou HTML.

V případě WordPressu samotného je to například psaní příspěvků uživatelům s různým druhem oprávnění, či podpory HTML v komentářích.

Název “KSES” je rekurzivní zkratka zmanenající

KSES Strips Evil Scripts

, což v překladu znamená KSES odstraňuje zlé skripty. S rekurzivními zkratkami se ve světě open source a free software můžeme setkat poměrně často.

Knihovnu naleznete v adresáři wp-includes/kses.php.

Rozdíl mezi wp_kses a strip_tags

PHP vývojář se může ptát, proč by měl využívat wp_kses namísto strip_tags s povolenými HTML elementy. Důvod je jednoduchý. Funkce strip_tags, ačkoli umožňuje definovat které elementy nemají být z textu odstraněny, neumožňuje definovat které attributy jsou povoleny.

A to může být pro některé případy celkem problém. Jeden příklad za všechny. Takový element img pro obrázek, zdánlivě neškodný, má attribut onerror, který spouští javascript v něm definovaný v případě, že se obrázek nepodaří načíst. Následující kód tudíž nelze považovat za dostatečné zabezpečení:

$post_content = '<h1>Title</h1><p>Lorem ipsum sit dolor ament</p><img src="x" alt="haxxored" onerror="alert(this.alt)"/>';
echo strip_tags( $post_content, '<h1><p><img>' );

wp_strip_all_tags

Zmínil jsem jeden důvod, proč strip_tags není vhodné používat, proč tedy ve WordPressu nalezneme funkci wp_strip_all_tags?

Důvodem je, že strip_tags odstraňuje pouze html tagy, nikoli celé elementy včetně textu. Podívejte se na tento kód:

$post_content = '<p>Lorem ipsum</p>';
echo strip_tags( $post_content, '' ); //vypíše "Lorem ipsum"

Toto chování může být v řadě případů žádoucí, ovšem existuje případ, kdy tomu tak není. A sice, pokud vstupní text obsahuje element style či script. Jejich obsah většinou opravdu nechat vypsat nechceme. Ačkoli se ve většině takových případech nejdená o bezpečnostní chybu, rozhodně to není něco, čeho bychom úmysleně chtěli docílit. Proto WordPress nabízí funkci wp_strip_all_tags, která před samotným odstraňováním HTML tagů kompletně odstraní script a style elementy i s textem, který je uvnitř.

Funkce wp_kses a její parametry

Knihovna obsahuje celou řadu funkcí, ne všechny jsou ovšem vhodné k přímému použití. Jsou využity v rámci větších celků. Přímo použitelná je funkce wp_kses, případně některý z wrapperů (funkce volající funkci s předdefinovanými atributy), například wp_kses_post.

Parametry funkce wp_kses jsou “$string”, “$allowed_html” a “$allowed_protocols”, který je jako jediný nepovinný (výchozí hodnota je prázdné pole – array):

wp_kses( $string, $allowed_html, $allowed_protocols = array() )

$string

“$string” je text, který chceme filtrovat. Ve WordPressu samém je to typicky obsah příspěvku – post_content. Obsah příspěvku je, například, zkontrolován funkcí wp_kses_post před tím, než je uložen do databáze. Pokud jste si někdy všimli, že uživatel s nižším oprávněním než administrátor nemůže vkládat do příspěvku JavaScript, tak za to může právě sanitizace pomocí funkce wp_ksess.

V rámci pluginu bude uživatel chtít sanitizovat text/vstup získaný od uživatele či z odpovědi vzdáleného serveru před tím, než jej vypíše na stránku či uloží do databáze.

$allowed_html

Tento parametr určuje, jaká sada HTML elementů a atributů se má použít. Vývojář může v rámci vlastního volání funkce definovat svou vlastní sadu, nebo využít sady již předdefinované – takovým potom říkáme kontext.

Mezi kontexty, neboli předdefinované sady, patří “post”, “user_description”, “pre_user_description”, “strip”, “entities” a “data” (který je také defaultním kontextem). Zde se vracíme k funkci “wp_kses_post”, která jediné co dělá, je že za vývojáře předává hodnotu “post” parametru $allowed_html funkce wp_kses.

$allowed_protocols

Tento parametr vyjmenovává povolené protokoly pro atributy, jejichž hodnotou je URI (typicky, “href” či “src”). Cílem je v textu ponechat jen protokoly, které považujeme za bezpečné, a odstranit například výskyty protokolou “javascript:”.

Vlastní sada HTML

Vlastní sadu povolených HTML elementů a atributů definuje vývojář v podobě pole (array), jehož klíče označují HTML elementy a hodnotami jsou opět pole (array), kde jsou klíči jednotlivé attributy a hodnotou je boolean hodnota (true či false). Definovat false není nutné, někdy ovšem může sloužit k názornému zakázání daného atributu:

Vlastní sadu lze definovat podle následujícího vzoru, ve kterém povolíme, aby text obsahoval elementy “img” a “a”. Pro “img” povolíme atributy “src”, “alt” a “title”. Pro odkaz poté “href”, “title”, “rel”, “class” a “id”:

$allowed_html = array(
   'img' => array( //obrázek
        'src' => true, //zdroj
        'alt' => true, //alternativní popisek
        'title' => true, //titulek
    ),
   'a' => array( //odkaz
        'href' => true, //url
        'title' => true, //titulek
        'rel' => true, //vztah k odkazovanému dokumentu
        'class' => true, //třída
        'id' => true, //id
    ),
);
$text = wp_kses( $text, $allowed_html );

Je důležité mít na paměti, že KSES opravdu ponechá v daném HTML textu jen a pouze ty elementy a atributy, které jsou vyjmenovány. Takže například atribut “class” pro obrázek (“img”) bude nemilosrdně odstraněn. Stejně tak budou z textu předaném v atributu “$string” odstraněny všechny nadpisy, odstavce, tučné písmo, kurzíva … na co si jen vzpomenete a co jste zapomněli uvést v proměnné “$allowed_html”.

Odvozená vlastní sada

Ovšem abyste nemuseli vždy vypisovat celý seznam existujících HTML elementů a jejich atributů, lze využít funcke wp_kses_allowed_html, které lze předat kontext (například již zmíněnmé “post”) a funkce poté vrací seznam předdefinovaných elementů a jejich atributů. To lze poté využít k modifikaci defaultních nastavení a jejich následné využití pro vlastní účely:

$allowed_html = wp_kses_allowed_html( 'post' ); //defaultní sada pro příspěvek
$allowed_html['script'] = array( //přidáme element script
    'src' => true, //povolíme zdroj (atribut src)
    'type' => true, //a typ (type)
);
$allowed_html['a']['rel'] = true; //povolíme elementu "a" mít attribut "rel"
$text = wp_kses( $text, $allowed_html );

Ovšem pozor, pokud povolíme elemet script v neznámém textu, tak jsou poté všechny snahy o zamezení XSS útoku v atributech jiných prvků naprosto zbytečné – útočník nemusí hledat zadní vrátky, když jsou vchodové dveře otevřeny dokořán. Výše uvedený kód, povolující element script, tedy v žádném případě nepoužívejte.

Filtry

WordPress sám funkce wp_kses využívá na mnoha místech. Ukládání příspěvku do databáze již bylo zmíněno několikrát.

V případě, že bychom chtěli předefinované sady HTML při použití v interních procesech naší WordPress instalace rozšířit, nebo některou naopak zůžit, přijdou nám vhod filtry ve funkci wp_kses_allowed_html.

Říkám filtry, ale v zásadě se jedná jen o jeden filtr. A sice “wp_kses_allowed_html”.

Důležité je vždy zkontrolovat o jaký kontext je jedná. V případě, že vývojář definoval vlastní sadu HMTL, tak druhým parametrem předaným našemu callbacku bude string “explicit”, defaultní hodnotou je poté string “data” a další hodnoty jsou rezervovány pro jednotlivé kontexty, ve kterých je funkce využívána:

CUSTOM_TAGS

Pokud byste chtěli, můžete si definovat vlastní základní sady elementů a atributů v rámci wp-config.php.

V takovém případě je třeba, ideálně dle vzoru, definovat následující proměnné: $allowedposttags, $allowedtags, $allowedentitynames a nezapomenout definovat konstantu “CUSTOM_TAGS” s hodnotou “true”.

Závěrem

Knihovna KSES je velmi robustní a dává vývojáři velmi detailní kontrolu nad tím, jaké HTML elementy s jakými atributy se mohou v textu přijatém od uživatele či cizího serveru vyskytovat v případě, že chceme těmto zdrojům povolit používat HTML v jejich vstupu.

Taková kontrola před tím, než je s textem nějak naloženo – například je uložen do databáze či zobrazen na stránce, efektivně zamezí XSS útokům, které si mohou brát na paškál povolené attributy typu “onerror” u obrázků, či “onclick” u odkazů a podobně.

Každý vývojář WordPress pluginů a šablon by funkci wp_kses měl znát a hlavně využívat ve svém kódu.

Ovšem “wp_kses” není jedinou funkcí, která je třeba k zabezpečení uživatelského vstupu. Pokud nechcete čekat na to, až se k napsání dalšího článku na téma “esc_*” funkcí dostanu, přečtěte si, co o takových funkcích říká kodex.

3 thoughts on “KSES odstraňuje zlé skripty”

  1. Super článok, vďaka za podrobný popis wp_kses! Túto funkciu využívam, no obávam sa o jej využitie pri zobrazení dát na stránke. Čítal som dakde, že je dosť výkonovo náročná a mala by sa preto cachovať a nepoužívať priamo na zobrazovanie vecí. Čo si o tom myslíš, je to s výkonom wp_kses až tak zlé?

    Like

    1. Co se náročnosti týče, tak ta je závislá na zpracování regexů – wp_kses je vlastně “jen” sada regulárních výrazů, které parsují stringy.

      PHP5, HHVM či PHP7 se liší v rychlosti, se kterou regulární výrazy zpracovávají, ovšem i v případě PHP5 je počet milisekund, které volání některé z funkcí rodiny wp_kses přidává, většinou ospravedlnitelný účelem, pro který se tato vrstva přidává.

      Rozhodně to chce přemýšlet, kdy je nutné danou funkci použít a kdy je naopak možné použít jinou – nedoporučuji ji vecpat bezmyšlenkovitě všude. Na druhou stranu, ve světě WordPress jsou jiné výkonnostní problémy, které stojí za pozornost a výkon funkcí z rodiny wp_kses je něco, co má cenu řešit až poté, co máte všechno ostatní perfektně odlazené. Do té doby bych se zrovna výkonem wp_kses moc netrápil.

      Like

      1. Vďaka za odpoveď! Ako vidím, nie je to zas taký strašiak ako som čítal, aj keď tie regex sú pravdepodobne dosť “husté”😉

        Súhlasím s tým, že je nutné poriadne premyslieť, či wp_kses použiť na front-end webstránky, alebo či existuje jednoduchší spôsob s rovnakou mierou bezpečnosti.

        Všimol som si, že Underscores téma používa wp_kses na front-end asi zotrikrát, a preto ma výkon tejto funkcie zaujímal.

        Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s