Please read CVE-2019-20790, and tell me you’ve found a way to tell
PyPolicyd not to trust the SMTP HELO to generate a passing AR
header.
This is possibly off-topic for the Postfix list... maybe not.
…with tongue in cheek, I’ll take all the discussion about mail
filtering over any about political correctness that’s happened this
year. That said, if people feel this is off topic, nudge me
privately and I’ll take a hint.
Ditto - perhaps some of these comments are better placed at
https://github.com/trusteddomainproject/OpenDMARC/issues/49
Re the risk from the CVE to my setup...
I run policyd-spf as a postfix check_policy with reject on fail (on
both HELO and MAILFROM), and assuming a mail gets past that (99%
do), I then use that Pass along with OpenDKIM and OpenDMARC to
contribute to Spamassassin scoring. My DMARC Spamassassin rules do
not apply a -ve on a pass, only a +ve on a fail.
The impact of a false pass from DMARC (particularly as our Webmail
system - Horde - does not show any added trust for a dmarc pass)
would be failure to apply a negative Spamassassin score from a the
missed DMARC fail. With my mail setup and users the impact of that
is probably manageable... but agreed it is not ideal.
What concerns me as much as anything is that each of the apps' devs
in question (pypolicyd-spf and opendmarc) are each pointing at each
other :-/
https://bugs.launchpad.net/pypolicyd-spf/+bug/1838816/comments/4:
"my belief that the issue here is a DMARC processor failing to do
alignment validation correctly"
https://github.com/trusteddomainproject/OpenDMARC/issues/49#issuecomment-802242666
"Honestly, this CVE seems more to belong to py-policyd than OpenDMARC."
What would be nice would be if the two of you worked together on
coming up with a fix...
The upcoming 1.4.1 will have a more detailed explanation of the CVE
in question as we go over the decisions that we made in taking
action on this.
My logic in writing up the CVE summary was: OpenDMARC is following
the protocols as designed. If an earlier milter in the chain says
SPF passed, it doesn’t feel right to pick that apart and say “but
did it really?”
This is (IMO) flawed reasoning. Hear me out... :)
The *sole* scope of pypolicyd-spf is *SPF* validation at the SMTP
transaction point. RFC7208 recommends that SPF verifiers check both
HELO and MAIL FROM (2.3), and pypolicyd-spf provides users with the
ability to choose whether to do MAIL FROM only or also HELO. Out of
the box it aligns with RFC 7208's recommended approach, checking both.
There is nothing in the SPF RFC which says *for the purposes of SPF*
that either HELO or MAIL FROM validation is more or less valid than
the other. As defined in SPF, both are valid. We know SPF is not
perfect, which is why it has been extended and added to by things like
DMARC... but remember this is at the moment *solely in context of an
SPF policy agent*.
Pypolicyd-spf then tags what has driven the result for later use:
E.g.
Apr 3 11:19:23 emp87 policyd-spf[1336326]: prepend
Authentication-Results: mail.simonandkate.net; spf=pass (mailfrom)
Apr 2 12:32:51 emp87 policyd-spf[1255235]: prepend
Authentication-Results: mail.simonandkate.net; spf=pass (helo)
Whether or not MTAs upstream have enforced matches of hostname to
EHLO, Pypolicyd-spf is meeting its defined brief. Whether or not you
think "trusting HELO is insane" it is clearly a valid interpretation
of the SPF RFC for an SPY policy agent to assign a pass based on HELO.
It is *not* "a bad authentication results header" if pypolicyd-spf
tags as such (quotes from
https://github.com/trusteddomainproject/OpenDMARC/issues/49#issuecomment-803634646,
https://github.com/trusteddomainproject/OpenDMARC/issues/49#issuecomment-800047724).
1. Setting pypolicyd to HELO_Reject = No_Check. RFC7208 indicates
that MAILFROM and HELO are both equally valid for an SPF result,
*BUT* the vast majority of my SPF checks trigger pass or fail on
MAILFROM (I've had 3 spf HELO passes in 10 days on HELO, thousands
on MAILFROM). Testing pypolicyd-spf with HELO_reject = No_Check
results in spf=none for those. Removing the HELO check means that
OpenDMARC can assume any spf result it is given is based on MAILFROM.
RFC7489 Section 4.1: "DMARC uses the result of SPF authentication
of the MAIL FROM identity.”
If we’re processing SPF ourselves, this is true. (The SPF RFC calls
HELO checking optional). If we’re parsing an earlier added header
(perhaps added by a border MX), we prioritize spf=pass over other
decisions. (Perhaps this is wrong to do).
With regard to OpenDMARC, we *could* add a check on an earlier SPF
header that checks *ONLY* the mail-from identity in a received-spf
header. This might be a better response to the CVE for future code,
but at the very least right now a workaround and a statement is
warranted.
So now we come to DMARC.
The DMARC RFC7489 says at 3.1 about SPF:
"while [SPF] can authenticate either the domain that appears in the
RFC5321.MailFrom (MAIL FROM) portion of [SMTP] or the RFC5321.EHLO/
HELO domain, or both"
...acknowledging that a valid SPF pass can be based on HELO.
It then says (4.1) that DMARC *must only use SPF MAIL FROM* authentication:
"DMARC uses the result of SPF authentication of the MAIL FROM identity"
pypolicyd-spf is giving OpenDMARC the necessary information to check
whether this is achievable. Look at my example log entries as noted
above with the prepend headers:
spf=pass (mailfrom)
spf=pass (helo)
OpenDMARC has the information needed to make an informed decision on
whether the SPF pass which has been made earlier can actually be used
in alignment with the DMARC RFC (i.e. based on MAIL FROM) or should be
ignored (i.e. based on HELO). If OpenDMARC stops parsing at "spf=pass"
how is that an issue with anything other than OpenDMARC? The DMARC RFC
*explicitly* says that DMARC must only use an SPF authentication of
MAIL FROM - It does not say that DMARC should trust an spf=pass.
Your comment: "we *could* add a check on an earlier SPF header that
checks *ONLY* the mail-from identity in a received-spf header" strikes
me as being the sensible fix. In fact, if OpenDMARC is *NOT* checking
against only an SPF MAILFROM pass, perhaps you can enlighten me on how
it is compliant with RFC7489 4.1?
In turn, that could lead users to point at an SPF=pass in a previous
A/R or received-spf header, and complain that we’re still
rejecting. It could also lead OpenDMARC to add its own SPF A/R
header with a different result than a previous one, which could
greater complicate things down the chain. Which header does a
downstream filter (say, Spamd) believe?
Again - spf=pass is not the issue.
Rejecting based on an SPF policy such as -all is carefully cautioned
when used with DMARC (RFC7489 10.1). Since SPF is broken by pretty
much every mailing list, and mailing lists are not SPF aware (but
some ARE dmarc aware), using py-policyd to reject is probably not a
great idea anyway, since as far as I know py-policyd doesn’t know
how to check if a _dmarc record also exists.
Not true.
I currently reject SPF on fail, am on dozens of mailing lists without
issue (I should say without *SPF* issues... ignoring issues like the
OpenDMARC mailing list's use of deprecated TLS protocols). I usually
get spf=none from those. I currently have only 1 dumb domain in my SPF
skip-check whitelist (I've contacted them, no change). On my small
server this results in (after extensive postscreen activity dropping
about 90% of connection attempts) typically 2 or 3 SPF rejects a day
(out of 300 to 500 emails. Of those, maybe 1 every three or four weeks
is a false positive. Yes I could accept and pass further down the line
for evaluation and e.g. DMARC tagging - I choose not to.
Your interpretation of RFC7489 10.1 is also flawed (again IMO)...
"Some receiver architectures might implement SPF in advance of any
DMARC operations. This means that a "-" prefix on a sender's SPF
mechanism, such as "-all", could cause that rejection to go into
effect early in handling, causing message rejection before any DMARC
processing takes place. Operators choosing to use "-all" should be
aware of this."
That places the onus on domain owners, not receivers. It acknowledges
that some receivers do SPF before DMARC and that may result in
rejection before DMARC - it then cautions Operators to be aware of
that risk. It is entirely valid under the SPF RFC for a receiver to
reject messages based on an SPF Fail (RFC7208 G.2)... and I choose to
do so before I get to DMARC.
Our advice to the administrators is to carefully evaluate the
behavior of this software — be aware that this is a failure mode,
and act accordingly.
I don’t use py-policyd, so I don’t know what else it can be used to
do (for example, if it’s a general purpose wrapper like Amavis is).
I don’t feel that we should be writing documentation or suggesting
specific configuration knobs for software we didn’t put out.
The CVE response is visible in the SECURITY folder in our github on
the “develop” branch. This specific callout will probably also be
in the RELEASE NOTES, which we’re still writing.
Fundamentally I agree with Scott Kitterman here
(https://bugs.launchpad.net/pypolicyd-spf/+bug/1838816/comments/3):
"If you look at https://tools.ietf.org/html/rfc7208#section-4.1 you can see
that both HELO and Mail From are treated as first class inputs to
check_host().
From a specification perspective, the result needs to consider what
identity it
relates to, which is why I think ***a DMARC processor that assumes any
reported
SPF result relates to the Mail From of the message is buggy***.
Scott K" (***emphasis added***)
Simon
--
Simon Wilson
M: 0400 12 11 16