Skip to content

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.

REPRO-2026-00151 twig/twig · composer May 22, 2026 CVE entry ↗ .txt
Severity
HIGH
CVSS
8.8
Reproduced in
13m 7s
Tool calls
172
Spend
$1.45
01 · Overview

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).

02 · Severity & CVSS

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 threat level
8.8 / 10 CVSS base
Weakness CWE-693 (Protection Mechanism Failure)

High — serious impact or readily exploitable. Prioritize remediation.

03 · Affected Versions

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
or curl -O https://pruva.dev/api/v1/reproductions/REPRO-2026-00151/artifacts/reproduction_steps.sh && chmod +x reproduction_steps.sh && ./reproduction_steps.sh
Run in a VM or disposable container. This exploits a real vulnerability.
06 · Proof of Reproduction

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 620 events · 172 tool calls · 13 min
13 minDuration
172Tool calls
147Reasoning steps
620Events
1Dead-ends
Agent activity over 13 min
Support
37
Repro
199
Variant
379
0:0013:07

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.0 through 3.25.x (and the 2.16.x line)
  • 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 — added needs_is_sandboxed to affected filters and updated checkArrow() signature.
  • src/Extension/SandboxExtension.php — added ensureSpreadAllowed() 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:

  1. Installs the vulnerable build (twig/twig:3.25.0) in a scratch directory.
  2. Installs the fixed build (twig/twig:3.26.0) in a separate scratch directory.
  3. Creates a PHP test harness for each version that:
    • Defines a helper function rce_helper($cmd, $ignored = null) that wraps shell_exec($cmd).
    • Configures a Twig Environment with a SourcePolicyInterface that returns true for all templates (enabling per-template sandboxing).
    • Allows only the map and join filters in the SecurityPolicy.
    • Renders the template {{ ['id']|map('rce_helper')|join }}.
  4. 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:

  1. In the vulnerable version, the per-template SourcePolicy sandbox is bypassed for the map filter, allowing arbitrary PHP function execution.
  2. In the fixed version, the same payload is correctly rejected with a sandbox security error.

Recommendations / Next Steps

  1. Upgrade immediately to Twig 3.26.0 or later. The fix is minimal and targeted; upgrading is low-risk.
  2. Verify sandbox configuration — if your application uses SourcePolicyInterface, ensure the runtime is on a patched version.
  3. Audit template inputs — any attacker-controlled template source should be considered potentially compromised until patched.
  4. Regression tests — the Twig project added testSourcePolicyBlocksNonClosureCallableInArrow in tests/Extension/SandboxTest.php. Adopt similar tests in your own CI pipeline.
  5. 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.sh was executed twice consecutively and produced identical results on both runs.
  • Payload details: The payload {{ ['id']|map('rce_helper')|join }} was chosen because:
    • map is one of the affected filters.
    • rce_helper accepts 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_exec enabled. If shell_exec is disabled (e.g., via disable_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.

Event 1/40
0:000:54
0:00
session startedaccounts/fireworks/models/kimi-k2p6 · cve-2026-24425 · cve-2026
0:02
0:04
0:07
0:09
web search
0:11
0:11
web search
0:13
0:14
0:16
0:19
web search
0:21
0:22
web search
0:44
0:44
extract_facts
no facts extracted
0:44
error

Unknown error

0:52
0:52
extract_facts
no facts extracted
0:53
0:53
0:53
supportrepro
0:54

Artifacts and Evidence for CVE-2026-24425

Scripts, logs, diffs, and output captured during the reproduction.

No artifacts available

08 · How to Fix

How to Fix CVE-2026-24425

Upgrade twig/twig · composer to 3.26.0 or later.

Coming soon

Step-by-step mitigation and hardening guidance for CVE-2026-24425 — configuration checks, workarounds where no patch exists, and how to verify you're protected — is on the way.

10 · FAQ

FAQ: CVE-2026-24425

How does the CVE-2026-24425 sandbox bypass work?

An attacker who can supply template content passes an arbitrary PHP callable (for example system) to one of the unprotected filters. Because the SourcePolicy check is skipped for those filters, Twig invokes the callable directly, escaping the sandbox and executing the attacker's PHP function.

Which versions of Twig are affected by CVE-2026-24425, and where is it fixed?

Twig 2.16.x and 3.9.0 through before 3.26.0 are affected. It is fixed in 3.26.0.

How severe is CVE-2026-24425?

High severity, CVSS 8.7 per the advisory — remote code execution via sandbox bypass, specifically when per-template sandboxing (SourcePolicyInterface) is in use rather than a global sandbox.

How can I reproduce CVE-2026-24425?

Download the verified script from this page and run it in an isolated environment against Twig before 3.26.0 with a per-template SourcePolicy sandbox enabled. Render a template that passes a PHP callable like system to the sort, filter, map, or reduce filter and confirm it executes; confirm 3.26.0 blocks it.
11 · References

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.