Vulnerability analysis · SMTP envelope

Postfix cuts a MAIL FROM address at the final at-sign, so a second unquoted one hands the envelope domain to the wrong label; Exim and OpenSMTPD refuse the command outright [Postfix ≤ 3.10.12, latest release checked 2026-07-30]

Yongzhe Xu, Virginia Tech  ·  yongzhe@vt.edu  ·  2026-07-31

The address <admin@trusted.test@sender.test> draws a 250 from Postfix and lands in the envelope as <"admin@trusted.test"@sender.test>. SPF then runs against sender.test. Cut the same string at its first at-sign and the sender domain reads trusted.test.

SoftwarePostfix
VendorThe Postfix project (Wietse Venema)
Sourcehttps://github.com/vdukhovni/postfix
AffectedPostfix 3.10.12, the current release under test. The differential peers are Exim 4.96 and OpenSMTPD, both of which reject the input Postfix accepts. Newest published release checked on 2026-07-30.
WhereSMTP envelope address parsing in smtpd, acceptance and silent rewriting of a Mailbox whose local part contains an unquoted @
WeaknessCWE-436 (Interpretation Conflict)
Reachable byRemote, unauthenticated. One SMTP transaction with a malformed MAIL FROM argument.
CVSS v3.1CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:N/I:L/A:N (4.0 (Medium))
VerificationReproduced on 2026-07-28 against the current release (run 20260728-120957) with a scripted reproducer, negative controls, and a second independent implementation as the differential oracle. Five of five deterministic across three MTAs, with the committed envelope domain read back from the delivered message's Return-Path, and the Postfix acceptance independently re-confirmed on a second host.

1. Overview

The Mailbox grammar of RFC 5321 section 4.1.2 builds a Local-part as either a Dot-string or a Quoted-string. Neither production admits a raw @, since @ is not atext. <a@b@c.test> falls outside that grammar, and so does a MAIL command that carries it.

Postfix takes the command anyway and answers 250. It cuts the address at the final @, wraps the text left of the cut in quotes, and stores the envelope sender as <"a@b"@c.test>. The envelope domain becomes c.test. Two other servers stop at the same bytes: Exim answers 501, OpenSMTPD answers 553. The Exim string spells out what is wrong: “malformed address: @c.test> may not follow <a@b”.

2. Analysis

Every server in the comparison received byte-identical MAIL FROM input. The rightmost column, the domain Postfix committed, was read out of the Return-Path header of the message that was actually delivered.

MAIL FROM argumentPostfixExim 4.96OpenSMTPDPostfix's committed envelope domain
<a@b@c.test>250, accepted501 “malformed address: @c.test> may not follow <a@b”553 syntax errorc.test (last @)
<admin@trusted.test@sender.test>250501553sender.test (last @)
<user@domain.test.> (trailing dot)250, stripped to user@domain.test501553domain.test
<@domain.test>250, rewritten to <""@domain.test>501553domain.test
<normal@ok.test> (control)250250250ok.test

Row five is the negative control. A well-formed Mailbox goes through on all three servers, which rules out the delivery path as the reason for the rejections in the rows above it; those rejections belong to the addresses themselves. All five rows repeated identically.

Two distinct behaviors sit behind the result. One is a grammar check that never fires, so Postfix admits a string two other widely deployed servers throw out. The other is what happens after the string is admitted. Postfix rewrites it, with no notice to anyone, into an address whose domain is not the domain a first-@ reader takes from the original bytes. That rewritten domain is what feeds SPF and the SPF-aligned DMARC identity, so the rewriting is where the security weight lands.

3. Reproduction

  1. Drive the session over a raw socket and write the command bytes by hand. A client

library would repair the address before it reached the wire.

EHLO probe.test
MAIL FROM:<admin@trusted.test@sender.test>
250 2.1.0 Ok                      <- Postfix
                                     Exim:      501 malformed address
                                     OpenSMTPD: 553 syntax error
RCPT TO:<user@receiver.test>
DATA
From: admin@trusted.test
To: user@receiver.test
Subject: invoice

...
.
  1. After delivery, read the message's Return-Path to see which domain Postfix stored.

Expected by RFC 5321 section 4.1.2: a rejection. A local part holding an unquoted @ matches neither Dot-string nor Quoted-string.

Observed from Postfix: 250, and the delivered message carries this envelope sender.

Return-Path: <"admin@trusted.test"@sender.test>

sender.test is thus the identity Postfix runs SPF against. Cut the same bytes at the first @ and the answer is trusted.test. Those two readings, together with Postfix settling on sender.test, are the whole of what the laboratory established.

4. Assessment

One string, two envelope domains. Postfix files <admin@trusted.test@sender.test> under sender.test and runs the SPF check, along with the SPF-aligned DMARC identity, against that domain. A parser that stops at the first @ files the same message under trusted.test. Producing the disagreement costs an attacker one SMTP transaction, with no credentials, from anywhere.

Exposure depends on the chain. Where Postfix is the only address parser, nothing disagrees. Add a perimeter appliance, a content filter, a reputation service, or a log pipeline that parses addresses on its own, ahead of Postfix or behind it, and that component can authorize or credit a domain Postfix never checked.

The gain is attribution confusion. An allowlist entry for trusted.test may release a message whose SPF result belongs to some other domain. Reporting may attribute the message to a domain that took no part in the authentication decision. Nothing here bypasses authentication. Acceptance on its own is also an interoperability problem, since Exim and OpenSMTPD refuse what Postfix admits.

5. Remediation

Apply the grammar. A Mailbox whose local part carries an unquoted @ should draw a 501 from smtpd, as it does from Exim and OpenSMTPD, and as RFC 5321 section 4.1.2 requires. Rejecting at the front door leaves no ambiguity for a later component to resolve differently.

Suppose compatibility argues for keeping the address. Then drop the rewriting rather than the check. An address requoted so that its domain no longer matches what a first-@ reader sees carries the disagreement forward into every downstream component and into the delivered headers. Failing that, emit a log line holding the address as received beside the address as committed, so an operator can see both readings.

6. Additional notes

Scope of the demonstration. One hop was tested. That hop establishes which servers accept and which reject, and it establishes the envelope domain Postfix writes. It establishes no exploit. A working bypass would need a chain of mixed MTAs in which some other component reads the first @ and acts on it. No such chain was assembled and none was measured. No authentication bypass is claimed, and the severity above is set accordingly.

A note about the evidence. One informal attempt to re-check the Exim and OpenSMTPD rejections went through a client library and produced nothing usable. That library puts MAIL FROM on the wire in a shape both servers reject for generic reasons, so a generic parse error cannot be told apart from an address-specific one. The evidence that counts is the transcript written byte by byte, which drew the address-specific errors quoted above, Exim's two-at-sign message among them.

Open for the vendor. Whether smtputf8 or address rewriting shifts the committed domain. Whether RCPT TO applies the same last-@ rule.

7. References