{"id":"CVE-2026-47732","aliases":["GHSA-pr2w-4gpj-cpq4"],"url":"https://o3.security/vulnerability/CVE-2026-47732","summary":"Twig Sandbox: multiple `__toString()` policy bypasses via unguarded string coercion points","details":"### Description\n\n`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.\n\nConfirmed bypass vectors:\n\n- Conditional expressions (`a ? b : c`, `a ?: b`, `a ?? b`) used as the input of a string-coercing filter or as a filter/function argument.\n- The `matches` operator and the loose comparison operators (`==`, `!=`, `<`, `>`, `<=`, `>=`, `<=>`), which coerce a `Stringable` operand 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).\n- Twig tests in general (which were never policy-gated), in particular `is empty` which casts a `Stringable` value via `(string) $value` in `CoreExtension::testEmpty()`.\n- Null-coalesce expressions nested in concatenation, and the direct output of allowed functions or filters that return a `Stringable` object.\n- Arguments passed to allowed object methods, template-name expressions of template-loading tags (`include`, `extends`, `use`, ...), dynamic attribute/property names, and spread arguments from `Traversable` objects.\n- The `do` tag and the `..` range operator.\n\n### Resolution\n\nThe 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()`.\n\n### Credits\n\nTwig would like to thank Anthropic Glasswing and El Kharoubi Iosif for reporting the issues, and Fabien Potencier for providing the fixes.","published":"2026-07-14T21:12:13.071Z","modified":"2026-08-12T03:51:31.790623685Z","cvss":null,"epss":{"score":0.0036,"percentile":0.2928,"asOf":"2026-08-20"},"cisaKev":null,"exploitsKnown":0,"affectedPackages":[{"ecosystem":"Packagist","name":"twig/twig","fixedVersion":"3.26.0"}],"fix":{"url":"https://github.com/twigphp/Twig/commit/447d0b2331e01b8fc6e08119ac984e1ef50caef9","label":"twigphp/Twig@447d0b2"},"references":[{"type":"WEB","url":"https://github.com/twigphp/Twig/releases/tag/v3.26.0"},{"type":"ADVISORY","url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/47xxx/CVE-2026-47732.json"},{"type":"ADVISORY","url":"https://github.com/twigphp/Twig/security/advisories/GHSA-pr2w-4gpj-cpq4"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-47732"},{"type":"FIX","url":"https://github.com/twigphp/Twig/commit/447d0b2331e01b8fc6e08119ac984e1ef50caef9"},{"type":"WEB","url":"https://github.com/FriendsOfPHP/security-advisories/blob/master/twig/twig/CVE-2026-47732.yaml"},{"type":"PACKAGE","url":"https://github.com/twigphp/Twig"},{"type":"WEB","url":"https://symfony.com/cve-2026-47732"}],"provenance":{"sources":["OSV.dev","FIRST.org (EPSS)"],"lastVerified":"2026-08-12T03:51:31.790623685Z"}}