自動更新功能的安全性基本 - 糟糕的模式與最佳實踐
· 更新日期: · 小村 豪 · Windows 開發, 安全性, updater, 自動更新, 簽章, MSIX, ClickOnce
更新紀錄(僅初版,2026年04月09日 發布)
- 初次發布
引用本文(DOI: 10.5281/zenodo.21616393)
本文保存於 Zenodo。以下同時提供一律指向最新版本的 DOI,以及固定於您正在閱讀版本的 DOI。
小村 豪(2026)。〈自動更新功能的安全性基本 - 糟糕的模式與最佳實踐〉。小村軟體有限公司。https://doi.org/10.5281/zenodo.21616393 https://comcomponent.com/zh-TW/blog/2026/04/09/000-comcomponent-autoupdate-security/
- DOI(最新版本)
- 10.5281/zenodo.21616393
- DOI(此版本)
- 10.5281/zenodo.21616394
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 為什麼會出現「Windows 已保護您的電腦」
從程式碼簽章、EV/OV 憑證、Azure Artifact Signing、MSIX、Microsoft Store、ClickOnce、內部發佈到 App Control,以實務角度整理 Windows 應用程式發佈時出現 SmartScreen 警告的原因。
Windows 應用發布方式怎麼選 - MSI / MSIX / ClickOnce / xcopy / 自訂 updater 的判斷表
整理 Windows 應用程式的發布方式,把 MSI、MSIX、ClickOnce、xcopy 與自訂 updater 各自的適用情境攤開比較。重點不是哪一種 installer 比較新,而是與 OS 的耦合深度與更新責任由誰扛兩條軸線;讀完能依 per-user/per-...
WinForms / WPF 應用程式的 CI/CD 實務 ── 用 GitHub Actions 把從建置到簽章・發布全部自動化
本文整理用 GitHub Actions 為 WinForms / WPF 應用程式建置 CI/CD 的實務指南,內容涵蓋在 windows-latest 上進行建置+測試的最小 YAML、以標籤驅動的版本編號、透過 signtool 整合簽章,以及依 MSI/MSIX/C...
ClickOnce 是什麼 - 以實務視角整理機制、更新、適合場面・不適合場面
本文以實務視角整理 ClickOnce 是什麼,從 manifest、快取、更新、簽章的構造,到適合公司內部 .NET 桌面業務應用程式的案件與不適合 machine-wide 或 service、driver 等深度 OS 整合的案件,幫助讀者判斷是否採用並掌握 depl...
Windows 應用程式開發中遵守最低限度安全性的檢核表
用檢核表形式整理 WPF / WinForms / WinUI / C++ / C# 等 Windows 應用程式發佈前最低限不想漏的安全性要點。涵蓋避免不必要的管理員權限、EXE 與更新物簽章加時間戳、改用 DPAPI 與 Credential Locker、保留 HTT...
相關主題
與本文相近的主題頁面。以本文為起點,可進一步連到相關服務與其他文章。
Windows 技術主題
彙整 KomuraSoft LLC 關於 Windows 開發、故障調查與既有資產活用文章的主題中心。
與本主題相關的服務
本文連結到以下服務頁面,歡迎從最接近的入口查看。
Windows 應用程式開發
支援包含常駐處理、設備連動、運作日誌與可維護結構的 Windows 桌面應用程式。
Windows 軟體維護 & 現代化
支援既有 Windows 軟體的階段性升級、功能追加、64 位元就緒以及可維護性的重構。
常見問題
整理諮詢這個主題時常見的問題。
- 自動更新只用 HTTPS 為什麼不夠?
- TLS 守護的主要是通訊路徑與連線對象的正當性。更新伺服器本身遭入侵、錯誤的成品被放到正規 CDN 上、未簽章的 manifest 被替換掉,這些光靠 TLS 是不夠的。安全的 updater 要以已簽章的 metadata 與用戶端驗證為中心,讓用戶端能自行確認「這個 metadata 是信任的簽章者發佈的最新版」。
- 更新 metadata(manifest)裡至少要放什麼?
- 至少要放 release version、成品的名稱與 URL、hash 與 size、channel、對象 OS 與架構、minimum updater version、expires_at、published_at、是否為必須更新,並把整份 metadata 作為簽章對象。把更新的判斷材料全部集中到 signed metadata 裡,URL 或版本被竄改的事故就會減少。
- 更新失敗時應該怎麼處理?
- 基本是 fail-closed:驗證結束之前不置換、失敗的話不前進。成品先下載到 staging 區域驗證 hash、size、簽章,在保留舊版的狀態下切換,初次啟動的健全性確認失敗就 rollback。「簽章驗證失敗只顯示警告就繼續」這種 fail-open,在故障或攻擊時會成為最弱的路徑。
- 一定要自製 updater 嗎?
- 如果需求符合,首先優先考慮 MSIX App Installer 或 ClickOnce 等既有的更新基礎架構,把更新本身的責任範圍一定程度交給平台。需要自製的典型情況是想嚴密控制 stable / beta 等多個 channel、想要階段性發佈或 rollout 比率、或有無法用 MSIX / ClickOnce 的構成。如果還沒辦法放入簽章驗證,比起自動更新,手動的已簽章 installer 發佈反而比較安全。