CVE-2026-24425: Verified Repro With Script Download
CVE-2026-24425: Twig: sandbox bypass via SourcePolicy filter check enables arbitrary PHP callables
CVE-2026-24425 is verified against twig/twig · composer. Affected versions: 2.16.*, >=3.9.0, <3.26.0. Fixed in 3.26.0. This high reproduction includes runnable sandbox proof, artifacts, and a plain-text agent view under REPRO-2026-00151.
What Is CVE-2026-24425?
CVE-2026-24425 is a high-severity (CVSS 8.7 per the advisory) sandbox bypass (CWE-693) in Twig, the PHP templating engine. Per-template SourcePolicy sandboxing is never applied to the sort, filter, map, reduce, and find filters, letting attackers pass arbitrary PHP callables and achieve code execution. Pruva reproduced it (reproduction REPRO-2026-00151).
CVE-2026-24425 Severity & CVSS Score
CVE-2026-24425 is rated high severity, with a CVSS base score of 8.8 out of 10.
High — serious impact or readily exploitable. Prioritize remediation.
Affected twig/twig Versions
twig/twig · composer versions 2.16.*, >=3.9.0, <3.26.0 are affected.
How to Reproduce CVE-2026-24425
pruva-verify REPRO-2026-00151 curl -O https://pruva.dev/api/v1/reproductions/REPRO-2026-00151/artifacts/reproduction_steps.sh && chmod +x reproduction_steps.sh && ./reproduction_steps.sh Proof of Reproduction for CVE-2026-24425
Reproduced by Pruva's autonomous agents — 172 tool calls over 13 min. Full root-cause analysis and the complete transcript are below.
How the agent worked
Root Cause and Exploit Chain for CVE-2026-24425
Summary
CVE-2026-24425 is a sandbox bypass vulnerability in Twig (the PHP templating engine). When the sandbox is enabled per-template through a SourcePolicyInterface (rather than globally for every template), the runtime security check for the sort, filter, map, reduce, and find filters fails to consult the current template's source. As a result, the SourcePolicy is never applied to those filters. An attacker who can supply template content can therefore pass an arbitrary PHP callable to one of these filters (e.g., system or shell_exec) and have it executed, escaping the sandbox and achieving arbitrary code execution.
Impact
- Package/component affected:
twig/twig(Twig templating engine for PHP) - Affected versions:
3.9.0through3.25.x(and the2.16.xline) - Fixed versions:
3.26.0 - Risk level: High — CVSS 8.7
- Consequences: Remote Code Execution (RCE) via sandbox bypass. An attacker who controls template content can execute arbitrary PHP functions even when per-template sandboxing is active.
Root Cause
The root cause lies in how the runtime security check for arrow/callable arguments was implemented in src/Extension/CoreExtension.php.
In the vulnerable code (Twig 3.25.0 and earlier), filters like map, filter, sort, reduce, and find declared needs_environment => true but did not declare needs_is_sandboxed => true. Their runtime methods (e.g., map(), filter()) called self::checkArrow($env, $arrow, ...).
Inside checkArrow(), the sandbox status was determined by querying the SandboxExtension directly:
if ($env->hasExtension(SandboxExtension::class) && $env->getExtension(SandboxExtension::class)->isSandboxed()) {
throw new RuntimeError(...);
}
However, SandboxExtension::isSandboxed() only returns true when the sandbox is enabled globally (via SandboxExtension::enableSandbox()). When sandboxing is instead enabled per-template through a SourcePolicyInterface, the runtime check isSandboxed() returns false, so the checkArrow() validation is bypassed. This means a non-Closure callable (e.g., a string function name like 'shell_exec') is accepted and executed.
The fix in Twig 3.26.0 adds needs_is_sandboxed => true to the filter declarations for sort, filter, map, reduce, find, and column. This causes Twig's runtime to pass the actual sandbox state (which correctly reflects per-template sandboxing via SourcePolicy) as a boolean parameter to the filter function. The checkArrow() method now receives this boolean directly:
public static function checkArrow(bool $isSandboxed, $arrow, $thing, $type)
{
if ($arrow instanceof \Closure) {
return;
}
if ($isSandboxed) {
throw new RuntimeError(...);
}
}
Because $isSandboxed is now derived from the template's source policy at runtime, the sandbox check correctly blocks non-Closure callables even when sandboxing is enabled per-template.
Fix commit/tag: The fix is contained in the v3.26.0 release tag. Key files changed:
src/Extension/CoreExtension.php— addedneeds_is_sandboxedto affected filters and updatedcheckArrow()signature.src/Extension/SandboxExtension.php— addedensureSpreadAllowed()helper.src/NodeVisitor/SandboxNodeVisitor.php— refactored to-string wrapping logic.
Reproduction Steps
The reproduction is fully automated by repro/reproduction_steps.sh. The script performs the following:
- Installs the vulnerable build (
twig/twig:3.25.0) in a scratch directory. - Installs the fixed build (
twig/twig:3.26.0) in a separate scratch directory. - Creates a PHP test harness for each version that:
- Defines a helper function
rce_helper($cmd, $ignored = null)that wrapsshell_exec($cmd). - Configures a Twig
Environmentwith aSourcePolicyInterfacethat returnstruefor all templates (enabling per-template sandboxing). - Allows only the
mapandjoinfilters in theSecurityPolicy. - Renders the template
{{ ['id']|map('rce_helper')|join }}.
- Defines a helper function
- Executes both test harnesses and captures output.
Expected Evidence
| Build | Behavior |
|---|---|
| 3.25.0 (vulnerable) | The template renders and rce_helper('id') is executed. The output contains uid=0(root) gid=0(root) groups=0(root), proving that shell_exec('id') ran inside the sandboxed context. |
| 3.26.0 (fixed) | Twig throws Twig\Error\RuntimeError: The callable passed to the "map" filter must be a Closure in sandbox mode, blocking the non-Closure callable before it executes. |
Evidence
- Log file:
logs/reproduction.log - Runtime manifest:
repro/runtime_manifest.json
Key excerpts from logs/reproduction.log
Vulnerable (3.25.0) — callable executed despite sandbox:
=== Testing VULNERABLE version (3.25.0) ===
VULNERABLE_OUTPUT:
uid=0(root) gid=0(root) groups=0(root)
Fixed (3.26.0) — callable correctly blocked:
=== Testing FIXED version (3.26.0) ===
FIXED_EXCEPTION: Twig\Error\RuntimeError: The callable passed to the "map" filter must be a Closure in sandbox mode in "index" at line 1.
These results prove that:
- In the vulnerable version, the per-template
SourcePolicysandbox is bypassed for themapfilter, allowing arbitrary PHP function execution. - In the fixed version, the same payload is correctly rejected with a sandbox security error.
Recommendations / Next Steps
- Upgrade immediately to Twig
3.26.0or later. The fix is minimal and targeted; upgrading is low-risk. - Verify sandbox configuration — if your application uses
SourcePolicyInterface, ensure the runtime is on a patched version. - Audit template inputs — any attacker-controlled template source should be considered potentially compromised until patched.
- Regression tests — the Twig project added
testSourcePolicyBlocksNonClosureCallableInArrowintests/Extension/SandboxTest.php. Adopt similar tests in your own CI pipeline. - Alternative mitigations (if immediate upgrade is impossible):
- Disable user-supplied template rendering entirely.
- Globally enable the sandbox via
SandboxExtension::enableSandbox()— while this does not fix the bug, it may reduce exposure in some configurations.
Additional Notes
- Idempotency confirmed:
reproduction_steps.shwas executed twice consecutively and produced identical results on both runs. - Payload details: The payload
{{ ['id']|map('rce_helper')|join }}was chosen because:mapis one of the affected filters.rce_helperaccepts two arguments (Twig passes value and key to the callable), avoiding PHP 8+ fatal errors from mismatched argument counts.- The output of
shell_exec('id')is a concrete, verifiable indicator of code execution.
- Limitations: The reproduction requires a PHP environment with
shell_execenabled. Ifshell_execis disabled (e.g., viadisable_functions), an alternative function with a visible side effect can be substituted.
CVE-2026-24425 Reproduction Transcript
The agent's step-by-step process — every tool call, every handoff, the moment the exploit fired.
Full session Replay every step — scrub the timeline or play it back.
Unknown error
Artifacts and Evidence for CVE-2026-24425
Scripts, logs, diffs, and output captured during the reproduction.
No artifacts available
How to Fix CVE-2026-24425
Upgrade twig/twig · composer to 3.26.0 or later.
FAQ: CVE-2026-24425
How does the CVE-2026-24425 sandbox bypass work?
Which versions of Twig are affected by CVE-2026-24425, and where is it fixed?
How severe is CVE-2026-24425?
How can I reproduce CVE-2026-24425?
References for CVE-2026-24425
Authoritative sources for CVE-2026-24425 — official vulnerability databases and the upstream advisory. Pruva's reproduction verifies the issue firsthand; these are the primary records to corroborate it.