HolyGhost logoHolyGhost
← cd ..
Analysis

Server Side Template Injection: From a Curious {{7*7}} to Owning the Server

Template engines mix data into pages. Feed user input into the template itself and an attacker can run code on the server. Here is how SSTI works and how to prevent it.

HolyGhost··7 min read

A tester types {{7*7}} into the "name" field of a website's greeting feature and submits it. The page comes back and says, cheerfully, "Hello 49". That tiny result, 49 instead of the literal text, is one of the most alarming things you can see in web security. It means the website did not treat the input as a name at all. It did the sum. And if the site will do arithmetic you sent it, the next question is what else it will run.

That is server side template injection, usually shortened to SSTI. It turns an innocent looking text field into a way to execute code on the server. Here is how a helpful feature becomes a foothold, and how to make sure yours never does.

Credit and scope

The SSTI vulnerability class was brought to wide attention by James Kettle of PortSwigger, with research published around 2015 that showed how far it could be pushed. This is a defensive explainer for people building and protecting web applications. Only test systems you own or are authorised to assess.

First, what a template engine is

Almost every website needs to build pages that mix a fixed layout with changing data. The page that says "Hello Saurav, you have 3 new messages" is the same template every time, with the name and the number slotted in. The tool that does this slotting is a template engine.

A template is just text with special placeholders. When the engine renders it, it swaps the placeholders for real values:

Template:   Hello {{ name }}, you have {{ count }} new messages
Data:       name = "Saurav", count = 3
Output:     Hello Saurav, you have 3 new messages

Different languages have their own popular engines: Jinja2 in Python, Twig in PHP, Freemarker and Velocity in Java, ERB in Ruby. The {{ ... }} style is common, though the exact syntax varies. This is everyday, sensible technology. The vulnerability is not in template engines. It is in how they are sometimes used.

The mistake that opens the door

There is a right way and a wrong way to get user data into a page.

The right way is to pass the user's data to a fixed template as a value, like the example above, where name is a variable handed to the engine. The engine treats it purely as text to display.

The wrong way is to build the template itself out of user input, gluing what the user typed into the template string before the engine renders it:

Unsafe: the app builds the template from user input
   template = "Hello " + userInput
   render(template)
 
If userInput is  {{7*7}}  the template becomes  "Hello {{7*7}}"
and the engine dutifully evaluates 7*7 and prints 49.

Now the user is not supplying data, they are supplying template code, and the engine runs it. That is the whole flaw. It usually creeps in through features that feel like they naturally involve user controlled text: customisable email templates, on the fly greetings, editable page fragments, anything where a developer thought "I will just build the template with their input".

Why {{7*7}} is the classic probe

Testers use {{7*7}} because it is harmless and unambiguous. If the page echoes back {{7*7}} unchanged, the input is being treated as plain text, which is safe. If it comes back as 49, the engine evaluated it, which proves the input is running as template code. It is a quick, non destructive way to tell data from code.

From arithmetic to running the server

Printing 49 is just the doorbell. The reason SSTI is severe is where it can go from there. Template engines are often far more powerful than "insert a value". Many can call functions, access objects, and reach into the surrounding programming language. An attacker who can write template code explores that power step by step, and in many engines they can eventually reach the underlying system and run operating system commands.

1. {{7*7}} returns 49            -> input is evaluated (SSTI confirmed)
2. probe the engine's objects    -> what can this template reach?
3. navigate to a dangerous call  -> reach the language or the OS
4. run an operating system command -> remote code execution

That final stage, remote code execution, means the attacker runs their own commands on your server from afar. The exact path depends heavily on the engine and on whether it has been locked down, but the pattern is consistent: a rendered 49 is the thread that, pulled far enough, can unravel the whole server. Even when full code execution is not reachable, SSTI often leaks server side configuration and secrets, which is damaging on its own.

It hides behind ordinary features

SSTI is easy to introduce without realising. A "let users customise their notification message" feature, built by concatenating their text into a template, looks like a nice touch and is quietly a remote code execution hole. Any place user input reaches the template layer as anything other than a plain passed in value deserves a hard look.

How it relates to the other injection flaws

If this feels familiar, it should. SSTI is a member of the same family as SQL injection, command injection, and cross site scripting. Every one of them is the same root mistake wearing different clothes: untrusted input crossing the line from data into code. In SQL injection the code is a database query. In command injection it is an operating system command. In cross site scripting it is JavaScript in the browser. In SSTI it is template syntax run by the engine on the server. Learn the shape once and you recognise it everywhere.

Preventing it

The cure follows the same principle as the rest of the family: keep user input firmly on the data side of the line.

  1. Never build templates from user input. This is the real fix. The template should be fixed, written by you, and chosen from a safe set. User data goes in as values passed to that template, never as part of the template source. If you are concatenating user input into a template string, stop and restructure.
  2. Pass user data as data. Hand the engine variables, and let it insert them as plain text. This is how template engines are meant to be used, and it makes SSTI impossible by construction.
  3. Prefer logic less or sandboxed engines. Some template engines are deliberately limited so templates cannot call arbitrary code, and some offer a sandboxed mode. Where user supplied templates are a genuine product requirement, a sandboxed or logic less engine dramatically reduces the blast radius. Treat the sandbox as a helpful layer, not a licence to feed it hostile input.
  4. Validate and contextually escape as supporting measures, but do not rely on them alone. The structural fix is keeping input out of the template source in the first place.

The takeaway

Server side template injection happens when user input is built into the template that the engine evaluates, rather than passed in safely as data, so the engine runs the attacker's template code. A rendered {{7*7}} returning 49 is the tell, and from there it can climb, in many engines, all the way to running commands on your server. It is the template engine member of the injection family, and the fix is the family fix: keep user input as data, never as code. Use fixed templates, pass values in as variables, prefer sandboxed engines when users must supply templates, and the innocent greeting field stays innocent.