自動更新功能的安全性基本 - 糟糕的模式與最佳實踐
· 小村 豪 · Windows 開發, 安全性, updater, 自動更新, 簽章, MSIX, ClickOnce
1. 先講結論
相當粗略但在實務上好用地說,是這樣的。
- 如果需求符合,首先優先考慮 MSIX App Installer 或 ClickOnce 等既有的更新基礎架構
- 如果需要自製 updater,首先要放進去的不是 UI 而是簽章驗證與失敗時的復原
latest.json之類的更新資訊,不要當成未簽章的設定檔處理,而要當成已簽章的 metadata 來處理- TLS 是必要的,但不是充分條件
- 更新判定不是「因為伺服器這麼說」,而是「因為用戶端可以驗證並判斷是正確的」
- 簽章金鑰要將開發用與正式用分離,並以 HSM 或簽章服務保護
- 更新失敗時不要 fail-open 而要 fail-closed
- 沒有回滾對策的 updater,最好以會被退回到脆弱版本為前提來考慮才安全
- 如果還沒辦法放入簽章驗證的階段,比起自動更新,手動的已簽章 installer 發佈還比較安全
簡言之,自動更新的核心不是「如何下載」,而是「信任什麼、在何處驗證、壞掉時怎麼還原」。
2. 為什麼自動更新是危險領域
通常的功能是封閉在應用程式中的。 另一方面,updater 則一次具備以下 3 項。
- 從外部取得檔案
- 信任該檔案
- 置換既有的執行檔
也就是說,任意程式碼執行的路徑從一開始就被內建在產品裡。
這裡常見的誤解是「因為是 HTTPS 所以安全」。 當然 TLS 是必要的。只是,它守護的主要是通訊路徑與連線對象的正當性。更新伺服器本身遭入侵、錯誤的成品被放到正規 CDN 上、未簽章的 manifest 被替換掉,這些光靠 TLS 是不夠的。
實際上,光是 TUF 整理的威脅,更新系統就有以下幾種。
- 被植入任意的惡意軟體
- 被降級回脆弱的舊版 rollback
- 讓人看不到新版 freeze
- 混入互相不一致的 metadata 與成品 mix-and-match
也就是說,自動更新不是「檔案傳輸」,而是「信任的發佈」。 設計好這裡之後,自動更新才能安全地運作。
3. 糟糕的模式
先整理實務上常見的危險形態。
| 糟糕的模式 | 哪裡危險 | 最低限度的修正方法 |
|---|---|---|
用 HTTPS 取得 version.json,直接執行 URL 的 zip / exe |
不耐 origin 入侵、設定替換、錯誤發佈 | 改成用簽章 metadata 與成品的用戶端驗證 |
| 只簽章二進位檔案,manifest 未簽章 | URL、version、channel、必須更新旗標可被竄改 | 做成包含 version / hash / size / channel / expiry 的 signed manifest |
| 把簽章金鑰放在開發 PC 或 CI 的檔案中 | 遭入侵則會派發具正規簽章的惡意程式 | HSM / 簽章服務 + 核准流程 + 稽核日誌 |
| 更新失敗時「忽略驗證錯誤繼續執行」 | 事故時最弱的路徑被打開 | 改成 fail-closed |
| 不保留舊版直接覆寫更新 | 停電、磁碟不足、中途失敗導致無法啟動 | staging + atomic activate + rollback |
| 只比較版本就允許舊版 | 允許 rollback 到脆弱版本 | 單調遞增的 release version 與最高已知版本的保存 |
| 整個 updater 以管理者權限執行 | 入侵時的傷害範圍廣 | 下載/驗證為低權限,只有置換分離到最小 helper |
| 從差分更新開始 | 實作複雜且驗證遺漏增加 | 先從完整套件更新開始 |
以下稍微詳細看看。
3.1 停在「因為是 HTTPS 所以沒問題」
這是最多的。
- 啟動時讀取
latest.json - 取出
downloadUrl - 下載 zip / exe
- 展開並替換
- 結束
外觀看起來還像樣,但信任的根過度偏向伺服器回應。 如果更新伺服器或發佈設定遭入侵,就可以在正確的 HTTPS 上發佈不正當的更新。
TLS 是必要的。 但是,只靠 TLS 並不能完成 updater 的設計。
3.2 雖然有簽章,但用戶端沒有驗證
就算發佈時對檔案進行簽章,如果用戶端沒有檢查,也沒有意義。
常見的是:
- CI 中有簽章
- 但 updater 只看 hash
- 而且該 hash 本身是來自未簽章的 manifest
這種形式。
如此一來,manifest 被替換的那一刻,hash 也一起被掉包。 「有看 hash 所以安全」,必須連 hash 的來源都守護才成立。
3.3 manifest 未簽章
在更新系統中真正該守護的,不僅是執行檔本身。 至少以下資訊,若被竄改就很危險。
- version / release id
- 下載目標的 URL 或檔名
- hash / size
- channel(stable / beta 等)
- 是否為必須更新
- 適用的 OS / 架構
- metadata 的有效期限
- 最低所需的 updater version
也就是說,用於更新判斷的資訊全部放入 signed metadata 這種感覺剛剛好。
3.4 簽章金鑰的處理太隨便
更新功能的安全性,有相當大的比例就是金鑰管理的安全性。
如果正式簽章金鑰被這樣放置,就相當危險。
- 一直放在開發 PC 的憑證存放區
- 將
.pfx上傳為 CI 的 secret - 多人將相同的私鑰分發到本機
- 開發用簽章與正式用簽章是相同的信任鏈
這樣的話,就算 updater 本身是正確的,也無法阻止「具正規簽章的不正當更新」。
3.5 不保留舊版的覆寫更新
更新,失敗時的設計比成功時更重要。
- 下載中斷
- 展開失敗
- 置換中途停電
- 新版雖然啟動但初次 migration 失敗
此時,如果舊版已經消失,復原就會很沉重。 實務上,「更新失敗」這個事實比「現場 app 無法啟動」還不嚴重。
3.6 沒考慮 rollback
就算是有簽章的正規版,舊版有漏洞的情況下對攻擊者也可能很方便。
例如:
- version 1.8 有已知漏洞
- 現場已升到 2.3
- 攻擊者重新派發 1.8
如果這個通過了,簽章本身是正確的卻很危險。
不是只看「是否有簽章」,還要看「現在可以安裝那個版本嗎」才夠。
3.7 fail-open
在正式環境最不該做的就是這個。
- 簽章驗證失敗時只顯示警告就繼續
- 有能忽略憑證期限錯誤的 hidden flag
- 除錯用的
skipVerify=true留在正式環境中
故障時或攻擊時,這種後門會成為主路徑。
4. 最佳實踐
4.1 首先乘坐既有的更新基礎架構
自製 updater 是否真的必要,首先懷疑一下比較安全。
在 Windows 上,只要需求符合,就容易優先檢討以下方案。
- MSIX + App Installer
- ClickOnce
- Store / MDM / 公司內部發佈基礎架構
- MSI + 企業端的發佈管理
理由很簡單,因為可以把更新本身的責任範圍一定程度交給平台。 當然自由度會降低,但更新 UI、發佈 manifest、套件簽章、與運營的整合會變得容易。
需要自製 updater 的情況,例如以下:
- 想要嚴密控制 stable / beta / preview 等多個 channel
- 想要階段性發佈或 rollout 比率
- 想要因獨自的業務需要細緻控制更新時機
- 有無法用 MSIX / ClickOnce 的構成
這種情況下,也比起「想要自由度」,理解為「自己承擔更新責任」比較不會搖擺。
4.2 將信任起點放在用戶端
安全的 updater 不會直接相信伺服器的回應。 用戶端至少需要以下 2 項。
- 信任的公鑰或憑證鏈
- 用該金鑰驗證已簽章 metadata 的機制
簡言之,需要製造這樣的狀態:不是「伺服器說是最新版」,而是「這個 metadata 是信任的簽章者發佈的最新版」,由用戶端確認。
4.3 以 signed metadata 為中心設計
最低限度,更新 metadata 中要放入以下項目並作為簽章對象。
| 項目 | 放入的理由 |
|---|---|
| release version / release id | rollback 防止、稽核 |
| artifact 名稱、URL、package type | 固定要取哪個檔案 |
| hash、size | 竄改檢測、損壞派送的檢測 |
| channel | stable 不混入 beta |
| 對象 OS / architecture | 防止錯誤發佈 |
| minimum updater version | protocol 變更時阻止舊 updater |
| expires_at | freeze 對策 |
| published_at | 稽核、切分 |
| mandatory / optional | 更新 UX 的分歧也變得不可竄改 |
這裡重要的是,將更新的判斷材料全部集中到 signed metadata 裡。 將邏輯放在用戶端,資訊的真實性以簽章守護的形式,事故會減少。
4.4 也要驗證成品本身
驗證 metadata 之後,對下載的成品也要確認以下項目。
- size
- hash
- 套件簽章 / 程式碼簽章
- 發行者或期待的識別符
如果處理 Windows 的 PE / MSI / MSIX,最好以用戶端進行 AuthentiCode 或套件簽章驗證為前提才安全。 macOS 的話,最好以 Developer ID 與 notarization 為更新路徑的前提才不會搖擺。
4.5 金鑰以運營而非功能來守護
金鑰管理,差距是由運營而非實作產生的。
至少以下這些要分開比較安全。
- 開發用簽章金鑰
- staging 用簽章金鑰
- 正式用簽章金鑰
此外正式用還要連同以下項目一起設計。
- HSM
- 雲端簽章服務
- 附有核准流程的 signing system
- 稽核日誌
- key rotation 流程
- 附有 timestamp 的簽章
「正式 build 通過 CI 就自動簽章」雖然方便,但入侵時的傷害半徑也會變大。 至少要能追蹤「誰在什麼時候簽章了什麼」。
運營穩定之後,將幾乎不改的 root trust 與頻繁重新簽章的更新 metadata 金鑰分開,會更安全。 將 root 保持在偏 offline 狀態,更新 metadata 使用別的金鑰的設計,能降低金鑰入侵時的傷害半徑。
4.6 fail-closed 與 staged update
更新流程的基本順序如下。
- 取得 metadata
- 驗證簽章、有效期限、version
- 下載成品到 staging 區域
- 驗證 hash / size / 簽章
- 保留舊版的狀態下準備 activation
- 重啟時或用專用 helper 切換
- 初次啟動的健全性確認
- 有問題則 rollback
這裡重要的是: 驗證結束之前不置換 失敗的話不前進 這兩點。
4.7 限制 updater 的權限
要避免整個 updater 以管理者權限執行。
理想是以下的分離。
- 下載與驗證:低權限
- 只有實際檔案置換:具最小權限的 helper
- helper 不做「將已驗證 package 放到指定位置」以外的事
需要權限提升的設計,如果不在提升前明確分開什麼已驗證,就會變得危險。
4.8 從一開始就消滅 rollback / freeze / mix-and-match
這裡之後才加會很痛苦,所以最好一開始就放入。
-
rollback 對策
用戶端保持「至今看到的最高 metadata version / release version」,拒絕比這個更舊的 -
freeze 對策
給 metadata 附加 expiry,拒絕過舊的 metadata -
mix-and-match 對策
讓 metadata 之間有整合性。至少在 manifest 本身固定對象 artifact 的 hash / size / version
加上,如果能用 signed metadata 派發特定 build 的 blocklist 或 minimum allowed version,事故時的封鎖就會快。
就算不直接採用 TUF,這 3 個性質都相當重要。
4.9 一開始從全量更新開始
差分更新對頻寬有效,但作為最初的實作很複雜。
- 從哪個舊版打到哪個新版的差分
- 差分適用前的前提 hash
- 差分適用後的最終 hash
- 中途失敗時的復原
- 部分適用或舊差分的清理
這些一口氣增加。 初期版本,安全地置換已簽章全量套件 就足夠了。
5. 最小安全構成
就算不像完整 TUF 那樣誇張,自製 updater 的最小安全構成大概如下。
5.1 用戶端持有的
- 信任的 root 公鑰,或固定的憑證鏈
- 目前運作中的 version
- 過去看過的最高 metadata version / release version
- 許可的 channel
- rollback 用的前一版
5.2 伺服器回傳的
- 已簽章的 update metadata
- 已簽章或 platform 簽章的成品
- 必要時 blocklist / minimum allowed version 資訊
5.3 典型流程
取得 metadata
↓
驗證簽章、expiry、version、channel
↓
將成品下載到 staging
↓
驗證 size / hash / package signature
↓
保留舊版的狀態下 activation
↓
初次啟動失敗則 rollback
這裡重要的是,光靠更新伺服器的回應什麼都不成立。 使其成立的,是用戶端持有的 trust anchor 與驗證邏輯。
6. Windows 案件如何思考
Windows 應用程式,先從發佈方式逆推會比較容易整理。
- 如果需求符合就 MSIX App Installer
- .NET 的公司內部 app,per-user 符合的話就 ClickOnce
- 需要服務、driver、shell extension、獨自 channel 控制的話,MSI + 獨自 updater 也是比較對象
但是,就算選擇獨自 updater,該做的事也不會減少。 反而會增加。
- Authenticode / 套件簽章的驗證
- signed manifest
- rollback 對策
- 更新 helper 的權限分離
- updater 本身的更新策略
Windows 上常見的危險形態是 DownloadFile -> unzip -> kill process -> overwrite -> restart 的一條直線。
這個雖然會動,但安全性與復原性兩方面都很弱。
讓 SmartScreen 或 UAC 的警告靠「詳細資訊 → 執行」闖過去的運營方式,不是更新設計而是警告馴化。 如果要做正確的更新路徑,應該不是讓人習慣警告,而是靠向不易出現警告的發佈與驗證的構成。
發佈方式的比較本身,在下一篇文章也有整理。
Windows 應用程式的發佈方式如何選擇 - MSI / MSIX / ClickOnce / xcopy / 獨自 updater 的判斷表
7. 最低限度的檢核表
在推出自製 updater 之前,至少要確認以下項目。
- 更新 metadata 已簽章
- metadata 中包含 version / hash / size / channel / expiry
- 用戶端進行簽章與 version 的驗證
- 驗證成品的 hash 與 platform 簽章
- 正式簽章金鑰與開發環境分離
- 保留金鑰的使用日誌與核准紀錄
- 使用附有 timestamp 的簽章
- 在 staging 更新中,保留舊版的狀態下切換
- 有 rollback 的條件與步驟
- 驗證失敗時以 fail-closed 停止
- 有 updater 本身的更新方針
- 可以派發 blocklist / minimum allowed version
- 有停止階段性發佈的 kill switch
- 可以觀測失敗率、rollback 率、簽章驗證失敗
如果這份檢核表空缺很多,比起先做 updater 的 UI,先紮實做好發佈信任模型會更有效果。
8. 總結
自動更新功能的安全性,可以相當濃縮地用以下一句話表達。
要設計的不是更新的便利性,
而是信任誰、用戶端如何驗證那份信任。
在這基礎上,實務方面的判斷大略如下。
- 如果既有基礎架構夠用,就先乘坐它
- 如果要做自製 updater,HTTPS 之前先放入簽章 metadata 與金鑰管理
- 不設計失敗時的復原與 rollback 的 updater,在正式環境會很痛苦
- updater 不是發佈功能,而是產品的安全邊界本身
如果現在的構成接近 latest.json + zip 替換,首先該修的不是下載處理,而是信任的放置方式。
光是修這裡,危險度就會相當改變。
9. 參考資料
- CISA Secure by Design Pledge
- NIST: Security Considerations for Code Signing
- NIST Secure Software Development Framework (SSDF)
- The Update Framework Specification
- TUF: Roles and metadata
- TUF: Security
- Microsoft Learn: Authenticode Digital Signatures
- Microsoft Learn: Auto-update and repair apps - MSIX
- Microsoft Learn: ClickOnce Deployment and Security
- Apple Developer: Developer ID
- CA/Browser Forum: Baseline Requirements for the Issuance and Management of Publicly-Trusted Code Signing Certificates
相關主題
這是與此主題相近的主題頁面。可從文章為起點前往相關的服務或其他文章。
Windows 技術主題
匯整 Windows 開發、不具合調查、既有資產活用等技術主題的入口。
此主題連接的服務
Windows 應用程式開發
自動更新不只是 UI 的話題,而是包含發佈方式、權限、復原、運營的設計。在 Windows 應用程式的新規開發或既有軟體的檢視中,可從更新方式的整理開始對應。
技術諮詢・設計審查
可從「是否需要獨自 updater」「MSIX / ClickOnce 是否足夠」「現在的更新設計哪裡危險」這類整理階段開始諮詢。
作者簡介
小村 豪
合同會社小村軟體 代表
以 Windows 軟體開發、技術諮詢、不具合調查為中心,在既有資產存留的案件與原因難以看見的故障調查上具備優勢。
相關文章
共用相同標籤的最新文章。能以相近的主題延伸理解。
Windows 應用發布方式怎麼選 - MSI / MSIX / ClickOnce / xcopy / 自訂 updater 的判斷表
整理 Windows 應用程式的發布方式,把 MSI、MSIX、ClickOnce、xcopy 與自訂 updater 各自的適用情境攤開比較。重點不是哪一種 installer 比較新,而是與 OS 的耦合深度與更新責任由誰扛兩條軸線;讀完能依 per-user/per-...
ClickOnce 是什麼 - 以實務視角整理機制、更新、適合場面・不適合場面
本文以實務視角整理 ClickOnce 是什麼,從 manifest、快取、更新、簽章的構造,到適合公司內部 .NET 桌面業務應用程式的案件與不適合 machine-wide 或 service、driver 等深度 OS 整合的案件,幫助讀者判斷是否採用並掌握 depl...
Windows 應用程式開發中遵守最低限度安全性的檢核表
用檢核表形式整理 WPF / WinForms / WinUI / C++ / C# 等 Windows 應用程式發佈前最低限不想漏的安全性要點。涵蓋避免不必要的管理員權限、EXE 與更新物簽章加時間戳、改用 DPAPI 與 Credential Locker、保留 HTT...
開發 COM 元件、OCX/ActiveX 時常見的坑 - 整理 Visual Studio 的 32bit/64bit、註冊、管理員權限
整理開發 COM、OCX、ActiveX 元件時最容易卡關的四個面向:宿主行程的 32bit/64bit、Visual Studio 2022 變成 64bit 後的設計時整合、regsvr32 與 Regasm 的註冊位置、以及管理員權限與 HKCU/HKLM 的關係,協...
用 Windows 沙箱加速 Windows 應用程式開發的驗證 - 以實務向整理管理員權限問題、乾淨環境、權限不足・資源不足的重現
整理在 Windows 應用程式開發中如何運用 Windows Sandbox 加速驗證的實務做法。透過按情境分檔的 .wsb、唯讀輸入與專用 Outbox 寫入分離、在容器內另建標準使用者重現權限不足、以及降低記憶體和關閉 vGPU 製造資源不足偏向,把每次的乾淨環境準備...
相關主題
與本文相近的主題頁面。以本文為起點,可進一步連到相關服務與其他文章。
Windows 技術主題
彙整 KomuraSoft LLC 關於 Windows 開發、故障調查與既有資產活用文章的主題中心。
與本主題相關的服務
本文連結到以下服務頁面,歡迎從最接近的入口查看。
Windows 應用程式開發
支援包含常駐處理、設備連動、運作日誌與可維護結構的 Windows 桌面應用程式。
Windows 軟體維護 & 現代化
支援既有 Windows 軟體的階段性升級、功能追加、64 位元就緒以及可維護性的重構。
作者檔案
本文作者的個人檔案頁面。
Go Komura
小村軟體有限公司 代表
以 Windows 軟體開發、技術諮詢與故障調查為中心,在難以重現的故障調查與既有資產仍在運作的專案上具有優勢。