Sandbox
El sandbox proporciona una capa de seguridad que le da control sobre qué etiquetas, funciones de PHP, métodos, etc., pueden usarse en las plantillas. Gracias al modo sandbox puede colaborar de forma segura con un cliente o un codificador externo en la creación de plantillas, sin temer que comprometan la aplicación o realicen operaciones no deseadas.
¿Cómo funciona? Simplemente definimos qué queremos permitir en la plantilla. Al principio todo está prohibido y vamos
concediendo permisos poco a poco. El siguiente código permite al autor de la plantilla usar las etiquetas {block},
{if}, {else} y {=} (esta última es la etiqueta para imprimir una variable o expresión) y todos los filtros:
$policy = new Latte\Sandbox\SecurityPolicy;
$policy->allowTags(['block', 'if', 'else', '=']);
$policy->allowFilters($policy::All);
$latte->setPolicy($policy);
También podemos permitir el acceso a funciones globales, métodos o propiedades de objetos concretos:
$policy->allowFunctions(['trim', 'strlen']);
$policy->allowMethods(Nette\Security\User::class, ['isLoggedIn', 'isAllowed']);
$policy->allowProperties(Nette\Database\Row::class, $policy::All);
Los permisos concedidos con allowMethods() y allowProperties() valen también para instancias de
subclases de la clase indicada (la comprobación usa is_a()).
¿No es fantástico? Puede controlarlo todo a muy bajo nivel. Si la plantilla intenta llamar a una función no permitida
o acceder a un método o propiedad no permitidos, lanza Latte\SecurityViolationException.
Política predeterminada segura
Crear una política desde cero, con todo prohibido, puede no resultar cómodo, así que puede partir de una base segura:
$policy = Latte\Sandbox\SecurityPolicy::createSafePolicy();
Esta base segura significa que están permitidas todas las etiquetas estándar salvo contentType,
debugbreak, dump, extends, import, include, layout,
php, sandbox, snippet, snippetArea, templatePrint,
varPrint, embed. Están permitidos todos los filtros estándar salvo datastream,
noescape y nocheck. Por último, se permite el acceso a los métodos y propiedades del objeto
$iterator.
Activar el sandbox
Las reglas se aplican a la plantilla que insertamos con la etiqueta {sandbox}. Esto es en cierto modo análogo a
{include}: activa el modo sandbox y, como {include}, no pasa automáticamente las variables del entorno.
Puede pasarlas explícitamente, por ejemplo {sandbox 'untrusted.latte', a: 1, b: 2}:
{sandbox 'untrusted.latte'}
Así, el layout y las páginas concretas pueden usar libremente todas las etiquetas y variables; las restricciones solo se
aplicarán a la plantilla untrusted.latte.
Algunas infracciones, como usar una etiqueta o un filtro prohibidos, se detectan en tiempo de compilación. Otras, como llamar a métodos no permitidos de un objeto, se detectan en tiempo de ejecución. La plantilla puede contener además cualquier otro error. Para evitar que una excepción de la plantilla en sandbox interrumpa todo el proceso de renderizado, puede definir su propio manejador de excepciones, que por ejemplo se limite a registrarla.
Si quisiéramos activar el modo sandbox directamente para todas las plantillas, es fácil:
$latte->setSandboxMode();
Comprobar el código generado
Para asegurarse de que un usuario no inserte en la página código PHP sintácticamente correcto pero prohibido, que provoque
un PHP Compile Error, recomendamos hacer que las plantillas las revise el linter de PHP. Puede
activar esta funcionalidad con el método Engine::enablePhpLinter(). Como necesita llamar al binario de PHP para la
comprobación, pase su ruta como parámetro:
$latte = new Latte\Engine;
$latte->enablePhpLinter('/path/to/php');
Lo que el sandbox no vigila
Más allá de las etiquetas, funciones, métodos y propiedades que usted permita, el sandbox prohíbe incondicionalmente varias
construcciones, sea cual sea la política: el operador new, la variable $this, las variables variables
($$var) y el filtro |noescape.
El sandbox vigila de forma fiable las operaciones explícitas: las llamadas a funciones, métodos y filtros, así como el acceso a las propiedades de los objetos. Hay una cosa, sin embargo, a la que sus comprobaciones no llegan, y conviene conocerla.
Cuando imprime un objeto o lo usa en cualquier contexto de cadena, PHP llama automáticamente a su método mágico
__toString(). La política no comprueba esta conversión implícita, de modo que se ejecuta aunque el método
__toString() no esté entre los permitidos. El autor de la plantilla puede así disparar __toString() en
cualquier objeto al que llegue desde la plantilla. Esto difiere de la forma explícita {$obj->__toString()}, que
el sandbox sí bloquea:
{$obj} {* llama a __toString() *}
{$obj . '!'} {* lo mismo (concatenación) *}
{="price: $obj"} {* lo mismo (interpolación de cadenas) *}
{$obj|upper} {* lo mismo (a través de un filtro) *}
La finalidad de __toString() es producir una representación textual del objeto, así que su accesibilidad no
suele causar daño. El problema surge solo cuando __toString() tiene efectos secundarios (como escribir en la base de
datos o consultarla) o cuando devuelve datos sensibles.
Latte podría atrapar los casos más directos, pero no de forma fiable en todos ellos. Convertir un objeto en cadena no es una
llamada a un método, sino una operación integrada del lenguaje que ocurre en muchos puntos de una expresión. En algunos de
ellos (por ejemplo, al comparar un objeto con una cadena, o dentro de una función o un filtro llamados) no se podría
interceptar de forma fiable sin bloquear también usos legítimos. Por eso hay que contar con que __toString() es
accesible.
No exponga al sandbox objetos cuyo __toString() tenga efectos secundarios o revele datos sensibles. Esto
vale no solo para los objetos que pasa directamente a la plantilla, sino también para los que el autor obtiene como valor de
retorno de una función, un método o una propiedad permitidos.