HexBrain

curl · Report #3414088

SMTP CRLF Command Injection in CURLOPT_MAIL_FROM and CURLOPT_MAIL_RCPT

Duplicatestatus
mediumseverity
11upvotes
10 Nov 2025disclosed

How it was found

libcurl's SMTP implementation accepts CR (\r) and LF (\n) bytes in mailbox address inputs without validation. These control characters are inserted directly into SMTP commands, allowing attackers to inject arbitrary SMTP protocol commands. This enables envelope manipulation, adding unauthorized recipients, and potentially bypassing application-level email controls.

Steps To Reproduce

Environment:

  • Target: curl/libcurl source code (https://github.com/curl/curl)
  • Tested versions:
    • curl 8.17.0 (latest official release) ✅ VULNERABLE
    • curl 8.12.0-DEV (commit 58023ba52273b05deb36ec1d395df18ba29b3bde) ✅ VULNERABLE
  • Build: Standard release build
  • Date tested: 2025-11-06

Prerequisites:

  1. Build curl from source (commit 58023ba52273b05deb36ec1d395df18ba29b3bde)
  2. Download the attached proof-of-concept script: poc_smtp_crlf_injection.sh

Steps:

  1. Make the script executable:
chmod +x poc_smtp_crlf_injection.sh
  1. Run the proof-of-concept with the path to your curl binary:
./poc_smtp_crlf_injection.sh /path/to/curl/src/curl

The script will:

  • Start an SMTP server that logs all received commands
  • Execute curl with a CRLF-injected mailbox address
  • Display clear before/after comparison
  • Highlight the injected commands
  1. Observe the output showing the split SMTP commands

What should happen: curl should reject the mailbox address containing control characters with an error like "invalid characters in mailbox address" or "control characters not allowed", similar to how it rejects null bytes in HTTP headers.

What actually happens: curl accepts the CRLF characters and sends them as part of the SMTP command, resulting in protocol-level command injection:

> MAIL FROM:<[email protected]
> RCPT TO:<[email protected]> SIZE=...    ← INJECTED COMMAND
> RCPT TO:<[email protected]>            ← LEGITIMATE RECIPIENT

The MAIL FROM command is split into two separate lines, with "RCPT TO:[email protected]" being injected as a separate SMTP command. The email is now sent to both the legitimate recipient AND the attacker's address.

Evidence

Attached file demonstrating the vulnerability:

poc_smtp_crlf_injection.sh - Complete, self-contained proof-of-concept script that:

  • Starts a local SMTP server to capture commands
  • Runs curl with CRLF-injected mailbox addresses
  • Displays clear evidence of command injection
  • Requires only Python 3 and bash (no other dependencies)
  • Takes ~30 seconds to run
  • Exit code 1 = vulnerable, 0 = patched

Sample output from the PoC script:

══════════════════════════════════════════════════════════════════
VULNERABILITY CONFIRMED
══════════════════════════════════════════════════════════════════

✗ VULNERABLE: CRLF injection successful!

SMTP Commands Sent by curl:
──────────────────────────────────────────────────────────────────
> EHLO smtp_test_body.txt
> MAIL FROM:<[email protected]
> RCPT TO:<[email protected]> SIZE=59    ← INJECTED!
> RCPT TO:<[email protected]>
> DATA

Result: Email sent to BOTH recipients:
  ✓ [email protected] (legitimate)
  ✓ [email protected] (INJECTED)

The script output clearly shows:

  1. The MAIL FROM command split into two lines
  2. The injected RCPT TO command on a separate line
  3. Both recipients being accepted by the SMTP server
  4. Confirmation that the email would be sent to both addresses

Code analysis showing the vulnerability:

File: lib/smtp.c, lines 838-846

result = Curl_pp_sendf(data, &smtpc->pp,
                       "MAIL FROM:%s%s%s%s%s%s",
                       from,                 /* Mandatory - NO VALIDATION */
                       auth ? " AUTH=" : "",
                       auth ? auth : "",
                       size ? " SIZE=" : "",
                       size ? size : "",
                       utf8 ? " SMTPUTF8" : "");

File: lib/smtp.c, lines 1875-1921 (smtp_parse_address)

  • Function performs basic parsing (strips <>, finds @)
  • No validation for control characters (CR, LF, NUL)
  • Strings are passed directly to command construction

Evidence that this is NOT by-design:

curl already rejects control characters in similar contexts. From lib/cookie.c, lines 436-446:

static bool invalid_octets(const char *ptr) {
  const unsigned char *p = (const unsigned char *)ptr;
  /* Reject all bytes \x01 - \x1f (*except* \x09, TAB) + \x7f */
  while(*p) {
    if(((*p != 9) && (*p < 0x20)) || (*p == 0x7f))
      return TRUE;
    p++;
  }
  return FALSE;
}

This function explicitly rejects CR (0x0D) and LF (0x0A) in cookie values. The same validation should apply to SMTP mailbox addresses but is missing.

Impact

An attacker who controls email address inputs can inject SMTP commands by adding CRLF characters. This lets them add extra recipients to emails without the application knowing. For example, a password reset email meant for one person gets secretly copied to the attacker's email address.

Real attack scenario: A web app sends password reset emails using curl. An attacker registers username victim\r\nRCPT TO:<[email protected]> on the platform. When the app constructs the email address and passes it to curl, curl injects the extra recipient command. The attacker receives a copy of the password reset link meant for the victim.

Why this matters:

  • Many apps build email addresses from usernames + domain without checking for control characters
  • Apps expect the library to handle protocol-level validation (like curl does for cookies and HTTP headers)
  • Attackers can steal sensitive emails: password resets, verification codes, confidential reports
  • The vulnerability affects all apps using CURLOPT_MAIL_FROM, CURLOPT_MAIL_RCPT, or CURLOPT_MAIL_AUTH with any user input

This report was disclosed publicly on HackerOne and belongs to its author and the program. See the original