Server Side Template Injection in Actix with Mutual Tls
Server Side Template Injection in Actix with Mutual Tls — how this specific combination creates or exposes the vulnerability
Server Side Template Injection (SSTI) occurs when an attacker can inject template expressions that are evaluated on the server. In Actix web applications that render dynamic content using server-side templates (for example via askama or tera), improperly sanitized inputs can lead to arbitrary code execution or information disclosure. When Mutual TLS (mTLS) is used, the server authenticates clients by requesting a client certificate. If the application uses the certificate fields (subject, common name, organization, or extensions) as template inputs without validation, the mTLS handshake—which supplies identity data into the application—becomes a vector for SSTI.
Consider an Actix service that terminates mTLS and then passes the peer’s distinguished name (DN) into a template for logging or status pages. If the DN is controlled by an authenticated (but malicious) client and the template engine evaluates expressions like {{ name }} without escaping, an attacker can inject template logic. For example, a crafted certificate with a Common Name such as {% import os %}{{ os.environ.get('SECRET') }} might cause the template engine to execute code if the engine supports arbitrary Python expressions (as with some Jinja2 or Tera configurations when macros or filters are exposed). The mTLS context supplies the tainted data, and the server processes it during rendering, creating a scenario where the trust boundary implied by mTLS is bypassed in the application logic.
Even when mTLS provides strong channel authentication, the application must still treat all client-supplied data—including certificate attributes—as untrusted. A misconfigured endpoint that exposes a debug or status route and renders certificate metadata in a template can disclose environment variables or trigger remote code execution. This is particularly relevant when developers assume mTLS alone suffices for integrity of identity data, leading to relaxed input validation. The 12 parallel security checks in middleBrick would flag this as an SSTI risk under Input Validation and identify unsafe consumption patterns under Unsafe Consumption, noting the presence of unvalidated mTLS-derived fields used in rendering.
Mutual Tls-Specific Remediation in Actix — concrete code fixes
To prevent SSTI when using mTLS in Actix, ensure certificate fields are treated as untrusted input and never passed directly into templates. Validate and sanitize any data derived from the client certificate, and use strict allowlists for expected values. Prefer structured metadata extraction with schema validation rather than raw DN string interpolation.
Below are concrete Actix examples that demonstrate safe handling of mTLS certificates.
Example 1: Extract and validate certificate fields in Actix middleware
use actix_web::{dev::ServiceRequest, Error, middleware::Next};
use actix_web_httpauth::extractors::Certificate;
use serde::{Deserialize, Serialize};
use regex::Regex;
#[derive(Debug, Deserialize, Serialize)]
struct ValidatedSubject {
cn: String,
org: Option,
}
async fn validate_certificate(cert: Option) -> Result {
let cert = cert.ok_or_else(|| actix_web::error::ErrorUnauthorized("missing certificate"))?;
let subject = cert.subject();
// Extract CN safely; reject unexpected OUs or attributes
let cn = subject.common_name().ok_or_else(|| actix_web::error::ErrorUnauthorized("missing CN"))?;
let re = Regex::new(r"^[A-Za-z0-9._-]{1,64}$").unwrap();
if !re.is_match(cn) {
return Err(actix_web::error::ErrorBadRequest("invalid CN format"));
}
let org = subject.organization().first().map(|s| s.to_string()).filter(|s| !s.is_empty());
Ok(ValidatedSubject { cn: cn.to_string(), org })
}
Example 2: Use validated data in handlers, avoiding direct template injection
use actix_web::{web, HttpResponse};
use askama::Template;
#[derive(Template)]
#[template(path = "status.html")]
struct StatusTemplate {
cn: String,
org: Option,
}
async fn status(
validated: web::ReqData,
) -> HttpResponse {
let body = StatusTemplate {
cn: validated.cn.clone(),
org: validated.org.clone(),
};
HttpResponse::Ok().content_type("text/html").body(body.render().unwrap_or_else(|_| "Error rendering".into()))
}
Example 3: Enforce mTLS at the Actix server level and reject unexpected extensions
use actix_web::web::Data;
use actix_web_httpauth::middleware::HttpAuthentication;
use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod};
fn create_ssl_config() -> SslAcceptor {
let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap();
builder.set_private_key_file("key.pem", SslFiletype::PEM).unwrap();
builder.set_certificate_chain_file("cert.pem").unwrap();
builder.set_client_ca_list(&[include_bytes("ca.pem")][..]);
builder.set_verify(openssl::ssl::SslVerifyMode::PEER | openssl::ssl::SslVerifyMode::FAIL_IF_NO_PEER_CERT,
openssl::ssl::SslVerifyCallback::new(|_, _| true));
builder.build()
}
// In main:
let ssl = create_ssl_config();
let server = actix_web::HttpServer::new(move || {
let auth = HttpAuthentication::with_callback(|req, head| {
// Use the validated extraction from Example 1
validate_certificate(req.certificate()).into()
});
App::new()
.wrap(auth)
.app_data(web::ReqData::new(ValidatedSubject { cn: String::new(), org: None }))
.service(web::resource("/status").to(status))
})
.bind_openssl("127.0.0.1:8443", ssl)?
.run();
By validating and whitelisting certificate fields, and by avoiding direct interpolation of raw DN strings into templates, you mitigate SSTI while preserving mTLS benefits. middleBrick can detect remaining risks by scanning endpoints that accept mTLS-authenticated requests and analyzing template usage patterns.