Like a bank vault with a back door that only opens if a customer is allowed to etch their own nameplate
CVE-2026-46633 is a code-injection flaw in Twig (the default PHP template engine for Symfony) prior to version 3.26.0. The Compiler::string() method escapes double quotes, dollar signs, backslashes, NUL, and TAB — but not single quotes. When a template name from a {% use %} tag is compiled via subcompile() → string() and placed inside a PHP single-quoted literal in ModuleNode::compileConstructor(), a name containing a ' terminates the literal early. The attacker's payload becomes arbitrary PHP that executes the first time the compiled cache file loads. Critically, SecurityPolicy allows {% use %} unconditionally regardless of allowedTags, so this bypasses the Twig sandbox entirely — meaning even applications that thought they safely sandboxed user-authored templates are exposed. Affected versions: twig/twig < 3.26.0. Fixed in 3.26.0 (Debian bookworm backport: 3.5.1-1+deb12u3; trixie: 3.27.0-0+deb13u1).
The vendor rated this CVSS 9.8 CRITICAL (AV:N/AC:L/PR:N/UI:N), implying zero-click, unauthenticated, network-reachable RCE. That score is overstated for the vast majority of deployments. The chain requires the attacker to control the *template name string* passed to {% use %}, not just template *content*. In standard Symfony/Drupal/CraftCMS setups, {% use %} arguments are hardcoded by developers — user input never reaches them. The realistic attack surface is limited to two scenarios: (1) CMS or multi-tenant platforms that let authenticated users author Twig templates inside a sandbox (CraftCMS template editors, Drupal modules with Twig authoring, custom email-template builders) — here the sandbox bypass is clean and reliable, but requires at minimum an authenticated content-editor role, making the true vector PR:L not PR:N; and (2) bespoke applications that dynamically construct {% use %} names from request parameters, which is architecturally unusual. The vendor score treats the worst theoretical case as the baseline. Reality narrows this to a HIGH-severity sandbox escape that matters most for CMS operators.
4 steps from start to impact.
Attain template-name influence
{% use %} tag. In the sandbox-bypass scenario, this means the attacker has authenticated access to a CMS template editor (CraftCMS, Drupal Twig modules, custom newsletter builders) that lets them write raw Twig source. In the rarer unauthenticated scenario, the application must dynamically construct the {% use %} name from request parameters — e.g. {% use request.get('layout') %}.- Authenticated CMS access with template-editing privileges OR application passes untrusted input as a
{% use %}template name
- Standard Symfony apps hardcode
{% use %}names in version-controlled.html.twigfiles — no user input path exists - CMS platforms gate template editing behind admin or editor roles, requiring credential compromise first
- The
{% use %}tag is far less common than{% include %}or{% extends %}— many codebases have zero{% use %}instances
Craft a single-quote breakout payload
{% use "payload'.system('id').'" %} (or similar). The single quote inside the name breaks out of the PHP single-quoted literal that ModuleNode::compileConstructor() generates, injecting an arbitrary PHP expression. The SecurityPolicy check does not inspect {% use %} arguments, so this is allowed even when the sandbox is enabled.- Knowledge of the Twig compiler's string-escaping gap (public since GHSA-7p85-w9px-jpjp disclosure on 2026-05-20)
- No public weaponized PoC exists yet — the attacker must independently craft the breakout payload from the advisory description
- Applications with a WAF inspecting template content may block raw single-quote sequences in POST bodies
{% use) can flag attempts. No Nuclei or Sigma template observed as of 2026-10-22.Trigger template compilation
- The Twig environment has caching enabled (default in production)
- The template is reachable via a route or included by another template
- If file-system caching is disabled (rare in production, common in dev), compiled output goes to
eval()which is still exploitable but may triggerdisable_functionsrestrictions - Some deployments use read-only container filesystems that prevent cache writes, though Twig typically falls back to in-memory compilation
Achieve remote code execution
require or include, the injected expression executes with the privileges of the PHP-FPM worker or web server user (typically www-data). The attacker now has RCE on the application server, can read environment variables (database credentials, API keys), pivot to connected datastores, or establish persistence.- PHP runtime does not have aggressive
disable_functionsoropen_basedirrestrictions that prevent the injected function from executing
- Modern container deployments often run PHP with restricted
open_basediranddisable_functionslists that blocksystem(),exec(),passthru()— the attacker must find an unrestricted function - Hardened PHP configurations (Suhosin, Snuffleupagus) can block dynamic code execution patterns
sh, curl, wget). RASP solutions like Sqreen or Datadog ASM can detect code-injection at runtime.composer require twig/twig:^3.26.0 and test. For Debian, apply DSA-6320-1 (bookworm) or DSA-6311-1 (trixie). Under the noisgate mitigation SLA for HIGH, deploy the compensating control within 30 days; under the noisgate remediation SLA, complete full patching within 180 days. Given the straightforward Composer update path, most teams should be able to patch well within 30 days.{% use %} tags where the template name is derived from user input, request parameters, or database values. Replace dynamic names with a whitelist of allowed template names. This eliminates the primary exploitation prerequisite even without patching. Deploy within 30 days per the noisgate mitigation SLA for HIGH.php.ini to disable dangerous functions: disable_functions = system,exec,passthru,shell_exec,popen,proc_open,pcntl_exec. Set open_basedir to the application root. This does not prevent the code injection but severely limits what the injected code can do, converting RCE to a constrained information-disclosure or DoS scenario. Deploy within 30 days.{% use followed by unescaped single quotes. This is a fragile defense (bypassable with encoding tricks) but raises the bar for opportunistic exploitation. Deploy within 7 days as an interim measure.- Twig sandbox (
SecurityPolicy) — the vulnerability exists precisely because{% use %}is allowed unconditionally by the sandbox. ConfiguringallowedTagsdoes not help;{% use %}is not subject to tag filtering. - CSP headers — Content Security Policy operates at the browser layer and has no effect on server-side PHP code injection.
- Network segmentation alone — while limiting lateral movement post-exploitation, it does nothing to prevent the initial code injection, which occurs within the PHP process on the application server itself.
- Upgrading only Symfony — the vulnerability is in
twig/twig, not the Symfony framework package. Updatingsymfony/symfonywithout updatingtwig/twigleaves the flaw in place.
The supporting signals.
| In-the-Wild Exploitation | None observed. Not listed in CISA KEV. No GreyNoise tags or Shadowserver scanning campaigns detected as of 2026-10-22. No threat actor campaigns reported. |
|---|---|
| Proof-of-Concept | No public PoC. pocindex.io returns no results. No GitHub repos named CVE-2026-46633. Feedly noted a possible Gist reference in social media but no confirmed weaponized exploit. The advisory description provides enough detail for a skilled attacker to reconstruct the payload independently. |
| EPSS | 0.00688 (0.69%) — 59th percentile. Indicates low predicted exploitation probability within 30 days. Consistent with the high friction required for real-world exploitation. |
| CISA KEV | Not listed as of 2026-10-22. |
| CVSS Vector | Vendor: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H = 9.8. GHSA scored CVSS 4.0 at 9.3. The PR:N and AC:L assumptions are overstated — realistic exploitation requires either authenticated CMS access (PR:L) or an unusual application architecture (AC:H). A more accurate CVSS 3.1 vector for the sandbox-bypass scenario: AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H = 8.8. |
| Affected Versions | twig/twig < 3.26.0 — all 3.x releases prior to the fix. Twig 2.x reached EOL in 2023 and likely contains the same flaw but will not receive a patch. |
| Fixed Versions | twig/twig 3.26.0+ (upstream). Debian bookworm: 3.5.1-1+deb12u3 (DSA-6320-1). Debian trixie: 3.27.0-0+deb13u1 (DSA-6311-1). Debian sid: 3.26.0-1. |
| Installed Base | 501M+ Composer installs, 7,280 dependent packages on Packagist. Twig is the default template engine for Symfony (23% of PHP framework market) and used by Drupal 8+, CraftCMS, Matomo, phpBB, OroCRM, and others. |
| Disclosure & Credits | Disclosed 2026-05-20 (GHSA), 2026-07-14 (NVD publish). Reported by Anvil Secure in collaboration with Claude and Anthropic Research. Coordinated disclosure through Symfony security process. |
| Scanner Coverage | Nessus plugins 316066 and 318004 (2026-05-22). GitHub Dependabot alerts on GHSA-7p85-w9px-jpjp. No Nuclei template or Sigma rule observed. Composer audit command flags the advisory. |
Sources.
- GitHub Advisory GHSA-7p85-w9px-jpjp
- Symfony Security Blog — CVE-2026-46633
- Twig 3.26.0 Release Notes
- Debian Security Tracker — CVE-2026-46633
- Feedly CVE Intelligence — CVE-2026-46633
- Packagist — twig/twig
- SentinelOne Vulnerability Database — CVE-2026-46633
- CraftCMS Prior Sandbox Bypass — GHSA-vfcw-xv8p-8rj2
Why this verdict
- Template-name control is the critical gate. The entire chain requires the attacker to inject a payload into the *name argument* of a
{% use %}tag — not the template body. In standard Symfony/Drupal deployments,{% use %}arguments are hardcoded in developer-authored.html.twigfiles under version control. User input never reaches this code path. This single prerequisite eliminates the vast majority of the 500M+ installs from the exploitable population. - Sandbox bypass is the realistic scenario, but requires authentication. The most plausible attack vector is a CMS or multi-tenant platform (CraftCMS, Drupal with Twig authoring modules, custom email-template editors) where authenticated users can author templates. The sandbox bypass via
{% use %}is clean and reliable here, but the attacker needs at minimum content-editor credentials — making the true CVSS PR:L, not PR:N. This drops the realistic CVSS from 9.8 to ~8.8 before other friction. - No exploitation evidence compounds the downward pressure. Zero public PoCs, zero KEV listing, zero GreyNoise/Shadowserver scanning activity, and an EPSS of 0.69% (59th percentile) all indicate this is not being weaponized. The advisory has been public for five months with no confirmed exploitation.
- Role multiplier: Web application template engine. Twig is not a canonically high-value-role component (not an IdP, hypervisor, PAM, backup, kernel agent, or network edge appliance). It runs inside PHP web applications. When exploited, the blast radius is *host-level* — the attacker gains the PHP-FPM worker's privileges (typically
www-data), with lateral movement potential to connected databases and internal networks. In CMS deployments hosting PII or e-commerce data, this is serious but bounded to the application tier. The blast radius does not inherently reach domain/fleet/supply-chain scale without additional pivoting. Estimated fraction of Twig installs in sandbox/CMS template-authoring scenarios: ~10-15% of production deployments. This exceeds the 1% floor threshold for HIGH but does not reach the canonical high-value-role threshold for a CRITICAL floor. - AC:L is defensible only for the sandbox-bypass sub-population. For the ~85-90% of Twig deployments where template names are hardcoded, the vulnerability is effectively unexploitable (AC:H at best, IGNORE in practice). The HIGH verdict reflects the weighted reality: a meaningful subset of deployments faces a reliable sandbox escape to RCE, but the supermajority is unaffected.
Why not higher?
CRITICAL would require either (a) a broadly reachable unauthenticated RCE affecting most deployments or (b) the affected component being canonically in a high-value role. Neither condition holds. The unauthenticated scenario (dynamic {% use %} names from request parameters) is architecturally abnormal — no major framework or CMS constructs {% use %} this way by default. The sandbox-bypass scenario requires authentication. With no PoC, no exploitation, and 0.69% EPSS after five months of disclosure, elevating to CRITICAL overstates the operational risk for most Twig consumers.
Why not lower?
MEDIUM would understate the risk for the non-trivial population of CMS and multi-tenant platforms that use Twig's sandbox. CraftCMS has an established history of Twig sandbox bypass CVEs being exploited (GHSA-vfcw-xv8p-8rj2), demonstrating that this attack surface is actively researched and targeted. The sandbox bypass is clean — {% use %} is unconditionally allowed by SecurityPolicy — requiring no complex gadget chains. Any CMS operator who relies on Twig sandboxing for security isolation faces a direct escalation to RCE. Twig's 500M+ install base and 7,280 dependents mean even a 10% exposure rate represents tens of thousands of vulnerable applications.
Crowdsourced verification payload.
Run this script on any host where a PHP application with Composer is deployed. Execute from the application's root directory (where composer.json lives). Requires read access to composer.lock. Example: bash check_cve_2026_46633.sh /var/www/myapp
#!/usr/bin/env bash
# check_cve_2026_46633.sh — Detect CVE-2026-46633 (Twig < 3.26.0)
# Usage: bash check_cve_2026_46633.sh [/path/to/app]
# Exit codes: 0 = PATCHED, 1 = VULNERABLE, 2 = UNKNOWN
set -euo pipefail
APP_DIR="${1:-.}"
LOCKFILE="${APP_DIR}/composer.lock"
if [ ! -f "$LOCKFILE" ]; then
echo "UNKNOWN — composer.lock not found at ${LOCKFILE}"
exit 2
fi
# Extract twig/twig version from composer.lock
TWIG_VERSION=$(python3 -c "
import json, sys
with open('${LOCKFILE}') as f:
data = json.load(f)
for pkg in data.get('packages', []) + data.get('packages-dev', []):
if pkg.get('name') == 'twig/twig':
print(pkg.get('version', '').lstrip('v'))
sys.exit(0)
print('')
" 2>/dev/null)
if [ -z "$TWIG_VERSION" ]; then
echo "UNKNOWN — twig/twig not found in ${LOCKFILE}"
exit 2
fi
echo "Detected twig/twig version: ${TWIG_VERSION}"
# Compare versions: vulnerable if < 3.26.0
VULN=$(python3 -c "
from packaging.version import Version
try:
v = Version('${TWIG_VERSION}')
print('VULNERABLE' if v < Version('3.26.0') else 'PATCHED')
except Exception:
print('UNKNOWN')
" 2>/dev/null || echo "UNKNOWN")
if [ "$VULN" = "VULNERABLE" ]; then
echo "VULNERABLE — twig/twig ${TWIG_VERSION} is affected by CVE-2026-46633"
echo " Fix: composer require twig/twig:^3.26.0"
exit 1
elif [ "$VULN" = "PATCHED" ]; then
echo "PATCHED — twig/twig ${TWIG_VERSION} is not affected"
exit 0
else
echo "UNKNOWN — could not parse version '${TWIG_VERSION}'"
exit 2
fi