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.

versión: 3.x