Go 如何減少供應鏈攻擊?

作者 | Filippo Valsorda

譯者 | Sambodhi

策劃 | 鈺瑩

現代軟體工程是協作性的,並且基於對開源軟體的重用。這就使目標暴露在供應鏈攻擊之下,而軟體專案則會因為其依賴性被破壞而遭到攻擊。

無論採用何種過程或技術手段,每個依賴性都必然存在著相互信任的關係。但是,Go 的工具和設計幫助降低了所有階段的風險。

所有構建都已“鎖定”

外部世界的變化,例如釋出依賴性的新版本,並不會影響 Go 的構建。

與大多數軟體包管理器檔案不同,Go 模組沒有單獨的約束列表和鎖檔案,但是它鎖定了某個特定的版本。任何 Go 構建的每個依賴性的版本完全取決於主模組的 go。mod 檔案。

從 Go 1。16 開始,這種決定論就會強制執行,並且在 go。mod 不完整的情況下,構建命令(gobuild、gotest、goinstall、gorun……)將會失敗。唯一會改變 go。mod(因此也會改變構建)的命令是 goget 和 gomodtidy。這些命令不會被自動或在 CI 中執行,所以對依賴樹的改變必須是故意的,並且有機會透過程式碼審查。

這對安全非常重要,因為當 CI 系統或新機器執行時,簽入 (checked-in) 的原始碼是最終的和完整的,程式碼將說明什麼會被構建,第三方沒有辦法影響它。

此外,當用 goget 新增依賴性時,由於最小版本的選擇,它的交叉依賴會按照依賴的 go。mod 檔案中指定的版本新增,而不是按照最新版本。同樣的情況也發生在呼叫 goinstallexample。com/cmd/devtoolx@latest 的情況下,在某些生態系統中,它的等價物會繞過 pinning。在 Go 中,example。com/cmd/devtoolx 的最新版本將被獲取,但所有的依賴性將由其 go。mod 檔案設定。

如果一個模組被破壞,新的惡意版本被髮布,在它們明確更新該依賴性之前,不會受到任何影響,這就提供了審查更改的機會,並讓生態系統有了足夠的時間來檢測事件。

版本內容永遠不會改變

確保第三方不能影響構建的另一個關鍵屬性是,模組版本的內容是不可改變的。如果攻擊者破壞了依賴性,可以重新上傳現有的版本,他們就可以自動破壞所有依賴它的專案。

這就是 go。sum 檔案的作用。它包含構建所需的每個依賴項的加密雜湊列表。同樣,一個不完整的 go。sum 會導致錯誤,而且只有 goget 和 gomod tidy 才會修改它,所以任何對它的修改都會伴隨著故意的依賴性改變。其他的構建被保證有一套完整的校驗和。

這是大多數鎖檔案的一個共同特徵。Go 透過 Checksum Database(簡稱 sumdb)超越了它,它是一個全域性性的、僅可附加的加密驗證的 go。sum 條目列表。當 goget 需要在 go。sum 檔案中新增一個條目時,它從 sumdb 中獲取該條目,並對 sumdb 的完整性進行加密證明。這不僅確保了某一模組的每一次構建都使用相同的依賴內容,而且確保了每一個模組都使用相同的依賴內容。

sumdb 使得被破壞的依賴內容,甚至谷歌運營的 Go 基礎設施不可能用修改過的(例如 backdoored)原始碼來針對特定的依賴內容。保證你使用的程式碼與其他使用例如 example。com/modulex 的 v1。9。2 的人所使用的程式碼完全一樣,並且已透過審查。

最後,我最喜歡 sumdb 的特性是:它不需要模組作者的任何金鑰管理,並且可以無縫地與 Go 模組的去中心化特性配合使用。

大標題 VCS 是真理的源泉

大多數專案是透過某種版本控制系統(VCS)開發的,然後在其他生態系統中,上傳到包儲存庫。這意味著有兩個賬戶可能被入侵,即 VCS 主機和包儲存庫,後者使用得更少,更容易被忽視。這也意味著在上傳到儲存庫的版本中更容易隱藏惡意程式碼,尤其是在上傳過程中經常修改原始碼的情況下,比如說將其最小化。

在 Go 中,不存在包儲存庫賬戶這樣的東西。包的匯入路徑嵌入了 gomoddownload 所需要的資訊,以便直接從 VCS 中獲取其模組,其中標籤定義了版本。

我們確實有 Go Module Mirror,但那只是一個代理。模組作者不需要註冊賬戶,也不需要向代理上傳版本。代理使用與 go 工具相同的邏輯(事實上,代理執行 gomoddownload)來獲取和快取版本。由於校驗資料庫保證給定的模組版本只能有一個源樹,每個使用代理的人都會看到與繞過代理直接從 VCS 獲取的結果相同。(如果該版本在 VCS 中不再可用,或者其內容發生了變化,直接獲取將導致錯誤,而從代理獲取可能仍然有效,提高了可用性並保護生態系統免受 “左鍵”問題的影響)。

在客戶端執行 VCS 工具會暴露出一個相當大的攻擊面。這也是 Go Module Mirror 的另一個作用:代理上的 Go 工具在一個強大的沙盒內執行,並被配置為支援所有的 VCS 工具,而預設的是隻支援兩個主要的 VCS 系統(git 和 Mercurial)。任何使用代理的人仍然可以獲取使用非預設的 VCS 系統釋出的程式碼,但攻擊者在大多數安裝中無法接觸到這些程式碼。

僅構建程式碼,但不會執行它

Go 工具鏈的一個清晰的安全設計目標是,即使程式碼是不可信和惡意的,也不能獲取或構建程式碼來執行該程式碼。這與大多數生態系統不同,其中許多生態系統對在獲取包時執行程式碼提供了一流的支援。這些“安裝後”的鉤子在在過去被用作一種最方便的攻擊方式:透過受到攻擊的依賴攻擊開發者的機器,並透過 module 作者進行蠕蟲攻擊。

公平地說,如果你要獲取一些程式碼,往往會在不久之後執行,要麼作為開發者機器上測試的一部分,要麼作為生產中二進位制檔案的一部分,所以缺乏安裝後鉤子只會減緩攻擊者。(在構建過程中沒有安全邊界:任何有助於構建的軟體包都可以定義一個初始函式)。然而,這也是一種有意義的風險緩解,因為你可能在執行一個二進位制檔案或測試一個包時,只使用了模組依賴的一個子集。例如,如果你在 macOS 上構建並執行 example。com/cmd/devtoolx,那麼只有 Windows 的依賴或 example。com/cmd/othertool 的依賴就不可能危害到你的機器。

在 Go 中,不為特定構建提供程式碼的模組對其沒有安全影響。

“一點複製比一點依賴要好”

在 Go 生態系統中,最後一個也許也是最重要的軟體供應鏈風險緩解措施是最沒有技術含量的一個:Go 有一種拒絕大型依賴樹的文化,寧願複製一下也不願意新增新的依賴。這可以追溯到 Go 的一個諺語:“一點複製比一點依賴要好”。高質量的可重用 Go 模組自豪地戴上了 “零依賴” 的標籤。如果你發現自己需要一個庫,你很可能會發現它不會導致你依賴其他作者和所有者的幾十個模組。

豐富的標準庫和其他模組(golang。org/x/……的模組)也支援這一點,這些模組提供了常用的高階構建模組,如 HTTP 棧、TLS 庫、JSON 編碼等。

所有這些意味著只需少量的依賴性就可以建立豐富、複雜的應用程式。無論工具有多好,它都不能消除重複使用程式碼的風險,所以最有力的緩解措施永遠是一個小的依賴樹。

https://go。dev/blog/supply-chain

群內定期釋出網際網路簡報、優質文章等內容。除此之外,我們還會不定期準備 InfoQ 周邊等各種福利送給大家!快來掃碼加入吧~