Sandbox

Le sandbox fournit une couche de sécurité qui vous donne le contrôle sur les balises, fonctions PHP, méthodes, etc. utilisables dans les templates. Grâce au mode sandbox, vous pouvez collaborer en toute sécurité avec un client ou un codeur externe sur la création de templates, sans craindre que l'application soit compromise ou que des opérations indésirables soient effectuées.

Comment cela fonctionne-t-il ? Nous définissons simplement ce que nous voulons autoriser dans le template. Au départ, tout est interdit et nous accordons les permissions au fur et à mesure. Le code suivant autorise l'auteur du template à utiliser les balises {block}, {if}, {else} et {=} (cette dernière sert à afficher une variable ou une expression) ainsi que tous les filtres :

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

$latte->setPolicy($policy);

Nous pouvons aussi autoriser l'accès à des fonctions globales, méthodes ou propriétés d'objets prises une à une :

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

Les permissions accordées par allowMethods() et allowProperties() valent aussi pour les instances des classes filles de la classe indiquée (la vérification utilise is_a()).

N'est-ce pas formidable ? Vous contrôlez tout à un niveau très fin. Si le template tente d'appeler une fonction non autorisée ou d'accéder à une méthode ou une propriété non autorisée, il lève une exception Latte\SecurityViolationException.

Politique par défaut sûre

Créer une politique de zéro, où tout est interdit, n'est pas forcément pratique ; vous pouvez donc partir d'une base sûre :

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

Cette base sûre signifie que toutes les balises standard sont autorisées, sauf contentType, debugbreak, dump, extends, import, include, layout, php, sandbox, snippet, snippetArea, templatePrint, varPrint, embed. Tous les filtres standard sont autorisés, sauf datastream, noescape et nocheck. Enfin, l'accès aux méthodes et propriétés de l'objet $iterator est autorisé.

Activation du sandbox

Les règles s'appliquent au template que nous insérons avec la balise {sandbox}. C'est en quelque sorte l'équivalent de {include} : la balise active le mode sandbox et, comme {include}, ne transmet pas automatiquement les variables environnantes. Vous pouvez toutefois les passer explicitement, par exemple {sandbox 'untrusted.latte', a: 1, b: 2} :

{sandbox 'untrusted.latte'}

Le layout et les différentes pages peuvent ainsi utiliser librement toutes les balises et variables ; les restrictions ne s'appliqueront qu'au template untrusted.latte.

Certaines infractions, comme l'utilisation d'une balise ou d'un filtre interdit, sont détectées à la compilation. D'autres, comme l'appel de méthodes non autorisées d'un objet, seulement à l'exécution. Le template peut aussi contenir n'importe quelle autre erreur. Pour éviter qu'une exception venue du template sandboxé ne perturbe tout le rendu, vous pouvez définir votre propre gestionnaire d'exceptions, qui se contentera par exemple de la journaliser.

Et si nous voulions activer le mode sandbox directement pour tous les templates, c'est facile :

$latte->setSandboxMode();

Vérification du code généré

Pour être sûr que l'utilisateur n'insère pas dans la page du code PHP syntaxiquement correct mais interdit, qui provoquerait une PHP Compile Error, nous recommandons de faire vérifier les templates par le linter PHP. Vous activez cette fonctionnalité avec la méthode Engine::enablePhpLinter(). Comme elle doit appeler le binaire PHP pour la vérification, passez son chemin en paramètre :

$latte = new Latte\Engine;
$latte->enablePhpLinter('/path/to/php');

Ce que le sandbox ne surveille pas

Au-delà des balises, fonctions, méthodes et propriétés que vous autorisez, le sandbox interdit inconditionnellement quelques constructions, quelle que soit la politique : l'opérateur new, la variable $this, les variables variables ($$var) et le filtre |noescape.

Le sandbox surveille de façon fiable les opérations explicites : les appels de fonctions, de méthodes et de filtres, ainsi que l'accès aux propriétés d'objets. Il y a toutefois une chose que ses contrôles n'atteignent pas, et il vaut mieux la connaître.

Lorsque vous affichez un objet ou l'utilisez dans un contexte de chaîne, PHP appelle automatiquement sa méthode magique __toString(). La politique ne vérifie pas cette conversion implicite, qui s'exécute donc même si la méthode __toString() ne fait pas partie des méthodes autorisées. L'auteur du template peut ainsi déclencher __toString() sur n'importe quel objet qu'il peut atteindre dans le template. C'est différent de la forme explicite {$obj->__toString()}, que le sandbox bloque :

{$obj}              {* appelle __toString() *}
{$obj . '!'}        {* pareil (concaténation) *}
{="price: $obj"}    {* pareil (interpolation de chaîne) *}
{$obj|upper}        {* pareil (via un filtre) *}

Le but de __toString() est de produire une représentation textuelle de l'objet, si bien que son accessibilité ne pose généralement pas de problème. La difficulté n'apparaît que si __toString() a des effets de bord (comme écrire dans une base de données ou l'interroger) ou s'il renvoie des données sensibles.

Latte pourrait intercepter les cas les plus directs, mais pas de façon fiable dans tous. La conversion d'un objet en chaîne n'est pas un appel de méthode, mais une opération intégrée au langage, qui survient à de nombreux endroits d'une expression. Dans certains d'entre eux (par exemple lors de la comparaison d'un objet avec une chaîne, ou à l'intérieur d'une fonction ou d'un filtre appelé), elle ne pourrait pas être interceptée de façon fiable sans bloquer aussi des usages légitimes. Il faut donc compter avec le fait que __toString() reste accessible.

N'exposez pas au sandbox les objets dont __toString() a des effets de bord ou révèle des données sensibles. Cela vaut non seulement pour les objets que vous passez directement au template, mais aussi pour ceux que l'auteur obtient comme valeur de retour d'une fonction, d'une méthode ou d'une propriété autorisée.

version: 3.x