SMTP Smuggling: One Bug That Spoofed Past SPF, DKIM, and DMARC

In December 2023 a researcher sent email that impersonated anyone he liked and still passed SPF, DKIM, and DMARC. He didn't break the authentication. He made the receiving server read one connection as two messages.

We spent twenty years stacking authentication on top of email. SPF to say which servers may send for a domain. DKIM to sign the message so tampering shows. DMARC to tie the two together and tell receivers what to do when they disagree. It is a lot of machinery, and when it all lines up it answers a real question: did this message actually come from a server the domain owner authorized, unchanged?

In December 2023, Timo Longin at SEC Consult published a technique that sailed straight past all three at once. He sent mail that impersonated addresses at large providers, and it arrived with SPF, DKIM, and DMARC all showing pass. Not “the checks were misconfigured.” Not “the domain had a weak record.” The checks ran, evaluated correctly, and returned pass on a forged message. He called it SMTP smuggling, and the reason it works is one of those things that is obvious in retrospect and unsettling once you see it: none of that authentication machinery gets a say in where one email ends and the next begins.

The line that means “I’m done”

SMTP is a conversation. A client connects, says who the mail is from and who it’s to, then says DATA and streams the message body. The body can be any length, so there has to be an agreed signal for “the message is over.” That signal is a line containing a single dot, framed by carriage-return-line-feed on both sides: <CR><LF>.<CR><LF>. The server reads bytes until it sees exactly that sequence, and then it knows the message is done and anything after it is a new command.

That end-of-data marker is the entire ballgame. Everything before it is one email’s content. Everything after it is the next SMTP transaction — a fresh MAIL FROM, RCPT TO, DATA. If two servers ever disagree about what counts as that marker, you have a seam, and a seam is where you smuggle.

Here’s the disagreement. The standard says the terminator is <CR><LF>.<CR><LF>. But servers have spent decades being forgiving about line endings, because clients over the years have been sloppy about them. Some servers will also accept a bare <LF>.<LF>, or a lone <CR>.<CR>, as end-of-data. Others won’t — they hold out for the exact canonical sequence. Neither behavior is crazy on its own. Being liberal in what you accept is practically the unofficial motto of internet protocols. But the moment an outbound server and an inbound server draw that line in different places, an attacker can put a message on the wire that the two of them chop into different pieces.

Two servers, two readings, one forged email

Walk through what the attacker actually does. They have a legitimate account on a provider whose outbound server holds out for the exact canonical terminator — call it the sending side. They compose a single message and bury inside its body a sequence like <LF>.<LF> followed by a complete second SMTP transaction: another MAIL FROM, another RCPT TO, another DATA block containing a spoofed email from, say, the CEO of some company the attacker wants to impersonate.

The sending server looks at that buried <LF>.<LF> and does not treat it as the end of the message — to its strict reading, only <CR><LF>.<CR><LF> ends a message, so that’s just some bytes inside the body. So it relays the whole thing as one email, over its own trusted connection, to the receiving server. And because it’s the provider’s own legitimate outbound server making that connection, the transaction passes SPF (the connecting IP is authorized), DKIM (the provider signs it), and DMARC (the two align). The envelope is genuinely the provider’s.

Then the receiving server reads the same bytes — and it’s one of the servers that honors a bare <LF>.<LF> as end-of-data. So at that exact byte, it decides the first message is over. Everything after becomes, in its eyes, a brand-new SMTP command sequence: the smuggled MAIL FROM, the smuggled RCPT TO, the smuggled DATA. It accepts a second email that the sending provider never knew it was sending, addressed however the attacker wrote it, and delivers it. That second message rode in on the trusted connection, so from the receiver’s logs it inherits the same authenticated context. SPF pass. DKIM pass. DMARC pass. On a forgery.

Nobody broke a signature. Nobody guessed a key. The attacker exploited the fact that “where does this email end” is a parsing decision, and two parsers made it differently.

Why the auth stack couldn’t have caught it

This is the part I keep chewing on, because it’s a lesson well beyond email. SPF, DKIM, and DMARC are all defined over a message and an envelope. They assume the framing question is already settled — that everyone agrees on which bytes constitute the message they’re authenticating. They validate the contents of a box. They have nothing to say about who decided where the box’s walls are.

SMTP smuggling attacks the walls, not the contents. By the time DKIM checks a signature, the receiving server has already decided this is a separate message; it authenticates the smuggled envelope, which is real, and the fraudulent body inside it, which the signature was never computed over in the first place. Every check does its job perfectly and the composite answer is still a lie. It’s the exact shape of HTTP request smuggling, which broke on the same disagreement — front-end and back-end proxies parsing Content-Length and Transfer-Encoding differently and slicing one HTTP stream into two. Same class of bug, different protocol. When two systems in a chain parse framing differently, no amount of authentication downstream saves you, because they’re not even authenticating the same units.

The vulnerabilities got real CVEs against real software: CVE-2023-51764 for Postfix, CVE-2023-51765 for Sendmail, CVE-2023-51766 for Exim — the mail transfer agents behind a very large share of the internet’s mail. Providers including GMX, Microsoft, and Cisco’s Secure Email were shown vulnerable in the disclosure. The scale wasn’t a handful of misconfigured hosts; it was default behavior in the software most mail servers run.

The fix is to stop being generous

The remedy is unglamorous and exactly right: stop accepting non-standard end-of-data. Servers were patched to reject bare newlines in the message stream — Postfix added smtpd_forbid_bare_newline to refuse a lone <LF> where the protocol demands <CR><LF>, and the other MTAs made equivalent changes. In other words, the fix is to be strict about the one sequence that decides where a message ends, and to stop honoring the sloppy shortcuts that decades of lenient parsing had normalized.

That’s the uncomfortable moral. The liberality that kept email working with badly-behaved clients for thirty years — accept a bare newline, don’t be a stickler about carriage returns — is the same liberality that let someone forge past the entire authentication stack. Postel’s law, “be liberal in what you accept,” reads like wisdom until you notice that every place two systems are liberal in slightly different ways is a place an attacker can wedge apart. Framing is not a detail beneath the security layer. Framing is a security layer, and it’s the one nobody was watching because it felt too low-level to matter. It mattered.

Continue the conversation

← Back to Blog