Novinky v Latte 2.8: opevnění uvnitř šablony

před 23 dny od David Grudl     edit

Latte 2.8 má pancéřový bunkr přímo pod kapotou. Jde o důležitou funkci chránící aplikace, ve kterých se používají šablony z nedůvěryhodných zdrojů. Například když je editují samotní uživatelé. Jak vybudovat opevnění uvnitř šablon?

Když se mě někdo zeptá, jaké jsou tři killer vlastnosti Latte, odpovím takto:

  1. jsou nejlépe zabezpečené proti XSS
  2. používají PHP syntaxi, takže se nemusím učit jiný jazyk
  3. mají sexy n:attributy a volitelné řetězení {$user?->address?->street}

Šablonovací systémy pro PHP překvapivě často používají syntaxi jazyka Python (například Twig, Volt, atd), což nejen vyžaduje nutnost se učit nový jazyk, ale pak mezi ním a PHP neustále přepínat. V jednom jazyce se píše foreach ($people as $person), ve druhém for person in people, a to vyloženě svádí k dělání chyb.

Jaké je pohodlí, že v Latte používáme staré dobré PHP!

Latte bývalo zcela benevolentní v tom, co v něm můžete za PHP kód psát, jaké funkce volat, atd. Tohle se mění od verze 2.8, která přichází s tzv. sandbox režimem. Sandbox znamená pískoviště a tento režim hlídá, aby se písek nedostal mimo vyhrazenou plochu. Tedy poskytuje omezený přístup k makrům, filtrům, funkcím, metodám atd. Umožňuje tak používání šablon od neověřených třetích stran, třeba koncových uživatelů, kteří si je sami editují.

Jak to funguje?

Jednoduše nadefinujeme, co všechno šabloně dovolíme. Přičemž v základu je všechno zakázané a my postupně povolujeme:

$policy = new Latte\Sandbox\SecurityPolicy;
$policy->allowMacros(['block', 'if', 'else', '=']);
$policy->allowFilters($policy::ALL);

// a předáme do Latte
$latte->setPolicy($policy);

// uvnitř presenteru:
// $this->template->getLatte()->setPolicy($policy);

Tímto kódem umožníme autorovi šablony používat značky {block}, {if}, {else} a {=}. Ta posledně jmenovaná představuje výpis {=$var}, ale i častěji používanou kratší formu {$var}. A dále jsme povolili všechny filtry, takže uživatel bude moci psát {$var|upper}.

Nepovolili jsme však žádné funkce, metody nebo properties objektů, takže volání {=trim($var)} nebo {$user->isLoggedIn()} skončí výjimkou Latte\SecurityViolationException. Povolit je lze takto:

$policy->allowFunctions(['trim', 'strlen']);
$policy->allowMethods(Nette\Security\User::class, ['isLoggedIn', 'isAllowed']);
$policy->allowProperties(Nette\Database\Row::class, $policy::ALL);

Jak vidíte, u objektu Nette\Database\Row jsme povolili přístup ke všem properties.

Není to úžasné? Můžete na velmi nízké úrovni kontrolovat úplně všechno.

Tvořit policy od bodu nula, kdy je zakázáno úplně vše, nemusí být pohodlné, proto můžete začít od bezpečného základu:

$policy = Latte\Sandbox\SecurityPolicy::createSafePolicy();

Bezpečný základ znamená, že jsou povoleny všechny standardní makra kromě contentType, debugbreak, dump, extends, import, include, includeblock, layout, php, sandbox, snippet, snippetArea, templatePrint, varPrint, widget. Jsou povoleny standardní filtry kromě datastream, noescape a nocheck. A nakonec je povolený přístup k metodám a properites objektu $iterator.

V sandboxované šabloně nelze také přistupovat k $this a používat celou řadu klíčových slov PHP (např. throw, new, echo, …) nebo operátor backtick.

Pravidla se aplikují pro šablonu, kterou vložíme novou značkou sandbox. Což je jakási obdoba include, která však zapíná bezpečný režim a také nepředává žádné vnější proměnné.

{sandbox untrusted.latte}

Tedy layout a jednotlivé stránky mohou nerušeně využívat všechna makra a proměnné, pouze na šablonu untrusted.latte budou uplatněny restrikce. Proměnné do ní lze předat explicitně:

{sandbox untrusted.latte, title => $article->title, logged => $user->isLoggedIn()}

Některé prohřešky, jako použití zakázaného marka nebo filtru, se odhalí v době kompilace. Jiné, jako třeba volání nepovolených metod objektu, až za běhu. Šablona také může obsahovat jakékoliv jiné chyby. Aby vám ze sandboxované šablony nemohla vyskočit výjimka, která naruší celé vykreslování, lze definovat vlastní exception handler, který ji třeba jen zaloguje:

$latte->setExceptionHandler(function (Throwable $e, Latte\Runtime\Template $template) use ($logger) {
	$logger->log($e);
});

Pokud bychom chtěli sandbox režim zapnout přímo pro všechny šablony, jde to snadno:

$latte->setSandboxMode();

Některé prohřešky, jako je například zakázaného marka nebo filtru, se odhalí v době kompilace. Jiné, jako třeba volání nepovolených metod objektu, až za běhu.

Komentáře (RSS)

  1. Příjemná změna a posun dále, Díky :). Určitě se to bude hodit všem.

    před 23 dny · replied [3] David Grudl
  2. „Umožňuje tak používání šablon od neověřených třetích stran, třeba koncových uživatelů, kteří si je sami editují.“
    „… takže volání {=trim($var)} nebo {$user->isLoggedIn()} skončí výjimkou.“

    Tak mě napadá, jestli to je v této kombinaci (uživatel a exception) dobré řešení – uživatelé jsou koumáci.
    Půjde nějak odchytit, aby se dala místo erroru na produkci vypsat vlastní hláška (třeba i před uložením uživatelem editované šablony)? Nebo třeba nepovolené věci vypsat jako plaintext, popř. vůbec?

    před 23 dny · replied [3] David Grudl [5] chemix
  3. #1 EncryptSL díky :)

    #2 Gappa
    Někdy to jde zjistit při kompilaci (třeba u {=trim($var)}), někdy až za provozu (třeba u {var $fn = 'trim'}{=$fn($var)}), takže je potřeba chyby odchytávat. Ale je pravda, že by to Latte mohlo nějak zjednodušit, popřemýšlím nad tím.

    před 23 dny · replied [6] ludek
  4. #3 Každopádně užitečná fíčura, díky! :)

    před 20 dny
  5. #2 Gappa A nemuzes si to pred ulozenim zkusit spustit, zda to projde nebo to hodi chybu? A kdyz to projde, tak to uzivateli ulozis?

    před 19 dny
  6. #3 David Grudl Před časem jsem něco podobného potřeboval: https://forum.nette.org/…pred-include
    Pokud se uživatelům umožňuje zasahovat do šablon, musí se nějak ošetřit stav, že v tom nadělají chyby.

    před 16 dny

Chcete-li odeslat komentář, přihlaste se