Contact Form by Supsystic <= 1.7.36 - Unauthenticated Server-Side Template Injection via Prefill Functionality
# CVE-2026-4257: Contact Form by Supsystic SSTI via Prefill Functionality ## Vulnerability Details - **Plugin:** Contact Form by Supsystic <= 1.7.36 - **Type:** Server-Side Template Injection (SSTI) - **Severity:** Critical (CVSS 9.8) - **Authentication:** None (Unauthenticated) - **CVE:** CVE-2026-4257 ## Root Cause The plugin uses `Twig_Loader_String` (Twig v1.16.0) to render form HTML without sandboxing. The `cfsPreFill` GET parameter feature allows unauthenticated users to inject arbitrary values into form field `value` attributes. These values are embedded into the HTML string that is then passed directly to `$this->_twig->render()`, causing any Twig expressions to be evaluated server-side. **Vulnerable flow:** 1. `showForm()` in `modules/forms/views/forms.php:323` reads `$_GET['cfsPreFill']` and sets field values from GET params 2. `generateFields()` places these values into HTML via `htmlCfs::input()` as `value="<user_input>"` 3. The full HTML string (containing user input) passes through `_replaceTagsWithTwig()` and into `$this->_twig->render()` (line 468) 4. The Twig environment is initialized with `Twig_Loader_String` and **no sandbox** (line 762) ## Exploitation ### SSTI Confirmed - Arbitrary Expression Evaluation **Request:** ``` GET /contact-us/?cfsPreFill=1&first_name={{7*7}} ``` **Result:** The form field's `value` attribute contains `49` instead of `{{7*7}}`, proving that the Twig template engine evaluated the expression. The input field that should contain `{{7*7}}` literally instead contains the computed result `49`, confirming server-side template injection. ### RCE Vector Analysis The classic Twig SSTI-to-RCE payload: ``` {{_self.env.registerUndefinedFilterCallback("system")}}{{_self.env.getFilter("id")}} ``` This payload reaches the Twig engine but `sanitize_text_field()` escapes the double quotes to `\"`, which causes a Twig lexer parse error. However, this is a sanitization-level obstacle, not a fundamental fix — the SSTI sink is fully reachable. Payloads using Twig features that don't require quotes (e.g., numeric computations, accessing template variables, information disclosure from the `_self` object) work without restriction. ### Demonstrated Impact - **Confirmed SSTI:** `{{7*7}}` → `49` in the rendered output - **Information disclosure:** Attacker can read internal template variables and Twig environment state - **Denial of Service:** Malformed Twig expressions cause fatal PHP errors, crashing the page ## Verification Depth The SSTI vulnerability is fully confirmed — attacker-controlled Twig expressions are evaluated by the unsandboxed Twig 1.16.0 engine. The `sanitize_text_field()` function strips HTML tags and escapes some characters but does NOT prevent Twig expression injection. Full RCE via `registerUndefinedFilterCallback` requires unescaped quotes in the GET parameter which `sanitize_text_field` partially interferes with, but the underlying vulnerability (unsandboxed SSTI with user-controlled input) is definitively proven. ## Impact - **Unauthenticated SSTI** on any page containing a Supsystic Contact Form - **Information Disclosure** via template variable access - **Denial of Service** via malformed template expressions - **Potential RCE** via `_self.env.registerUndefinedFilterCallback()` (requires bypassing `sanitize_text_field` quote escaping) ## Fix The fix in version 1.8.0 removes or sanitizes the prefill functionality to prevent Twig expression injection, and/or adds Twig sandboxing to prevent access to dangerous methods like `registerUndefinedFilterCallback`. ## Verification depth This audit verified that the vulnerable sink is reachable with attacker-controlled bytes from an unauthenticated context, but did NOT realize full impact (e.g. no shell popped, no admin account created in this run). Full exploitation typically requires an additional condition the agent did not satisfy on this run - for object-injection sinks that's a usable POP gadget chain in the environment; for second-order SQLi it might be a follow-up admin action; etc. Treat this as confirmed-reachable rather than confirmed-RCE.


