Server-side template injection (SSTI) happens when user input gets concatenated directly into a template string before that template is rendered by the server, letting an attacker break out of the intended data context and execute template-engine syntax as code. Unlike cross-site scripting, which runs in a victim's browser, SSTI runs on the server itself, so a successful exploit can lead directly to remote code execution, file system access, and internal network pivoting. The bug class was formally cataloged by James Kettle in his 2015 Black Hat USA paper "Server-Side Template Injection: RCE For The Modern Web App," which showed that engines like Jinja2, Twig, FreeMarker, and Velocity all expose enough introspection to escalate a simple template payload into full command execution. Nearly a decade later, SSTI is still landing in production: Atlassian patched a CVSS 10.0 SSTI in Confluence (CVE-2023-22527) in January 2024, and it was weaponized by ransomware crews within days.
What Is Server-Side Template Injection?
Server-side template injection is a vulnerability where attacker-controlled data is embedded into a server-side template and evaluated by the template engine instead of being treated as plain text. Template engines like Jinja2, Twig, Freemarker, Velocity, Smarty, ERB, and Pug exist to let developers mix static markup with dynamic expressions -- things like {{ user.name }} in Jinja2 or ${user.name} in Velocity. The vulnerability appears when developers build a template string dynamically, for example render_template_string("Hello " + request.args.get('name')) in a Flask app, instead of passing user input as a variable into a static template. When that happens, an attacker can submit {{7*7}} instead of a name; if the response comes back with "Hello 49," the application is concatenating input into the template before rendering, and the engine's full expression language -- including, in many cases, access to Python builtins, Java reflection, or OS-level calls -- is exposed to the attacker.
How Is SSTI Different From Cross-Site Scripting (XSS)?
SSTI executes on the server in the template engine's language runtime, while XSS executes in the victim's browser as JavaScript, which is why SSTI is generally far more severe. An XSS payload like <script>alert(1)</script> can steal a session cookie or deface a page for one user, but a Jinja2 SSTI payload such as {{ self.__init__.__globals__.__builtins__.__import__('os').popen('id').read() }} runs id as an operating system command on the web server itself. That distinction is why bug bounty payouts for confirmed SSTI-to-RCE findings routinely run into the thousands of dollars -- HackerOne and Bugcrowd reports tagged as SSTI are consistently triaged at "critical" severity because the blast radius includes the host, any credentials or environment variables loaded into that process, and every downstream system reachable from it. A related but weaker case, client-side template injection (CSTI), affects frameworks like AngularJS and stays sandboxed to the browser -- it is the SSTI label that should trigger an incident response process, not a routine ticket.
Which Template Engines Get Hit Most Often?
Jinja2 (Python), Twig (PHP), FreeMarker and Velocity (Java), and Smarty (PHP) account for the large majority of publicly disclosed SSTI findings because they are the default rendering engines behind Flask, Django add-ons, Symfony, Confluence, and dozens of Java-based enterprise products. Jinja2's self and config objects are a favorite entry point because Flask's default configuration exposes secret keys and application internals through them, turning an SSTI into an immediate SECRET_KEY leak and session-forging attack. FreeMarker and Velocity are common in Java enterprise software specifically because they're bundled into products like Confluence, and both provide direct access to java.lang.Runtime through their built-in object models, which is exactly the path exploited in CVE-2023-22527. Twig-based SSTI shows up heavily in PHP CMS platforms -- Craft CMS's CVE-2023-41892, disclosed in September 2023 with a CVSS score of 9.8, let an unauthenticated attacker inject a malicious Twig template through an image upload endpoint and reach full remote code execution, and it was added to CISA's Known Exploited Vulnerabilities catalog within two weeks of disclosure.
How Do Attackers Find and Exploit an SSTI Bug?
Attackers find SSTI by fuzzing every reflected input field with a small set of polyglot payloads and watching for arithmetic evaluation in the response. A typical detection string like ${{<%[%'"}}%\ is designed to trip syntax errors or evaluate cleanly across Jinja2, Twig, FreeMarker, and ERB simultaneously; if 7*7 comes back as 49 instead of literal text, the tester knows both that injection is possible and, from which delimiter triggered it, which engine is in play. From there, exploitation follows a documented escalation path: identify the engine (Jinja2 vs. Twig vs. FreeMarker), find an object that exposes the underlying language runtime (self.__class__.__mro__ in Python, getClass() in Java), and chain that into a call to os.popen, Runtime.getRuntime().exec(), or an equivalent. Public tools like tplmap and the PayloadsAllTheThings SSTI repository automate this entire chain, which is part of why the Confluence CVE-2023-22527 exploit moved from advisory to active ransomware deployment (the Cerber/C3RB3R campaign) in under three weeks in January 2024.
What Real-World Breaches Trace Back to SSTI?
Several disclosed CVEs show SSTI moving from proof-of-concept to active exploitation within days of publication. CVE-2023-22527, an SSTI in older Confluence Data Center and Server versions patched on January 16, 2024, was scored CVSS 10.0 and was being used by ransomware operators to deploy Cerber ransomware and cryptominers within two to three weeks of Atlassian's advisory. CVE-2023-41892 in Craft CMS, patched in version 4.4.15 in September 2023 at CVSS 9.8, was exploited in the wild almost immediately -- researchers observed active scanning and exploitation attempts before most self-hosted instances had patched, and CISA added it to the KEV catalog on the same month it was disclosed. Further back, Uber's public bug bounty program paid out for a Flask/Jinja2 SSTI discovered in 2016 that gave a researcher shell access to an internal server, an early, widely cited example that helped push SSTI from an academic curiosity into a line item every pentest scope now checks. The consistent pattern across all three: a templating feature meant for convenience (theming, custom email templates, image metadata rendering) became the initial-access vector.
How Do You Prevent Server-Side Template Injection?
The most reliable fix is to never construct templates from untrusted input -- pass user data in as template variables, never as part of the template string itself. In Flask/Jinja2 terms, that means using render_template('page.html', name=user_input) instead of render_template_string('Hello ' + user_input); the first treats user_input strictly as data, the second lets it become code. Where dynamic templates are unavoidable (theming engines, customer-editable email templates), run them in a sandboxed environment -- Jinja2's SandboxedEnvironment, Twig's sandbox extension, or Velocity's SecureUberspector -- and pair that with strict input validation and allow-listing rather than blocklisting characters like {{ or ${, which are trivially bypassed with alternate delimiters or encoding. Patch cadence matters just as much as code fixes: both the Confluence and Craft CMS SSTI CVEs had working patches available before mass exploitation began, and organizations that patched within the first week were not among the confirmed ransomware victims in either campaign.
How Safeguard Helps
Safeguard's reachability analysis determines whether a vulnerable templating library or unsafe rendering pattern is actually invoked with attacker-controlled input in your specific codebase, so an SSTI advisory like CVE-2023-22527 or CVE-2023-41892 gets triaged by real exploitability instead of package presence alone. Griffin AI reviews the surrounding code path -- including where user input enters a render_template_string, Twig render, or FreeMarker Template.process call -- to confirm the injection is reachable and to explain the exploit chain in plain language for your team. Safeguard's SBOM generation and ingest give you a live inventory of every template engine version in use across services, so when a new SSTI CVE drops you know within minutes which repos are affected instead of grepping manifests by hand. When a fix is available, Safeguard opens an auto-fix PR that upgrades the vulnerable dependency or refactors the unsafe template call, cutting the time between advisory and patched code from weeks to hours.