Description
Server-side template injection (SSTI) occurs when untrusted input is compiled or evaluated as executable template source, rather than ordinary data. An attacker can supply directives and expressions that access exposed objects, methods, helpers or application data.
Distinguish these three kinds of values:
- Template source: text that the engine interprets as syntax. Building it from request data can cause SSTI.
- Template identifier: a name or path selecting a server-managed template. An untrusted identifier can cause path traversal or unauthorized template selection without itself being template source.
- Model or context values: data bound to a fixed template. Pass user input through this channel.
For example, Configuration.getTemplate(userInput) with an ordinary file loader selects a template name, whereas new Template("dynamic", userInput, configuration) parses the value as FreeMarker source. If a loader is configured to interpret a string as source, that string must also be trusted.
Potential impact
- Disclosure of data or application objects exposed to the template
- Calls to application functionality through registered helpers or accessible methods
- CPU and memory exhaustion from loops, recursion or large output
- Server-side code or OS command execution where engine features, exposed objects and process permissions allow it
- XSS when rendered output enters HTML without appropriate output encoding
Not every engine or configuration permits immediate remote code execution. The impact depends on language features, helpers, object-access policies, the data model and file/network permissions.
Remediation
- Load template source only from trusted fixed resources or constant strings under server control.
- Pass request values as model or context data; never concatenate them into template source.
- Apply encoding for the final output context, such as HTML, JavaScript or a URL. Output encoding reduces XSS risk but does not make untrusted template source safe.
- Treat user-authored templates as a high-risk feature. Minimize exposed objects and helpers, apply engine-specific restrictions and render in an isolated low-privilege process with CPU, memory and time limits.
Do not treat an engine's security options as a universal sandbox.
- Velocity's
SecureUberspectoris a starting point, but the engine cannot anticipate every dangerous object an application places in its context. - If untrusted FreeMarker templates are unavoidable, review
TemplateClassResolver.ALLOWS_NOTHING_RESOLVERor a stricter allow policy using the official FAQ.SAFER_RESOLVERalone is insufficient, and FreeMarker cannot itself enforce CPU or memory limits. - Groovy template engines compile templates as Groovy code. Avoid running attacker-controlled template bodies in the application process.
Examples
Apache Velocity
Before
The request value is evaluated as VTL source.
import java.io.StringWriter;
import jakarta.servlet.http.HttpServletRequest;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
class InsecureVelocityTemplate {
void render(HttpServletRequest request) {
String userTemplate = request.getParameter("template");
Velocity.evaluate(
new VelocityContext(),
new StringWriter(),
"request-template",
userTemplate
);
}
}
After
The template is fixed and the request value is passed as context data.
import java.io.StringWriter;
import jakarta.servlet.http.HttpServletRequest;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
class SecureVelocityTemplate {
void render(HttpServletRequest request) {
VelocityContext context = new VelocityContext();
context.put("name", request.getParameter("name"));
Velocity.evaluate(
context,
new StringWriter(),
"welcome-template",
"Hello $name"
);
}
}
This comparison addresses SSTI only. If the result is sent as HTML, apply HTML-context encoding to name separately.
FreeMarker
Before
The request value is parsed as the template body.
import java.io.StringWriter;
import java.util.Map;
import jakarta.servlet.http.HttpServletRequest;
import freemarker.template.Configuration;
import freemarker.template.Template;
class InsecureFreeMarkerTemplate {
void render(HttpServletRequest request, Configuration configuration) throws Exception {
String userTemplate = request.getParameter("template");
Template template = new Template("dynamic", userTemplate, configuration);
template.process(Map.of(), new StringWriter());
}
}
After
Load a server-managed template using a fixed name and place request values only in the data model.
import java.io.StringWriter;
import java.util.Map;
import jakarta.servlet.http.HttpServletRequest;
import freemarker.template.Configuration;
import freemarker.template.Template;
class SecureFreeMarkerTemplate {
void render(HttpServletRequest request, Configuration configuration) throws Exception {
Template template = configuration.getTemplate("welcome.ftl");
Map<String, Object> model = Map.of("name", request.getParameter("name"));
template.process(model, new StringWriter());
}
}
References
- CWE-1336: Improper Neutralization of Special Elements Used in a Template Engine
- OWASP ASVS 5.0.0 V1.3.7
- Apache Velocity Engine 2.4.1
Velocity.evaluate - Apache Velocity
SecureUberspector - FreeMarker 2.3.34
Template - FreeMarker template security FAQ
- Apache Groovy 5.1 downloads and release status
- Apache Groovy security policy
- Handlebars.java releases
- Pebble 4.1.2
getLiteralTemplate