Server Side Template Injection in Axum with Cockroachdb
Server Side Template Injection in Axum with Cockroachdb — how this specific combination creates or exposes the vulnerability
Server Side Template Injection (SSTI) occurs when an attacker can control template input that is later rendered by a server-side templating engine. In an Axum application using a server-side templating engine (such as Askama or Tera) with a Cockroachdb backend, the risk arises when user-supplied data is passed into templates without proper escaping or validation and that data is subsequently used to influence database queries or template logic.
Consider an endpoint that builds a SQL query string using user input and then renders a template with the query results. If the input is not validated, an attacker may supply a payload designed to break out of the data context and manipulate the template engine. For example, a search parameter that is concatenated into a SQL string and then passed to a template can enable injection into both the database layer and the rendering layer.
With Cockroachdb, which speaks PostgreSQL wire protocol, typical Axum integrations use a PostgreSQL driver (e.g., postgres or sqlx) and an ORM or query builder. If Axum dynamically constructs SQL strings that include user input and then uses a template to display results, an SSTI vector can emerge when the same user input is reused in both contexts. A malicious payload like {{7*7}} in a search field could be stored in Cockroachdb and later rendered by a template engine that evaluates expressions, leading to unintended code execution within the template context.
In a typical Axum handler, if you accept a query parameter and embed it directly into a SQL string and then into a template, you create two adjacent attack surfaces: one at the database interaction layer and one at the presentation layer. For instance, an attacker might attempt to manipulate template control structures to access other data or to probe the database schema through error messages reflected in rendered output. Because Cockroachdb enforces strong consistency and supports complex queries, any injection that alters query logic can lead to data leakage or unauthorized data manipulation.
The combination increases the blast radius: a vulnerable template can expose database schema details via error messages or introspection, while a vulnerable query path can allow an attacker to infer database structure through timing or error-based techniques. Proper separation of concerns — using parameterized queries for database access and strict context-aware escaping for templates — is essential to prevent chained vulnerabilities across the Axum application and the Cockroachdb layer.
Cockroachdb-Specific Remediation in Axum — concrete code fixes
Remediation focuses on three layers: database access, data storage, and template rendering. Always use parameterized SQL with the PostgreSQL driver, avoid interpolating user input into SQL strings, and apply strict output encoding in templates based on the context (HTML, attribute, JavaScript, or URL).
For Axum handlers that interact with Cockroachdb, prefer using sqlx with compile-time verification or the lower-level postgres crate, binding parameters explicitly. Never construct SQL by string concatenation with user-controlled values.
Safe query patterns with Cockroachdb in Axum
- Use prepared statements with placeholders for all dynamic values.
- Validate and sanitize input before storage; store raw user input safely and escape at render time.
- Apply context-specific escaping in templates to prevent reflected injection.
Example: Safe Axum handler with Cockroachdb and Askama
use axum::{routing::get, Router};
use askama::Template;
use sqlx::postgres::PgPoolOptions;
use sqlx::PgPool;
use std::net::SocketAddr;
// Define a template that escapes variables by default
#[derive(Template)]
#[template(path = "search.html")]
struct SearchTemplate {
results: Vec,
}
// Safe handler: parameterized query, no string interpolation
async fn search(
pool: &PgPool,
query: String,
) -> Result {
// Use $1 style positional parameters; Cockroachdb supports this
let rows: Vec<(String,)> = sqlx::query_as("SELECT name FROM products WHERE name LIKE $1")
.bind(format!("%{}%", query))
.fetch_all(pool)
.await
.map_err(|e| (axum::http::StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
let results: Vec = rows.into_iter().map(|(name,)| name).collect();
let template = SearchTemplate { results };
template.render().map_err(|e| (axum::http::StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))
}
#[tokio::main]
async fn main() {
let database_url = std::env::var("DATABASE_URL").expect("DATABASE_URL must be set");
let pool = PgPoolOptions::new()
.connect(&database_url)
.await
.expect("Failed to create pool.");
let app = Router::new().route("/search", get(move |query: String| search(&pool, query)));
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
axum::Server::bind(&addr)
.serve(app.into_make_service())
.await
.unwrap();
}
Example template (search.html) using Askama auto-escaping
<!DOCTYPE html>
<html>
<body>
<ul>
{% for result in results %}
<li>{{ result | safe }}</li>
{% endfor %}
</ul>
</body>
</html>
If you must conditionally include user input in template logic, use strict allowlists and avoid eval-like features of the templating engine. For Tera, prefer filters and explicit escaping; for Askama, rely on the type system and {{ var }} auto-escaping, and avoid the safe filter unless you have validated the content.
Additionally, enforce least-privilege database roles for the Cockroachdb connection used by Axum, and prefer environment-injected connection strings over hardcoded values. Regularly rotate credentials and monitor query patterns to detect anomalous injection attempts that bypass application-layer defenses.