GitHub Security Advisory: GHSA-9j88-vvj5-vhgr
Package: MailKit (NUGET)
Severity: MODERATE (CVSS 6.5)
CWE: CWE-74: Improper Neutralization of Special Elements in Output Used by a Downstream Component ('Injection')
Vulnerable versions: < 4.16.0
Patched in: 4.16.0

Summary: MailKit has STARTTLS Response Injection via unflushed stream buffer that enables SASL mechanism downgrade

Details:
### Summary

A STARTTLS Response Injection vulnerability in MailKit allows a Man-in-the-Middle attacker to inject arbitrary protocol responses across the plaintext-to-TLS trust boundary, enabling SASL authentication mechanism downgrade (e.g., forcing PLAIN instead of SCRAM-SHA-256). The internal read buffer in `SmtpStream`, `ImapStream`, and `Pop3Stream` is not flushed when the underlying stream is replaced with `SslStream` during STARTTLS upgrade, causing pre-TLS attacker-injected data to be processed as trusted post-TLS responses. This is the same vulnerability class as CVE-2021-23993 (Thunderbird), CVE-2021-33515 (Dovecot), and CVE-2011-0411 (Postfix).

### Details

The `Stream` property in `SmtpStream` (line 84-86), `ImapStream`, and `Pop3Stream` is a simple auto-property with no buffer reset:

```csharp
public Stream Stream {
    get; internal set;  // ← No buffer reset on set!
}
```

During the STARTTLS upgrade in `SmtpClient.cs` (lines 1372-1389):

```csharp
// Reads STARTTLS response — "220 Ready" consumed, any extra data stays in buffer
response = Stream.SendCommand("STARTTLS\r\n", cancellationToken);

// Swaps to TLS — buffer NOT flushed!
var tls = new SslStream(stream, false, ValidateRemoteCertificate);
Stream.Stream = tls;
SslHandshake(tls, host, cancellationToken);

// Reads EHLO response — processes INJECTED pre-TLS data from buffer first!
Ehlo(true, cancellationToken);
```

A MitM appends extra data after the `"220 Ready\r\n"` STARTTLS response. Both arrive in one TCP read into `SmtpStream`'s 4096-byte internal buffer. `ReadResponse()` parses `"220 Ready"` and stops — the injected data remains at `inputIndex`. After `Stream.Stream = tls`, the buffer is not cleared. When `Ehlo()` calls `ReadResponse()`, it checks `inputIndex == inputEnd` — this is FALSE (injected data exists), so it processes the buffered pre-TLS data without reading from the new TLS stream.

The same pattern exists in `ImapClient.cs` (lines 1485-1509) and `Pop3Client.cs`.

**Attack flow:**
```
Client                    MitM                     Real Server
  |--- STARTTLS ---------->|--- STARTTLS ----------->|
  |                        |<-- 220 Ready -----------|
  |<-- "220 Ready\r\n"-----|                         |
  |    "250-evil\r\n"       |  ← INJECTED            |
  |    "250 AUTH PLAIN\r\n" |  ← INJECTED            |
  |    "250 OK\r\n"         |  ← INJECTED            |
  |===== TLS HANDSHAKE ====|==== PASSES THROUGH =====|
  |--- EHLO (over TLS) --->|                         |
  | Reads from BUFFER:     |                         |
  | "250 AUTH PLAIN"       |  ← PRE-TLS DATA        |
  | PROCESSED AS POST-TLS! |                         |
```

**Suggested fix:** Reset buffer indices when the stream is replaced:
```csharp
internal set { stream = value; inputIndex = inputEnd; }
```

### PoC

Self-contained C# PoC — creates a fake SMTP server that injects a crafted EHLO response into the STARTTLS reply:

```csharp
using System; using System.Net; using System.