Email Injection in Actix with Basic Auth
Email Injection in Actix with Basic Auth — how this specific combination creates or exposes the vulnerability
Email Injection is a subset of injection vulnerabilities where attacker-controlled data is reflected into email headers or the email body in a way that can manipulate header structure, for example by injecting additional headers or newlines. When an Actix-web service builds email messages using user input and does not strictly validate or sanitize that input, newline characters (\r and \n) can allow an attacker to inject extra headers such as CC, BCC, or even a second From header.
Combining this with HTTP Basic Auth introduces a specific risk pattern. Basic Auth sends credentials with each request as a base64-encoded string that is easily decoded; while this does not directly cause email injection, it changes the threat context. If your Actix endpoint proxies or logs the Authorization header and incorporates it into an email (for example, including the username in a From or Reply-To field or logging credentials alongside user-controlled input), you may inadvertently expose credentials or provide an entry point for newline-based injection through the auth value. An attacker could supply crafted input that, when concatenated with the decoded Basic Auth username or realm, forms valid email header lines. Because Basic Auth lacks the protections of a session or token abstraction, developers may mistakenly trust the decoded username as safe, leading to insufficient sanitization when composing emails.
Consider an Actix handler that builds a password-reset email by concatenating user input directly into a header string:
use actix_web::{web, HttpResponse, Responder};
async fn send_reset_email(form: web::Form) -> impl Responder {
let to = &form.email;
let username = &form.username;
// Unsafe: newline in `username` or `email` can inject extra headers
let header = format!("From: {} <[email protected]>\r\nReply-To: {}", username, to);
// ... pass header to email library
HttpResponse::Ok().body("email triggered")
}
If an attacker provides username as [email protected]\r\nBcc: [email protected], the resulting header can redirect the message or add unintended recipients. Even with Basic Auth, if the endpoint also logs or echoes the decoded username into headers, the injected newline can escape the intended header context. The vulnerability is less about authentication mechanism and more about how the service composes messages with untrusted data; Basic Auth simply adds a field that may be mishandled in that composition.
Effective mitigations in Actix include strict allow-listing for email-related fields, rejecting or percent-encoding newlines, and avoiding concatenation of any user-controlled or auth-derived strings directly into header syntax. Treat Basic Auth credentials as untrusted input when they are used in message composition, and validate/sanitize them with the same rigor as any other parameter.
Basic Auth-Specific Remediation in Actix — concrete code fixes
Remediation focuses on two aspects: (1) never incorporate Basic Auth-derived values (such as the decoded username or password) into email headers without strict validation, and (2) ensure any user-controlled input used in headers is sanitized to prevent newline injection. Below are concrete, safe patterns for Actix-web.
1. Avoid composing headers with raw auth or user input
Do not directly interpolate decoded credentials or request parameters into header strings. Instead, use a dedicated email library that builds headers from structured data and enforces safe formatting. If you must construct headers manually, normalize line endings and reject newlines.
use actix_web::{web, HttpResponse, Result};
/// Safe header construction: reject newlines in critical fields.
fn build_email_header(from_name: &str, from_email: &str, reply_to: &str) -> Result {
// Reject newlines to prevent header injection
if from_name.contains('\n') || from_name.contains('\r') ||
from_email.contains('\n') || from_email.contains('\r') ||
reply_to.contains('\n') || reply_to.contains('\r') {
return Err("invalid header value");
}
// Use a format that does not embed raw line breaks in the body of headers.
Ok(format!("From: {} <{}>\r\nReply-To: {}", sanitize_header(from_name), from_email, sanitize_header(reply_to)))
}
fn sanitize_header(value: &str) -> String {
// Remove or replace control characters; keep only printable ASCII for simplicity.
value.chars().filter(|c| c.is_ascii_graphic() || *c == ' ' || *c == '@' || *c == '.' || *c == '-').collect()
}
2. Use structured data and escape user input
When building the email body, escape or encode user input. For headers, rely on a library that handles folding and encoding. If you must use Basic Auth, treat the decoded username as an opaque identifier and do not place it in headers that can be influenced by other input.
async fn send_reset_email_safe(form: web::Form) -> Result {
// Do not use decoded username in a header directly.
let from_display = "noreply";
let from_email = "[email protected]";
// Validate email format strictly.
if !is_valid_email(&form.email) {
return Ok(HttpResponse::BadRequest().body("invalid email"));
}
let header = build_email_header(from_display, from_email, &form.email)?;
// Pass `header` to your mailer; user input is not concatenated into raw header lines.
// Include authentication context via your mailer's authenticated session or metadata,
// rather than embedding it in the message headers.
Ok(HttpResponse::Ok().body("email triggered"))
}
fn is_valid_email(email: &str) -> bool {
// Simple structural validation; use a robust validator in production.
email.contains('@') && email.contains('.')
}
3. Middleware for stripping or normalizing newlines
In Actix, you can add a request guard or extractor that normalizes or rejects dangerous characters for specific fields. This keeps validation centralized and reduces the chance of accidental leakage via headers.
use actix_web::{dev::Payload, FromRequest, HttpRequest, error::ErrorBadRequest, web::Form};
use futures::future::{ok, Ready};
struct ValidatedForm(T);
impl FromRequest for ValidatedForm {
type Error = actix_web::Error;
type Future = Ready>;
type Config = ();
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
let form = Form::::from_request(req, payload).into_inner();
match form {
Ok(data) => {
// Example: ensure no newlines in fields that will be used in headers.
// Implement custom validation for your fields here.
ok(ValidatedForm(data))
}
Err(e) => ok(Err(ErrorBadRequest(e.to_string()))),
}
}
}
By using structured validation and avoiding raw concatenation, you mitigate Email Injection even when Basic Auth is in use. Remember that middleBrick scans can detect missing input validation and header injection risks; combine these code-level fixes with periodic scans to maintain a strong security posture.