Almost every application sends email — password resets, contact forms, notifications. Most of them build those messages by dropping user input into headers and bodies. If a single newline character slips through unfiltered, an attacker can rewrite the recipient list, forge the sender, and turn your transactional mail server into a spam and phishing engine that speaks with your domain's reputation.
What is SMTP injection?
SMTP injection (also called email header injection) is a vulnerability where an application builds an email from unsanitized user input, letting an attacker inject carriage-return/line-feed (CRLF) sequences to add or alter mail headers and body content. It is a specific case of CRLF injection, catalogued under CWE-93 (Improper Neutralization of CRLF Sequences). Because email headers are separated by \r\n, any input field that flows into a header and permits a newline lets the attacker start writing headers of their own.
How the attack works
Email uses a simple structure: headers, then a blank line, then the body. Suppose a contact form uses the visitor's email address as the From or Reply-To header. An attacker submits:
attacker@evil.tld%0d%0aBcc:victim1@corp.com,victim2@corp.com%0d%0aSubject:You have won
Decoded, the %0d%0a (CRLF) sequences terminate the header the developer intended and inject a Bcc to arbitrary recipients plus a new Subject. With two CRLFs the attacker can inject the blank-line separator and replace the entire message body — perfect for phishing that appears to originate from your trusted domain. Classic exploitation targets PHP's mail() function, whose additional-headers parameter has historically been a reliable injection point, but the pattern applies to any language or mail library that concatenates unvalidated input into headers.
Vulnerable vs. fixed
A PHP contact handler that trusts the submitted email for From:
// VULNERABLE — user input flows straight into headers
$email = $_POST['email'];
$message = $_POST['message'];
$headers = "From: $email\r\n"; // CRLF in $email injects new headers
mail("support@corp.com", "Contact form", $message, $headers);
The fix has two parts: strictly validate the address, and use a mail library that constructs headers through an API instead of string concatenation so newlines can never escape a field:
// FIXED — validate the address, let the library build safe headers
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Mime\Email;
$email = filter_var($_POST['email'], FILTER_VALIDATE_EMAIL);
if ($email === false) {
throw new InvalidArgumentException('Invalid email address');
}
$msg = (new Email())
->from('support@corp.com') // fixed, trusted sender
->replyTo($email) // library rejects CRLF in the field
->to('support@corp.com')
->subject('Contact form')
->text($_POST['message']);
$mailer->send($msg);
Two things changed. First, FILTER_VALIDATE_EMAIL rejects anything that is not a well-formed address, so a value containing CRLF fails outright. Second, the user's address becomes Reply-To rather than a raw From, and the mailer library treats each header as structured data — it will not let a newline in a field bleed into adjacent headers.
Prevention checklist
- Validate every address and subject field with a strict format check. Reject any input containing
\r,\n, or their encoded forms outright. - Use a well-maintained mail library (Symfony Mailer, Nodemailer, JavaMail, Python's
emailpackage) that builds headers via an API. Never concatenate user input into raw header strings. - Keep user input out of headers entirely where possible. Use a fixed
From, and put anything the user supplied into the body, not the envelope. - Encode or strip control characters from any value that must appear in a header.
- Rate-limit and monitor outbound mail so a sudden surge of recipients triggers an alert before your domain lands on a blocklist.
- Harden deliverability controls — SPF, DKIM, and DMARC limit how far a forged message can travel, blunting the impact if injection slips through.
Beyond spam: what attackers actually do with it
It is tempting to rate email header injection as low severity — "they can only send email." In practice, a forged message that genuinely originates from your transactional mail infrastructure inherits your SPF and DKIM alignment and your domain's sending reputation, which is exactly what makes phishing from it so effective. Attackers use it to send credential-harvesting mail that passes authentication checks, to add a hidden recipient on password-reset or invoice emails, and to smuggle attachments through a trusted sender. The blast radius is your domain reputation and your users' trust, not merely one extra email — which is why header injection deserves the same rigor as any other input-handling flaw.
Watch the newer sinks
The classic case is PHP's mail(), but the same bug appears anywhere headers are built from input: a Reply-To set from a form, a dynamic Subject, a Content-Disposition filename on an attachment, or raw SMTP commands built by a custom client. Structured mail libraries close most of these, but only if you pass values through their typed API rather than pre-building header strings yourself and handing the library a finished blob.
How Safeguard helps
Email-building code is easy to overlook because it rarely looks security-sensitive. Griffin AI code review reads how your handlers assemble messages and flags user input flowing into mail headers without validation — the exact From/Reply-To/Subject concatenation patterns that enable header injection. Safeguard's DAST engine exercises contact forms and account-email flows with CRLF payloads to confirm whether headers can actually be manipulated at runtime, so you get proof of exploitability rather than a lint warning. When the fix is swapping raw concatenation for a library API or adding address validation, Safeguard's auto-fix can open the pull request, and developers can catch the issue pre-merge with the Safeguard CLI. Vulnerable mail libraries in your dependency tree are tracked by Safeguard's software composition analysis.
See our full pricing and plans to find the tier that fits your team.
Create a free account at app.safeguard.sh/register, or read the secure-email guidance at docs.safeguard.sh.