Server-side template injection (SSTI) happens when user input becomes part of a template instead of data passed into it, so the template engine evaluates that input as expressions or code on the server. Modern engines are expressive enough that this often leads to remote code execution, and SSTI has produced critical, actively exploited flaws such as CVE-2023-22527 in Atlassian Confluence. The risk is growing as more products let users customise emails, documents, notifications and AI prompts with template syntax. This guide explains how SSTI arises, where it hides, how to detect it and how to prevent it, for developers, AppSec engineers, detection teams and the leaders who decide whether users may edit templates at all. It contains no payloads. For the wider family of web attacks, see the Web Application Attacks Guide.
Key takeaways
- Templates are code. Pass user input into a fixed template as data; never build the template string from input. Treat any feature that lets users author templates as letting them write code.
- Prefer less powerful engines. Logic-less engines reduce what injected syntax can do. Choose the least expressive engine that meets the requirement.
- Sandboxes reduce risk, they do not remove it. Jinja's own documentation says the sandbox alone is not a solution for perfect security, and PortSwigger notes sandboxes are prone to bypasses. Add isolation around them.
- Limit what the template can reach. Expose only plain data to templates, never objects with methods that touch files, the network, class loading or credentials.
- Detect the outcome, not the syntax. Template syntax is legitimate in many apps; a rendering process spawning shells or reading secrets is not.
How the attack works
MITRE classifies SSTI as CWE-1336, Improper Neutralization of Special Elements Used in a Template Engine, a Base weakness and a child of CWE-94 Code Injection, with CWE-917 Expression Language Injection as a peer. CWE names Twig, Jinja2, Pug, JavaServer Pages, FreeMarker, Velocity, ColdFusion and Smarty as engines involved, and notes that both server-side and client-side engines are affected. CAPEC covers the pattern under CAPEC-242 Code Injection. See the CWE Top 25 Guide.
Template engines combine a fixed template with a data context. Used safely, user input only ever appears in the context and is rendered as text. PortSwigger describes the root cause as user input being concatenated into the template rather than passed in as data. Typical routes:
- Dynamic template construction: code builds a template string with string formatting (a greeting, a subject line, an error message) and then renders it.
- User-authored templates: the product deliberately lets administrators, tenants or content editors write templates for emails, invoices, reports or pages. If any such account is compromised or under-trusted, the template engine becomes an execution surface.
- Stored input rendered later: a value saved in one feature (a profile field, a ticket title, a product description) is later fed into template compilation by another feature, often a background job.
- Expression language evaluation: frameworks that evaluate OGNL, SpEL or Java EL on request data behave the same way; Confluence CVE-2023-22527 was an OGNL template injection.
From there the attacker uses the engine's features (attribute access, method calls, object introspection, built-in functions) to reach objects that read files, run commands or load classes. How far they get depends on the engine and on what objects are in scope.
SSTI and its relatives
| Class | Where evaluation happens | Typical impact | Primary fix |
|---|---|---|---|
| Server-side template injection (CWE-1336) | Server template engine | Data access, file read, remote code execution | Never build templates from input; sandbox and isolate user-authored templates |
| Client-side template injection | Browser framework template engine | Script execution in the user's browser, similar to XSS | Do not mix server-rendered user content into client template scopes; output encoding. See the Cross-Site Scripting (XSS) Prevention Guide |
| Expression language injection (CWE-917) | Framework expression evaluator | Remote code execution | Never evaluate expressions built from input |
| CI expression injection | Workflow expressions substituted into scripts | Command execution on runners | Pass values through environment variables. See the Command Injection Guide |
| Remote template injection (document attacks) | Office applications loading a template from a URL | Malware delivery | Email and endpoint controls; a different problem despite the name |
Where it shows up
- Customisable notifications and documents: email templates, PDF and invoice generators, report builders, marketing tools and helpdesk macros.
- CMS, wiki and collaboration platforms: theme and macro systems. The CISA KEV catalog lists Confluence CVE-2023-22527, which Atlassian rated CVSS 10.0, as an unauthenticated template injection leading to remote code execution; Atlassian published the advisory on 16 January 2024 and fixed it in 8.5.4 and later.
- File servers and appliances: in the local KEV snapshot used for this guide (catalog 2026.10.21), three entries are tagged CWE-1336, including CVE-2024-23692 in Rejetto HTTP File Server (unauthenticated command execution) and CVE-2024-4040 in CrushFTP (virtual file system sandbox escape). Many template-related KEV entries carry broader CWE tags such as CWE-74 or CWE-94, so the true count is higher.
- AI and LLM applications: prompt templates are often rendered with Jinja2. CWE lists CVE-2024-34359, in which LLM Python bindings parsed model-supplied chat templates without sandboxing, as an observed SSTI example. Treat model metadata and model output as untrusted input. See the MCP Security Guide.
- Low-code and workflow platforms: expressions in automations and integrations that interpolate record fields into templates.
Impact
- Remote code execution on the rendering host, typically mapped to ATT&CK T1190 Exploit Public-Facing Application followed by T1059 Command and Scripting Interpreter.
- Secret exposure: templates frequently have access to configuration objects, environment variables or settings holding database passwords, signing keys and API tokens (T1552.001 Credentials In Files covers the file case). CWE lists CVE-2018-20465 as an SSTI that exposed database credentials.
- Persistence: a malicious stored template is itself a persistence mechanism, and execution allows web shells (T1505.003).
- Denial of service: both the Jinja and FreeMarker documentation warn that a small template can consume large amounts of output, CPU or memory.
For executives the business question is simple: any feature that lets customers or staff edit templates is a code execution feature, and needs the same design review, isolation and ownership as one. Where identity fits: the rendering service's identity and secrets set the blast radius, so give it a dedicated, low-privilege service account with short-lived credentials. See the Service Account Security Guide.
Detection
In code review and testing
- Find template compilation from strings: search for engine APIs that compile or render a template from a string rather than a file (for example rendering from a string in Jinja2, Twig or FreeMarker), and trace whether any part comes from request data, the database or model output.
- Inventory user-authored template features and record which engine, configuration and sandbox each uses.
- Test every reflected and stored field that is later rendered, including asynchronous outputs such as emails and PDFs, using engine-agnostic polyglot probes in a test environment. See the Web Application Security Testing Guide.
- Check dependencies: SCA alerts for template engines and frameworks matter, since sandbox bypasses are fixed in engine releases.
At runtime
| Signal | Source | Notes |
|---|---|---|
| Application runtime (java, python, php-fpm, node) spawning shells or system utilities | EDR, Sysmon Event ID 1, Windows event 4688, auditd execve | D3FEND D3-PSA Process Spawn Analysis; highest-confidence signal |
| Template engine errors: SecurityError, sandbox violations, unknown attribute errors | Application logs | Jinja raises SecurityError on blocked access; spikes indicate probing |
| Template edits by unusual accounts or at unusual times | Application audit log | Treat template changes like code changes |
| Rendering hosts making unexpected outbound connections | Egress proxy, DNS, flow logs | Blind SSTI and exfiltration |
| Rendering jobs with abnormal CPU, memory or output size | Container and APM metrics | Resource abuse or failed exploitation |
See the Detection Engineering Guide for turning these into tested rules.
Prevention and hardening
Decision guidance: what to allow
| Requirement | Recommended design | Trade-offs |
|---|---|---|
| Developers own all templates; users supply values | Templates from files only; user input passed as context data; autoescaping on | No trade-off; this is the baseline |
| Users need placeholders in text (for example a name in an email) | A small substitution format with a fixed list of placeholders, or a logic-less engine such as Mustache | Less flexible; removes code execution paths |
| Trusted administrators need logic in templates | Full engine in sandbox mode, restricted object exposure, change approval and audit | Sandbox bypasses remain possible; needs isolation |
| Untrusted tenants or the public author templates | Sandboxed engine inside an isolated, network-restricted, short-lived container with no secrets | Highest cost; the only defensible option at this trust level |
OWASP ASVS 5.0 V1.3.7 requires that templates are not built from untrusted input, and where there is no alternative, that untrusted input included during template creation is sanitised or strictly validated. V1.3.5 requires user-supplied scriptable or expression template content (such as Markdown, CSS or XSL stylesheets and BBCode) to be sanitised or disabled.
Engine configuration
- Jinja2: use
SandboxedEnvironmentorImmutableSandboxedEnvironmentfor any untrusted template. The sandbox blocks private and internal attributes and raisesSecurityError, and can be tightened by overridingis_safe_attribute()andis_safe_callable(). The documentation warns that templates can still produce very large output, so set CPU and memory limits, and that rendering errors must be caught. - FreeMarker: the project FAQ says users should not be allowed to upload templates unless they are application developers. Where they must, use a
DefaultObjectWrapperwith aWhitelistMemberAccessPolicy, keep?apidisabled (the default), set the class resolver toALLOWS_NOTHING_RESOLVER(the FAQ saysSAFER_RESOLVERis not restrictive enough), and restrict the template loader. FreeMarker cannot enforce CPU or memory limits, so isolate it. - Other engines: CWE recommends choosing an engine that offers a sandbox or restricted mode and using it. Disable features that load files, call static methods or instantiate classes, and keep autoescaping on.
- Context hygiene: pass plain values (strings, numbers, lists of plain records), never service objects, ORM sessions, request objects or configuration objects.
- Isolation: render user-authored templates in a separate process or container with no credentials, no outbound network, a read-only file system and strict timeouts. MITRE D3FEND D3-EAL Executable Allowlisting on rendering hosts limits what a breakout can run.
Rollout plan
- Inventory: list every template engine, every string-compiled template and every feature where users author templates, with owners.
- Fix construction bugs first: refactor any code that formats input into template source so that input moves into the render context. This is usually a small change.
- Classify user-authored features by trust level using the decision table and set a target design for each.
- Enable sandboxing in staging, render the existing template corpus and log every violation; fix legitimate templates before enforcing in production. Keep the previous configuration behind a feature flag for rollback.
- Move untrusted rendering into isolated workers and remove secrets from their environment.
- Add guardrails: SAST rules blocking string-compiled templates outside an approved module, and process spawn alerts on rendering services.
Small and large organisations: smaller teams should avoid user-authored logic entirely and offer fixed placeholders; platforms that sell template customisation need the isolated rendering tier and a security review for every engine upgrade.
Measuring progress
- Templates compiled from strings that include external data, trending to zero.
- Percentage of user-authored template features running sandboxed and isolated.
- Time to upgrade template engines after a security release.
- Number of sandbox violations and rendering-process spawn alerts, investigated within SLA.
Framework mapping
| Framework | Reference | Relevance |
|---|---|---|
| NIST SP 800-53 Rev. 5 | SI-10 Information Input Validation; SC-39 Process Isolation; AC-6 Least Privilege; SA-11 Developer Testing and Evaluation; SI-4 System Monitoring | Validation, isolation, testing, monitoring |
| NIST CSF 2.0 | PR.PS-06; DE.CM-09 | Secure development and runtime monitoring |
| OWASP ASVS 5.0 | V1.3.7, V1.3.5 | Verification requirements |
| ISO/IEC 27001:2022 | Annex A 8.28 Secure coding | Policy anchor |
| MITRE ATT&CK | T1190, T1059, T1505.003, T1552.001 | Detection engineering |
| MITRE D3FEND | D3-PSA, D3-EAL | Countermeasures |
Lessons from incidents
- Collaboration platforms are high-value targets. Confluence CVE-2023-22527 needed no authentication and Atlassian listed no workaround, only upgrading. Keep internet-facing platforms with template or expression engines on the fastest patch track.
- Expression interpolation into execution contexts recurs outside web apps. In the Ultralytics PyPI compromise of 2024, a branch name was inserted directly into a shell command in a CI workflow. It is command injection rather than SSTI, but the root cause is identical: untrusted text placed inside something that is evaluated, instead of passed as data.
- AI components inherit the problem. The LLM bindings case listed by CWE shows that model files and chat templates are untrusted input.
Practitioner checklist
- Inventory template engines, string-compiled templates and user-authored template features, with owners.
- Refactor any template built from input so the input is passed as render context data.
- Offer fixed placeholders or a logic-less engine wherever users only need substitution.
- Run any user-authored template in the engine's sandbox with a restrictive object and class policy.
- Render untrusted templates in isolated workers with no secrets, no outbound network and resource limits.
- Expose only plain data to templates, never service, request or configuration objects.
- Log sandbox violations and template changes, and alert on rendering processes spawning shells.
- Track template engine security releases and patch internet-facing platforms with template flaws first.
Standards and references
- MITRE CWE: CWE-1336 Improper Neutralization of Special Elements Used in a Template Engine
- OWASP: Application Security Verification Standard 5.0
- Pallets: Jinja Sandbox documentation
- Apache FreeMarker: FAQ, allowing users to upload templates
- Atlassian: CVE-2023-22527 RCE Vulnerability in Confluence Data Center and Server
- CISA: Known Exploited Vulnerabilities Catalog
- PortSwigger Web Security Academy: Server-side template injection
- MITRE CAPEC: Code Injection (CAPEC-242)
Related NHI Mgmt Group resources: Command Injection Guide · Path Traversal Guide · Insecure Deserialisation Guide · Secure Coding Guide