Email Injection in Axum with Mutual Tls
Email Injection in Axum with Mutual Tls — how this specific combination creates or exposes the vulnerability
Email Injection occurs when user-controlled data is concatenated into email headers or message bodies without validation or sanitization, enabling an attacker to inject additional headers such as Cc:, Bcc:, or to chain messages. In Axum, this often happens when route handlers build email payloads (for example via SMTP or an email-sending library) directly from request inputs such as JSON bodies or query parameters. When Mutual Tls is enforced, the server presents a client certificate requirement, which can create a false sense of strong identity assurance. Operators may assume that because the transport is mutually authenticated, inputs are safe, leading to relaxed input validation. This is a misconfiguration: Mutual Tls secures channel identity but does not constrain what application-level data is allowed into email headers.
With Mutual Tls, the server validates the client certificate before the application code runs. If the application then uses certificate subject fields (e.g., Common Name or email address) in email headers without sanitization, an attacker who possesses a valid client certificate can supply crafted fields that lead to header injection. For example, a subject like "Bob, [email protected]" in the certificate’s Common Name could be placed directly into a Cc header, resulting in unintended recipients or header splitting. Additionally, if the Axum handler reflects user input into email message bodies without escaping newlines (\r\n), an attacker can inject SMTP commands or additional headers after the message body has started, manipulating the email flow.
Consider an Axum handler that accepts a JSON payload with recipient and uses the authenticated peer’s certificate subject as the sender. If the handler does not validate the recipient field for newline or header injection characters, and directly passes values to an email library, the following can occur:
- Input
{"recipient": "[email protected]\r\nCc: [email protected]"}results in an additionalCcheader being added to the email. - If the email library serializes headers naively, the injected
Cccan bypass distribution lists or send copies to unintended parties. - In log outputs or error messages, reflected unsanitized input can lead to information disclosure or facilitate further injection chains.
middleBrick can detect such issues by correlating OpenAPI specifications (where email-related endpoints are documented) with runtime probes that test for header manipulation and input validation weaknesses. Even when Mutual Tls is used, the scanner’s unauthenticated attack surface testing can highlight missing sanitization on email-related inputs and improper handling of authenticated identity data.
Mutual Tls-Specific Remediation in Axum — concrete code fixes
To remediate Email Injection in an Axum service with Mutual Tls, you must treat certificate-derived data as untrusted input and apply strict validation and encoding for any email headers or message content. Do not rely on the transport layer to enforce application-level correctness. Below are concrete patterns and code examples.
1. Validate and sanitize all user inputs used in email messages
Never concatenate user input directly into email headers. Use a dedicated email library that enforces header separation and provides safe building APIs. In Rust, the lettre crate is a common choice. Validate and encode recipient and sender fields, and avoid using certificate subject fields in headers unless you explicitly sanitize them.
use axum::{routing::post, Router};
use lettre::message::Mailbox;
use serde::Deserialize;
#[derive(Deserialize)]
struct EmailRequest {
recipient: String,
// other fields...
}
async fn send_email(Json(payload): Json<EmailRequest>) -> (StatusCode, String) {
// Validate recipient: allow only email characters, limit length, reject control characters
if !payload.recipient.chars().all(|c| c.is_ascii_alphanumeric() || c == '@' || c == '.' || c == '-' || c == '_') {
return (StatusCode::BAD_REQUEST, "Invalid recipient");
}
// Parse using a safe mailbox builder; this prevents header injection
let mailbox: Mailbox = format!("User <{}>", payload.recipient).parse().expect("valid mailbox");
// Build message with lettre, which keeps headers separate from body
let email = Message::builder()
.from("[email protected]".parse().unwrap())
.to(mailbox)
.subject("Notification")
.body(String::from_utf8_lossy(b"Hello, this is safe.").to_string())
.unwrap();
// Send via SMTP transporter (not shown)
StatusCode::ACCEPTED
}
fn app() -> Router {
Router::new().route("/email", post(send_email))
}
2. Treat Mutual Tls identity data as untrusted when used in emails
If you use certificate fields (e.g., subject) as sender identifiers, sanitize them strictly. Do not place them directly into headers. Instead, map them to a trusted sender list or encode them.
use axum::extract::Extension;
use std::sync::Arc;
struct Config {
allowed_senders: Vec<String>,
}
async fn send_email_with_mtls(
Json(payload): Json<EmailRequest>,
Extension(config): Extension<Arc<Config>>
) -> (StatusCode, String) {
// Suppose we want to use certificate subject as sender; first validate it
let sender_from_cert = get_cert_subject_from_request(); // hypothetical extraction
if !config.allowed_senders.contains(&sender_from_cert) {
return (StatusCode::FORBIDDEN, "Sender not allowed");
}
// Still sanitize before using in headers; use a safe mapping
let safe_sender = format!("{}", sender_from_cert.replace("\r", "").replace("\n", ""));
let mailbox: Mailbox = format!("{}<{}>", safe_sender, payload.recipient).parse().unwrap();
let email = Message::builder()
.from(mailbox)
.to("[email protected]".parse().unwrap())
.subject("Notification")
.body(String::from_utf8_lossy(b"Hello").to_string())
.unwrap();
StatusCode::ACCEPTED
}
3. Reject or escape newlines in any user-controlled strings used in email contexts
Ensure that any data placed into headers or the start of the message body cannot contain \r or \n. For message bodies, use libraries that handle MIME boundaries safely rather than string concatenation.
fn assert_no_crlf(s: &str) -> bool {
!s.contains('\r') && !s.contains('\n')
}
// Apply to any field that may end up in a header
if !assert_no_crlf(&payload.recipient) {
return (StatusCode::BAD_REQUEST, "Invalid input: CRLF not allowed");
}
By combining strict input validation, safe email libraries, and careful treatment of Mutual Tls identity data, you can prevent Email Injection while preserving the security benefits of mutual authentication.