mirror of
https://github.com/twigphp/Twig.git
synced 2026-08-19 06:52:26 +00:00
Fix sandbox bypass in the "column" filter under SourcePolicyInterface
This commit is contained in:
@@ -6,6 +6,7 @@
|
||||
* Restrict allowed classes in `Twig\Profiler\Profile::unserialize()` to prevent arbitrary class instantiation
|
||||
* Escape root profile name in `HtmlDumper`
|
||||
* Deprecate the `Twig\Sandbox\SourcePolicyInterface` interface with no replacement
|
||||
* Fix sandbox bypass in the "column" filter when sandboxing is enabled via `SourcePolicyInterface`
|
||||
|
||||
# 3.26.0 (2026-05-20)
|
||||
|
||||
|
||||
@@ -1960,12 +1960,15 @@ final class CoreExtension extends AbstractExtension
|
||||
}
|
||||
|
||||
if ($isSandboxed) {
|
||||
$sandbox = $env->getExtension(SandboxExtension::class);
|
||||
// The sandbox might be enabled via a SourcePolicyInterface, in which case the SandboxExtension
|
||||
// would not consider the sandbox active without the current Source: $isSandboxed is already
|
||||
// computed against the call-site source, so check the policy directly to honor that decision.
|
||||
$policy = $env->getExtension(SandboxExtension::class)->getSecurityPolicy();
|
||||
foreach ($array as $item) {
|
||||
if (\is_object($item)) {
|
||||
$sandbox->checkPropertyAllowed($item, (string) $name);
|
||||
$policy->checkPropertyAllowed($item, (string) $name);
|
||||
if (null !== $index) {
|
||||
$sandbox->checkPropertyAllowed($item, (string) $index);
|
||||
$policy->checkPropertyAllowed($item, (string) $index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1173,6 +1173,84 @@ EOF
|
||||
$this->assertSame('bar', $twig->load('index')->render($params));
|
||||
}
|
||||
|
||||
public function testSourcePolicySandboxBlocksColumnFilterOnDisallowedProperty()
|
||||
{
|
||||
$sourcePolicy = new class implements SourcePolicyInterface {
|
||||
public function enableSandbox(Source $source): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
$params = ['obj' => new ColumnObject()];
|
||||
$twig = $this->getEnvironment(false, [], ['index' => "{{ [obj]|column('bar')|first }}"], [], ['column', 'first'], [], [], [], $sourcePolicy);
|
||||
|
||||
try {
|
||||
$twig->load('index')->render($params);
|
||||
$this->fail('Sandbox should reject the "column" filter when the requested property is not in allowedProperties (SourcePolicyInterface).');
|
||||
} catch (SecurityNotAllowedPropertyError $e) {
|
||||
$this->assertSame('Twig\Tests\Extension\ColumnObject', $e->getClassName());
|
||||
$this->assertSame('bar', $e->getPropertyName());
|
||||
}
|
||||
}
|
||||
|
||||
public function testSourcePolicySandboxBlocksColumnFilterOnDisallowedIndex()
|
||||
{
|
||||
$sourcePolicy = new class implements SourcePolicyInterface {
|
||||
public function enableSandbox(Source $source): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
$params = ['obj' => new ColumnObject()];
|
||||
$twig = $this->getEnvironment(false, [], ['index' => "{{ [obj]|column('bar', 'foo')|keys|first }}"], [], ['column', 'first', 'keys'], [], ['Twig\Tests\Extension\ColumnObject' => ['bar']], [], $sourcePolicy);
|
||||
|
||||
try {
|
||||
$twig->load('index')->render($params);
|
||||
$this->fail('Sandbox should reject the "column" filter when the index argument targets a disallowed property (SourcePolicyInterface).');
|
||||
} catch (SecurityNotAllowedPropertyError $e) {
|
||||
$this->assertSame('Twig\Tests\Extension\ColumnObject', $e->getClassName());
|
||||
$this->assertSame('foo', $e->getPropertyName());
|
||||
}
|
||||
}
|
||||
|
||||
public function testSourcePolicySandboxAllowsColumnFilterOnAllowedProperty()
|
||||
{
|
||||
$sourcePolicy = new class implements SourcePolicyInterface {
|
||||
public function enableSandbox(Source $source): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
$params = ['obj' => new ColumnObject()];
|
||||
$twig = $this->getEnvironment(false, [], ['index' => "{{ [obj]|column('bar')|first }}"], [], ['column', 'first'], [], ['Twig\Tests\Extension\ColumnObject' => ['bar']], [], $sourcePolicy);
|
||||
|
||||
$this->assertSame('bar', $twig->load('index')->render($params));
|
||||
}
|
||||
|
||||
public function testSourcePolicySandboxBlocksColumnFilterOnMagicGetter()
|
||||
{
|
||||
$sourcePolicy = new class implements SourcePolicyInterface {
|
||||
public function enableSandbox(Source $source): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
$params = ['magic' => new MagicObject()];
|
||||
$twig = $this->getEnvironment(false, [], ['index' => "{{ [magic]|column('anything')|first }}"], [], ['column', 'first'], [], [], [], $sourcePolicy);
|
||||
|
||||
try {
|
||||
$twig->load('index')->render($params);
|
||||
$this->fail('Sandbox should reject the "column" filter before invoking __get on a non-allowlisted property (SourcePolicyInterface).');
|
||||
} catch (SecurityNotAllowedPropertyError $e) {
|
||||
$this->assertSame('Twig\Tests\Extension\MagicObject', $e->getClassName());
|
||||
$this->assertSame('anything', $e->getPropertyName());
|
||||
}
|
||||
}
|
||||
|
||||
protected function getEnvironment($sandboxed, $options, $templates, $tags = [], $filters = [], $methods = [], $properties = [], $functions = [], $sourcePolicy = null, bool $strict = false)
|
||||
{
|
||||
$loader = new ArrayLoader($templates);
|
||||
|
||||
Reference in New Issue
Block a user