Jun 5, 2026·6 min read·96 visits
A dynamic sandbox bypass in Twig (CVE-2026-24425) allows remote code execution because runtime checks fail to propagate the active template's source context to filters like map or sort, allowing arbitrary PHP callables to run.
CVE-2026-24425 is a high-severity sandbox bypass vulnerability in the Twig template engine (affecting versions 2.16.x and 3.9.0 through 3.25.x). The flaw arises when dynamic sandboxing is enabled via a SourcePolicyInterface. During runtime checks of callback-accepting filters (such as map, sort, filter, and reduce), Twig queries the global sandbox status instead of the active template's context. This mismatch allows template authors to pass arbitrary PHP string callables to executive functions, resulting in unauthenticated or low-privilege Remote Code Execution (RCE) on the host.
The Twig template engine, widely used within the Symfony framework and modern PHP ecosystems, includes an optional sandbox mode designed to compile and execute untrusted templates securely. Many production deployments configure dynamic sandboxing using a SourcePolicyInterface. This interface evaluates whether a template must be sandboxed on a per-template basis, separating trusted system layouts from untrusted user-authored templates.
The attack surface is found in multi-tenant web applications, Content Management Systems (CMS), and software-as-a-service (SaaS) environments where untrusted users can submit custom template layouts. Because dynamic templates are marked sandboxed via the policy rather than a global switch, the execution engine must trace and apply isolation policies dynamically across all active contexts.
The vulnerability, identified as CVE-2026-24425, is a protection mechanism failure (CWE-693). When a sandbox is evaluated dynamically, the runtime validation engine fails to propagate the template's source context to specific array filter callback validations. This failure allows standard template authors to execute arbitrary PHP functions outside the sandbox environment.
To prevent remote code execution, Twig strictly limits the callback parameters allowed inside native template filters such as sort, filter, map, reduce, and find. Inside a sandbox environment, these filters must only accept PHP Closure objects (anonymous functions and short arrows) rather than arbitrary string callables. This prevention mechanism is governed by the checkArrow routine within the CoreExtension module.
public static function checkArrow(Environment $env, $arrow, $thing, $type)
{
if ($arrow instanceof \Closure) {
return;
}
if ($env->hasExtension(SandboxExtension::class) && $env->getExtension(SandboxExtension::class)->isSandboxed()) {
throw new RuntimeError(\sprintf('The callable passed to the "%s" %s must be a Closure in sandbox mode.', $thing, $type));
}
}The vulnerability exists because isSandboxed() is invoked without arguments. Calling isSandboxed() on the SandboxExtension without passing a specific template Source object queries the global sandbox state, which returns false under dynamic policy-based sandboxing.
Consequently, the validation check passes silently because the execution environment believes no sandbox is active. The engine then passes the unrestricted string callable directly to the underlying PHP runtime functions (such as uasort or array_map), leading to raw system execution.
The patch addresses the context propagation failure by modifying how the compiler and the extension filters access sandbox state. Filters now request the sandboxed state explicitly by utilizing the needs_is_sandboxed attribute.
In the patched version of src/Extension/CoreExtension.php, the core callback-accepting filters are updated to receive the template's active sandbox status as a boolean argument.
@@ -263,14 +263,14 @@ public function getFilters(): array
new TwigFilter('join', [self::class, 'join']),
new TwigFilter('split', [self::class, 'split'], ['needs_charset' => true]),
- new TwigFilter('sort', [self::class, 'sort'], ['needs_environment' => true]),
+ new TwigFilter('sort', [self::class, 'sort'], ['needs_environment' => true, 'needs_is_sandboxed' => true]),
new TwigFilter('merge', [self::class, 'merge']),
new TwigFilter('batch', [self::class, 'batch']),
new TwigFilter('column', [self::class, 'column']),
- new TwigFilter('filter', [self::class, 'filter'], ['needs_environment' => true]),
- new TwigFilter('map', [self::class, 'map'], ['needs_environment' => true]),
- new TwigFilter('reduce', [self::class, 'reduce'], ['needs_environment' => true]),
- new TwigFilter('find', [self::class, 'find'], ['needs_environment' => true]),
+ new TwigFilter('filter', [self::class, 'filter'], ['needs_environment' => true, 'needs_is_sandboxed' => true]),
+ new TwigFilter('map', [self::class, 'map'], ['needs_environment' => true, 'needs_is_sandboxed' => true]),
+ new TwigFilter('reduce', [self::class, 'reduce'], ['needs_environment' => true, 'needs_is_sandboxed' => true]),
+ new TwigFilter('find', [self::class, 'find'], ['needs_environment' => true, 'needs_is_sandboxed' => true]),The internal helper function signature drops its dependency on the Environment object, checking the newly supplied boolean directly.
@@ -2091,13 +2091,13 @@ public static function arrayEvery(Environment $env, $array, $arrow)
- public static function checkArrow(Environment $env, $arrow, $thing, $type)
+ public static function checkArrow(bool $isSandboxed, $arrow, $thing, $type)
{
if ($arrow instanceof \Closure) {
return;
}
- if ($env->hasExtension(SandboxExtension::class) && $env->getExtension(SandboxExtension::class)->isSandboxed()) {
+ if ($isSandboxed) {
throw new RuntimeError(\sprintf('The callable passed to the "%s" %s must be a Closure in sandbox mode.', $thing, $type));
}Additionally, legacy compiler pathways recover the calling template's context dynamically by walking the PHP stack trace. The twig_resolve_caller_source routine inspects the trace to ensure the sandbox configuration is robustly recovered and applied.
To exploit this vulnerability, an attacker must have privileges to author, upload, or modify a Twig template rendered under a dynamic SourcePolicyInterface sandbox configuration.
The attacker crafts a template payload containing a callback-accepting filter, such as map, sort, filter, or reduce. Instead of providing a secure anonymous function, the attacker passes a sensitive system callable string (e.g., 'system', 'passthru', 'shell_exec').
{# Attack vector using the map filter to invoke system execution #}
{{ ["id"]|map("system")|join }}During rendering, the compiler processes this block into PHP. Since the sandbox check is bypassed, the application maps the array containing the command argument ("id") directly to the PHP system() command. The host server executes the command and injects the output of the process into the rendered template context returned to the attacker.
The impact of CVE-2026-24425 is rated high (CVSS 8.8) because it leads directly to Remote Code Execution on the application server. Any host that accepts dynamically sandboxed templates from external users can be completely compromised by an attacker with standard template authoring permissions.
Successful execution grants shell access under the privileges of the web application user (e.g., www-data). Attackers can perform local file read operations, access database configuration strings, write web shells to public directories, or attempt local privilege escalation within containerized hosts.
Because the vulnerability bypasses Twig's sandboxing architecture, it undermines the security assumption of template isolation. Applications that rely on dynamic templates for multi-tenant customer dashboards or dynamic newsletter designs are highly susceptible to data leakage and host compromise.
The primary remediation strategy is upgrading the twig/twig dependency to a secure version. Organizations should audit their dependencies and apply the patch as soon as possible.
composer update twig/twigIf upgrading immediately is not feasible, developers should replace the dynamic SourcePolicyInterface dynamic sandbox architecture with a globally enabled sandbox. Global sandboxing is unaffected by this flaw because the global check evaluates to true, triggering the exception block on non-closure callbacks.
// Global configuration workaround
$policy = new \Twig\Sandbox\SecurityPolicy($allowedTags, $allowedFilters, $allowedMethods, $allowedProperties, $functions);
$sandbox = new \Twig\Extension\SandboxExtension($policy, true); // True enforces global isolation
$twig->addExtension($sandbox);Furthermore, web application firewalls (WAF) can be configured to filter template input blocks containing array filters combined with string constants. Implement standard security scanning to flag Twig templates containing patterns such as |map(, |sort(, or |filter( followed directly by a single or double-quoted string literal.
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
Twig twigphp | >= 2.16.0, <= 2.16.1 | N/A |
Twig twigphp | >= 3.9.0, <= 3.25.1 | 3.26.0 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-693 |
| Attack Vector | Network |
| CVSS v3.1 | 8.8 |
| EPSS Score | 0.00114 |
| Exploit Status | Proof-of-Concept |
| CISA KEV Status | No |
The product does not use or incorrectly implements a protection mechanism, allowing attackers to bypass security restrictions.
SiYuan before version 3.7.4 contains an authentication bypass vulnerability within its graph visualization API endpoints, allowing unauthenticated remote attackers to extract sensitive node metadata and content from password-protected documents.
SiYuan Note versions prior to v3.7.4 contain an information disclosure vulnerability in the `/api/asset/resolveAssetPath` endpoint. This endpoint returns absolute backend filesystem paths unmodified to CheckAuth-only requests. Low-privileged users or unauthenticated readers under publish mode can exploit this to leak the local directory layout, operating system username, and overall host deployment structure.
An access control vulnerability in the SiYuan personal knowledge management platform before version v3.7.4 exposes notebook encryption parameters to unauthenticated remote attackers. When the platform is configured in Publish Mode, specific API endpoints fail to enforce authorization checks. This access failure leaks key-derivation materials, password verifiers, and wrapped database keys to anonymous network clients.
A security vulnerability in the SiYuan local-first personal knowledge management system allows unauthenticated remote attackers to bypass logical boundary controls in publish (read-only) mode. By interacting with endpoints that lack proper publish-access validation, an attacker can disclose the application's internal database schemas and harvest block IDs across both public and private notebooks. This metadata leakage compromises the confidentiality of restricted documents and provides foundational information for targeted extraction.
An information disclosure vulnerability exists in the SiYuan personal knowledge management system versions prior to v3.7.4. The application fails to enforce publish-access filters on block attribute retrieval endpoints. Consequently, unauthenticated remote attackers can bypass document-level protection rules (such as password protection or disabled-publish flags) to retrieve sensitive block-level attributes, including aliases, memos, block names, and custom metadata fields, by querying the API using guessed or known block IDs.
An authentication bypass vulnerability (classified as CWE-288) exists in the publish-mode component of SiYuan, a Go-based note-taking application. This security flaw allows unauthenticated remote attackers to bypass password-protected note boundaries by leveraging auxiliary block endpoints that fail to enforce document access checks. Attackers can exploit this issue by first harvesting document metadata via a public search endpoint and subsequently fetching full rendered document contents using vulnerable block endpoints. This technical analysis explores the root cause, exploitation methodology, and remediation path.