Server Side Template Injection in Rocket
How Server Side Template Injection Manifests in Rocket
Server Side Template Injection (SSTI) in Rocket occurs when user-controlled data is passed directly to template rendering engines without proper sanitization. Rocket's template system, particularly when using Handlebars or other template engines, can execute arbitrary code if an attacker injects malicious template syntax.
The most common attack vector in Rocket applications is through query parameters, form data, or URL path segments that get interpolated into templates. For example, a route like:
#[get("/user//profile")]
fn profile(id: String) -> Template {
let context = json!({ "user_id": id });
Template::render("profile", context)
} If the Handlebars template contains {{user_id}} and the attacker passes {{7+7}} as the ID, the template will render 14. This escalates to code execution when template engines allow arbitrary code blocks:
{{user_id}}
{{#with user_id}}
{{#with "".constructor.fromCharCode(97, 108, 101, 114, 116, 40, 49, 41)}}
{{#with this.split("")}}
{{#with joinN(0, this, 1)}}
{{this}}
{{/with}}
{{/with}}
{{/with}}
{{/with}}Rocket's default configuration doesn't enable template sandboxing, making it vulnerable to classic SSTI payloads. The issue compounds when Rocket applications use tera templates with custom filters or when template context includes request data directly from Request objects.
Another Rocket-specific manifestation occurs through template inheritance and partials. An attacker who can control which template is loaded (through path traversal or parameter manipulation) can inject malicious content into base templates that affect all rendered pages.
Rocket-Specific Detection
Detecting SSTI in Rocket applications requires both static analysis and dynamic testing. For static analysis, examine all template rendering calls and trace data flow from request inputs to template context:
// Vulnerable pattern - direct interpolation
let context = json!({ "data": request.get_query() });
Template::render("template", context);
// Safer pattern - explicit sanitization
let safe_data = sanitize_input(request.get_query());
let context = json!({ "data": safe_data });
Template::render("template", context);
Dynamic detection with middleBrick's API security scanner specifically tests for SSTI by injecting template syntax payloads into all parameters and analyzing responses. The scanner sends payloads like:
{{7*7}}(basic arithmetic){{'a'.repeat(10)}}(string operations){{this.constructor.constructor('return process')().mainModule.require('os').hostname()}}(Node.js template engines){{7*'7'}}(type coercion)
middleBrick's LLM/AI security module also detects if your Rocket application uses AI features that might be vulnerable to prompt injection, which often co-exists with template injection vulnerabilities in modern web applications.
For Rocket applications using Handlebars specifically, middleBrick tests for the Handlebars sandbox escape ({}).constructor.constructor pattern and other engine-specific bypasses. The scanner reports findings with severity levels based on the potential for code execution and provides the exact payload that succeeded.
Runtime detection can be implemented using Rocket's request guards to validate template context data before rendering:
struct SafeContext { data: String }
impl<'r> FromRequest<'r> for SafeContext {
type Error = ();
fn from_request(request: &'r Request) -> Outcome<Self, Self::Error> {
let query = request.get_query();
if contains_template_syntax(&query) {
Outcome::Failure((Status::BadRequest, ()))
} else {
Outcome::Success(SafeContext { data: query })
}
}
}
Rocket-Specific Remediation
Remediating SSTI in Rocket applications requires a defense-in-depth approach. The first layer is input validation and sanitization before template rendering:
use rocket::http::RawStr;
fn sanitize_template_input(input: &RawStr) -> String {
// Remove template syntax characters
let sanitized = input.percent_decode().unwrap_or_default()
.replace("{{", "")
.replace("}}", "")
.replace("{{#", "")
.replace("{{/", "")
.replace("{{#", "");
sanitized
}
#[get("/user//profile")]
fn profile(id: String) -> Template {
let safe_id = sanitize_template_input(&id);
let context = json!({ "user_id": safe_id });
Template::render("profile", context)
}
For production Rocket applications, implement a Content Security Policy (CSP) header to mitigate XSS that might result from successful SSTI:
use rocket::response::content;
#[get("/")]
fn index() -> content::Html<String> {
content::Html(String::from("
<html>
<head>
<meta http-equiv=\"X-Content-Type-Options\" content=\"nosniff\">
<meta http-equiv=\"X-Frame-Options\" content=\"DENY\">
<meta http-equiv=\"Content-Security-Policy\" content=\"default-src 'self'; script-src 'none';\">
</head>
<body>
<h1>Secure Template Rendering</h1>
</body>
</html>
"))
}
Rocket's template engine configuration should be hardened by disabling dangerous features. For Handlebars, configure a sandbox:
use rocket_contrib::templates::Template;
use handlebars::Handlebars;
fn create_sandbox() -> Handlebars {
let mut handlebars = Handlebars::new();
handlebars.register_template_string("profile", "User: {{user_id}}").unwrap();
handlebars
}
#[get("/user//profile")]
fn profile(id: String, handlebars: &State<Handlebars>) -> Template {
let context = json!({ "user_id": id });
Template::render(handlebars, "profile", context)
}
The most robust remediation is to avoid dynamic template rendering entirely for user-controlled data. Instead, use pre-compiled templates or static rendering:
#[get("/user//profile")]
fn profile(id: String) -> content::Html<String> {
let safe_content = format!("
<div class=\"user-profile\">
<h2>User {}</h2>
<p>Profile information</p>
</div>
", html_escape(&id));
content::Html(safe_content)
}
For applications that must use dynamic templates, implement a template allowlist that validates template names against a known-safe list:
const ALLOWED_TEMPLATES: [&str; 3] = ["profile", "dashboard", "settings"];
fn validate_template_name(name: &str) -> Result<(), ()> {
if ALLOWED_TEMPLATES.contains(&name) {
Ok(())
} else {
Err(())
}
}