關於 email security 的大小事 — 設定篇 SPF

上一次我們深入瞭解了 email security 的原理與應用場景,這次來看看 SPF、DKIM、DMARC 該如何設置吧!

請注意,這裡的『設定』並不是回答你『如何在 google 或是 Office365 設好這些紀錄』、『用 XX 服務結果信寄不到怎麼辦』,這種操作配置的教學文請參考官方文件,畢竟 email provider 千千百百家,各自可能有的問題更是難以彙整。

我想告訴你的『設定』是 SPF、DMARC 的 DNS 紀錄本身有哪些標籤,以及設置這些選項時可能不小心踩到哪些地雷,導致收信方的 email server 在驗證你的郵件時出現非預期地報錯,而判斷驗證失敗。另外,也會告訴你如果有多個子網域,或有使用第三方寄信服務時該怎麼辦。

總之,可以想成是回答你『我該怎麼理解這些紀錄』和『每個機制有哪些選項跟限制』,而不是針對單一 email provider 的教學文。

如果你還不太懂 SPF、DKIM、DMARC 是什麼,或是不清楚郵件傳遞過程中的各個角色與職責(MDA、MTA⋯⋯們),或是想了解更多範例與延伸議題,請看本系列其他篇:

有不懂或是想了解更多範例與延伸議題,請看本系列其他篇~
如果你還不太懂 SPF、DKIM、DMARC 👉 關於 email security 的大小事 — 原理篇
如果你在找 DKIM、DMARC 的設定 👉 關於 email security 的大小事 — 設定篇 DKIM、DMARC
如果你想搭配設定範例 👉 關於 email security 的大小事 — 範例篇
如果你想了解延伸議題 👉 關於 email security 的大小事 — 延伸篇

# SPF

先來看看 SPF。SPF 紀錄的語法說複雜不複雜,但各種小細節常常讓人頭疼,一個很完整的語法介紹可以參考 Dmarcian-SPF record syntax

如果想配著真實的信件看,建議你可以用 mxtoolbox 之類的工具一邊查詢 SPF 紀錄,一邊點開信件原始內容看 SPF 驗證結果:

這裡我們先講一下 SPF 驗證時會出現的幾種結果:

SPF 的小地雷就在於,很多種設置上的小失誤可能導致 PermError、TempError、或是其他削弱 SPF 安全性的結果。因為我們無法預期收信方 email server 會採取什麼行為,所以也無法確保送到收件人手上的信件都有受到 SPF 紀錄的保護。要是對方遇到 Error 一律採取 accept,或是乾脆選擇忽略這筆 SPF 紀錄,那不就形同虛設了嗎?

下面我們看看 SPF 紀錄中的標籤: 8 個機制(mechanism)跟 2 種修飾(modifier),以及他們可能踩到地雷的情形。所有的標籤(tag)與標籤值(tag value)中間都不能有空白。此外,至少在出現更新版本的 SPF spec之前,現階段 SPF 紀錄開頭必須是 v=spf1

簡短的 SPF record(取自 維基百科)

以下我們稱信件的來源(也就是 smtp.MailFrom)為 sender-domain 或 sender-ip,標籤值為 target-domain 或 target-ip。

# Mechanisms

8 個 mechanism 為: all, ip4, ip6, a, mx, ptr, exists, include

SPF 紀錄驗證時是照著 mechanism 出現的順序比對的,比對結果為 match、 not-match、或 error 之一,一旦成功找到 match 或是發生 error 就會停下。

在每個 mechanism 前都可能帶有一個 qualifier 符號,為下列其一:

# all

語法為: [qualifier]all

必須在紀錄最末,是最後一個判斷條件。一般來說應設置最嚴謹的 -all,表示除了前述 mechanism 指定的 target IP 外一律拒絕(fail(-)),所以如果前面的 mechanism 都沒對到,最後就會失敗。

地雷:

# ip4, ip6

語法為:[qualifier]ip4:target-ip[cidr-length][qualifier]ip6:target-ip[cidr-length]

分別用 ipv4 與 ipv6 定義的 IP 列表,只要是 CIDR 表示法都可以,例如 192.168.0.1/16。斜線後的 prefix length 如果省略,會預設為 /32(ipv4)與 /128(ipv6)。

地雷:

# a

語法為:[qualifier]a:[target-domain][cidr-length]

檢查 sender IP 是否在 target-domain 的 A 或 AAAA 紀錄中,即是否為 target-domain 所擁有的 IP。如果沒有寫 target-domain 就會默認為當前 SPF 紀錄的網域, a 等同 a:sender-domain

# mx

語法為:[qualifier]mx:[target-domain][cidr-length]

檢查 sender IP 是否在 target-domain 的 MX 紀錄中。如果沒有寫 target-domain 就會默認為當前 SPF 紀錄的網域, mx 等同 mx:sender-domain

地雷:

# ptr (已廢棄)

語法為:[qualifier]ptr:[target-domain]

進行 reverse DNS lookup,若得到的網域是 smtp.MailFrom 或其子網域,則 pass。此機制速度慢且會 .arpa name servers 的負擔,請勿使用。

地雷:

# exists

語法為:[qualifier]exists:target-domain

若 target-domain 存在 A 紀錄,視為 pass。

# include

語法為:[qualifier]include:target-domain

跟寫程式呼叫另一個函數的概念類似,會檢查 target-domain 的 SPF 紀錄並且一直遞迴查詢下去,直到比對過每一個 IP。但 include 不代表把對方的 SPF 紀錄 inline 插入自己的,而是跳到對方的 SPF 紀錄的 context 中比對,最後得到 match、 not-match、或 error 的結果。

在遞迴過程中,子紀錄的驗證結果對母紀錄的 include 機制的影響為:

include 機制適合用在核准外部(跨域)的 email provider,例如當我們使用第三方寄件服務時,就要把對方的 SPF 紀錄用 include 機制放到我們的SPF 紀錄。

# Modifiers

2 種修飾為: redirect, exp

# redirect

語法為:redirect:target-domain

必須在紀錄最末,與 all 不可同時出現。若前面的 mechanism 驗證完畢但都沒有找到 match,就用 target-domain 的 SPF 紀錄取代自己的。與 include 的母子關係不同,這裡用 inline 概念插入,所以任何報錯視為當前 SPF 紀錄的 Error。

地雷:

# exp

語法為:exp:target-domain

代表 explanation,若 SPF 紀錄驗證結果為 Fail,會返回 target-domain 的 TXT 紀錄內的字串。

地雷:

# 其他地雷:

# 多個子網域

如果你有多個會寄信的網域,就要為每一個分別設置 SPF 紀錄。如果它們是從同一個 email server 寄出去的,可以用 redirect 統一指向一筆紀錄,方便管理。

不寄信的網域(parked domain)請設置 v=spf1 -all

# 第三方服務

如果你有使用第三方的寄件服務,例如 SendGrid,你可以在 SPF 紀錄中用 include 機制把第三方的 email server 涵蓋進來,例如 OneDegree 使用微軟 Outlook、MailChimp、FreshDesk、SendGrid 等服務:

v=spf1 include:spf.protection.outlook.com include:servers.mcsv.net include:email.freshdesk.com -all

眼尖的人可能會發現,上面的紀錄怎麼少了 include:sendgrid.net

其實,你如果去查這幾個第三方服務的的 SPF 紀錄就會發現,FreshDesk 的 SPF 紀錄就已經有 include:sendgrid.net 這一行,因此我們就不用加啦!這樣也可以省下一筆 DNS lookup 的扣打,畢竟 10 次 DNS lookup 是很容易超過的。

這裡也順便回答一個非常常見的問題:

『超過 10 次 DNS lookup 怎麼辦』

只要你多用幾個第三方寄件服務,馬上就超過 10 次了,畢竟你 include 他算一次,他用到的 include、a、mx 等等也通通算你的!還不含你自己的一些 email server 呢!

解決這個問題有幾個撇步:

如果你有 lookup 到你自己的一些 email server,可以考慮用 ip4 ip6 直接 inline 插進來,例如:

// before  
mydom.com -> include:sub1.mydom.com mx:sub2.mydom.com include:a.com
sub1.mydom.com(SPF) -> ip4:192.x.x.3 ip4:172.x.x.x/16
sub2.mydom.com(MX) -> 192.x.126.5

//after
mydom.com -> ip4:192.x.x.3 ip4:172.x.x.x/16 ip4:192.x.126.5 include:a.com

或者,可以考慮把這個寄信的 domain 拆成幾個不同功用的 subdomain 然後把第三方寄件服務也依據用途瓜分下去,這樣每個 domain 都有自己的 10 次扣打。例如:

// before  
mydomain.com -> include:a.com include:b.com include:c.com

//after
mydomain.com -> include:a.com
customer.mydomain.com -> include:b.com
partnership.mydomain.com -> include:c.com

如果你很不幸地用到了一個第三方服務,他自己的 SPF 紀錄就要用到快 10 次 lookup,那只能說….換一個吧?(或是聯繫對方看看他們有什麼建議啦)

# 結論

恭喜大家搞懂三劍客裡最難設定的 SPF 啦!你可以在網路上找到的 SPF 紀錄檢查器通常不會把我們討論的地雷全都檢查一次,因為實在是太麻煩啦QQ 推薦你可以多用幾種工具配著這篇仔細檢視一下你的 SPF 紀錄,然後跟著下一篇設好 DMARC 後,就可以在每日的彙整報告中看到 SPF 通過跟失敗的紀錄囉!

# Reference:

  1. SPF RFC
  2. DKIM RFC
  3. DMARC RFC

标签

推荐

  1. 秒懂 Directory Traversal(目錄遍歷)
  2. 不識廬山真面目:Clickjacking 點擊劫持攻擊
  3. 關於 email security 的大小事 — 範例篇
  4. 關於 email security 的大小事 — 原理篇
  5. 比較 Java 和 Golang 在撰寫併發時處理共享變數的差異

Discussion(login required)