Affected versions
Twig versions <=3.25.0 are affected by this security issue.
The issue has been fixed in Twig 3.26.0.
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
We would like to thank Anthropic Glasswing and El Kharoubi Iosif for reporting the issues, and Fabien Potencier for providing the fixes.