Песочница
Песочница представляет собой слой безопасности, который даёт вам контроль над тем, какие теги, функции PHP, методы и так далее можно использовать в шаблонах. Благодаря режиму песочницы вы можете спокойно работать над шаблонами вместе с клиентом или внешним верстальщиком, не опасаясь компрометации приложения или выполнения нежелательных операций.
Как это работает? Мы просто определяем, что хотим в шаблоне разрешить.
Изначально запрещено всё, и мы постепенно выдаём разрешения. Следующий
код позволяет автору шаблона использовать теги {block}, {if},
{else} и {=} (последний служит для вывода переменной или выражения), а также
все фильтры:
$policy = new Latte\Sandbox\SecurityPolicy;
$policy->allowTags(['block', 'if', 'else', '=']);
$policy->allowFilters($policy::All);
$latte->setPolicy($policy);
Мы можем также разрешить доступ к отдельным глобальным функциям, методам или свойствам объектов:
$policy->allowFunctions(['trim', 'strlen']);
$policy->allowMethods(Nette\Security\User::class, ['isLoggedIn', 'isAllowed']);
$policy->allowProperties(Nette\Database\Row::class, $policy::All);
Разрешения, выданные через allowMethods() и allowProperties(),
распространяются и на экземпляры потомков указанного класса (проверка
использует is_a()).
Разве не замечательно? Вы контролируете всё на очень низком уровне.
Если шаблон попытается вызвать запрещённую функцию либо обратиться к
запрещённому методу или свойству, будет выброшено исключение
Latte\SecurityViolationException.
Безопасная политика по умолчанию
Создавать политику с нуля, когда запрещено всё, может быть неудобно, поэтому вы можете начать с безопасной основы:
$policy = Latte\Sandbox\SecurityPolicy::createSafePolicy();
Эта безопасная основа означает, что разрешены все стандартные теги,
кроме contentType, debugbreak, dump, extends, import,
include, layout, php, sandbox, snippet, snippetArea,
templatePrint, varPrint, embed. Разрешены все стандартные
фильтры, кроме datastream, noescape и nocheck. Наконец,
разрешён доступ к методам и свойствам объекта $iterator.
Активация песочницы
Правила действуют для шаблона, который мы подключаем тегом {sandbox}. Это чем-то похоже на
{include}: тег включает режим песочницы и, как и {include}, не
передаёт автоматически окружающие переменные. Впрочем, вы можете
передать их явно, например {sandbox 'untrusted.latte', a: 1, b: 2}:
{sandbox 'untrusted.latte'}
Таким образом макет и отдельные страницы могут свободно
использовать все теги и переменные; ограничения применятся только к
шаблону untrusted.latte.
Часть нарушений, например использование запрещённого тега или фильтра, обнаруживается на этапе компиляции. Другие, например вызов запрещённых методов объекта, обнаруживаются во время выполнения. Шаблон может содержать и любые другие ошибки. Чтобы исключение из шаблона в песочнице не нарушило весь процесс отрисовки, вы можете определить собственный обработчик исключений, который, например, просто запишет его в лог.
Если бы мы захотели включить режим песочницы сразу для всех шаблонов, это просто:
$latte->setSandboxMode();
Проверка сгенерированного кода
Чтобы пользователь не вставил в страницу PHP-код, который
синтаксически корректен, но запрещён и вызывает PHP Compile Error, мы
рекомендуем проверять
шаблоны линтером PHP. Включить эту возможность можно методом
Engine::enablePhpLinter(). Поскольку для проверки нужно вызвать бинарник PHP,
передайте путь к нему в параметре:
$latte = new Latte\Engine;
$latte->enablePhpLinter('/path/to/php');
Что песочница не охраняет
Помимо тегов, функций, методов и свойств, которые вы разрешаете,
песочница безусловно запрещает несколько конструкций независимо от
политики: оператор new, переменную $this, переменные
переменные ($$var) и фильтр |noescape.
Песочница надёжно охраняет явные операции: вызовы функций, методов и фильтров, а также доступ к свойствам объектов. Есть, однако, одна вещь, до которой её проверки не дотягиваются, и о ней стоит знать.
Когда вы выводите объект или используете его в любом строковом
контексте, PHP автоматически вызывает его магический метод
__toString(). Политика это неявное преобразование не проверяет,
поэтому оно выполняется, даже если метод __toString() не входит в
число разрешённых. Автор шаблона таким образом может вызвать
__toString() у любого объекта, до которого дотянется в шаблоне. Это
отличается от явной формы {$obj->__toString()}, которую песочница
блокирует:
{$obj} {* вызывает __toString() *}
{$obj . '!'} {* то же самое (конкатенация) *}
{="price: $obj"} {* то же самое (интерполяция строки) *}
{$obj|upper} {* то же самое (через фильтр) *}
Назначение __toString() в том, чтобы создать текстовое
представление объекта, поэтому его доступность обычно не вредит.
Проблема возникает только тогда, когда у __toString() есть побочные
эффекты (например, запись в базу данных или запрос к ней) или когда он
возвращает конфиденциальные данные.
Latte мог бы перехватить самые прямые случаи, но не надёжно во всех них.
Преобразование объекта в строку – это не вызов метода, а встроенная
операция языка, которая происходит во многих местах выражения. В
некоторых из них (например, при сравнении объекта со строкой или внутри
вызванной функции или фильтра) её нельзя было бы перехватить надёжно,
не заблокировав заодно и легитимное использование. Поэтому вам нужно
считаться с тем, что __toString() доступен.
Не отдавайте в песочницу объекты, у которых __toString() имеет
побочные эффекты или раскрывает конфиденциальные данные. Это
относится не только к объектам, которые вы передаёте в шаблон напрямую,
но и к тем, которые автор получает как возвращаемое значение
разрешённой функции, метода или свойства.