Overview
About vulnerability
Description
SandboxNodeVisitor enforces SecurityPolicy::checkMethodAllowed() for implicit __toString() calls by wrapping selected AST nodes in CheckToStringNode. The set of wrapped nodes is incomplete, and several Twig language constructs still trigger PHP string coercion on a Stringable operand without first consulting the policy. A sandboxed template author can therefore invoke __toString() on any object reachable in the render context, even when __toString on its class is not allowlisted.
Confirmed bypass vectors:
- Conditional expressions (
a ? b : c,a ?: b,a ?? b) used as the input of a string-coercing filter or as a filter/function argument. - The
matchesoperator and the loose comparison operators (==,!=,<,>,<=,>=,<=>), which coerce aStringableoperand to string and can be used as an oracle to recover the value byte by byte (no tag, filter or function needs to be allowlisted). - Twig tests in general (which were never policy-gated), in particular
is emptywhich casts aStringablevalue via(string) $valueinCoreExtension::testEmpty(). - Null-coalesce expressions nested in concatenation, and the direct output of allowed functions or filters that return a
Stringableobject. - Arguments passed to allowed object methods, template-name expressions of template-loading tags (
include,extends,use, …), dynamic attribute/property names, and spread arguments fromTraversableobjects. - The
dotag and the..range operator.
Resolution
The sandbox now wraps every child node that the parent will string-coerce at runtime, instead of relying on a hardcoded list of node types in SandboxNodeVisitor. A new Twig\Node\CoercesChildrenToStringInterface lets nodes declare which of their children must be guarded; core nodes (concatenation, comparison and range binaries, filter/function/test expressions, do, include, extends, use, …) implement it. Spread arguments are materialised and policy-checked via the new SandboxExtension::ensureSpreadAllowed(), and dynamic attribute names are checked at runtime inside CoreExtension::getAttribute().
Credits
Twig would like to thank Anthropic Glasswing and El Kharoubi Iosif for reporting the issues, and Fabien Potencier for providing the fixes.
Details
- Affected product:
- drupal/core , twig/twig
- Affected packages:
- twig @ v2.16.1 (+1 more)
Description
SandboxNodeVisitor enforces SecurityPolicy::checkMethodAllowed() for implicit __toString() calls by wrapping selected AST nodes in CheckToStringNode. The set of wrapped nodes is incomplete, and several Twig language constructs still trigger PHP string coercion on a Stringable operand without first consulting the policy. A sandboxed template author can therefore invoke __toString() on any object reachable in the render context, even when __toString on its class is not allowlisted.
Confirmed bypass vectors:
- Conditional expressions (
a ? b : c,a ?: b,a ?? b) used as the input of a string-coercing filter or as a filter/function argument. - The
matchesoperator and the loose comparison operators (==,!=,<,>,<=,>=,<=>), which coerce aStringableoperand to string and can be used as an oracle to recover the value byte by byte (no tag, filter or function needs to be allowlisted). - Twig tests in general (which were never policy-gated), in particular
is emptywhich casts aStringablevalue via(string) $valueinCoreExtension::testEmpty(). - Null-coalesce expressions nested in concatenation, and the direct output of allowed functions or filters that return a
Stringableobject. - Arguments passed to allowed object methods, template-name expressions of template-loading tags (
include,extends,use, …), dynamic attribute/property names, and spread arguments fromTraversableobjects. - The
dotag and the..range operator.
Resolution
The sandbox now wraps every child node that the parent will string-coerce at runtime, instead of relying on a hardcoded list of node types in SandboxNodeVisitor. A new Twig\Node\CoercesChildrenToStringInterface lets nodes declare which of their children must be guarded; core nodes (concatenation, comparison and range binaries, filter/function/test expressions, do, include, extends, use, …) implement it. Spread arguments are materialised and policy-checked via the new SandboxExtension::ensureSpreadAllowed(), and dynamic attribute names are checked at runtime inside CoreExtension::getAttribute().
Credits
Twig would like to thank Anthropic Glasswing and El Kharoubi Iosif for reporting the issues, and Fabien Potencier for providing the fixes.