Sandbox

Die Sandbox bietet eine Sicherheitsschicht, die Ihnen die Kontrolle darüber gibt, welche Tags, PHP-Funktionen, Methoden usw. in Templates verwendet werden dürfen. Dank des Sandbox-Modus können Sie gefahrlos mit einem Kunden oder einem externen Coder an Templates arbeiten, ohne befürchten zu müssen, dass die Anwendung kompromittiert wird oder unerwünschte Operationen ausgeführt werden.

Wie funktioniert das? Wir legen einfach fest, was wir im Template erlauben wollen. Anfangs ist alles verboten, und wir erteilen die Berechtigungen nach und nach. Der folgende Code erlaubt dem Autor des Templates die Tags {block}, {if}, {else} und {=} (letzterer ist der Tag zum Ausgeben einer Variablen oder eines Ausdrucks) sowie alle Filter:

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

$latte->setPolicy($policy);

Ebenso können wir den Zugriff auf einzelne globale Funktionen, Methoden oder Properties von Objekten erlauben:

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

Die mit allowMethods() und allowProperties() erteilten Berechtigungen gelten auch für Instanzen von Unterklassen der angegebenen Klasse (die Prüfung verwendet is_a()).

Ist das nicht großartig? Sie haben alles auf sehr niedriger Ebene unter Kontrolle. Versucht das Template, eine nicht erlaubte Funktion aufzurufen oder auf eine nicht erlaubte Methode oder Property zuzugreifen, wirft es eine Latte\SecurityViolationException.

Sichere Standard-Policy

Eine Policy von Grund auf zu erstellen, bei der alles verboten ist, muss nicht bequem sein, deshalb können Sie von einer sicheren Ausgangsbasis starten:

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

Diese sichere Ausgangsbasis bedeutet, dass alle Standard-Tags erlaubt sind außer contentType, debugbreak, dump, extends, import, include, layout, php, sandbox, snippet, snippetArea, templatePrint, varPrint, embed. Alle Standardfilter sind erlaubt außer datastream, noescape und nocheck. Schließlich ist der Zugriff auf die Methoden und Properties des Objekts $iterator erlaubt.

Die Sandbox aktivieren

Die Regeln gelten für das Template, das wir mit dem Tag {sandbox} einbinden. Das ist gewissermaßen analog zu {include}: Es schaltet den Sandbox-Modus ein und übergibt, wie {include}, die umgebenden Variablen nicht automatisch. Sie können sie jedoch ausdrücklich übergeben, zum Beispiel {sandbox 'untrusted.latte', a: 1, b: 2}:

{sandbox 'untrusted.latte'}

Das Layout und die einzelnen Seiten können also ungehindert alle Tags und Variablen verwenden; die Einschränkungen gelten nur für das Template untrusted.latte.

Manche Verstöße, etwa die Verwendung eines verbotenen Tags oder Filters, werden zur Kompilierzeit erkannt. Andere, etwa der Aufruf nicht erlaubter Methoden eines Objekts, erst zur Laufzeit. Das Template kann außerdem beliebige andere Fehler enthalten. Damit eine Exception aus dem Sandbox-Template nicht den gesamten Rendering-Vorgang stört, können Sie einen eigenen Exception-Handler definieren, der sie zum Beispiel nur protokolliert.

Wollten wir den Sandbox-Modus gleich für alle Templates einschalten, ist das leicht:

$latte->setSandboxMode();

Prüfung des generierten Codes

Damit ein Benutzer keinen PHP-Code in die Seite einfügt, der syntaktisch korrekt, aber verboten ist und einen PHP Compile Error verursacht, empfehlen wir, die Templates vom PHP-Linter prüfen zu lassen. Diese Funktion aktivieren Sie mit der Methode Engine::enablePhpLinter(). Da sie für die Prüfung das PHP-Binary aufrufen muss, übergeben Sie dessen Pfad als Parameter:

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

Wovor die Sandbox nicht schützt

Über die Tags, Funktionen, Methoden und Properties hinaus, die Sie erlauben, verbietet die Sandbox unabhängig von der Policy einige Konstrukte grundsätzlich: den Operator new, die Variable $this, variable Variablen ($$var) und den Filter |noescape.

Die Sandbox schützt zuverlässig vor expliziten Operationen: Aufrufen von Funktionen, Methoden und Filtern sowie Zugriffen auf Properties von Objekten. Eine Sache erreichen ihre Prüfungen jedoch nicht, und davon sollten Sie wissen.

Wenn Sie ein Objekt ausgeben oder es in einem beliebigen String-Kontext verwenden, ruft PHP automatisch dessen magische Methode __toString() auf. Diese implizite Umwandlung prüft die Policy nicht, sie läuft also auch dann, wenn die Methode __toString() nicht unter den erlaubten ist. Der Autor des Templates kann damit __toString() bei jedem Objekt auslösen, das er im Template erreichen kann. Das unterscheidet sich von der expliziten Form {$obj->__toString()}, die die Sandbox blockiert:

{$obj}              {* ruft __toString() auf *}
{$obj . '!'}        {* dasselbe (Verkettung) *}
{="price: $obj"}    {* dasselbe (String-Interpolation) *}
{$obj|upper}        {* dasselbe (über einen Filter) *}

Der Zweck von __toString() ist es, eine Textdarstellung des Objekts zu erzeugen, deshalb schadet seine Erreichbarkeit meist nicht. Ein Problem entsteht erst, wenn __toString() Seiteneffekte hat (etwa in eine Datenbank schreibt oder sie abfragt) oder wenn es sensible Daten zurückgibt.

Latte könnte diese unmittelbarsten Fälle abfangen, aber nicht in allen zuverlässig. Die Umwandlung eines Objekts in einen String ist kein Methodenaufruf, sondern eine eingebaute Sprachoperation, die an vielen Stellen eines Ausdrucks auftritt. An einigen davon (zum Beispiel beim Vergleich eines Objekts mit einem String oder innerhalb einer aufgerufenen Funktion oder eines Filters) ließe sie sich nicht zuverlässig abfangen, ohne auch legitime Verwendungen zu blockieren. Deshalb müssen Sie damit rechnen, dass __toString() erreichbar ist.

Geben Sie der Sandbox keine Objekte, deren __toString() Seiteneffekte hat oder sensible Daten preisgibt. Das gilt nicht nur für Objekte, die Sie dem Template direkt übergeben, sondern auch für solche, die der Autor als Rückgabewert einer erlaubten Funktion, Methode oder Property erhält.

Version: 3.x