設定 MTA-STS 和原理說明

· 約 15 分鐘閱讀

上次淺談了 DANE for SMTP 怎麼設定,若你的供應商支援,那你可以先看這篇,我們這邊不是維護者,要設定很簡單。

淺談 MTA-STS 的定位#

但多數供應商並不支援,因為 DNSSEC 的 DS、KSK 和 ZSK 一旦輪換錯誤,或者設定錯誤,整個域名會 (暫時) 從網際網路上消失。
再加上 DANE for SMTP 也需要供應商維護,所以沒有良好的 TLSA、KSK、ZSK 輪替機制,出錯機率高,成本也高得離譜。

因此 MTA-STS 就是為了這種情況而生,建立在本就成熟的 WebPKI 機制,並且將 DNS 規定成不需要 DNSSEC 保護的狀態。
對比之下,TLSA 沒有 DNSSEC,記錄 (憑證) 就輕易地被篡改,並且 TLSA 設計之初,就是建立在域名管理員開啟 DNSSEC 的情況。

如果你還是只想看實作,就跳到這裡吧。

原理說明#

如果你有去讀過 RFC 8461,你會發現 Section 3 介紹運作方式,和 Section 8 說明怎麼設定時,兩者的順序是反過來的,為什麼會這樣?

運作過程#

先來看 Section 3,MTA-STS 是怎麼運作的?

MTA-STS 被分為 2 個部分,一個是 MTA-STS TXT 記錄,另一個是 MTA-STS Policies (政策)。

在 Section 3 中,MTA-STS Policies 是參照 RFC 5785 (-> RFC 8615)1 設計出來的,透過 HTTPS 前往 .well-known 來拿到 MTA-STS 設定機制。
MTA 在寄信時,會看對方域名有沒有 MTA-STS 的 TXT 記錄 (也就是 MTA-STS Discovery),如果有設定,那就會去 .well-known 底下拿 Policies,決定要不要寄信。

TXT 記錄的角色比較像「開關 + 版本號」,告訴 MTA:「我有 MTA-STS,目前版本是 id」,真正的規則則寫在 Policies 裡。
所以如果只有 TXT 記錄,卻抓不到 Policies,MTA 會當作你沒有實作 MTA-STS。(Section 3.3)
而 Section 8 的維運指南,則建議更新設定時,先改 Policies,再去更新 TXT 記錄。

你可能會疑惑說「我 Policies 改好了,那我去抓 Policies 不就是新的 Policies 嗎?」
是這樣沒錯,這是你手動訪問 URL 的結果,但是對於 MTA 而言,並不是這樣。

如果每次寄信,都要去 curl 對方的 Policies,這對於 MTA,尤其是 Gmail 這類大型供應商,是個負擔。
再來,在 Section 10.2 中提到,攻擊者可以想辦法擋掉 MTA 對 TXT 記錄或 Policies 的查詢,讓 MTA 以為你沒有設定 MTA-STS。

所以 Policies 有個參數是 max_age,代表 MTA 可以把 Policies 快取多久。
在這段期間,MTA 平常只查 TXT 記錄 (DNS 查詢成本低很多),只要 id 跟快取的一樣,就沿用快取的 Policies,不會重抓。
(RFC 也建議 MTA 每天主動刷新一次,但這是「建議」,不能指望每家都照做)

這就是順序要反過來的原因:如果先改 TXT 記錄,MTA 看到新的 id 跑去抓 Policies,抓到的卻還是舊內容,接著就帶著新的 id 把舊 Policies 快取起來。
在它下次刷新之前,都會一直用錯的 Policies。

機制的優缺#

可以看到,MTA-STS 的安全機制,是建立在既有的 WebPKI 體系之上,不像是 DANE,從 DNSSEC 為起點,疊加一個依賴 DNSSEC 的安全設定。
正如前面所說,一旦 DNSSEC 沒設定好,簽章對不上,就會讓你的域名從網際網路上消失一段時間,這也是 MTA-STS 誕生的理由。

而且假如你要 Self-host MTA,從頭到尾包括安全性設定,MTA-STS 無論設定難度,還是維護難度,都會比 DANE 簡單很多。(TLSA 記錄本身就比較複雜)

但是 MTA-STS 的缺陷也很明顯,像是你必須有 Web Server、設定被分開到兩處…
以及作為安全協定,最重要的安全性反而偏弱,如果你有看我前一篇說明 DANE,可以發現 DANE for SMTP 為了避免 MTA 雙方不信任對方 CA,所以要避免使用 PKIX (CA) 驗證的 TLSA 記錄。
MTA-STS 反過來擁抱 CA 驗證,但這並沒有解決「雙方信任的 CA 不一致」的問題。
RFC Section 3.3 只寫了「預期」(It is expected) 寄件端會使用跟主流瀏覽器、作業系統相近的 CA 清單,並沒有強制規定。

而且不依賴 DNSSEC,所以假如攻擊者能竄改目標域名的 DNS,就能把 MX 指向自己的主機,再發布一份 mx 指向該主機、max_age 很長的 Policies。
寄件端快取之後,即使受害者把 DNS 修好,寄件端仍會照著惡意 Policies,拒絕投遞到真正的 MX,達成 DoS (阻斷服務) 攻擊。
受害者能做的,就是重新發布正確的 Policies 並更新 id,把惡意快取蓋掉。

如果 DoS 不了,阻止寄件端查詢到 MTA-STS 記錄,同樣也可以將安全協定降級成 STARTTLS,這時只要把對付 STARTTLS 那套搬來用就可以完成攻擊。
解法同樣簡單 (但被動),只要第一次 MTA-STS Discovery 沒被阻擋,Policies 就會被快取在寄件端的 MTA,之後就算查詢被擋,MTA 也會繼續套用快取的 Policies,直到 max_age 到期。
這也是為什麼 RFC 期望 max_age 設在「數週以上」。

除此之外 MTA-STS 還有一些比較嚴峻的地方,像是取得收件端有效憑證,只要攻破 CA,或者繞過 CA 驗證流程等,那收件端的 STS 驗證就可以被繞過。
假如網路層那邊,也能做到重新導向,那此時就能輕易攔截寄件端的信。
與此同時,MTA-STS 也繼承了其餘 WebPKI 的弱點,HTTPS 怕的攻擊,到了 MTA-STS 也該擔心。

但好消息是,弱點和 WebPKI 基本一致,解法也大致相同。
RFC 建議 MTA 實作者支援 OCSP 或 CRL 來檢查憑證撤銷,不過這是寄件端要做的事,而且 OCSP 近年正在被淘汰 (Let’s Encrypt 已在 2025 年停止 OCSP 服務)。
身為域名管理者,我們能做的是設定 CAA 記錄,限制只有指定的 CA 能簽發你域名的憑證,再搭配 CT (Certificate Transparency) 監控,有人替你的域名簽了憑證,你就能發現。

這裡補充一個,那就是「約束能力弱的政策」,也就是你把 Policies 的 mx 參數設定得比較寬,比如不存在的 MX,以及懶人大法萬用遮罩,比如 *.example.com。
這比較偏向人為問題,政策型安全性設定都會有相關問題,實作時會說明如何避免。

以上這些攻擊,RFC 8461 都整理在 Section 10,對照如下:

Section攻擊攻擊者需要緩解
10.1取得合法憑證騙過/攻破 CA + 能導流CAA、CT 監控
10.2阻擋 Policies 探索能攔截 DNS/HTTPS快取、長 max_age
10.3阻斷服務能竄改 DNS重新發布 Policies
10.4弱政策控制同域名任一主機mx 逐筆列出
10.5WebPKI 被攻破同 HTTPS撤銷機制、CAA、CT

你可能會覺得「哇,MTA-STS 問題這麼多,我設定了這麼多洞的安全性協定,是不是浪費時間?」
其實,也不會,因為上述的弱點實現起來都有難度,況且 WebPKI 本身的歷史也很悠久,發展得十分成熟。
如果供應商會 follow 現代網頁安全標準,那 MTA-STS 的安全性,是能拉到跟瀏覽器同等級的。

只是在 RFC 中,很多在 WebPKI 上的強制性要求,到了 MTA-STS 變成選配,所以使得 MTA-STS 安全性下限沒比 STARTTLS 高多少。

所以總結來說,MTA-STS 還是勝在不需要 DNSSEC,DNSSEC 的部署率放到今天,有部署的域名還是以歐洲居多。
同時這也造就了 WebPKI + DNSSEC 兩套體系,再加上你不能確定,對方是否只支援其中一個協定,所以實務上會建議兩個都設定。

MTA-STS 設定#

前面已經分析完運作,因此推導出跟 Section 8 一樣的結論,那就是先設定 HTTPS Policies,再設定 MTA-STS TXT 記錄。

無論是第一次設定,還是後續要更新記錄,依照以下順序即可。

RFC 中,除了 Section 3 詳細說明了各個欄位的值和功能之外,最後也有收斂成 Appendix A。

Policies 怎麼寫#

其實就是一個 mta-sts.txt,大致分成 4 個欄位:

  • version: STSv1,版本就是這樣寫,除非哪天有 RFC 定義了 MTA-STS v2。
  • mode:這裡有 testing、enforce 和 none 2,對於剛設定的人,先用 testing 會更好,搭配 TLS-RPT 報告觀察,後面會介紹 TLS-RPT。
  • max_age:Policies 的刷新時間,單位是秒,最大上限是 31557600 (365.25 天)。
    RFC 中還有舉例 604800 (7 天) 和 1296000 (15 天),這些數值基本用在 mode: testing 的情況。
    如果是 mode: enforce 的情況下,就建議設定至少數週,最大值也可以,只是後續需要關閉,或者調整政策,就會需要更長時間更新。
  • mx:你的域名的 mail server,域名設哪些 MX 記錄,就設定哪些。
    每個 mail server 都是獨立的,有三筆就是三個 mx: 記錄,不可以偷懶使用萬用字元 *.example.com,這樣若碰到 MX 記錄偽造會出事。

至於上述欄位順序,沒什麼差,通常服務供應商,設定自己的 MTA-STS,都是 version: STSv1 在最上面,剩下的自由心證。

那以我目前的狀態為例,我沒架設過 MTA-STS,所以理想的情況是先測試,再正式部署。
因此模式使用 testing,測試時大概用個 1、2 週,看個報告,運作正常,就可以轉成 enforce 狀態。

搭配上我這篇文章的設定,MX 記錄設定了 mxext1~4.mailbox.org.,因此 mta-sts.txt (Policies) 如下:

version: STSv1
mode: testing
max_age: 604800
mx: mxext1.mailbox.org
mx: mxext2.mailbox.org
mx: mxext3.mailbox.org
mx: mxext4.mailbox.org

Cloudflare Pages 部署 HTTPS 站點#

知道了 Policies 怎麼寫,接著就來把 MTA-STS HTTPS 站點架起來。

你可能會疑惑,為什麼是用 Cloudflare Pages,不是 Worker,看完以下步驟,再跟你說是哪一步出問題。

首先找個喜歡的地方,建立名叫 .well-known 的資料夾:

接著進入 .well-known,建立 mta-sts.txt,把剛剛規劃好的 Policies 寫進去。

寫完後,把 .well-known 壓縮,變成 zip 檔,等等會用到。

完成後,打開 Cloudflare Dashboard,進入 Workers & Pages,按右上角的 Create application。
接著你會進入「建立 App」的頁面,選擇下面的 Continue to Pages。

然後就會進到 Cloudflare Pages 的 Get started 的介面,選下面的「Drag and drop your files」。
接下來你會看到,整頁只有兩個項目和步驟。

首先,名字隨便取,為了方便識別,我設定成 mta-sts。
接下來就是上傳檔案,可以上傳檔案或資料夾,把你的 .well-known.zip 拖進去,它就會連同 .well-known 的目錄結構一起保存下來,成為這網頁的資源。
整體設定完後,畫面如下:

NOTE

上傳時,你拖進去的那個資料夾會被當成網站根目錄。
如果直接拖 .well-known 資料夾,裡面的檔案會被放到根目錄,變成 /mta-sts.txt,而不是 /.well-known/mta-sts.txt,所以要用上面的 zip 檔上傳。

NOTE

追記10/15:直接把有 .well-known/ 的父目錄拖上去,可以做到一樣的效果,最上層的資料夾會被當根目錄,保留底下的目錄結構。

建立 Cloudflare Pages 時,跟 Worker 一樣,會有測試網址,只要訪問測試網址,就可以知道有沒有成功,測試過程如下:

~
❯ curl -s https://***.pages.dev/.well-known/mta-sts.txt
version: STSv1
mode: testing
max_age: 604800
mx: mxext1.mailbox.org
mx: mxext2.mailbox.org
mx: mxext3.mailbox.org
mx: mxext4.mailbox.org

這樣就是成功了。

至於為什麼要用 Cloudflare Pages,而不是 Worker,原因在於目錄配置的問題。
我沒辦法在 Worker 內,把 mta-sts.txt 指定到 .well-known 底下,即使整個資料夾丟上來也是。

目前查到的方法,要麼寫成動態網頁 (用 Hello world 那個去改),要麼用 wrangler 部署。
我自己也想過,用 GitHub Repo 然後串到 Cloudflare Worker 上面,但是這樣太繞了,所以作罷。

另外,Cloudflare 近年推薦新專案改用 Workers,Pages 目前仍可使用,但介面和流程未來可能會變,本文以 2026 年 10 月的介面為準。

回到正題,接下來要設定域名,進入部署 MTA-STS Policies 的 Cloudflare Pages 設定頁,上面選擇 Custom domains。
下面有個「Setup a custom domain」,按下去。
在上面輸入 mta-sts.<你的 Email 域名>。

下一步後,你會看到 Pages 會替你新增一個 CNAME,接下來按 Activate domain 即可。

等到 Status 變成 Active 後,你就可以測試,看看可不可以用 mta-sts 這個域名,拿到 Policies:

~
❯ curl -s https://mta-sts.just-passersby.net/.well-known/mta-sts.txt
version: STSv1
mode: testing
max_age: 604800
mx: mxext1.mailbox.org
mx: mxext2.mailbox.org
mx: mxext3.mailbox.org
mx: mxext4.mailbox.org

需要注意的是,RFC 8461 要求不能經過重新導向,所以用 curl 測試時,還要再看有 I 參數的情況,不能看到 3xx:

~
❯ curl -sI https://mta-sts.just-passersby.net/.well-known/mta-sts.txt
HTTP/2 200 
date: Mon, 12 Oct 2026 04:52:50 GMT
content-type: text/plain; charset=utf-8
report-to: {"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=NBX7vSv4Fe33j2OWwJmmbScbIVIDesP8NCRAffFmwJHjVzSZKnIOFkGerch7HTBtNuqNFOoGWI98FZKdTwIS4ZUI4pQWUN0AFI%2FSLhDS8ijbT11xhshQeVToU5m8%2FI3yDxTLqrli5U7Sb8s%2BzA%3D%3D"}]}
nel: {"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}
access-control-allow-origin: *
cache-control: public, max-age=0, must-revalidate
etag: "da5f45b3c9a51a82fae494829d523d30"
referrer-policy: strict-origin-when-cross-origin
x-content-type-options: nosniff
server: cloudflare
cf-cache-status: DYNAMIC
cf-ray: a4937f54f872a9b3-TPE
alt-svc: h3=":443"; ma=86400

MTA-STS TXT 記錄#

接下來就要設定 MTA-STS 的 DNS 記錄了。

MTA-STS 的 TXT 記錄非常簡單,僅有一行:

_mta-sts.example.com.  IN TXT "v=STSv1; id=20160831085700Z;"

需要注意的是 id,長度最多 32 個字元,僅支援英數,所以像是 ISO 8601 擴充版 (比如 2026-10-07T16:00:00) 因為有符號,所以就不支援。
除此之外,id 不需要比前一次大,只要跟上次不一樣即可。

但如果你是比如 A -> B -> A 這類,後面改回來,那某個 MTA 如果在 A 的時候快取了 Policies,B 那段期間剛好沒寄信給你,等它再查到 A,就會以為你沒更新,沿用最早那份 Policies。
所以唯一性很重要,通常你用 YYYYMMDD 甚至 YYYYMMDDHHMM 就能保證唯一性,上面 RFC 範例的 20160831085700Z (ISO 8601 基本/緊湊版) 就是這種格式。

我懶惰一點,因為 MTA-STS 記錄通常不怎麼變動,理想上,只會變動 2 次 (第一次部署使用 testing,以及確認設定沒問題,改成 enforce)。
所以我用 YYYYMMDD,然後尾隨字母作為設定,如果一天內真的動到 1 次以上,改動後面的字母即可。

以下是參考,實際規則套用到我的域名,你也不一定要學我這麼做:

這樣 MTA-STS 就設定完成了,日後要把 Policies 改成 enforce,或是遷移 MTA,就是依照一樣的步驟。

不過如果你跟我現在一樣,Policies 是 testing 的話,那其實跟沒開差不多,那為什麼要開 testing?
這就要有請下一個協定,這個協定可以讓我們收到報告,知道 SMTP Inbound 的安全性設定情況!

TLS-RPT#

沒錯,就是 TLS-RPT!

TLS-RPT 定義於 RFC 8460,在 MTA-STS 前面,MTA-STS testing 模式就是為了搭配 TLS-RPT 使用。

那 TLS-RPT 同樣非常簡單,只有一行 TXT 記錄。
既然是收 Report 用的,那自然要有收的位置,TLS-RPT 支援 HTTPS 和 Email 的方式收報告。

如果是個人使用的話,我推薦用 Email 就好,因為不管用不用 Email,對方都是寄 JSON 然後包在 .gz 裡面。
用 HTTPS 的話,基本等於架設一個能收資料的網頁,即使不做 Monitor 頁面,自動化收 .gz + 串一個檔案儲存也是一個不小的成本。
其次是 TLS-RPT 的 Reporter,一般來說只有大公司會做,也就是 Gmail、Outlook 之類的,小一點的公司,不一定會做。
如果又沒什麼人寄信給你,那 Report 少得可憐。

那指定收報告的位置,跟 DMARC 一樣,用 rua 指定,如果像我一樣用 Email 收報告,要用 mailto: 來指定收報告的信箱。
所以設定 TLS-RPT 之前,建議先把帳號,或者 alias 設好。

TLS-RPT 的設定方式如下:

# Email
_smtp._tls.example.com. IN TXT "v=TLSRPTv1; rua=mailto:reports@example.com"
# HTTPS
_smtp._tls.example.com. IN TXT "v=TLSRPTv1; rua=https://reporting.example.com/v1/tlsrpt"

對,前面就是 _smtp._tls,然後 Version 跟 MTA-STS 一樣,只有 TLSRPTv1 可以選。

那套用到我的環境上,在 Cloudflare 上的設定長這樣:

等別人寄信給你,最快隔天就有 TLS-RPT 報告了。

檢查#

收到 TLS-RPT 報告前#

設定完成後,MTA-STS 除了可以用 TLS-RPT 獲得更詳細的報告,也可以用其他驗證器檢查,有的驗證器會額外驗證其他東西。

這裡以 MxToolbox 為例,它可以查詢 MX、MTA-STS 和 TLS-RPT 設定。
以下是我的域名設定完後,用 MxToolbox 測試的結果:

解讀 TLS-RPT#

你用自己的 Google、Office 365 帳號寄給自己,應該會陸續收到 TLS-RPT 報告。
根據 RFC 8460 的說明,理論上會在格林威治時間 0 點結算,算上延遲發信建議,還有寄出時的延遲,1~3 天內會收到。

以 Google 為例,從寄信那天開始算,第二天晚上 8 點收到 TLS-RPT 報告,延遲了半天。
我目前收到了兩份,可以確認一下:

  • Report 1:
    {"organization-name":"Google Inc.","date-range":{"start-datetime":"2026-10-05T00:00:00Z","end-datetime":"2026-10-05T23:59:59Z"},"contact-info":"smtp-tls-reporting@google.com","report-id":"2026-10-05T00:00:00Z_just-passersby.net","policies":[{"policy":{"policy-type":"no-policy-found","policy-domain":"just-passersby.net"},"summary":{"total-successful-session-count":1,"total-failure-session-count":0}},{"policy":{"policy-type":"sts","policy-string":["version: STSv1","mode: testing","max_age: 604800","mx: mxext1.mailbox.org","mx: mxext2.mailbox.org","mx: mxext3.mailbox.org","mx: mxext4.mailbox.org"],"policy-domain":"just-passersby.net","mx-host":["mxext1.mailbox.org","mxext2.mailbox.org","mxext3.mailbox.org","mxext4.mailbox.org"]},"summary":{"total-successful-session-count":3,"total-failure-session-count":0}}]}
  • Report 2:
    {"organization-name":"Google Inc.","date-range":{"start-datetime":"2026-10-06T00:00:00Z","end-datetime":"2026-10-06T23:59:59Z"},"contact-info":"smtp-tls-reporting@google.com","report-id":"2026-10-06T00:00:00Z_just-passersby.net","policies":[{"policy":{"policy-type":"sts","policy-string":["version: STSv1","mode: testing","max_age: 604800","mx: mxext1.mailbox.org","mx: mxext2.mailbox.org","mx: mxext3.mailbox.org","mx: mxext4.mailbox.org"],"policy-domain":"just-passersby.net","mx-host":["mxext1.mailbox.org","mxext2.mailbox.org","mxext3.mailbox.org","mxext4.mailbox.org"]},"summary":{"total-successful-session-count":1,"total-failure-session-count":0}}]}

寄送時是用 gzip 包起來,並且解壓縮後,你會看到「一條」JSON…
那應該怎麼解讀呢?重點看 policies 和 summary。
前者會列出安全協定和設定,後者會記錄連線次數。

先交代一下我的設定順序:MTA-STS (先 Policies、再 TXT 記錄) 全部設定完成後,我才設定 TLS-RPT,接著才開始寄信測試。
所以 Report 1 裡的所有 Session,都發生在 MTA-STS 設定好之後。

可以看到 Report 一共有 4 個 Session (剛好對應我收到 4 封信),沒有失敗,但其中一個的 policy-type 是 no-policy-found。
既然 MTA-STS 已經設好了,為什麼還會這樣?
最可能的原因就是「先寄再查」:RFC 8461 Section 5.1 提到,MTA 手上沒有快取時,可以用非同步的方式去抓 Policies,避免卡住寄信。
也就是說,Google 寄第一封時還沒拿到我的 Policies,這封就先照沒有 MTA-STS 的方式寄出;等 Policies 抓回來快取後,後面 3 封就都是 sts 了。

另外,我在前一篇文章已經設定好 DANE,但報告裡完全沒有 tlsa 類型的 policy (RFC 8460 定義的 policy-type 只有 tlsa、sts、no-policy-found 三種),所以可以確定 Google 寄信時只走 MTA-STS,不會做 DANE 驗證。

換句話說,Google 寄信時只看 MTA-STS,如果你沒有設定 MTA-STS,那就只剩 opportunistic STARTTLS,也就是能加密就加密,被降級了也不會發現。

我也用學校的 Office 365 帳號寄了信給自己,第一封寄出後等了 3 天,還是沒收到報告。
之後又補寄了兩封,等了超過 24 小時,依然沒有消息。

我猜微軟可能在 no-policy-found 的情況下不會寄報告,不過我沒找到官方文件佐證,只能先當作推測。
這篇文章寫太久了,就先不等了。3

真不懂微軟寄送 TLS-RPT 的條件是什麼…?

下一步#

如果你前面設定 MTA-STS 是用 testing,等你收到 TLS-RPT 的報告後,確認 MTA-STS 有在運作,就可以回到這裡,把 mode 改成 enforce 同時拉長 max_age 到數週至最大時長,依照前面所說的步驟,更新 MTA-STS 即可。

之後除非你要換 Email 供應商,或者你想關閉 MTA-STS,才會需要回來動 MTA-STS 的設定。

那如果你的 Email 供應商,支援 DANE for SMTP 的話,我也建議把 DANE 設定起來,文章我已經寫在這裡。

Reference#

註解#

  1. RFC 8615 是 RFC 5785 的更新版,定義了更詳細的規範和內容,定義了 HTTP/HTTPS 以外的協定如何使用,以及針對安全性做了一些加強。 ↩

  2. mode 是必填欄位,沒有預設值。none 的用途是明確告訴寄件端「這個域名正在關閉 MTA-STS」(Section 8.3),跟完全沒有 Policies 是不同的狀態。 ↩

  3. 之後如果收到微軟的 TLS-RPT 報告,會再回來更新這段。 ↩

留言