Windows 應用程式開發中遵守最低限度安全性的檢核表
· 小村 豪 · Windows 開發, 安全性, 設計, C# / .NET, Win32
講到 Windows 應用程式的安全性,話題容易突然變大。 零信任、EDR、SBOM、憑證運營、漏洞管理。每個都重要,但實務上在這之前不想漏的最低限度相當多。
特別是以下這類應用程式,比起「高階防禦」,先堵基本的漏洞更有效。
- WPF / WinForms / WinUI 的桌面應用程式
- C++ / C# 的 Win32 應用程式
- 裝置連動、檔案連動、DB 連線、公司內部發佈工具
- 擁有自動更新機構的業務應用程式
- 含 Windows 服務或輔助 EXE 的構成
Windows 應用程式開發中,比起一次做到完美,先不要留下明顯危險的洞比較現實。 這裡依設計、實作、發佈、運營的順序,把最低限不想漏的要點以易檢核的形式整理。
1. 先講結論
- 最先不想漏的是 不要求不必要的管理員權限、簽章、不以明文持有機密資訊、不停用憑證驗證。
- Windows 應用程式 發佈物本身 會成為攻擊面。包含 EXE / DLL / MSI / MSIX / 自動更新模組在內看較安全。
ServerCertificateValidationCallback => true、明文連線字串、LoadLibrary("foo.dll")的粗略載入、以字串串接執行 SQL,即使是最低限度也是要避免的項目。- 若只有部分處理需要管理員權限,不要讓整個應用程式提升,只把那部分分離到別的 EXE 或 service 比較安全。
- 在 Windows 發佈的應用程式以 簽章 + 時間戳 為前提思考比較好。不只對使用者的可信度,也容易做竄改偵測或運營說明。
- 儲存時的機密資訊依用途區分使用 DPAPI / ProtectedData 或 Credential Locker。至少要脫離以明文放在
appsettings.json的狀態。 - 日誌不是越多越好。原樣留下 token、密碼、連線字串、個人資訊、完整請求本文 會讓日誌本身成為事故主角。
最低限度的安全性不是加入特殊功能,而是 不要留下危險的預設行為或粗略的實作。
2. 本文對象與「最低限度」的意思
2.1. 對象範圍
本文對象是例如以下的 Windows 應用程式。
- WPF / WinForms / WinUI 的桌面應用程式
- C++ / C# 的 Win32 應用程式
- 公司內部發佈工具、裝置連動工具、監視工具
- 含輔助 EXE、Windows 服務、更新程式的構成
- 以 EXE / MSI / MSIX 發佈的業務用軟體
這裡的「最低限度」不是 通過稽核的最終形式,而是 這個漏了相當容易出事 的項目。
2.2. 對象外
另一方面,本文中心也有不包含的。
- 組織整體的零信任設計
- EDR / SIEM / DLP / MDM 的整體運營
- 核心驅動程式的詳細加固
- 從零開始做加密設計本身
- 高階威脅分析或數位鑑識步驟
也就是說不是「組織整體的巨大安全性施策」,而是處理 Windows 應用程式開發者在發佈前自力難漏的基礎線。
3. 先看的檢核表
在細節議論前先放能一覽全貌的表。 光這裡就能抓出該重新檢視的地方。
3.1. 整體圖
| 要確認的項目 | 至少該做的 | 典型 NG |
|---|---|---|
| 執行權限 | 以 asInvoker 為基本,只把需要提升的處理分離 |
把整個應用程式設為 requireAdministrator |
| 發佈物的可信度 | 對 EXE / DLL / MSI / MSIX 做程式碼簽章,也加時間戳 | 未簽章就發佈 |
| 更新 | 固定更新源,用 HTTPS 和簽章確認偵測竄改 | HTTP 下載後直接覆寫 |
| 機密資訊 | 不在原始碼或明文設定放機密,使用 DPAPI / Credential Locker 等 | 把 API 金鑰或連線字串以明文放在設定檔 |
| 通訊 | 使用 HTTPS,不停用憑證驗證 | 以 return true 常態跳過憑證驗證 |
| 外部輸入 | 把 SQL、檔案、IPC、URI、CSV、JSON 等全部驗證 | 以「公司內部工具所以」放行 |
| DLL 載入 | 使用絕對路徑、SetDefaultDllDirectories、安全的搜尋順序 |
讓 LoadLibrary("foo.dll") 依賴當前目錄 |
| 日誌 | 遮罩 token、密碼、PII,分開使用者用錯誤的輸出 | 原樣顯示、儲存例外詳情或連線字串 |
| 相依關係 | 持續更新 SDK、NuGet、VC++ 執行環境、OSS 相依 | 數年為單位固定,也不追蹤漏洞資訊 |
3.2. 權限以 asInvoker 為基本
Windows 應用程式最先想重新檢視的是這裡。 以管理員權限執行整個應用程式,bug 或 DLL 替換、設定檔誤讀、外部輸入的不足都會直接以強權限執行。
基本方針如下。
- 一般 UI 應用程式用
asInvoker - 只需要管理員權限的處理分離到別的行程或 service
- 只在必要的瞬間提升
- 傳給輔助 EXE 或 service 的輸入也要驗證
例如,平常只做閱覽和編輯的桌面應用程式,只有安裝或防火牆設定變更需要管理員權限的話,比起把整個應用程式設為 requireAdministrator,只把需要提升的部分靠到 broker 比較安全。
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
</requestedPrivileges>
</security>
</trustInfo>
「以管理員執行比較輕鬆」通常之後會有後遺症。 以最小權限執行,再切出真正需要的操作,事故半徑會小很多。
3.3. 對二進位檔和安裝程式簽章
在 Windows 發佈物的可信度 相當重要。 使用者碰的不是原始碼,是 EXE、DLL、MSI、MSIX、更新程式。這裡未簽章,運營說明、竄改偵測、發佈時的安心感都會弱。
最低限度想看的是以下。
- 對 EXE / DLL / MSI / MSIX 簽章
- 不只安裝程式,更新用的輔助二進位也簽章
- 加時間戳
- 把憑證的期限與更新步驟納入 release 步驟
特別是未加時間戳的簽章,在憑證到期後驗證時會出問題。 不是「簽了就好」,而是把 簽章 + 時間戳 都放入 release 步驟較穩定。
用 MSIX 的話套件簽章是前提。 即使 MSI / EXE 發佈,至少安裝程式本體和主要可執行二進位要簽章。
3.4. 固定更新路徑,加入竄改偵測
現在的 Windows 應用程式比起首次安裝,更新路徑 被用的時間更長。 這裡粗略,即使本體做得精細,更新程式也會成為最弱的地方。
最低限度的思考方式如下。
- 更新檔案的取得以 HTTPS 為前提
- 驗證下載的更新物的 簽章或雜湊
- 不讓更新源 URL 能以程式碼或設定無限制替換
- 更新模組本身也簽章
- 決定回滾或失敗時的復原步驟
若能採用 MSIX + App Installer,更新機制能靠向 OS 側。 另一方面若有自訂更新程式,需要確認 通訊安全性 和 發佈物的真實性 兩方。光 HTTPS 只能守「通訊路徑」,不保證「那個檔案真的是自己的發行物」。
3.5. 機密資訊不放在原始碼或明文設定
這裡在實務上真的很容易出事。 以「公司內部工具所以」「反正只是發 exe」,把連線字串、API 金鑰、共享資料夾憑證、固定 token 放在原始碼或設定檔。
最低限度想避免的如下。
- 原始碼直寫的 API 金鑰
appsettings.json或app.config的明文密碼- 進入儲存庫的連線字串
- 解密金鑰與密文放同處的設計
- 不是每個使用者而是全員共通的固定憑證
Windows 應用程式的實務選項大致如下。
- 想儲存 Windows 的憑證 packaged desktop app / WinUI 系可檢討 Credential Locker
- 想在本地加密儲存機密
Win32 / .NET 可用 DPAPI /
ProtectedData - 連線目標能用 Windows 驗證或整合驗證 盡可能不讓應用程式持有密碼
- 能在雲或伺服器側做機密管理 優先不在用戶端埋長期機密的設計
在 C# 中至少如下用 DPAPI 也比明文儲存好得多。
using System.Security.Cryptography;
using System.Text;
byte[] plaintext = Encoding.UTF8.GetBytes(secretText);
byte[] ciphertext = ProtectedData.Protect(
plaintext,
optionalEntropy: null,
scope: DataProtectionScope.CurrentUser);
這裡重要的不是「加密了所以安全」,是以設計決定 誰能解密。
設為 CurrentUser 或 LocalMachine 意義相當不同。
SQL Server 連線的話,地端環境能把 Windows 驗證作第一候補的情況有。
若無論如何需要在連線字串含憑證,至少維持 Persist Security Info=False,不要放著明文設定檔不管比較安全。
3.6. 通訊以 HTTPS 為前提,不要殺掉憑證驗證
以開發中臨時的想法放入的逃避路直接留到正式。 在通訊周邊這相當經典。
特別要注意的是以下。
ServicePointManager.ServerCertificateValidationCallback += ... => trueHttpClientHandler.DangerousAcceptAnyServerCertificateValidator- 停用吊銷確認就出貨
- 以開發自簽憑證為前提的程式碼留到正式
最低限度的方針單純。
- 正式通訊用 HTTPS
- 不要常態跳過憑證驗證
- 例外的驗證放寬時 限定對象主機與憑證
- 開發用的迴避程式碼要以建置條件或設定確實排除
- .NET 的話也意識吊銷確認
不好的例子大致如下。
ServicePointManager.ServerCertificateValidationCallback +=
(_, _, _, _) => true;
看起來輕鬆,但這近似「這個 HTTPS 通訊連到誰都通過」。 拿掉憑證驗證,即使用 HTTPS 內容也相當被挖空。
3.7. 把外部輸入全當「不信任的輸入」處理
Windows 應用程式不是 Web 應用程式,輸入驗證容易鬆懈。 但實際上外部輸入的入口相當多。
- 檔案路徑
- CSV / Excel / JSON / XML
- 命令列引數
- named pipe / socket / COM / RPC / gRPC
- 傳給 DB 的字串
- 登錄值
- 剪貼簿
- URL / deep link
- 從外部裝置或 SDK 回傳的資料
特別最低限度不想漏的是以下 3 個。
- SQL 一定參數化 不用字串串接組 SQL。
- 檔案路徑要正規化後使用 不要把使用者指定的路徑直接用於刪除、覆寫、展開。
- 外部檔案讀取要加入大小上限和格式檢查 「能開就安全」不對。
SQL 的例子,這要避免。
var sql = "SELECT * FROM Users WHERE Name = '" + userName + "'";
至少也要靠到這邊。
using System.Data;
using Microsoft.Data.SqlClient;
using var cmd = connection.CreateCommand();
cmd.CommandText = "SELECT * FROM Users WHERE Name = @name";
cmd.Parameters.Add("@name", SqlDbType.NVarChar, 256).Value = userName;
「公司內部工具所以輸入可信」是相當危險的前提。 實際上壞的 CSV、預期外的檔名、舊的 DB 資料、運營者的手輸錯誤、其他工具寫的半成品 JSON 會普通進來。
3.8. 不要讓 DLL 的載入位置曖昧
這是 Windows 風的陷阱。
像 LoadLibrary("foo.dll") 只以名稱讓它讀 DLL,依搜尋順序可能撿到預期外位置的 DLL。
最低限度的方針如下。
- 可能的話指定 DLL 的 絕對路徑
- 在早期階段設定
SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_DEFAULT_DIRS) - 用
AddDllDirectory明確追加搜尋對象 - 避免把
SearchPath的結果直接傳給LoadLibrary的設計 - 不要完全依賴 safe DLL search mode
例如 native code 可在行程初始化的早期階段放入以下設計。
SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_DEFAULT_DIRS);
然後用 AddDllDirectory 登錄必要的追加目錄。
這裡「平常會動」所以容易被放置,但在發佈處工作目錄變了或其他產品的 DLL 在 PATH 的話會悄悄壞。 不只安全性,作為故障預防也相當有效。
3.9. 不要在日誌和例外輸出機密
為故障調查增加日誌是重要的。 但日誌也容易成為機密的墓地。
最低限度想重新檢視的如下。
- 不把密碼、Bearer token、API 金鑰輸出到日誌
- 不整個輸出連線字串
- 遮罩個人資訊或業務資料本文
- 例外詳情分使用者用畫面和內部日誌
- 不要在正式啟用 debug 用的 PII 日誌
- 重新檢視 dump 或 trace 的儲存位置權限
最近的 .NET 也較容易以 redaction 為前提整理。 至少要停止「什麼都字串化直接放 log」。
常見失敗如下。
- 整個儲存 HTTP request / response body
- 驗證失敗時輸出 token 或整個標頭
- 例外訊息原樣放到 MessageBox
- 在維護用 ZIP 整個同捆機密日誌
錯誤顯示例如分如下。
- 使用者用: 「連線伺服器失敗。請確認網路設定與 URL。」
- 內部日誌: 失敗對象主機、TLS 錯誤種別、相關 ID、stack trace、重試次數
光這個分離,資訊洩漏與調查性的平衡就會變好很多。
3.10. 不要放置相依函式庫和開發工具
最後是樸素但相當有效的項目。 即使應用程式本體做得精細,載著舊的執行環境或已知漏洞的相依函式庫,腳下會垮。
最低限度想看的如下。
- 把 .NET SDK / runtime 維持在支援內版本
- 定期確認 NuGet / OSS 相依的更新
- C++ 的話管理執行環境可轉散發物或外部 DLL 的版本
- 把漏洞資訊的確認放入 release 前檢查
- 準備 smoke test 以免相依更新壞東西
這裡「之後一起做」最危險。 半年、一年放置,更新差異太大,安全對應本身會變重工。
4. 發佈前檢核表
作為審查或出貨判定的範本,以能直接使用的形式。 為了易以表確認,把發佈前最低限度想看的項目依類別排列。
4.1. 權限、執行方式
| 檢核項目 | 確認 | 備註 |
|---|---|---|
一般啟動以 asInvoker 動作 |
□ | |
| 需要管理員權限的處理分離到別 EXE / service 等 | □ | |
| 使用 service 時不使用超過必要的強執行帳戶 | □ | |
分開 %ProgramFiles% 下與使用者資料下的職責 |
□ |
4.2. 發佈、簽章
| 檢核項目 | 確認 | 備註 |
|---|---|---|
| 對 EXE / DLL / MSI / MSIX / updater 簽章 | □ | |
| 在簽章加時間戳 | □ | |
| 把憑證的期限與更新步驟納入 release 流程 | □ | |
| 決定了發佈物的雜湊確認或竄改偵測方法 | □ |
4.3. 更新
| 檢核項目 | 確認 | 備註 |
|---|---|---|
| 更新取得用 HTTPS | □ | |
| 下載後驗證簽章或雜湊 | □ | |
| 設計上不易隨意替換更新源 URL | □ | |
| 有更新失敗時的回滾或重試方針 | □ |
4.4. 機密資訊
| 檢核項目 | 確認 | 備註 |
|---|---|---|
| 密碼、API 金鑰、連線字串沒直寫到原始碼 | □ | |
| 沒把機密放在明文設定檔 | □ | |
| 需要本地儲存的機密以 DPAPI / Credential Locker 等保護 | □ | |
| 可能處靠向 Windows 驗證或使用者憑證 | □ |
4.5. 通訊
| 檢核項目 | 確認 | 備註 |
|---|---|---|
| 正式通訊使用 HTTPS | □ | |
出貨物沒留 DangerousAcceptAnyServerCertificateValidator 或 => true |
□ | |
| 意識吊銷確認或主機名驗證 | □ | |
| 正式中沒混入以開發憑證為前提的程式碼或設定 | □ |
4.6. 輸入、資料存取
| 檢核項目 | 確認 | 備註 |
|---|---|---|
| SQL 已參數化 | □ | |
| 命令列、檔案、IPC、URI 等輸入有上限和格式檢查 | □ | |
| 路徑操作正規化防止根逸脫 | □ | |
| 例外訊息沒直接顯示到畫面 | □ |
4.7. DLL 與執行環境
| 檢核項目 | 確認 | 備註 |
|---|---|---|
| 明示 DLL 的載入位置 | □ | |
用 SetDefaultDllDirectories / AddDllDirectory 等控制搜尋順序 |
□ | |
| 沒讓 DLL 載入依賴當前目錄或 PATH | □ | |
| 在發佈處掌握動態載入所需的檔案群 | □ |
4.8. 日誌、運營
| 檢核項目 | 確認 | 備註 |
|---|---|---|
| 沒把 token、密碼、PII 放到日誌 | □ | |
| 分開內部日誌與使用者用訊息 | □ | |
| 重新檢視 dump / trace / log 的儲存位置權限 | □ | |
| 確認 SDK 與相依函式庫的更新狀況 | □ |
5. 常見 NG
實務常見的大致如下。
5.1. 「公司內部工具所以沒問題」
公司內部工具也會普通有壞檔案、誤操作、攜入終端、共享資料夾、舊 DLL、粗略權限設定。 就算沒公開到網際網路,攻擊面也不會消失。
5.2. 「是 HTTPS 所以安全」
HTTPS 重要,但停用憑證驗證意義會相當薄。 另外在更新發佈中不只 HTTPS,也需要 發佈物的真實性確認。
5.3. 「加密了所以安全」
解密金鑰的位置、解密權限、使用者邊界、機器邊界沒整理的話,光加密不夠。
特別以 LocalMachine 保護的值當「每使用者的機密」使用,之後會混亂。
5.4. 「日誌多就能調查」
光日誌多,若 token 或個人資訊流出,那本身成為事件。 想要調查性,決定要留什麼不留什麼 比較先。
5.5. 「以管理員跑就解決」
最初輕鬆,但之後在 UAC、發佈、支援、權限邊界、DLL 載入、檔案儲存位置通常會痛苦。 最小權限長期較穩定。
6. 粗略的優先順位
全部一次做太重的話,順序大致如下。
- 重新檢視管理員權限
先停止
requireAdministrator的常用。 - 簽章與時間戳 整頓發佈物的可信度。
- 機密資訊的撤退 從原始碼、明文設定中移除機密。
- HTTPS + 憑證驗證的矯正
從出貨物刪除
=> true系。 - 重新檢視 SQL / 檔案 / IPC 輸入 減少字串串接或未驗證輸入。
- 固定 DLL 載入 停止只用名稱載入、依賴 PATH。
- 日誌的遮罩 讓事故時日誌不成為二次災害。
- 相依更新的常態化 做成每次發佈都確認的流程。
這個順序,以「先堵明顯危險的洞」的意義容易推進。
7. 總結
Windows 應用程式開發的安全性,在放入特殊產品或巨大機制之前, 光是整頓 權限、簽章、機密資訊、通訊、輸入、DLL、日誌 的 7 點就會相當改變。
最低限度想掌握的如下。
- 不要讓整個應用程式以管理員權限執行
- 對發佈物與更新物簽章、加時間戳
- 不把機密資訊放在原始碼或明文設定
- 即使用 HTTPS 也不殺憑證驗證
- 不信任 SQL、檔案、IPC 等外部輸入
- 不要讓 DLL 的載入位置曖昧
- 不要把機密放到日誌
- 不要放置相依函式庫
安全性的話題廣,但不需要從一開始就全部做。 但 不要把危險的預設行為原樣出貨 的最低限度,值得在相當早的階段就備齊。
8. 參考資料
- Administrator Broker Model - Win32 apps
- How User Account Control works
- Authenticode Digital Signatures
- Time Stamping Authenticode Signatures
- Sign a Windows app package
- Credential Locker for Windows apps
- CryptProtectData function (dpapi.h)
- CA5359: Do not disable certificate validation
- CA5399: Enable HttpClient certificate revocation list check
- Configuring parameters - ADO.NET Provider for SQL Server
- Connection String Syntax - ADO.NET
- Dynamic-Link Library Security - Win32 apps
- SetDefaultDllDirectories function (libloaderapi.h)
- Data redaction in .NET
相關文章
共用相同標籤的最新文章。能以相近的主題延伸理解。
發生非預期例外時的 checklist - 要讓應用結束還是繼續,先看的判斷表
本文以 C# / .NET 與 Windows 應用為前提,把非預期例外發生後該結束還是繼續的判斷拆成失敗單位、共用狀態、外部副作用、原生邊界四個觀察點,並提供判斷表與典型情境,協助讀者在 catch 之前先判斷是否還能信任應用狀態。
Windows 應用程式中把「僅需要系統管理員權限的處理」分離出來的具體寫法
本文以 .NET 8 桌面應用程式為例,具體展示如何讓 UI 保持 asInvoker,把僅需系統管理員權限的處理切到 helper EXE。涵蓋 manifest、runas 啟動、具名管道 ACL、PID 驗證、固定 operation 與請求驗證,以及 Explore...
Windows 應用程式不要把機密資訊以明文存進設定檔的最佳實踐
本文整理 Windows 桌面應用程式儲存連線憑證或 API Token 時的實務做法,說明為何 DPAPI 與 ProtectedData 比明文或自做加密更能切斷檔案外流即等同機密外流的鏈條,並比較 CurrentUser 與 LocalMachine 的適用情境、優先...
應該在哪裡 `catch` 例外並輸出日誌、進行錯誤處理 - 以實務向整理呼叫階層的邊界與職責
本文整理在呼叫階層中應該於哪一層 catch 例外、輸出主日誌與進行錯誤處理的實務判斷標準。深層 helper 不寬泛接捕,外部 I/O 邊界負責翻譯例外,UseCase 把預期內失敗結果化,UI 與請求邊界輸出 1 次主日誌並決定回應,未處理例外處理器只承擔最終記錄與終止...
自動更新功能的安全性基本 - 糟糕的模式與最佳實踐
把自動更新當成信任的發佈而非檔案傳輸來重新整理,從只靠 HTTPS 不夠的理由、signed metadata 與用戶端驗證、簽章金鑰的運營分離、staging 與 fail-closed、rollback 與 freeze 的對策,到 Windows 上 MSIX 與 C...
相關主題
與本文相近的主題頁面。以本文為起點,可進一步連到相關服務與其他文章。
Windows 技術主題
彙整 KomuraSoft LLC 關於 Windows 開發、故障調查與既有資產活用文章的主題中心。
與本主題相關的服務
本文連結到以下服務頁面,歡迎從最接近的入口查看。
Windows 應用程式開發
支援包含常駐處理、設備連動、運作日誌與可維護結構的 Windows 桌面應用程式。
技術諮詢 & 設計審查
協助釐清設計方向、架構邊界、生命週期責任,以及既有 Windows 資產的處理方式。
作者檔案
本文作者的個人檔案頁面。
Go Komura
小村軟體有限公司 代表
以 Windows 軟體開發、技術諮詢與故障調查為中心,在難以重現的故障調查與既有資產仍在運作的專案上具有優勢。