CVE-2026-48807: Twig: Sandbox `__toString()` policy bypass via `Traversable` in `join` and `replace` filters
Description
This is a residual bypass of CVE-2026-47732 / GHSA-pr2w-4gpj-cpq4 left after the initial fix for unguarded toString() calls. It covers two related coercion points that were not caught by the original patch.
Traversable in join and replace filters. SandboxExtension::ensureToStringAllowed() recurses into PHP arrays so that a Stringable object hidden inside an array argument cannot be string-coerced without consulting the security policy. The recursion stops at PHP arrays: a Traversable value passed at the same position is not materialised, so its contents are not policy-checked. CoreExtension::join() and CoreExtension::replace() later materialise such Traversable inputs through self::toArray() and feed them to implode() / strtr(), both of which implicitly call toString() on contained Stringable objects. The bypass also reproduces when the container implements both Stringable and Traversable: the container's own toString() is policy-checked, but the elements yielded by getIterator() are not, and the consuming filters still coerce them to string.
in and not in operators. InBinary and NotInBinary compile to CoreExtension::inFilter(), which falls through to PHP's <=> operator when comparing a string with a Stringable object. PHP coerces the object to string via toString() without the sandbox policy being consulted. Beyond the direct side effect, in can also be used as a content-leak oracle: each probe against an attacker-chosen needle leaks one bit of equality, and chained probes can reconstruct the string returned by toString() even when every method is denied. The bypass reproduces with both array and Traversable haystacks, and on both operand sides.
A sandboxed template author who is allowed to call join / replace, or to use the in / not in operators, can therefore trigger a disallowed toString() method on objects reachable from the render context, even when that method is not on SecurityPolicy::$allowedMethods. The bypass reproduces both under global sandbox mode and when sandboxing is enabled through SourcePolicyInterface.
Resolution
SandboxExtension::ensureToStringAllowed() now also recurses into Traversable operands when sandboxing is active for the current source: each value is materialised once and run through the same array-recursion path, so the policy is consulted before the filter implementation can coerce contained objects to strings. This applies to plain Traversable operands as well as to containers that implement both Stringable and Traversable: the container's own toString() is still policy-checked, and the yielded elements are additionally checked. The materialisation is guarded by isSandboxed($source) so that non-sandboxed code paths do not pay the cost or change generator-exhaustion semantics.
InBinary and NotInBinary now implement Twig\Node\CoercesChildrenToStringInterface and declare both operands as string-coerced, so SandboxNodeVisitor wraps each operand in CheckToStringNode. The policy is consulted before CoreExtension::inFilter() reaches PHP's <=> operator, matching the existing protection on the other comparison binaries (Equal, Less, Greater, Spaceship, ...).
Credits
Twig would like to thank Vincent55 Yang and Fabien Potencier for reporting the issues and Fabien Potencier for providing the fix.
Other sources
Twig is a template language for PHP. Prior to 3.27.0, the sandbox toString() checks do not fully cover Traversable values passed to join and replace filters or operands evaluated by the in and not in operators, allowing contained Stringable objects to be coerced to strings without consulting the sandbox policy. This issue is fixed in version 3.27.0.
— MITRE
Affected Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Upgrade
Upgrade
composer/twig/twigto a version that resolves this vulnerability.Fixed in 3.27.0 - Upgrade
Upgrade to a fixed release to a version that resolves this vulnerability.
Fixed in 3.27.0
Event History
Frequently Asked Questions
What is the severity of CVE-2026-48807?
The severity of CVE-2026-48807 is rated at 65.
What software is affected by CVE-2026-48807?
CVE-2026-48807 affects the Composer Twig Twig library.
What does CVE-2026-48807 address?
CVE-2026-48807 addresses a residual bypass related to unguarded __toString() calls.
How do I fix CVE-2026-48807?
To fix CVE-2026-48807, you should update the Twig library to the latest version that contains the patch.
What are the coercion points involved in CVE-2026-48807?
CVE-2026-48807 involves coercion points in the join and replace filters that were not addressed in the original patch.