Email header injection lets anyone who can type into a contact form, newsletter signup or "share this page" feature turn your application into a relay that sends mail under your domain to recipients you never chose. The flaw is simple: user input that ends up in a mail header carries a carriage return and line feed (CRLF), and the mail library or MTA reads the text after it as new headers such as Bcc or a second body. The result is spam and phishing sent from your infrastructure, damage to your sending reputation and SPF/DKIM-aligned mail that receivers trust. This guide is for developers, application security engineers and assessors who own forms and transactional mail. It covers how the flaw works at protocol level (without payloads), where it hides, which library protections exist, how to test for it as a defender and how to detect abuse.
Key takeaways
- It is CRLF injection in a mail context. MITRE classifies it under CWE-93 (Improper Neutralization of CRLF Sequences), a child of CWE-74, and lists a real case where CRLF in an address or name field created a spam proxy.
- Headers are the danger zone, not the body. Any user value placed in From, Reply-To, Subject, To, Cc or a display name must never contain CR or LF; the body is safer but can still carry SMTP-level issues if you speak SMTP directly.
- Modern libraries help if you use their structured APIs. Python's email.policy raises ValueError for CR or LF in a header value and, since Python 3.13, refuses to generate headers that would split; PHP's mail() only warns you to sanitise additional_headers yourself.
- Never put the visitor's address in From. Send from a fixed, authenticated address you own and place the validated visitor address in Reply-To, which also keeps DMARC alignment intact.
- Detection lives in the mail logs. A spike in recipients per form submission, unknown Bcc recipients or bounces from addresses nobody entered are the clearest signs that a form is being abused.
How header injection works
An internet message is a block of header lines followed by a blank line and a body. RFC 5322 section 2.2 defines each header field as a field name, a colon and a field body, terminated by CRLF; a body may not contain a bare CR or LF except as part of folding, where a CRLF is followed by a space or tab. Section 3.6 limits To, Cc, Bcc and Subject to at most one occurrence and requires exactly one From. Those rules mean that the only thing separating "data inside the Subject" from "a brand new Bcc header" is a CRLF sequence.
When an application builds headers by concatenating strings, for example taking the name or email address a visitor typed and writing it into a From or Reply-To line, an attacker who submits a value containing an encoded line break can end the intended header and start their own. Typical outcomes are added Bcc or Cc recipients, an overridden Subject, a replaced Content-Type that turns the message into attacker-controlled HTML, or an early blank line that starts a new body. Because the message leaves your server, it inherits your IP reputation and any SPF and DKIM authorisation you have configured.
The OWASP Web Security Testing Guide treats the server-side variant as WSTG-INPV-10, Testing for IMAP/SMTP Injection: applications that talk directly to a mail server can, if input is not sanitised, pass attacker-chosen protocol commands to a back-end server that is often less hardened than the web tier. The listed impacts include evading application restrictions and anti-automation controls, leaking information and relaying spam. See the CRLF Injection and HTTP Response Splitting Guide for the same root cause in HTTP headers, and the Host Header Attacks and Validation Guide for a related flaw that poisons password reset emails.
| Variant | Where the input lands | What an attacker gains | Primary fix |
|---|---|---|---|
| Header injection | From, Reply-To, Subject, To, Cc, display names built by string concatenation | Extra recipients (Bcc relay), spoofed content, overridden MIME type | Structured header API that rejects CR and LF; fixed From |
| SMTP command injection | Values written raw into an SMTP conversation (MAIL FROM, RCPT TO, DATA) | Extra envelope recipients, additional messages in one session | Use a maintained SMTP client library; never hand-write protocol lines |
| IMAP command injection | Webmail mailbox names, message IDs, search terms | Reading or altering mailboxes beyond the user's scope | Library quoting, allow-list of mailbox identifiers |
| Sendmail argument injection | Extra parameters passed to a local sendmail binary (for example PHP's additional_params) | Changed envelope sender or extra sendmail options | Do not pass user input to MTA arguments; use a library or API |
| Body abuse without injection | Free-text message field echoed to a third party ("send to a friend") | Phishing text delivered under your brand to arbitrary recipients | Only send to fixed internal addresses or verified users |
Where it hides in real applications
- Contact and enquiry forms: the visitor's name and email are commonly copied into From or Reply-To. This is the classic spam relay. See the WordPress Security Checklist for keeping form plugins patched.
- "Email this page" and referral features: both the recipient and a personal message come from the visitor, so even without injection the feature can send arbitrary content to arbitrary people.
- Account flows: registration, password reset and email change forms put user-supplied addresses into To. Validation gaps here also cause enumeration and token leakage, which the OWASP email verification cheat sheet addresses with single-use, time-limited tokens that are never logged.
- Notification templates: order confirmations or ticket systems that place a customer-entered subject or reference number into the Subject header.
- Legacy mail calls: PHP mail() with a hand-built additional_headers string, Perl or shell scripts piping into sendmail, and custom SMTP code written before modern libraries were available.
- Webmail and mail-adjacent tools: anything that sends IMAP commands built from user input, which is the main target of WSTG-INPV-10.
Library and language protections
The safest design is to let a maintained library build the message from typed fields and never assemble raw header text yourself. How much a library protects you varies, so know what yours actually does.
| Platform | Documented behaviour | What you still need to do |
|---|---|---|
| Python email package (email.policy.default / SMTP) | Setting a header raises ValueError if the value contains CR or LF; verify_generated_headers (added in Python 3.13, default True) makes the generator refuse headers that would be parsed as multiple headers | Use EmailMessage with policy.default, not the legacy compat32 policy; never set verify_generated_headers to False; catch the exception and reject the request |
| PHP mail() | The manual states that if outside data are used to compose additional_headers, the data should be sanitised so that no unwanted headers can be injected; additional_params is passed through escapeshellcmd(), which stops command execution but still allows extra parameters; since PHP 7.2 headers may be passed as an array | Strip or reject CR and LF in every header value yourself, pass headers as an array, never pass user input in additional_params; prefer a maintained mail library |
| Transactional email APIs (HTTP JSON) | Recipients and headers are separate JSON fields rather than raw text | Still validate addresses and lengths, restrict who can be a recipient, and keep the API key server-side with least privilege |
| Framework mailers | Most provide address objects and header setters rather than raw strings | Check the framework's documentation for CR and LF handling in custom headers and display names before relying on it |
Two points apply everywhere. First, input validation and output handling are separate controls: validate that an email field is an address, and also make sure the code that writes headers neutralises line breaks. See the Input Validation and Output Encoding Guide. Second, OWASP advises using a well-tested address validation library rather than a custom regex, because strict regexes reject valid addresses, and notes that format validation does not prove mailbox access.
Secure design for forms and transactional mail
- Inventory every send path. Search code for mail(), sendmail invocations, SMTP client usage and calls to email APIs. Record which fields are user-controlled and which header each lands in.
- Fix the sender. Send every form message from a fixed address on a domain you control (for example noreply@ or forms@), authenticated with SPF, DKIM and DMARC. Put the visitor's validated address in Reply-To only.
- Fix the recipients. A contact form should only ever deliver to a configured internal address or ticket queue. If a feature must send to a visitor-entered address, require the visitor to be a signed-in, verified user and cap recipients per message.
- Reject, do not clean. If any header-bound value contains CR, LF or other control characters, reject the whole submission and log it. Silent stripping hides attacks and can produce surprising results.
- Use structured APIs. Build messages with the library's message and address objects, pass headers as typed fields or arrays, and remove any string concatenation of header text.
- Constrain lengths and character sets. Cap name, subject and reference fields at sensible lengths and encode non-ASCII subjects as the library requires (PHP's manual notes the subject must satisfy RFC 2047).
- Add anti-automation. Rate limit submissions per IP and per session, add bot controls, and alert on high-frequency patterns, as the OWASP email cheat sheet recommends for email flows. See the Bot Management Guide.
- Scope the sending credential. Give the application its own SMTP or API credential, restricted to the sending domain and addresses it needs, stored in a secrets manager and rotated. See the Application Email Credentials Guide.
Testing it safely
Treat this as an assurance activity on systems you own or are authorised to test, ideally in a non-production environment with a mail sink so no real recipient ever receives test messages.
- Code review first: grep for header strings assembled with user input, legacy mail() calls with concatenated headers and any raw SMTP or IMAP protocol writing. Code review finds most cases faster than black-box testing.
- Unit tests: for each header-bound field, add tests asserting that values containing CR, LF or both are rejected and that the message generated has exactly one of each singleton header (From, To, Subject) as RFC 5322 section 3.6 requires.
- Dynamic tests: follow WSTG-INPV-10 to identify injection points and assess impact, and confirm in the mail sink that no extra recipients or headers appear. Keep test cases in the tester's methodology rather than in tickets that circulate widely.
- Requirement mapping: OWASP ASVS 5.0 requirement V1.3.11 states that the application must sanitise user input before passing it to mail systems to protect against SMTP or IMAP injection. Use it as the acceptance criterion for remediation.
- Regression: keep the CR and LF unit tests in CI so a future refactor or library change cannot silently reintroduce the flaw.
Detection and measurement
Injection that succeeded shows up as mail that looks wrong relative to the form that generated it. Log enough at the application layer to tie each message to a submission.
| Signal | Where to look | Why it matters |
|---|---|---|
| Rejected submissions containing CR or LF | Application logs (field name, source IP, timestamp, never the full payload in shared dashboards) | Direct evidence of probing |
| More than the expected number of recipients per form message | MTA or email API logs | Classic Bcc relay pattern |
| Sudden rise in outbound volume from the form sender address | Email API dashboards, MTA queue metrics | Abuse in progress or a bot campaign |
| Bounces and complaints for addresses nobody entered | Bounce processing, feedback loops, DMARC aggregate reports | Mail is going to attacker-chosen lists |
| Blocklisting of the sending IP or domain | Reputation monitoring | Lagging indicator that relay abuse already happened |
Useful metrics: percentage of send paths using structured APIs (target 100%), number of header-bound fields covered by CR and LF unit tests, form submissions rejected per week for control characters, average recipients per form message (should be exactly the configured number), and mean time from first abuse signal to form disablement.
If you find active abuse, disable the form or feature, rotate the sending credential, review outbound logs for the abuse window, notify your email provider and check blocklists. See the Email Forensics and Header Analysis Guide for reading the headers of the messages that were sent.
Common failure patterns
- Putting the visitor in From: spoofs your own domain, breaks DMARC alignment and is the field most often concatenated into headers.
- Validating only on the client: browser-side email checks are bypassed by sending the request directly; validation and neutralisation must be on the server.
- Stripping "\n" but not "\r": partial filters miss variants. Reject any control character in header values instead of filtering selected ones.
- Trusting a legacy wrapper: old helper functions that build header strings survive refactors. Python code on the compat32 policy or with verify_generated_headers turned off loses protections the modern policy gives.
- Open "send to a friend" features: even with perfect header handling, letting anonymous users choose the recipient and the message text creates a phishing relay.
- Over-privileged sending credentials: an API key that can send as any address on the domain turns a form flaw into domain-wide spoofing.
Framework mapping
| Framework | Reference | Relevance |
|---|---|---|
| MITRE CWE | CWE-93 Improper Neutralization of CRLF Sequences (child of CWE-74) | Root weakness class |
| OWASP ASVS 5.0 | V1.3.11 | Sanitise user input passed to mail systems against SMTP or IMAP injection |
| OWASP WSTG | WSTG-INPV-10 Testing for IMAP/SMTP Injection | Test procedure for assessors |
| NIST SP 800-53 Rev 5 | SI-10 Information Input Validation; SI-4 System Monitoring | Validation of inputs and monitoring of outbound abuse |
| ISO/IEC 27001:2022 Annex A | A.8.28 Secure coding | Secure coding practice for mail-sending code |
Practitioner checklist
- List every code path that sends mail and mark each user-controlled field and the header it reaches.
- Send all form mail from a fixed authenticated address; put the visitor's validated address in Reply-To only.
- Restrict recipients to configured internal addresses unless the sender is a verified, signed-in user.
- Reject any submission with CR, LF or other control characters in a header-bound field, and log the rejection.
- Replace string-built headers with structured library APIs; on Python use policy.default and keep verify_generated_headers on.
- Never pass user input to sendmail arguments or PHP's additional_params.
- Add CR and LF unit tests per field and keep them in CI; use ASVS V1.3.11 as the acceptance criterion.
- Rate limit and bot-protect forms, and alert on recipients per message and outbound volume.
- Scope, vault and rotate the application's sending credential.
Standards and references
- MITRE: CWE-93 Improper Neutralization of CRLF Sequences
- OWASP: WSTG-INPV-10 Testing for IMAP/SMTP Injection
- OWASP: Application Security Verification Standard 5.0
- IETF: RFC 5322 Internet Message Format
- Python Software Foundation: email.policy Policy Objects
- PHP: mail() function manual
- OWASP: Email Validation and Verification Cheat Sheet
- OWASP: Input Validation Cheat Sheet
Related NHI Mgmt Group resources: CRLF Injection and HTTP Response Splitting Guide · Host Header Attacks and Validation Guide · Application Email Credentials Guide · Email Security Guide