Server Side Template Injection in Actix with Firestore
Server Side Template Injection in Actix with Firestore — how this specific combination creates or exposes the vulnerability
Server Side Template Injection (SSTI) occurs when an attacker can inject template directives that are subsequently evaluated by a server-side templating engine. In an Actix web application that renders responses using server-side templates and incorporates user-controlled data into Firestore document lookups or display logic, SSTI can arise through two primary vectors: (1) unsafe construction of Firestore queries or document paths using unsanitized user input, and (2) unsafe rendering of Firestore-derived data within server-side templates.
Consider an endpoint that retrieves a Firestore document by an identifier provided via a query parameter. If the identifier is used to build a Firestore document path without strict validation, and the retrieved document content is then passed to a server-side template for rendering, an attacker may attempt to inject template syntax via the identifier or document fields. For example, if the application uses a templating language that supports variable substitution or expression evaluation (e.g., Handlebars with unsafe helpers, or a custom Jinja-like engine), injected expressions could trigger remote code execution or data exfiltration.
In the Firestore context, exploitation might involve manipulating document field values stored in Firestore to contain template-like constructs, which are later rendered unsafely. An attacker who can write to a Firestore collection (e.g., via misconfigured Firestore rules or a separate injection flaw) could store a document with a field value such as {{ this.constructor.constructor('return process')() }} (in a JavaScript-based rendering context). When the Actix service retrieves and renders this field using an unsafe template, the injected expression executes in the server process, leading to sensitive information disclosure or further compromise.
Additionally, SSTI in this combination can expose metadata or configuration stored in Firestore, such as endpoint URLs or internal service identifiers, which may aid in further attacks. Because Firestore rules do not inherently protect against template rendering flaws, the onus is on the application to validate and sanitize all inputs and to ensure retrieved data is escaped or sandboxed before template evaluation.
middleBrick detects this risk in the Unauthenticated Attack surface checks, specifically under Input Validation and Property Authorization, assigning a severity based on exploitability and potential impact. Findings include evidence of template syntax in user-influenced data and recommendations to enforce strict input validation, context-aware output encoding, and avoidance of dynamic template evaluation.
Firestore-Specific Remediation in Actix — concrete code fixes
Remediation centers on strict input validation, avoiding direct concatenation of user input into Firestore paths or queries, and ensuring all data rendered in templates is properly escaped. Below are concrete, Firestore-aware examples for Actix in Rust.
1. Safe Firestore document retrieval with validated IDs
Always validate document identifiers against a strict allowlist or regex before using them in document paths. Do not rely on Firestore security rules alone to prevent malicious path traversal.
use actix_web::{web, HttpResponse};
use google_cloud_firestore::client::Client;
use regex::Regex;
async fn get_document(client: web::Data, path: web::Path) -> HttpResponse {
let id = path.into_inner();
// Strict allowlist: only alphanumeric IDs
let re = Regex::new(r"^[a-zA-Z0-9_-]{1,100}$").unwrap();
if !re.is_match(&id) {
return HttpResponse::BadRequest().body("Invalid document ID");
}
// Safe document reference: no string interpolation of user input
let doc_ref = client.collection("items").doc(&id);
match doc_ref.get().await {
Ok(doc) => {
if let Some(data) = doc.data() {
// Serialize to a safe structure; do not pass raw Firestore data directly to a template engine that supports code execution
HttpResponse::Ok().json(data)
} else {
HttpResponse::NotFound().body("Not found")
}
}
Err(_) => HttpResponse::InternalServerError().body("Database error"),
}
}
2. Context-aware output encoding before template rendering
If you must render Firestore data in server-side templates, use context-aware escaping provided by the templating engine and never mark user data as safe unless absolutely necessary and properly sanitized.
use askama::Template;
// Assume Firestore document is deserialized into a safe struct
#[derive(Template)]
#[template(path = "item.html")]
struct ItemTemplate {
title: String,
description: String,
}
async fn render_item(client: web::Data, id: String) -> HttpResponse {
// Validate ID as above, then fetch
let doc_ref = client.collection("items").doc(&id);
match doc_ref.get().await {
Ok(doc) => {
if let Some(data_map) = doc.data().get::>() {
let template = ItemTemplate {
title: data_map.get("title").cloned().unwrap_or_default(),
description: data_map.get("description").cloned().unwrap_or_default(),
};
match template.render() {
Ok(html) => HttpResponse::Ok().body(html),
Err(_) => HttpResponse::InternalServerError().body("Render error"),
}
} else {
HttpResponse::InternalServerError().body("Invalid data format")
}
}
Err(_) => HttpResponse::InternalServerError().body("Database error"),
}
}
3. Avoid dynamic template evaluation and enforce schema validation
Do not construct queries or document paths by interpolating user input. Enforce Firestore schema validation on the server and treat all retrieved data as untrusted. For example, do not do this:
// UNSAFE: concatenating user input into a document path
let doc_ref = client.collection("users").doc(user_supplied_collection).doc(user_supplied_id);
// Instead, scope collections explicitly and validate IDs separately
let doc_ref = client.collection("users").doc(validated_user_id);
middleBrick highlights these concerns in the BOLA/IDOR and Input Validation checks, emphasizing secure handling of identifiers and encoded outputs.