Sandbox
Sandbox tworzy warstwę bezpieczeństwa, która daje Ci kontrolę nad tym, jakie tagi, funkcje PHP, metody itd. mogą być używane w szablonach. Dzięki trybowi sandbox możesz bezpiecznie współpracować z klientem lub zewnętrznym koderem przy tworzeniu szablonów, nie obawiając się naruszenia bezpieczeństwa aplikacji ani wykonania niepożądanych operacji.
Jak to działa? Po prostu określamy, co chcemy w szablonie dopuścić. Na początku zabronione jest wszystko i stopniowo
nadajemy uprawnienia. Poniższy kod pozwala autorowi szablonu używać tagów {block}, {if},
{else} i {=} (ten ostatni to tag do wypisania zmiennej lub
wyrażenia) oraz wszystkich filtrów:
$policy = new Latte\Sandbox\SecurityPolicy;
$policy->allowTags(['block', 'if', 'else', '=']);
$policy->allowFilters($policy::All);
$latte->setPolicy($policy);
Możemy też zezwolić na dostęp do poszczególnych funkcji globalnych, metod lub właściwości obiektów:
$policy->allowFunctions(['trim', 'strlen']);
$policy->allowMethods(Nette\Security\User::class, ['isLoggedIn', 'isAllowed']);
$policy->allowProperties(Nette\Database\Row::class, $policy::All);
Uprawnienia nadane przez allowMethods() i allowProperties() obowiązują także dla instancji klas
potomnych danej klasy (sprawdzenie używa is_a()).
Czy to nie wspaniałe? Wszystko kontrolujesz na bardzo niskim poziomie. Jeśli szablon spróbuje wywołać niedozwoloną
funkcję albo sięgnąć po niedozwoloną metodę czy właściwość, zgłosi wyjątek
Latte\SecurityViolationException.
Bezpieczna domyślna polityka
Tworzenie polityki od zera, gdzie wszystko jest zabronione, może nie być wygodne, dlatego możesz wyjść od bezpiecznej podstawy:
$policy = Latte\Sandbox\SecurityPolicy::createSafePolicy();
Ta bezpieczna podstawa oznacza, że dozwolone są wszystkie standardowe tagi z wyjątkiem contentType,
debugbreak, dump, extends, import, include, layout,
php, sandbox, snippet, snippetArea, templatePrint,
varPrint, embed. Dozwolone są wszystkie standardowe filtry z wyjątkiem datastream,
noescape i nocheck. Wreszcie dozwolony jest dostęp do metod i właściwości obiektu
$iterator.
Aktywacja sandboxa
Reguły dotyczą szablonu, który wstawiamy tagiem {sandbox}. Jest
to poniekąd analogiczne do {include}: włącza tryb sandbox i, tak jak {include}, nie przekazuje
automatycznie otaczających zmiennych. Możesz je jednak przekazać jawnie, na przykład
{sandbox 'untrusted.latte', a: 1, b: 2}:
{sandbox 'untrusted.latte'}
Layout i poszczególne strony mogą więc swobodnie korzystać ze wszystkich tagów i zmiennych; ograniczenia zostaną
zastosowane tylko do szablonu untrusted.latte.
Niektóre naruszenia, jak użycie zabronionego tagu lub filtra, są wykrywane w czasie kompilacji. Inne, jak wywołanie niedozwolonych metod obiektu, w czasie działania. Szablon może też zawierać dowolne inne błędy. Aby wyjątek z szablonu w sandboxie nie zakłócił całego procesu renderowania, możesz zdefiniować własny handler wyjątków, który na przykład tylko go zaloguje.
Gdybyśmy chcieli włączyć tryb sandbox od razu dla wszystkich szablonów, to proste:
$latte->setSandboxMode();
Kontrola wygenerowanego kodu
Aby użytkownik nie wstawił na stronę kodu PHP, który jest wprawdzie poprawny składniowo, ale zabroniony i powoduje PHP
Compile Error, zalecamy kontrolę szablonów przez linter PHP. Tę
funkcjonalność włączysz metodą Engine::enablePhpLinter(). Ponieważ do sprawdzenia musi wywołać binarkę PHP,
przekaż jej ścieżkę jako parametr:
$latte = new Latte\Engine;
$latte->enablePhpLinter('/path/to/php');
Czego sandbox nie pilnuje
Poza tagami, funkcjami, metodami i właściwościami, na które zezwolisz, sandbox bezwarunkowo zabrania kilku konstrukcji
niezależnie od polityki: operatora new, zmiennej $this, zmiennych zmiennych ($$var)
i filtra |noescape.
Sandbox niezawodnie pilnuje operacji jawnych: wywołań funkcji, metod i filtrów oraz dostępu do właściwości obiektów. Jest jednak jedna rzecz, do której jego kontrole nie sięgają, i warto o niej wiedzieć.
Kiedy wypisujesz obiekt lub używasz go w dowolnym kontekście łańcuchowym, PHP automatycznie wywołuje jego magiczną
metodę __toString(). Polityka nie sprawdza tej niejawnej konwersji, więc wykona się ona nawet wtedy, gdy
metody __toString() nie ma wśród dozwolonych. Autor szablonu może więc uruchomić __toString() na
dowolnym obiekcie, do którego w szablonie sięgnie. Różni się to od jawnej postaci {$obj->__toString()},
którą sandbox blokuje:
{$obj} {* wywoła __toString() *}
{$obj . '!'} {* to samo (konkatenacja) *}
{="price: $obj"} {* to samo (interpolacja łańcucha) *}
{$obj|upper} {* to samo (przez filtr) *}
Zadaniem __toString() jest utworzenie tekstowej reprezentacji obiektu, więc jego dostępność zwykle nie
szkodzi. Problem pojawia się dopiero wtedy, gdy __toString() ma efekty uboczne (na przykład zapis do bazy danych
lub zapytanie do niej) albo gdy zwraca wrażliwe dane.
Latte mogłoby wyłapać te najbardziej bezpośrednie przypadki, ale nie niezawodnie we wszystkich. Konwersja obiektu na
łańcuch nie jest wywołaniem metody, lecz wbudowaną operacją języka, która występuje w wielu miejscach wyrażenia. W
części z nich (na przykład przy porównaniu obiektu z łańcuchem albo wewnątrz wywołanej funkcji lub filtra) nie dałoby
się jej przechwycić niezawodnie bez blokowania legalnych zastosowań. Dlatego musisz liczyć się z tym, że
__toString() jest dostępne.
Nie udostępniaj sandboxowi obiektów, których __toString() ma efekty uboczne albo ujawnia wrażliwe dane.
Dotyczy to nie tylko obiektów przekazywanych do szablonu bezpośrednio, ale też tych, które autor otrzyma jako wartość
zwracaną dozwolonej funkcji, metody lub właściwości.