Server Side Template Injection in Axum with Jwt Tokens
Server Side Template Injection in Axum with Jwt Tokens
Server Side Template Injection (SSTI) in an Axum application that uses JWT tokens for authentication can occur when user-controlled data is passed into a server-side template without proper escaping or validation, and that data later influences how JWT claims are interpreted or rendered in a template context. Axum does not include a built-in templating engine, but developers commonly integrate third-party template engines such as Askama or Tera. If a route deserializes a JWT token, extracts claims (for example, roles or display names), and injects those claims into a template string that is rendered without sanitization, an attacker may be able to inject template logic.
Consider an endpoint that receives an Authorization header with a JWT token, decodes it to obtain a username claim, and then passes that username into a template. If the template engine supports variable substitution and the developer does not treat the username as plain text, an attacker could supply a specially crafted token payload that, when rendered, executes unintended template commands. For example, in a Tera template, the variable interpolation syntax could be abused to access context objects or invoke filters if the input is not properly escaped. This becomes a server-side template injection vector because the attacker influences the template evaluation through a trusted authentication artifact (the JWT token).
The risk is amplified when templates perform logic based on JWT-derived data, such as rendering UI elements or constructing dynamic filters. An attacker may attempt to leverage SSTI to access application metadata, read files, or probe internal structures, depending on the capabilities of the template engine and the runtime environment. Because the JWT token is often treated as an authoritative source of identity, developers may overlook the need to sanitize its embedded claims before using them in presentation logic.
To detect this class of issue, scanners look for patterns where JWT token claims are passed into a rendering function without escaping, combined with the use of a templating engine that supports dynamic execution. For instance, if a route handler extracts a claim via a library like jsonwebtoken and directly supplies it to a template context, this is a potential indicator of unsafe usage. Remediation focuses on strict separation of data and template logic, ensuring that values derived from JWT tokens are treated as untrusted input and are either escaped according to the template engine’s rules or rendered in contexts that do not allow code execution.
Real-world examples of unsafe patterns include passing a JWT claim map directly into a template context map and using template conditionals or filters on those values, or concatenating JWT-subject strings into template snippets. These patterns can enable unintended behavior even when the JWT itself is cryptographically valid. Defense in this scenario requires both secure token handling and secure template usage, ensuring that authentication data does not become a conduit for template injection.
Jwt Tokens-Specific Remediation in Axum
Remediation for SSTI risks when using JWT tokens in Axum centers on treating all claims from the token as untrusted data and enforcing strict output encoding or avoiding dynamic template evaluation entirely. Below are concrete code examples that demonstrate secure handling of JWT tokens in Axum routes.
First, use a well-maintained JWT library such as jsonwebtoken to decode and validate tokens. Configure validation to enforce expected issuer, audience, and expiration. After decoding, extract claims and map them to a strongly typed struct rather than passing a raw claim map into a template.
use axum::{routing::get, Router};
use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation, TokenData};
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
struct Claims {
sub: String,
role: String,
}
async fn handler(
auth_header: Option<axum::http::HeaderValue>
) -> Result {
let token = auth_header.ok_or((axum::http::StatusCode::UNAUTHORIZED, "missing header".to_string()))?;
let token_str = token.to_str().map_err(|_| (axum::http::StatusCode::UNAUTHORIZED, "invalid header".to_string()))?;
let decoded = decode::<Claims>(token_str, &DecodingKey::from_secret("secret".as_ref()), &Validation::new(Algorithm::HS256))
.map_err(|_| (axum::http::StatusCode::UNAUTHORIZED, "invalid token".to_string()))?;
let claims = decoded.claims;
// Use claims safely, do not inject raw claims into templates
Ok(format!("User: {}, Role: {}", claims.sub, claims.role))
}
fn app() -> Router {
Router::new().route("/profile", get(handler))
}
In this example, the JWT claims are deserialized into a typed struct, which prevents arbitrary claim keys from being exposed to a template. If you use a template engine, pass only the necessary, validated fields as plain strings or numbers, and ensure the template does not evaluate arbitrary expressions.
For Tera templates, explicitly escape variables using the filter syntax or configure the template context to treat variables as plain text. Avoid passing a map that contains nested objects or functions that the template engine could interpret.
use axum::routing::get;
use tera::{Tera, Context};
async fn handler_with_tera(
auth_header: Option<axum::http::HeaderValue>
) -> Result {
let token = auth_header.ok_or((axum::http::StatusCode::UNAUTHORIZED, "missing header".to_string()))?;
let token_str = token.to_str().map_err(|_| (axum::http::StatusCode::UNAUTHORIZED, "invalid header".to_string()))?;
// decode and validate token as above
let claims = decode_and_validate(token_str)?; // assume a function that returns Claims
let mut context = Context::new();
// Only pass primitive, escaped values
context.insert("username", &claims.sub);
context.insert("role", &claims.role);
let rendered = Tera::one_off(
"User: {{ username }}<br>Role: {{ role | e }}", // apply escaping filter
&context,
false,
).map_err(|e| (axum::http::StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
Ok(rendered)
}
fn decode_and_validate(token: &str) -> Result<Claims, (axum::http::StatusCode, String)> {
// validation logic similar to previous example
unimplemented!()
}
Additionally, avoid concatenating JWT-derived strings into template source code or using them to select template names. Enforce a strict allowlist for any template-related decisions and validate the structure of the JWT token to ensure it matches expected formats before use. These practices reduce the attack surface and help prevent SSTI when JWT tokens are part of the request context.