軟體套件管理原理:為什麼來源信任
這是整篇文章最關鍵的一句話:套件管理器本質上是一個以最高權限執行遠端程式碼的代理。安裝套件時,它會解壓縮檔案到 /usr/bin、/usr/lib、/etc 等系統目錄,並且執行套件內附的安裝腳本(Debian 的 preinst、postinst,RPM 的 %pre、%post)。這些腳本以 root 身分執行,權限沒有限制。
換句話說,只要你能讓某個套件進入受信任的儲存庫,你就等於取得了該系統的完整控制權。這不是漏洞,而是設計上的必然——安裝軟體本來就需要管理權限。問題在於,這個權限的授予是建立在「來源可信」這個假設上。一旦假設不成立,整個模型就崩塌了。
理解這一點之後,你就會明白為什麼「來源信任」不是資安人員的潔癖,而是套件管理系統的地基。下一節我們來看,這個地基是怎麼被建造出來的。
二、信任鏈的構造:簽章、雜湊與信任錨
如果套件管理器要從遠端拿東西並以 root 執行,那它至少必須回答兩個問題:這份檔案有沒有被篡改?這份檔案是不是真的來自它宣稱的來源?前者靠雜湊,後者靠數位簽章。兩者經常被混為一談,但它們解決的是不同層次的問題。
2.1 雜湊解決完整性,簽章解決來源
雜湊函式(如 SHA-256)可以把任意長度的資料壓縮成固定長度的指紋。只要資料改變一個位元,指紋就會完全不同。所以如果你手上有「正確的指紋」,下載完檔案後比對一下,就能確認檔案沒有在傳輸過程中被破壞或篡改。
但這裡有個雞生蛋的問題:你手上的指紋是從哪裡來的?如果指紋跟檔案是從同一個伺服器下載的,那攻擊者只要同時改檔案和指紋,你的比對就毫無意義。這就是為什麼單靠雜湊不夠——它只能防止「意外損壞」與「部分中途篡改」,無法防止「來源本身是惡意的」。
數位簽章解決的正是這個問題。發行商用私鑰對中介資料簽章,使用者用對應的公鑰驗證。攻擊者即使能控制伺服器,只要沒有私鑰,就無法產生有效的簽章。於是信任的對象從「伺服器」轉移到「私鑰持有者」——通常就是發行商的安全團隊。
2.2 兩層式簽章:先簽清單,再靠清單保護套件
理論上,發行商可以對每個套件個別簽章。但一個儲存庫可能有數萬個套件,逐一驗證簽章會非常耗時。所以多數系統採用兩層式設計:先對「清單」簽章,清單裡再包含每個套件的雜湊值。
以 Debian/Ubuntu 為例,流程大約是這樣:
Release 檔案,裡面記載了各架構的 Packages 索引檔的雜湊值,以及整份儲存庫的版本資訊。Release 簽章,產生 Release.gpg(分離式簽章),或直接把簽章內嵌成 InRelease。apt update 時,APT 先下載 InRelease,用本機儲存的公鑰驗證簽章。Release 裡的雜湊值,並用這些雜湊值去驗證 Packages 索引檔。.deb 後再比對雜湊。這條鏈的每一環都依賴上一環,只要最頂端的簽章有效,整條鏈就成立。這種設計的效率很高:你只需要驗證一個簽章,就能保護數萬個套件。
RPM 陣營的做法略有不同。Fedora、RHEL、openSUSE 的套件檔本身帶有 GPG 簽章,DNF 在下載每個套件後直接驗證該套件的簽章(由 gpgcheck=1 控制)。中介資料也可以額外簽章(repo_gpgcheck=1),但預設不一定開啟。兩種設計各有取捨:APT 的模型是「信任清單,清單保證套件」,RPM 的模型是「逐一套件驗證」,前者效率高,後者對單一套件的保護更直接。
2.3 信任錨與金鑰管理
整條信任鏈的起點,是「你怎麼知道那把公鑰是真的」。這個起點稱為信任錨(trust anchor)。如果信任錨本身是錯的,後面所有驗證都是徒勞。
在實務上,信任錨的取得方式有以下幾種:
值得注意的是,金鑰管理在過去幾年有明顯的演進。Debian/Ubuntu 曾經廣泛使用 apt-key add 把金鑰加到全域信任圈,但這種做法等於「這把金鑰可以簽任何儲存庫」,一旦某個第三方儲存庫的金鑰外洩,攻擊者就能簽署偽造的官方套件。為此,APT 從 2.4 版起棄用 apt-key,改用 signed-by 參數,讓每把金鑰只能驗證特定的來源:
deb [signed-by=/etc/apt/keyrings/example.gpg] https://repo.example.com stable main
這個小小的語法改變,其實是信任模型的一次重要修正:從「全域信任」收斂為「範圍限定的信任」。第三方儲存庫的金鑰再怎麼外洩,也無法冒充官方套件。
2.4 TOFU、Pinning 與金鑰輪替
信任不是一次性的決定,而是需要持續維護的關係。這裡有幾個實務上常被忽略的概念:
TOFU(Trust On First Use):第一次連線時信任對方,之後若金鑰改變就發出警告。SSH 的 known_hosts 就是典型例子。TOFU 的優點是簡單,缺點是第一次連線若被中間人攻擊,後續所有驗證都會建立在錯誤的基礎上。
金鑰固定(Key Pinning):把特定金鑰或憑證的指紋寫死在程式或設定中,只接受這把金鑰。安全性高,但缺乏彈性,金鑰輪替時必須更新設定。
金鑰輪替(Key Rotation):定期更換金鑰以降低外洩風險。但輪替本身就是一個信任問題——你怎麼知道新金鑰是合法的?多數系統靠「舊金鑰簽署新金鑰」的方式建立過渡信任,形成一條可追溯的鏈。
這三個概念共同說明了一件事:來源信任不是「有簽章就安全」這麼簡單的二元判斷,而是一套需要持續營運的治理流程。發行商如何保管私鑰、多久輪替一次、外洩時如何撤銷、使用者如何得知——這些問題的答案,決定了簽章機制在真實世界中的有效程度。
三、主流生態系的信任模型差異
不同平台對「信任」的定義與實作差異很大,這些差異反映了各自的歷史脈絡與使用情境。了解這些差異,有助於你在混用多個來源時做出正確判斷。
3.1 Debian/Ubuntu:APT 與 Release 簽章
APT 的信任模型是「發行商中心制」。Ubuntu 的儲存庫由 Canonical 維護,Debian 由 Debian 專案維護,使用者透過來自安裝媒體的公鑰建立信任。整個模型假設發行商是唯一的權威,第三方來源必須明確加入並指定金鑰。
這個模型的優點是簡單明確,缺點是權力高度集中。一旦發行商的簽章金鑰外洩(歷史上發生過幾次,例如 2019 年某個第三方 PPA 的金鑰問題),影響範圍會非常大。此外,Ubuntu 的 universe 與 multiverse 元件由社群維護,安全審查程度低於 main,但預設同樣被信任。這種「同一個信任圈裡混雜不同審查等級」的設計,是實務上要特別注意的地方。
3.2 Fedora/RHEL:RPM 內嵌簽章
RPM 體系的特點是「簽章跟著套件走」。每個 .rpm 檔案在下載後都會被驗證 GPG 簽章,而不是只驗證清單。這種設計的好處是單一套件的保護更直接,即使中介資料被篡改,套件本身的簽章仍然能攔下惡意內容(前提是攻擊者沒有私鑰)。
DNF 的設定中有兩個關鍵選項:gpgcheck 控制是否驗證套件簽章,repo_gpgcheck 控制是否驗證中介資料簽章。前者預設開啟,後者預設關閉。這意味著在預設狀態下,攻擊者若能控制中介資料,仍可能誘導你安裝「簽章有效但你不想要的套件」——例如把依賴指向某個舊版有漏洞的套件。這是 RPM 模型的一個微妙之處:它保護了「套件沒被篡改」,但沒有完全保護「你裝的是對的套件」。
3.3 Arch Linux:Web of Trust 的殘餘
Arch 的 pacman 採用 SigLevel 設定來控制信任等級。歷史上 Arch 依賴 PGP 的信任網路(Web of Trust):開發者彼此簽署金鑰,使用者透過信任這些開發者來建立信任鏈。這種模型在社群規模小時很有效,但隨著規模擴大,維護成本急遽上升。
近年 Arch 已經轉向更集中的金鑰管理,由 archlinux-keyring 套件統一提供開發者金鑰。pacman 的 SigLevel 可以設定為 Required、Optional、TrustedOnly、TrustAll 等組合,讓使用者自行決定嚴格程度。這種彈性是 Arch 的特色,但也意味著「安全與否取決於使用者是否正確設定」——預設值雖然合理,但一旦為了方便而放寬,風險就會悄悄累積。
3.4 Homebrew 與語言層套件管理器
macOS 上的 Homebrew 長期以來是信任模型相對寬鬆的代表。它的 Formula 是 Ruby 腳本,從 GitHub 上的 tap 儲存庫取得,安裝時執行任意 Ruby 程式碼。雖然近年導入了 bottles(預編譯二進位)與部分簽章機制,但整體信任基礎仍偏向「信任 GitHub 上的維護者」。
語言層的套件管理器(npm、PyPI、Cargo、Go modules)情況更複雜,因為它們的設計初衷是「讓開發者快速分享程式碼」,安全模型往往是後補的。
Cargo.lock 會記錄每個依賴的 checksum,對「鎖定版本後不被替換」有一定的保護。3.5 信任模型對照
為了讓差異更清楚,可以用下表做個簡單對照:
生態系
信任單位
簽章對象
預設嚴格度
主要風險
Debian/Ubuntu APT
發行商
中介資料(Release)
高(強制驗證)
第三方 PPA 金鑰範圍過大
Fedora/RHEL DNF
發行商
套件本體
中高(中介資料預設不驗)
依賴被導向舊版或有漏洞版本
Arch pacman
開發者金鑰圈
套件本體
可調(預設嚴格)
使用者自行放寬 SigLevel
Homebrew
GitHub 維護者
部分 bottles
低至中
Formula 執行任意程式碼
npm
註冊帳號
選用(provenance)
帳號劫持、惡意套件
Go modules
校驗資料庫
模組雜湊
私有模組設定錯誤
這張表的重點不是排名誰比較安全,而是提醒你:當你把多個生態系混用在同一個系統上時,整體安全性會向最弱的一環傾斜。一個嚴格驗證簽章的 Linux 伺服器,如果同時跑了 npm install,那它的實際信任邊界可能比你想像的寬鬆得多。
四、當信任被攻破:供應鏈攻擊的幾種典型
過去十年,軟體供應鏈攻擊從理論風險變成日常新聞。理解這些攻擊的運作方式,比背誦防禦清單更有用,因為每一種攻擊都對應到信任模型中某個特定環節的鬆動。
4.1 帳號劫持與維護者疲勞
2018 年的 event-stream 事件是經典案例:一個廣泛使用的 npm 套件,原作者因為維護疲勞,把發布權限轉交給一位看似熱心的貢獻者;這位貢獻者隨後在依賴鏈中插入了一個針對特定錢包應用的惡意套件。整個過程沒有涉及任何密碼學漏洞,攻擊的是「人」——維護者的信任判斷。
2021 年的 ua-parser-js 事件、2022 年的 colors 與 faker 作者惡意破壞事件,都是同一類問題的變體。這些案例說明:在語言層生態系中,「誰有發布權限」往往比「有沒有簽章」更關鍵。簽章只能證明「這是由持有私鑰的人發布的」,但如果私鑰本身就是被騙來的,簽章毫無幫助。
4.2 依賴混淆與命名搶註
2021 年安全研究者 Alex Birsan 揭露「依賴混淆」(dependency confusion)攻擊:企業內部常使用與公開儲存庫同名的私有套件(例如 @company/utils),但套件管理器在解析時可能優先查詢公開儲存庫;攻擊者只要在公開儲存庫註冊同名套件,就能讓企業內部的建置流程下載到惡意版本。
相關的還有「命名搶註」(typosquatting):註冊與熱門套件名稱相似的套件(例如 reqeusts 對 requests),引誘打錯字的開發者安裝。這類攻擊不需要任何密碼學突破,只需要利用人類的注意力盲點與套件管理器的解析邏輯。
4.3 長期潛伏:xz-utils 案例
2024 年發現的 xz-utils 後門(CVE-2024-3094)是近年最令人震驚的供應鏈攻擊。攻擊者以「Jia Tan」的身分,花了大約兩年時間在開源社群中建立聲譽,逐步取得 xz 專案的維護權限,然後在測試檔案中藏入經過混淆的後門程式碼,並透過建置腳本在特定條件下將其注入 liblzma。
這個案例的重要意義在於:它攻擊的不是技術漏洞,而是開源治理的社會結構。攻擊者耐心地參與社群、提交合理的修補、取得維護者信任,最後才動手。整個過程中有多次機會被發現,但都因為「這個人看起來很認真」而被忽略。它也暴露了另一個問題:許多關鍵基礎設施由極少數志工維護,而這些志工往往承受著巨大的壓力與時間成本。
4.4 基礎設施層攻擊
除了套件本身,儲存庫基礎設施也是目標。DNS 劫持、CDN 設定錯誤、鏡像站被入侵,都可能讓使用者在「看起來正常」的網域下載到惡意內容。這也是為什麼單純依賴 HTTPS 不夠——HTTPS 保護的是傳輸過程,不是內容來源。如果攻擊者能取得憑證或控制 CDN 節點,HTTPS 反而會讓人更安心地掉入陷阱。
這也是簽章機制存在的根本理由:它把信任從「連線」轉移到「金鑰」,讓即使傳輸通道被攻破,內容仍然可以被驗證。
五、實務上如何建立可審計的來源策略
理解了原理與風險之後,實務上該怎麼做?以下幾個方向,適用於個人開發環境與企業內部基礎設施。
5.1 鏡像與代理:把來源收斂到可控範圍
企業內部最有效的做法,是建立自己的套件鏡像或代理。對外只允許代理伺服器連線到官方儲存庫,內部機器一律從代理取得套件。這樣做有三個好處:第一,可以集中驗證簽章;第二,可以記錄所有下載行為,便於事後審計;第三,可以在發現惡意套件時快速封鎖。
常見的工具包括 Aptly、Pulp、Nexus Repository、Artifactory,以及針對容器映像的 Harbor。選擇時要注意它們是否支援中介資料簽章驗證,以及是否能保留原始簽章資訊。有些代理工具會重新打包套件,若不謹慎處理,可能在過程中破壞原本的簽章鏈。
5.2 金鑰衛生與最小信任
在 APT 環境中,務必使用 signed-by 為每個第三方來源指定專屬金鑰,不要再使用全域的 trusted.gpg。在 RPM 環境中,確認 gpgcheck=1 且盡可能開啟 repo_gpgcheck=1。在 pacman 環境中,把 SigLevel 維持在 Required 或更嚴格,不要為了省事改成 Never。
此外,定期盤點系統上的受信任金鑰。很多機器上累積了多年前加入、早已不再使用的第三方金鑰,這些都是潛在的攻擊面。金鑰外洩的風險不會因為你忘記它而消失。
5.3 可重複性、鎖定與 SBOM
對應用程式開發而言,「鎖定版本」是最基本的防線。npm 的 package-lock.json、Python 的 requirements.txt 搭配 hash、Cargo 的 Cargo.lock、Go 的 go.sum,都應該納入版本控制,並且在 CI 流程中嚴格使用(例如 npm ci 而非 npm install)。
更進一步,可以導入 SBOM(軟體物料清單)與 SLSA 框架,讓每個建置產物都能追溯到原始碼、建置環境與依賴清單。這不僅有助於安全審計,也能在漏洞爆發時快速判斷影響範圍。
5.4 組織層的政策與文化
技術手段之外,政策與文化同樣重要。具體而言:
建立「允許清單」機制,只允許經過審查的來源,而非封鎖已知的壞來源。
要求第三方依賴定期更新,並針對高風險套件進行人工審查。
對內部私有套件使用命名空間(scope),避免與公開套件名稱衝突。
在 CI 流程中加入簽章驗證步驟,讓不合法來源的套件無法進入建置產物。
鼓勵開發者對開源專案回饋,因為健康的維護者社群本身就是最好的供應鏈防禦。
這些措施單獨看都不新奇,但它們共同構成一種「預設不信任、處處驗證」的文化。這樣的文化比任何單一工具都更持久。
六、結語:信任不是按下確認,而是持續的對話
回到最初的問題:當你輸入安裝指令時,你把什麼交了出去?答案是:你把系統的控制權,交給了某條信任鏈最頂端的那些人。這條鏈可能很長,從安裝媒體上的公鑰,到發行商的簽章團隊,到套件維護者,到上游專案的貢獻者——每一個人都是這條鏈上的一環。
套件管理的藝術,不在於消除風險(那不可能),而在於讓風險變得可見、可管理、可追溯。簽章讓篡改變得困難,雜湊讓損壞無所遁形,鎖定讓變動受到控制,而透明的治理讓社群能夠互相監督。這些機制沒有一個是完美的,但它們共同把「信任」從一種直覺,變成一種可以被檢驗的關係。
下次在「雅寶社區 · 頂客論壇」看到有人問「這個 PPA 能不能加」或「這個 npm 套件安不安全」時,或許可以換個角度回答:問題不是「它安不安全」,而是「你願不願意把這條信任鏈的某一環,交給這個來源」。想清楚這一點,很多選擇就會變得清楚許多。