Let's Encrypt 做爲一家免費提供 SSL 證書的組織,旨在推動互聯網向更安全的 HTTPS 遷移,受到了大量小型網站的支持和承認。然而不少站長在 3 月 3 日收到了來自 Let's Encrypt 名爲 ACTION REQUIRED: Renew these Let's Encrypt certificates by March 4 的郵件,警告站長儘快更新證書。那麼爲何須要更新證書?不更新證書有什麼危害?如何更新證書?本文將爲讀者分析本次 Let's Encrypt 證書漏洞事故的真相。
首先摘錄一下郵件中的部份內容:html
We recently discovered a bug in the Let's Encrypt certificate authority code,described here:https://community.letsencrypt.org/t/2020-02-29-caa-rechecking-bug/114591Unfortunately, this means we need to revoke the certificates that were affectedby this bug, which includes one or more of your certificates. To avoiddisruption, you'll need to renew and replace your affected certificate(s) byWednesday, March 4, 2020. We sincerely apologize for the issue.If you're not able to renew your certificate by March 4, the date we arerequired to revoke these certificates, visitors to your site will see securitywarnings until you do renew the certificate. Your ACME client documentationshould explain how to renew.
郵件大意爲:Let's Encrypt 的證書校驗代碼中存在一個 BUG,部分證書受到了這個 BUG 的影響。咱們將會在 3 月 4 日(週三)開始吊銷受影響的證書,若是你的證書在吊銷列表中,請當即更新證書到最新版本。git
那麼問題來了:發生了什麼 BUG 致使這一後果?證書吊銷是什麼?如何更新證書?這就是接下來要講解的內容。程序員
首先我來說解一下到底發生了什麼 BUG 引發瞭如此大的事故。算法
根據郵件中的連接原文,Let's Encrypt 使用了自行研發的一款證書籤發軟件稱爲 Boulder,但該軟件在 2019 年 7 月 25 日引入了一個 BUG,致使 CAA 記錄認證出現錯誤。瀏覽器
原文以下:安全
On 2020-02-29 UTC, Let’s Encrypt found a bug in our CAA code. Our CA software, Boulder, checks for CAA records at the same time it validates a subscriber’s control of a domain name. Most subscribers issue a certificate immediately after domain control validation, but we consider a validation good for 30 days. That means in some cases we need to check CAA records a second time, just before issuance. Specifically, we have to check CAA within 8 hours prior to issuance (per BRs §3.2.2.8), so any domain name that was validated more than 8 hours ago requires rechecking.The bug: when a certificate request contained N domain names that needed CAA rechecking, Boulder would pick one domain name and check it N times. What this means in practice is that if a subscriber validated a domain name at time X, and the CAA records for that domain at time X allowed Let’s Encrypt issuance, that subscriber would be able to issue a certificate containing that domain name until X+30 days, even if someone later installed CAA records on that domain name that prohibit issuance by Let’s Encrypt.We confirmed the bug at 2020-02-29 03:08 UTC, and halted issuance at 03:10. We deployed a fix at 05:22 UTC and then re-enabled issuance.Our preliminary investigation suggests the bug was introduced on 2019-07-25. We will conduct a more detailed investigation and provide a postmortem when it is complete.
那麼 CAA 是什麼呢?根據維基百科的解釋,CAA 全稱爲『DNS 證書頒發機構受權』,用於避免非受權的證書生成。服務器
看完定義,可能一些讀者依舊對 CAA 的概念感到模糊。這裏咱們要明確一下證書的做用以及證書在 SSL 數據傳輸中所起的角色:app
SSL 數據傳輸有兩個做用,一個是加密,這依靠的是非對稱加密,準確說是公私鑰構成的加密體系即公開密鑰加密,避免互聯網中傳輸的數據被中間人竊聽;另外一個是鑑證,也就是依靠數字簽名和證書鏈實現的身份鑑別,若是出現中間人對數據進行竊聽或重定向,因爲證書包含數字簽名,這樣客戶就能區分什麼證書是可信的、什麼是不可信的。dom
互聯網中大部分證書都是由可信第三方,即證書頒發機構(Certificate Authority)簡稱 CA 簽發。但若是你不須要驗證身份,只須要加密數據就可使用不包含可信第三方的自簽名證書,這時候證書的鑑證效果再也不存在,只起到了加密的效果。大部分瀏覽器都不會認可自簽名證書,所以會使用醒目的紅色標識告訴用戶:沒法肯定證書是否有效。ide
那麼 CAA 究竟是作什麼用的呢?我舉一個例子好了。若是我在甲 CA 憑藉正當身份註冊了http://example.comHTTP),對於自簽名證書,前面提到了瀏覽器會攔截,而對於 HTTP 的降級一樣有 HSTS 協議能夠保證其絕對不可能發生。的證書,那麼全部訪問我網站的用戶均可以經過證書中的信息(例如申請單位、簽發單位等)瞭解這個證書的可信性。若是有攻擊者企圖攔截數據,就必定會破壞證書(好比使用自簽名證書或降級到
但不要忘了,CA 的本質也是企業。不一樣的 CA 之間不太可能共享信息,也就是說假若有一個乙 CA 對客戶信息鑑別不充分,攻擊者就能夠在乙 CA 上假冒個人身份也註冊一個http://example.comhttp://example.com 的有效證書就有兩份,攻擊者能夠在攔截數據後向用戶返回來自乙 CA 的那一份證書,用戶依舊沒法鑑別是否遭受中間人攻擊。證書。這時候關於
圖 1. 兩個 CA 爲同一個域名生成了兩份證書
圖 2. 普通的中間人攻擊,自簽名證書將不被用戶信任
圖 3. 如何使用圖 1 的漏洞實現用戶無感知的中間人攻擊
從圖中能夠看出:不一樣的 CA 沒法共享信息形成了中間人攻擊的隱患,而這就是 CAA 存在的目的。
CAA 和 A 記錄同樣,都是 DNS 記錄的一部分,若是一個 CA 接受到了證書生成的請求,它首先會訪問這個域名在 DNS 中對應的 CAA 記錄,查看其中包含的信息。若是 CAA 記錄容許這個 CA 生成證書,它纔會進行接下來的操做,不然將會拒絕證書申請。除此以外,CAA 記錄還支持在證書申請時告知特定郵箱(好比域名持有者),警戒持有者:有用戶正在僞造你的身份。
CAA 記錄並不是強制標準,但絕大多數的 CA 都遵照了這一規定,畢竟由於違反規定致使證書被濫用,瀏覽器廠商是有權利吊銷 CA 的根證書的。舉一個例子:中國沃通由於違規簽發證書,致使其根證書被吊銷,全部新簽發的證書都再也不獲得主流操做系統和瀏覽器的認可,相關新聞能夠查看這篇知乎問答。根證書被吊銷將會毀滅一家 CA 的信譽和所有業務,這也是爲何 CA 如此少、證書申請如此麻煩、Let’s Encrypt 官方對這次漏洞如此重視的主要緣由。
上面提到了,本次事故出在 CAA 部分代碼。那麼 Let’s Encrypt 應該如何校驗 CAA,又如何進行了錯誤校驗呢?
根據官方說明:Let’s Encrypt 的服務器會在用戶申請證書的八小時內對證書對應域名的 CAA 記錄進行檢查,若是檢查經過,接下來的 30 天內都不會對其進行從新檢查。
這裏的規則實際上不是 Let’s Encrypt 本身制定的,而是來源於 CA/Browser Forum,一個制定 CA 和瀏覽器關於證書處理規範的論壇。CA/Browser Forum 提供了一份規範 (Baseline Requirements),要求全部 CA 按照規範中的內容進行證書籤發和吊銷,其中在§3.2.2.8:CAA Records 要求瞭如下內容:
As part of the issuance process, the CA MUST check for CAA records and follow the processing instructions found, for each dNSName in the subjectAltName extension of the certificate to be issued, as specified in RFC 6844 as amended by Errata 5065 (Appendix A). If the CA issues, they MUST do so within the TTL of the CAA record, or 8 hours, whichever is greater. This stipulation does not prevent the CA from checking CAA records at any other time.
大致上就是:CA 須要在簽發證書的八小時內對所簽發域名的 CAA 記錄進行覈查,除此以外還能夠在任什麼時候間進行其餘覈查以進一步確保安全。
結合上面官方的說明能夠了解到,Let’s Encrypt 嚴格遵照了這一標準。但 Let’s Encrypt 的 CA 系統犯了一個錯誤,若是一個證書包含 N 個域名,CA 系統應該對每一個域名都單獨進行 CAA 檢查,結果卻將 N 個域名中的某一個檢查了 N 次,其餘 N-1 個域名均未被檢查而直接經過。
也就是說:若是攻擊者發現了這一漏洞,它就能夠經過申請多域名證書的方式來繞過 CAA 記錄對證書申請的限制。舉例而言,攻擊者能夠申請包含如下域名的證書:
在沒有以上漏洞的狀況下,CA 軟件會對三個域名的 CAA 進行檢查,這時若是第一個域名的 CAA 記錄拒絕 Let’s Encrypt 簽發證書,簽發流程會所以停止,攻擊者沒法獲得證書。
但若是以上漏洞存在,CA 軟件可能只會對 another-domain-controlled-by-hacker.com 或 some-domain-controlled-by-hacker.com 進行 CAA 記錄檢查(並且是檢查三次),由於這個域名被攻擊者所控制,所以他能夠容許 Let’s Encrypt 進行證書籤發,這樣就繞過了 example.com 的 CAA 記錄限制。
固然,這並不意味着 Let’s Encrypt 的這一漏洞可讓攻擊者隨意僞造身份進行證書申請,由於解除 CAA 限制只是破除 CA 衆多檢查中的一個,Let’s Encrypt 的 HTTP 驗證、DNS 驗證分別須要對服務器或 DNS 進行實質性控制。
須要注意的是,利用難度大,也不意味着這一漏洞的存在是合理的。
首先 Let’s Encrypt 是一家 CA,必須遵照相關規定,且爲客戶的安全負責(儘管客戶並未付費);更重要的是,Let’s Encrypt 並不是一個獨立組織,而是隸屬於互聯網安全研究小組,致力於加強全互聯網的信息安全,做爲互聯網安全的推動者絕對不能首先破除規則。
其次,若是攻擊者恰好拿到了服務器控制權,那麼有 CAA 的限制,攻擊者依舊沒法成功申請證書。但若是 Let’s Encrypt 未能合理對 CAA 進行檢查,即攻擊者不只發現了此漏洞,還拿到了服務器控制權,那麼僞造身份將會變得易如反掌。至於控制 DNS,攻擊者徹底能夠刪除 CAA 記錄,所以引起的事故屬於 CA 能力範圍之外,CA 也無需爲此負責。
這就是爲何 Let's Encrypt 對這一事故的處理如此嚴肅,甚至在事故發生後馬上關閉了受影響的兩臺 CA 服務器,還發布了所受影響 300 萬個證書的 Hash(壓縮包高達 300MB+),同時向全部在申請證書時附帶郵件地址的用戶緊急發送郵件。做爲一家 CA,Let's Encrypt 無疑是負責的;做爲互聯網安全研究小組的項目,Let's Encrypt 對事故的處理態度無疑也爲其餘 CA 起到了模範做用。
圖 4. Let’s Encrypt 官方的服務中斷公告,在事故發生後馬上關閉了受影響的 CA 服務器
圖 5. 用戶反饋申請了 100 個域名的證書後,發現出現了 100 次如出一轍的報錯,全部報錯都由於其中 一個域名的 CAA 記錄不容許 Let’s Encrypt 簽發證書。而 Let’s Encrypt 收到用戶的 BUG 反饋後馬上意識到這是一個安全事故,進行了相關處理。
Let’s Encrypt 的態度無疑讓人對其肅然起敬,但這並不意味着 Let’s Encrypt 不須要爲此負責。
閱讀完上面的事故分析,可能仍是有不少讀者不清楚:明明應該校驗每一個域名,究竟是什麼 BUG 致使了 Let’s Encrypt 只校驗了其中一個呢?
在文章的最開始,我提到了 Let’s Encrypt 使用了一款叫作 Boulder 的軟件。其實這是一款開放源代碼的軟件,地址爲 letsencrypt/boulder。
該軟件使用 Golang 開發,旨在實現一個 ACME 協議的 CA 服務器,Let’s Encrypt 的官方 CA 服務器運行着該軟件。
那麼這個軟件到底出現了什麼問題纔會致使如此滑稽的故障?我翻看着 Let’s Encrypt 最近的 commit,找到了一個 Pull Request:#4690。看完這個 Pull Request 後,我立刻意識到問題所在:Golang 最經典的錯誤——循環迭代變量陷阱。
對於不熟悉 Golang 的讀者,可能不知道我在說什麼,這裏我使用 C 語言舉一個例子:
int main() { int* arr[3]; for (int i = 0; i < 3; i++) { arr[i] = &i; } printf("%d %d %d", *arr[0], *arr[1], *arr[2]); return 0;}
大部分讀者應該都熟悉 C 語言,應該能夠看出上面的例子返回的結果是3 3 3
而非1 2 3
,由於arr
的三個元素都是i
的地址,而i
最終的值爲3
。
做爲『21 世紀的 C 語言』,Golang 一樣存在這一問題:
func main() { var out []*int for i := 0; i < 3; i++ { out = append(out, &i) } fmt.Println("Values:", *out[0], *out[1], *out[2]) fmt.Println("Addresses:", out[0], out[1], out[2])}
輸出結果爲:
Values: 3 3 3Addresses: 0x40e020 0x40e020 0x40e020
因爲這一問題過於廣泛,Golang 甚至將其寫入了文檔的『常見錯誤』部分:文檔
而這一『常見錯誤』,就出如今 Let’s Encrypt 的代碼中。
咱們倒回這個 Pull Request 以前的代碼,來看看這一錯誤如何在 Boulder 中重現:
// authzModelMapToPB converts a mapping of domain name to authzModels into a// protobuf authorizations mapfunc authzModelMapToPB(m map[string]authzModel) (*sapb.Authorizations, error) { resp := &sapb.Authorizations{} for k, v := range m { // Make a copy of k because it will be reassigned with each loop. kCopy := k authzPB, err := modelToAuthzPB(&v) if err != nil { return nil, err } resp.Authz = append(resp.Authz, &sapb.Authorizations_MapElement{Domain: &kCopy, Authz: authzPB}) } return resp, nil}// ...func modelToAuthzPB(am * authzModel)( * corepb.Authorization, error) { expires: = am.Expires.UTC().UnixNano() id: = fmt.Sprintf("%d", am.ID) status: = uintToStatus[am.Status] pb: = & corepb.Authorization { Id: & id, Status: & status, Identifier: & am.IdentifierValue, RegistrationID: & am.RegistrationID, Expires: & expires, } //...}
看到這裏,眼尖的讀者可能已經意識到問題了。對於循環變量 k,該函數拷貝了一份(甚至還貼心的加了一個註釋),而後再在resp.Authz = append(resp.Authz, &sapb.Authorizations_MapElement{Domain: &kCopy, Authz: authzPB})
將其以引用的方式傳遞出去。須要注意的 Golang 對於重複聲明的變量會使用不一樣地址,所以每次循環傳遞出去的地址都不同。
但滑稽的是,另外一個循環變量 v 卻未能獲得寵幸,開發者不知道什麼緣由忘記對其進行拷貝。代碼中的authzPB, err := modelToAuthzPB(&v)
這部分,傳遞出去的是未經複製的引用,形成了 resp 中全部的 authzPB 數據都被設定爲循環的最後一個 v,其中包含對應域名的全部信息。
更多代碼能夠在這裏看到。
那麼這個 BUG 是如何引入的呢?我使用 git blame 對附近的代碼進行檢查,發現這段代碼在2019年4月24日隨着 Pull Request #4134 帶入。
此次 Pull Request 新增代碼量高達 2750 行,並且幾乎全是新增功能,在測試不充分的狀況下的確容易將這一 BUG 遺漏。有趣的是:2019 年引入 BUG 的做者和 2020 年 Merge 對應代碼的人是同一人,即_@_rolandshoemaker。
看來就算是頂尖的程序員,也沒法保證寫出徹底沒有 BUG 的軟件🤣。
寫到這裏,相信你們應該對此次事故有着很是詳細的瞭解了。接下來咱們要談的是如何解決這次事故的影響。
根據官方描述,這次受到影響的域名簽發日期在 2019-12-04 到 2020-02-29 之間。你能夠在瀏覽器中點擊域名左邊的小鎖圖標來查看簽發時間:
若是你的域名簽發時間在此日期以外,那麼基本無需擔憂,但若是簽發時間在此日期以內,請接着往下讀:
對於收到警告郵件的讀者,請留意郵件中的域名。我收到的郵件中就有我本身博客的域名。
若是沒有收到警告郵件,但不肯定本身的域名是否受影響,有兩種方式能夠驗證:
go openssl s_client -connect example.com:443 -servername example.com -showcerts </dev/null 2>/dev/null | openssl x509 -text -noout | grep -A 1 Serial\ Number | tr -d :
我我的推薦第二種,第一種更適合代理分發 Let’s Encrypt 證書的第三方如寶塔面板等。
若是你的域名不在受影響範圍內,不用進行任何操做。不過其實就算是在受影響範圍內也無需進行操做,由於這一次吊銷列表實在太大,幾乎全部的瀏覽器都不會根據這一列表對證書進行吊銷,而 Let’s Encrypt 的證書三個月以後就會過時,過時後從新申請是不會出現任何問題的。
但本着安全起見,最好使用你的證書申請客戶端對證書進行強制重申請。這裏我以 certbot-auto 爲例:
certbot-auto renew --force-renewal
執行後全部的證書都會從新進行簽發。
簽發完成後,記得重啓你的 Web 服務器如 Nginx,以確保新的證書被正確裝載,這樣就能讓本身的域名完全免遭這次事故影響。
在事故狀態更新的帖子下面,Let’s Encrypt 官方向用戶保證了接下來將會對其餘模塊進行一樣的安全檢查:
Improve TestGetValidOrderAuthorizations2 unittest (3 weeks).Implement modelToAuthzPB unittest (3 weeks).Productionize automated logs verification for CAA behavior (8 weeks).Review code for other examples of loop iterator bugs (4 weeks).Evaluate adding stronger static analysis tools to our automated linting suite and add one or more if we find a good fit (4 weeks).Upgrade to proto3 (6 months).
做爲一家 CA,咱們其實無需過多擔憂 Let’s Encrypt 從此是否會出現相似事故,由於它們對於此次事故的重視程度實在使人驚歎,這也是我決定撰寫這篇文章的緣由。
經歷這次事故後的 Let’s Encrypt 不只沒有像其餘 CA 同樣損失用戶,反而更進一步贏得了用戶的信賴:不管是開源 CA 服務器、是公開服務狀態、是主動覆盤故障、是故障後馬上中止服務且提示受影響用戶仍是快速解決問題,這都讓 Let’s Encrypt 與其餘不負責任的 CA 不一樣。
的確,Let’s Encrypt 的 SSL 證書只起到了加密的做用,對於鑑證不如其餘商業 CA 有效,但在過去的數年裏,Let’s Encrypt 用本身的行動讓全互聯網變得更加安全——三年前我還在吐槽各個瀏覽器使用『使人噁心』的方式警告 HTTP 站點,三年後竟很難找到一個不是 HTTPS 的網站。
這就是互聯網開放之魅力:任何人均可以參與到互聯網基礎設施的建設中,而這偏偏是在其餘傳統行業所很難見到的。開放意味着人人平等,意味着每一個人均可以發現問題、能夠參與到問題的分析中、甚至能夠幫助解決問題。互聯網的建設者們也和現實世界的官僚不一樣,他們不多表露出傲慢,不管是規範的制定、是開放源代碼軟件的開發仍是社區的討論,你的貢獻和你所獲得的聲望永遠是對等的。
互聯網爲何如此有魅力?魅力在於人人生來平等。
那麼,對於咱們普通用戶,此次事故有哪些值得吸收的教訓呢?
最淺顯的教訓應該是在申請證書時附上本身正確的郵箱地址。在我收到這一封郵件後,我和其餘幾個好友分享了郵件內容,他們卻表示申請時亂填了一個地址,致使沒有收到警告。這一次事故可能比較小,但若是下一次事故是能夠無條件僞造身份呢?
目前的郵箱都有着很複雜的 spam 識別規則,所以個人建議不只是在申請證書時,而是在進行任何註冊操做時都附上正確郵箱地址。不用擔憂 spam 騷擾——按規則將它們拉入垃圾箱便可。
可能一些讀者還會吸收另外一個教訓:對本身的域名在 DNS 中增長 CAA 記錄:這是一個很是好的習慣,但若是是小型網站,在確保不包含關鍵業務,且沒有潛在競爭者狀況下,基本上無需擔憂。
但我以爲教訓應該不止於此。在我從事軟件開發工做的兩年半時間裏,不少比我資歷還要久的前輩常常會感到困惑:爲何我能在這麼短的時間裏學習這麼多東西?個人回答其實很簡單:不要放過任何一點細節。
我其實不是很是聰明的人,我一直以來對本身的要求就是『笨鳥先飛』,我在算法、理論、計算機基礎方面相比其餘同齡從業者都屬於底層。但我永遠相信勤能補拙,我堅信學習的力量,認爲膚淺的瞭解不如不學,認同終生學習。
實際上在寫到這裏的時候(本文從收到郵件開始撰寫,花了三天時間撰寫 + 校對,寫到這裏的時候是 3 月 7 日)我查了一下國內外的新聞。事故發生已經超過三天,竟然沒有一個媒體/博客對這個事故的根源進行分析!這太使人感到遺憾了。
但我相信,讀到這裏的讀者必定已經收穫了不少新知識。寫到這裏的我一樣學習了很是多——在寫這篇文章以前,我其實對於 SSL 證書的概念處於只知其一;不知其二狀態,因而我邊寫邊理順思路,邊寫邊查資料,全文寫完以後,我對於 SSL 證書的理解就已經徹底不一樣於寫做前。閱讀和寫做都是學習的一種過程,前者被動學習、後者主動學習,除此以外並沒有高下之分。
是否只有閱讀和寫做才能幫助本身?不徹底是。由於平常事務太多,個人博客中所分享的實際上是平常所掌握知識的不多一部分,也不多閱讀那些『計算機經典書籍』。
而學習應該是隨時隨地的,是不放過任何一點細節的,是無論內容是否與本身所從事事業相關,只要是未知領域都勇於探索的。若是隻是爲了從事某項特化的崗位而成爲軟件工程師,未免太沒勁了。
有一句成語叫作『求賢若渴』,我想把它改造一下,做爲本文的結束,那就是:
求學若渴
感謝讀者們能耐心讀完本文,也但願讀到這裏的讀者能有所啓發。
本文受權轉載自:公衆號GoCN(GolangChina 論壇原創投稿)
做者博客地址: https://untitled.pw/software/...
論壇閱讀連接: https://gocn.vip/topics/9967