This article describes a Server-Side Template Injection (SSTI) lab that demonstrates how email preview features can be exploited when template engines evaluate user input as expressions rather than treating it as plain data. The vulnerability occurs when applications allow users to supply template code instead of just data values, enabling attackers to access server-side objects and configuration secrets. Understanding SSTI is critical because it appears frequently in customizable features and can escalate from information disclosure to remote code execution.
- Template engine evaluates user expressionsMarketingHub's preview feature accepts placeholders like {{name}} and {{company}}, but the underlying template engine does not restrict evaluation to variable names—it evaluates any expression within the braces. This means an attacker can place arbitrary code such as arithmetic calculations or references to configuration objects, and the engine will compute and return the result into the rendered preview.
- Attack confirms evaluation with arithmeticThe lab attack strategy begins by inserting a simple calculation between braces to prove expressions are evaluated rather than printed literally. Once a computation renders correctly in the preview output, the attacker knows the template engine is executing code, not just substituting variables.
- Access server configuration through contextAfter confirming expression evaluation, an attacker can reference the template context's configuration object—such as config.flag—within the preview expression. The template engine then evaluates this reference and outputs the secret value into the rendered preview, making it visible to the attacker.
- Root cause: user supplies the templateThe vulnerability exists because the application allows users to control the template itself rather than only the data that fills it. This hands users an expression evaluator running on the server; the template was designed to substitute a name, but now executes arbitrary code the user provides.
- SSTI maps to OWASP Top 10 InjectionThe article notes that SSTI in customizable features like email templates, invoices, and report builders is a frequent high-severity finding and classifies as Injection (A03) in OWASP Top 10 (2021). In engines like Jinja2, Twig, or ERB, SSTI can escalate beyond reading values to achieving remote code execution.
- Separate template from user dataThe core defense is architectural: keep the template fixed and developer-controlled while users supply only data values. Users never provide or modify the template itself, eliminating the ability to inject expressions into the rendering engine.
- Use logic-less templating for user contentFor any user-facing templating feature, employ a logic-less template language that cannot evaluate expressions. This ensures that even if a user's input reaches the template engine, it cannot be executed as code—only substituted as literal data.
- Testing strategy: arithmetic injection probeThe article recommends a quick detection method: drop a simple arithmetic expression into any 'customize your template' field and observe whether it is computed or printed verbatim. If computed, the engine is evaluating user input and SSTI is present.
Bottom line: Server-Side Template Injection occurs when applications allow users to supply template expressions rather than just data, granting attackers access to server-side objects and potentially enabling code execution. The defense is architectural: always keep templates developer-controlled, pass user input only as data, and use logic-less templating for customizable features.
{{name}} and {{company}}. It looks like simple variable substitution — but the engine actually evaluates whatever expression you put between the braces, not just a variable name. That gap is Server-Side Template Injection.📋 In this walkthrough
What this lab teaches
Server-Side Template Injection happens when user input is evaluated as a template expression instead of inserted as plain data. A preview that promises 'just put your name here' becomes dangerous the moment the engine runs arbitrary expressions, because those expressions can reach server-side objects and values.
The lesson: letting users supply the template — not just the data — hands them an expression evaluator running on your server.
How MarketingHub renders your preview
MarketingHub feeds your preview text through the template engine. Instead of restricting the braces to a fixed set of variable names, it evaluates any expression inside them. So an expression that references the engine's configuration object is evaluated and its value is rendered back into the preview — including the secret config flag.
The attack approach
- Confirm the braces evaluate expressions, not just variable names, by putting a simple calculation inside them.
- See the calculation resolve in the rendered preview — proof of evaluation.
- Reference the configuration object the lab exposes through a template expression.
- Read the secret config value from the rendered output.
Full walkthrough (spoiler)
If a calculation renders but the config is not showing, adjust the expression — open the steps.
Show the step-by-step solution
- Put a trivial arithmetic expression between the braces in the preview and render it; the computed result confirms expressions are evaluated.
- Replace it with an expression that reads
config.flagthrough the template context. - Render the preview. The engine evaluates the expression and prints the config secret into the output.
- Read the flag from the rendered preview. The lab detects the access and marks you solved.
Why it works
The engine treats the braces as an expression to evaluate rather than a key to look up. Once arbitrary expressions run, the template context — including configuration and other objects — is reachable. The preview was meant to substitute a name; instead it executes whatever you write.
Where this happens in the real world
SSTI in 'customisable' features — email templates, invoices, report builders — is a frequent high-severity finding, and in engines like Jinja2, Twig or ERB it can escalate from reading values to remote code execution. It maps to Injection (A03) in the OWASP Top 10 (2021).
How to defend against it
- Separate template from data. Users supply values that fill a fixed, developer-controlled template — never the template itself.
- Use a logic-less template language for anything user-facing so expressions cannot be evaluated.
- Restrict placeholders to an allowlist of known variable names if users must customise.
- Run the renderer with least privilege and keep template engines patched.
Try it, then go further
Reading about it is the easy part. Open the lab, try your own approach before peeking at the spoiler, and see how many attempts it takes.
