GHSA-54wq-72mp-cq7c: Mailpit has an SMTP Header Injection via Regex Bypass
Vulnerability Report: SMTP Header Injection via Regex Bypass**Vulnerable Code:** `mailpit/internal/smtpd/smtpd.go`Executive SummaryMailpit's SMTP server is vulnerable to **Header Injection** due to an insufficient Regular Expression used to validate `RCPT TO` and `MAIL FROM` addresses. An attacker can inject arbitrary SMTP headers (or corrupt existing ones) by including carriage return characters (`\r`) in the email address. This header injection occurs because the regex intended to filter control characters fails to exclude `\r` and `\n` when used inside a character class.RFC Compliance & Design Analysis**"Is this behavior intentional for a testing tool?"**
No. While testing tools are often permissive, this specific behavior violates the core SMTP protocol and fails the developer's own intent.**RFC 5321 Violation:** The SMTP protocol strictly forbids Control Characters (CR, LF, Null) in the envelope address (`Mailbox`).*RFC 5321 Section 4.1.2:* A `Mailbox` consists of an `Atom` or `Quoted-string`. An `Atom` explicitly excludes "specials, SPACE and CTLs" (Control Characters).**Failed Intent:** The existence of `\v` in the regex `[^<>\v]` proves the developer **intended** to block vertical whitespace. The vulnerability is that `\v` in Go regex (`re2`) inside brackets `[]` matches *only* Vertical Tab, not CR/LF. If the design were to allow everything, the `\v` exclusion wouldn't exist.**Data Corruption:** Allowing `\r` results in the generation of malformed `.eml` files where the `Received` header is broken. This is not a feature; it's a bug that creates invalid email files.RFC 5321 also enforces address lengths which are not applied in Mailpit.Technical AnalysisThe FlawThe vulnerability exists in the regex definitions used to parse SMTP commands:The developer likely intended `[^<>\v]` to mean "Match anything that is NOT a `<` OR `>` OR `Vertical Whitespace`".However, in Go's `regexp` (RE2) syntax, the behavior of `\v` changes depending on context:**Outside** brackets: `\v` matches all vertical whitespace: `[\n\v\f\r\x85\u2028\u2029]`.**Inside** brackets (`[...]`): `\v` matches **only** the Vertical Tab character (`\x0B`).**Result:** The regex `[^<>\v]` **allows** Carriage Return (`\r`) and Line Feed (`\n`) characters to pass through, as they are not `<` or `>` or `\x0B`.Exploit ScenarioExploit ScenarioWhen Mailpit constructs the `Received` header, it uses the validated recipient address directly:If `to[0]` contains `victim\rINJECTED-HEADER: YES`, the resulting string in memory becomes:While `bufio.ReadString` prevents injecting immediate `\n` (newlines), `\r` (Carriage Return) bypasses this check.**The Result:** The stored EML file contains a "Bare CR".**RFC Violation:** RFC 5321 strictly forbids Bare CR. Lines must end in CRLF.**UI Behavior:** Browsers typically render Bare CR as a space, so it may look like `victim INJECTED` in the Mailpit UI.**Real Impact:** The raw email is corrupted. If this email is exported or relayed, downstream systems (Outlook, older MTAs) may interpret the Bare CR as a line break, triggering a full **Header Injection**. Furthermore, Mailpit failing to reject this gives developers a **false sense of security**, as their code might be generating malformed emails that work in Mailpit but fail in production (e.g., with Gmail or Exchange).Raw EML VerificationThe following screenshot of the raw `.eml` file confirms that the `\r` character successfully broke the `Received` header structure in the stored file, effectively creating a new line for the injected content.As seen in lines  of the screenshot:The `INJECTED_VIA_CR:YES` payload is treated as a start of a new line by the text editor (VS Code), which honors `\r` as a line break. This proves the injection matches the "Bare CR" attack vector.Additional Proof of Concepts1. Null Byte Injection (`\x00`)The regex `[^<>\v]+` also allows the Null Byte (`\x00`).
**Test:** `test_null_byte.py` sent `RCPT TO:<victim\x00-NULL-BYTE-HERE>`.
**Result:** Server accepted the message (`250 OK`).
**Impact:** The API returns an empty `[]` for the To field in the message summary, indicating the parser failure in the UI/API layer. The raw message content confirms the Null Byte is stored in the database.3. Detailed Character CompatibilityTests (0-127 ASCII) confirm that the regex `[^<>\v]` blocks **only** the following:`<` (Less Than)`>` (Greater Than)`\x0B` (Vertical Tab)**Crucially, it ALLOWS:**
| Character | Hex | Regex Status | Network Status | Impact |
| :--- | :--- | :--- | :--- | :--- |
| **Carriage Return** | `\r` (`0x0D`) | **ALLOWED** | **Passed** | **Header Injection** |
| **Line Feed** | `\n` (`0x0A`) | **ALLOWED** | Blocked* | *Blocked by `bufio.ReadString`, not regex. |
| **Null Byte** | `\x00` (`0x00`) | **ALLOWED** | **Passed** | API DoS / Corrupt Data |
| **Tab** | `\t` (`0x09`) | **ALLOWED** | **Passed** | Formatting issues |
| **Delete** | `\x7F` (`0x7F`) | **ALLOWED** | **Passed** | Potential obfuscation |
| **Controls** | `0x01`-`0x1F` | **ALLOWED** | **Passed** | (Except `0x0A`, `0x0B`, `0x0D`) |*This confirms that the regex fails to implement a proper "Safe Text" allowlist, defaulting instead to a flawed denylist.*Proof of ConceptThe following Python script demonstrates the injection of a "bare CR" into the headers, which is successfully accepted by the server.RemediationUpdate the regex to explicitly exclude `\r` and `\n`, or use the correct character class escape for control characters.**Recommended Fix:**
Use `\x00-\x1F` to exclude all ASCII control characters.Alternatively, strictly exclude CR and LF:Classification & References**CWE-93:** [Improper Neutralization of CRLF Sequences ('CRLF Injection')](https://cwe.mitre.org/data/definitions/93.html)**CWE-150:** [Improper Neutralization of Escape, Meta, or Control Sequences](https://cwe.mitre.org/data/definitions/150.html)**OWASP:** [Injection Flaws](https://owasp.org/www-community/attacks/Injection_Flaws)**CAPEC-106:** [Command Injection](https://capec.mitre.org/data/definitions/106.html) (Related usage pattern)[[RFC 5321 Section 4.5.3.1 - Size Limits](https://datatracker.ietf.org/doc/html/rfc5321#section-4.5.3.1)](https://datatracker.ietf.org/doc/html/rfc5321#section-4.5.3.1)
CVSS v3.1: 5.3