deltachat-core-rust icon indicating copy to clipboard operation
deltachat-core-rust copied to clipboard

Mitigate From-forgery (impersonation attacks) through DKIM Authentication-Results checking

Open hpk42 opened this issue 2 years ago • 3 comments

It is a known issue that Autocrypt protects against mass surveillance and data collections but does not protect against active attacks. One particular class of active attacks is around forging the "From" address of an e-mail user, e.g. for phishing attack purposes. The e-mail ecosystem has come up with the Domain Key Identified Mail system which allows mail-servers to authenticate messages as originating from the proper domain. The DMARC specification moreover allows a mail server to signal to other mail servers about what they can expect. Both these specs are defined for MTA-to-MTA communication, and can not easily be applied in an end-to-end setting.

However, MTAs do record authentication results in each message through the well specified Authentication-Results header. Therefore in Delta chat we could parse this header to determine if our mail provider could positively check DKIM-signatures without having to do the checks ourselves. To harden Autocrypt and prevent from-forgery more specifically @hocuri and me discussed the following basic steps:

  • [ ] investigate which providers offer "Authentication-Results" header and if it can be derived from DNS information. Preliminary checking suggests several providers offer this header.
  • [ ] track in our contacts and/or peerstate db whether we get positive Authentication-Results for mails from a contact (meaning that their provider properly authenticates against our provider)
  • [ ] once a contact is known to come with positive Authentication-Resutls (dkim: pass) then don't accept Autocrypt key changes if they come with negative Authentication-Results.
  • [ ] consider how we can represent positive authentication in a contact's profile, and how we deal with "suddenly" failing authentication-results.

hpk42 avatar Jul 15 '22 11:07 hpk42

Documenting which headers some email providers add (just pasting all headers that contain "Authentication-Results", in the order they appear in the message):

Credits to @adbenitez with his great mailinglist setup to test all of this!

GMX

  • Correct sender (email from Slack):
    Authentication-Results:  gmx.net; dkim=pass [email protected]
    Authentication-Results:  gmx.net; dkim=pass [email protected]
    
  • Forged: GMX completely blocked adb's server, so we don't know

Testrun.org

  • Correct sender (email from [email protected]):

    ARC-Authentication-Results: i=1;
    	testrun.org;
    	dkim=pass header.d=gmx.net header.s=badeba3b8450 header.b=Gug6p4zD;
    	dmarc=pass (policy=none) header.from=gmx.de;
    	spf=pass (testrun.org: domain of [email protected] designates 212.227.17.21 as permitted sender) [email protected]
    Authentication-Results: testrun.org;
    	dkim=pass header.d=gmx.net header.s=badeba3b8450 header.b=Gug6p4zD;
    	dmarc=pass (policy=none) header.from=gmx.de;
    	spf=pass (testrun.org: domain of [email protected] designates 212.227.17.21 as permitted sender) [email protected]
    
  • Forged (email from adb's mailing list; if he directly sends an email with forged from, it looks similar):

    Authentication-Results: box.hispanilandia.net; dmarc=none (p=none dis=none) header.from=nauta.cu
    Authentication-Results: box.hispanilandia.net; spf=pass [email protected]
    ARC-Authentication-Results: i=1;
    	testrun.org;
    	dkim=fail ("body hash did not verify") header.d=nauta.cu header.s=nauta header.b=YrWhU6qk;
    	dmarc=none;
    	spf=pass (testrun.org: domain of "[email protected]" designates 51.15.127.36 as permitted sender) smtp.mailfrom="[email protected]"
    Authentication-Results: testrun.org;
    	dkim=fail ("body hash did not verify") header.d=nauta.cu header.s=nauta header.b=YrWhU6qk;
    	dmarc=none;
    	spf=pass (testrun.org: domain of "[email protected]" designates 51.15.127.36 as permitted sender) smtp.mailfrom="[email protected]"
    
    
    Some more interesting headers
    X-Spamd-Result: default: False [10.91 / 15.00];
    	R_DKIM_REJECT(8.00)[yandex.com:s=mail];
    	FORGED_W_BAD_POLICY(3.00)[];
    	MISSING_SUBJECT(2.00)[];
    	IP_REPUTATION_HAM(-1.17)[asn: 12876(-0.33), country: FR(-0.01), ip: 51.15.127.36(-0.83)];
    	RCVD_IN_DNSWL_HI(-0.50)[5.45.198.253:received];
    	R_SPF_ALLOW(-0.20)[+mx:c];
    	MAILLIST(-0.20)[mailman];
    	MIME_GOOD(-0.10)[text/plain];
    	DMARC_POLICY_SOFTFAIL(0.10)[yandex.com : SPF not aligned (relaxed),none];
    	MX_GOOD(-0.01)[];
    	HAS_LIST_UNSUB(-0.01)[];
    	ARC_NA(0.00)[];
    	FROM_NEQ_ENVFROM(0.00)[[email protected],[email protected]];
    	RCVD_COUNT_FIVE(0.00)[5];
    	BCC(0.00)[];
    	FORGED_SENDER_MAILLIST(0.00)[];
    	RCPT_COUNT_ONE(0.00)[1];
    	FROM_NO_DN(0.00)[];
    	NEURAL_HAM(-0.00)[-1.000];
    	TAGGED_FROM(0.00)[hocuri1=testrun.org];
    	RCVD_TLS_LAST(0.00)[];
    	ASN(0.00)[asn:12876, ipnet:51.15.0.0/17, country:FR];
    	ARC_SIGNED(0.00)[testrun.org:s=dkim:i=1];
    	DKIM_TRACE(0.00)[yandex.com:-];
    	TO_DN_NONE(0.00)[];
    	FREEMAIL_FROM(0.00)[yandex.com];
    	MID_RHS_MATCH_FROM(0.00)[];
    	FORGED_SENDER(0.00)[[email protected],[email protected]];
    	RCPT_MAILCOW_DOMAIN(0.00)[testrun.org];
    	FORGED_RECIPIENTS_MAILLIST(0.00)[];
    	GREYLIST(0.00)[pass,meta];
    	MIME_TRACE(0.00)[0:+];
    	RCVD_VIA_SMTP_AUTH(0.00)[]
    X-Spam-Flag: YES
    X-Rspamd-Queue-Id: 9B70941353
    X-Spam: Yes
    
    Weird that SPF passes, the From address was _not_ `[email protected]`.

Gmail

  • Correct sender (from [email protected]):
    ARC-Authentication-Results: i=1; mx.google.com;
           dkim=pass [email protected] header.s=mail header.b="Ih5Sz2/P";
           spf=pass (google.com: domain of [email protected] designates 51.15.127.36 as permitted sender) [email protected];
           dmarc=pass (p=QUARANTINE sp=QUARANTINE dis=NONE) header.from=hispanilandia.net
    Authentication-Results: mx.google.com;
           dkim=pass [email protected] header.s=mail header.b="Ih5Sz2/P";
           spf=pass (google.com: domain of [email protected] designates 51.15.127.36 as permitted sender) [email protected];
           dmarc=pass (p=QUARANTINE sp=QUARANTINE dis=NONE) header.from=hispanilandia.net
    
  • Forged (mailinglist): We don't know (also non-forged were blocked)
  • Forged (not from mailinglist): doesn't arrive

Nauta

  • Forged: (From adb's mailinglist)
    Authentication-Results: box.hispanilandia.net;
    	dkim=fail reason="signature verification failed" (2048-bit key; secure) header.d=disroot.org [email protected] header.b="kqh3WUKq";
    	dkim-atps=neutral
    Authentication-Results: box.hispanilandia.net; dmarc=pass (p=quarantine dis=none) header.from=disroot.org
    Authentication-Results: box.hispanilandia.net; spf=pass [email protected]
    

Hocuri avatar Aug 23 '22 16:08 Hocuri

domain of "[email protected]" designates 51.15.127.36 as permitted sender

Weird that SPF passes, the From address was not [email protected].

it's not weird that this passes:

hispanilandia.net.	86400	IN	TXT	"v=spf1 mx -all"
hispanilandia.net.	86400	IN	MX	10 box.hispanilandia.net.
box.hispanilandia.net.	86388	IN	A	51.15.127.36

51.15.127.36 is a valid IP for hispanilandia.net's SPF entry :upside_down_face: Not what I expected the check to check, but that's what it checks apparently

missytake avatar Aug 27 '22 11:08 missytake

[Edit] This comment probably isn't worth reading anymore, except if you are very interested in how we arrived at where we are.

We finally went with "Go back to somehow finding out our own server's authserv-id.", this comment talks about lots of other things.[/Edit]

Possible solutions:

  • Revert https://github.com/deltachat/deltachat-core-rust/pull/3510, try to make https://github.com/deltachat/deltachat-core-rust/pull/3491 work by remembering that there was a transition and
  • What we thought about today: Only allow keychanges when all present Authentication-Results headers are PASS.
    • To be exact: We group the Authentication-Results headers by authserv-id. If all authserv-ids have at least one header that 1. says pass and 2. matches the From address, then the email is considered DKIM-valid, and we remember that this contact (or sending domain, to be decided) sends DKIM-valid emails. Then, key changes are rejected if they the contact sent DKIM-valid emails, but now this messages in DKIM-invalid. (To be decided, probably: An AEAP transition is only accepted if the sending contact is DKIM-valid). This also means that if my own email server doesn't add the Authentication-Results header, I am vulnerable to AEAP transition attacks. This should be rare enough, but prevents an inconsistent group state (because otherwise I might be the only person in a group whose email provider doesn't add these headers).
      There are more reasons for this choiceEach email server, e.g. `gmail.com` has its own `authserv-id` which it uses to mark that the Authentication-Results header is from it, in this case `mx.google.com` It will then remove any Authentication-Results headers in incoming mails that come from `mx.google.com` (and leave others). So, if an email server doesn't add these headers, it won't remove any such header in incoming mails, so an attacker could just add their own "pass"ed Authentication-Results header. So, for this approach we just have to accept that if the own server doesn't provide Authentication-Results, then an attacker can mark their emails as DKIM-valid.
    • Note that this whole plan only works if the email servers also add a (then failed/neutral) Authentication-Results header when there is no DKIM header, we still have to test this. Otherwise all emails without a DKIM header will count as DKIM-valid. Edit 2022-09-29: The plan doesn't work exactly like this, because sometimes there is MTA at the sender side that adds a line like Authentication-Results: secure-mailgate.com; auth=pass smtp.auth=*.*.*.*@webbox222.*****.org. This is a domain that didn't say that DKIM passed, so the end result would be FAIL. Instead, we could:
      • Keep the idea with grouping the Authentication-Results headers, but say that all authserv_ids with at least one dkim=... line must have a dkim=pass header with the correct sender address. I.e. keep the same idea, but ignore authserv-ids that don't have a dkim=... header at all. Disadvantage: Some providers like mail.ru and zohomail.eu leave away the dkim=... part from their Authentication-Results header if there is no DKIM, so for them, the check would incorrectly PASS. Also, the checking-logic becomes kinda weird.
      • For each sender/sending domain, not only remember whether DKIM-checking works, but also for which authserv-ids. Disadvantage: If an upstream (like, sender-side) MTA suddenly stops checking DKIM (which is fine, actually it was weird that it checked DKIM in the first place), then we don't accept key changes from there anymore. E.g. Yandex is an example for a server that checks its own DKIM for outgoing emails, so if they stop doing this, then we don't accept key changes from yandex users (until when we still have to decide). Also: More complicated database scheme
      • Go back to somehow finding out our own server's authserv-id. Like, configure it using autoconfig & the provider db. Or, as soon as we get an incoming message with only Authentication-Results header, assume that that's our server's authserv-id; maybe with a confidence level: email with only this authserv-id in Authentication-Results: +10; email with also this authserv-id in Authentication-Results: +1; email without this authserv-id in Authentication-Results: -100 Edit 2022-10-15: That's what we did
      • Last option: Ignore the problem and say that Authentication-Results-checking doesn't work if an upstream MTA added an Authentication-Results header that didn't check the DKIM.

cc @hpk42

Hocuri avatar Sep 19 '22 16:09 Hocuri

We talked to mailcow today - mailcow DKIM-signs internal mails :) (not sure if also the Autocrypt header)

missytake avatar Sep 27 '22 17:09 missytake

ARC-Authentication-Results is the version of Authentication-Results used by mailing lists. ARC refers to Authenticated Received Chain. Almost unrelated, but incorrectly wrapped ARC headers caused problems to Delta Chat previously: #3078

link2xt avatar Oct 15 '22 15:10 link2xt