Server Side Template Injection in Axum
How Server Side Template Injection Manifests in Axum
Server Side Template Injection (SSTI) occurs when user input is unsafely embedded into a template engine, allowing attackers to execute arbitrary code. In Axum, a Rust web framework, SSTI risks arise when developers use templating engines like tera or askama and pass unsanitized user data into template rendering contexts. Unlike traditional frameworks, Axum’s minimalist design requires explicit integration of templating, which can lead to oversight in input handling.
A common vulnerable pattern involves extracting query parameters or form data and directly inserting them into a template without validation. For example, consider an Axum handler that uses Tera to render a user profile page:
use axum::extract::Query;
use axum::response::Html;
use tera::{Tera, Context};
async fn profile(Query(params): Query>) -> Html {
let mut tera = Tera::default();
tera.add_raw_template("profile.html", "Hello, {{ name }}!").unwrap();
let mut context = Context::new();
context.insert("name", ¶ms.get("name").unwrap_or(&"Anonymous".to_string()));
let rendered = tera.render("profile.html", &context).unwrap();
Html(rendered)
}
If an attacker supplies name={{7*7}}, the template engine evaluates the expression, returning "Hello, 49!". This indicates a SSTI vulnerability. In Axum, this risk is heightened when handlers dynamically construct templates from user input (e.g., template names or content) or when using askama with derived structs that incorporate unsanitized data. Real-world analogues include CVE-2020-28493 in Jinja2-like engines, where sandbox escapes lead to remote code execution.
The vulnerability manifests in Axum when:
- Template strings are built from concatenated user input (e.g.,
format!("{{{{ {} }}}}", user_input)). - Error messages leak template engine details, aiding attackers in crafting payloads (e.g., exposing Tera syntax in 500 responses).
- Async state is mismanaged, allowing shared template caches to be poisoned via concurrent requests.
These patterns are detectable as they deviate from Axum’s ownership-driven safety model, introducing runtime interpretation of untrusted data.
Axum-Specific Detection — How to Identify This Issue
Detecting SSTI in Axum requires analyzing both static code patterns and runtime behavior. Static analysis can flag handlers where user input flows into template rendering contexts without sanitization. For instance, using tera::Context with values extracted directly from axum::extract::Query, Form, or Path without validation is a red flag. Dynamic detection involves injecting payloads that trigger template evaluation and observing responses.
middleBrick identifies SSTI in Axum endpoints through its Input Validation and Property Authorization checks. During a scan, it sends sequential probes such as:
{{7*7}}to detect basic expression evaluation{{config.__class__.__mro__[2].__subclasses__()}}(adapted for Rust templating engines) to explore object chains{{request.application.__globals__}}to attempt global object access
If the response contains evaluated results (e.g., "49" in the first case) or error messages revealing template syntax, middleBrick flags the endpoint with high severity. It cross-references these findings with OpenAPI specs to confirm whether input parameters are defined as strings without constraints, increasing exploit likelihood.
For example, scanning an Axum service exposing a /search endpoint that renders results via Tera might yield:
Request: GET /search?q={{7*7}}
Response: Results for 49
This indicates successful SSTI. middleBrick’s report includes the exact payload, affected parameter (q), and remediation guidance. Unlike source-code scanners, it requires no access to the Axum project—only the deployed URL—making it suitable for black-box testing of staging or production APIs.
Developers can also manually test using Axum’s test harness. A unit test simulating malicious input helps catch SSTI early:
#[tokio::test]
async fn test_ssti_in_profile() {
let app = create_app().await;
let response = app
.oneshot(axum::http::Request::builder()
.uri("/profile?name={{7*7}}")
.body(axum::body::Body::empty())
.unwrap())
.await
.unwrap();
assert!(!response.text().await.unwrap().contains("49"), "SSTI detected");
}
This test fails if the template evaluates the expression, signaling a vulnerability.
Axum-Specific Remediation — Code Fixes
Remediating SSTI in Axum centers on preventing unsanitized user input from reaching template engines. Axum’s strength lies in its type safety and explicit data handling—leverage these to enforce boundaries between user data and template contexts.
The primary fix is to validate and sanitize all user input before insertion into templates. Use Axum’s extractors with validation (e.g., axum::extract::Query with serde::Deserialize and validation) or middleware to sanitize inputs. For example, refactor the vulnerable profile handler:
use axum::extract::Query;
use axum::response::Html;
use serde::Deserialize;
use tera::{Tera, Context};
#[derive(Deserialize)]
struct ProfileParams {
#[serde(default = "default_name")]
name: String,
}
fn default_name() -> String {
"Anonymous".to_string()
}
async fn profile(Query(params): Query) -> Html {
// Sanitize: allow only alphanumeric and spaces
let safe_name: String = params.name
.chars()
.filter(|c| c.is_alphanumeric() || c.is_whitespace())
.collect();
let mut tera = Tera::default();
tera.add_raw_template("profile.html", "Hello, {{ name }}!").unwrap();
let mut context = Context::new();
context.insert("name", &safe_name);
let rendered = tera.render("profile.html", &context).unwrap();
Html(rendered)
}
Here, serde ensures structured input, and a filter strips dangerous characters. This prevents template injection while preserving usability.
Alternative approaches include:
- Using
askamawith compile-time templates: Since askama templates are compiled into Rust structs, user data must fit predefined fields, eliminating injection points if structs are properly typed. - Implementing a rejection layer: Use Axum middleware to check for template syntax in inputs (e.g., rejecting strings containing
{{or}})—though this is less reliable than validation. - Employing template engine sandboxing: While Tera lacks a robust sandbox, ensure the environment is restricted (e.g., disable
to_jsonfilter if not needed).
For Axum services using dynamic template loading (e.g., tera.add_template_files), never allow user input to influence template names or paths. Hardcode template references or use a whitelist.
After fixes, rescan with middleBrick to confirm the SSTI finding is resolved. The report should show the Input Validation check passing for the affected endpoint. Continuous monitoring via the Pro plan ensures regressions are caught if templating logic changes.
Remember: middleBrick detects and reports SSTI with remediation guidance—it does not apply fixes. Developers must implement these code changes in their Axum service to eliminate the vulnerability.
Frequently Asked Questions
Can middleBrick detect SSTI in Axum endpoints that use askama instead of Tera?
Does enabling Axum’s default error handling increase SSTI risk?
fallback or custom ErrorHandler to return generic messages in production, while retaining detailed logs server-side. This aligns with the principle of failing securely without aiding reconnaissance.