T1-2 確認成立:只要能換掉負責「核對簽章」的那個工具(cryptography 函式庫),開機檢查、以及之後每四小時一次的抽查,全部會照樣放行一支已經被改過的核心檔案,而且不會留下任何錯誤訊息。原本判 HIGH 的嚴重度維持不變,建議不降級。
FR-084 掃描(CM-1645)之前只靠讀程式碼推論出一件事:防竄改機制開機時會逐一核對檔案有沒有被改過,但負責「怎麼核對」的那個工具本身,不在核對範圍內。這聽起來像是「鎖大門,但鑰匙孔本身沒人看守」,可是這只是讀程式碼得到的推論,還沒有人真的動手試過。
這一棒的任務就是真的動手做一次:在自己的電腦上,假裝自己是拿到主機權限的攻擊者,把「核對簽章」這個工具換成一個永遠說「沒問題」的假貨,看看被改過的檔案能不能就這樣蒙混過關。全程只在暫存目錄(tmp)裡操作,沒有連過任何正式環境,也沒有改過任何一行產品程式碼。
| # | 問題 | 結論 | 屬於「實測」還是「推導」 |
|---|---|---|---|
| ① | 驗章工具(cryptography)是不是真的落在「開機不核對」的第三方層? |
是 | 分層邏輯推導 + 套件實際安裝資訊佐證(沒有跑完整正式 build,見下方說明) |
| ② | 掉包驗章工具後,開機檢查是否仍會放行被改過的檔案? | 會放行——閘門印出「驗證通過」,正常繼續啟動 | 實測(真的跑了套件自帶的實驗台) |
| ③ | 每四小時一次的抽查,是不是也一起失效? | 是,一起失效,回傳「通過」 | 實測 |
cryptography 是不是真的在「開機不核對」的第三方層是。 這裡分兩步驗證。
第一步:開機到底核對哪些層? 防竄改機制把產品的檔案分成三層——core(核心程式)、resources(資源檔)、thirdparty(別人寫的公開套件,例如 pandas、cryptography 這些)。開機時只核對前兩層,thirdparty 層完全不核對,只交給「每四小時一次的抽查」去隨機抽樣覆蓋。
這個設定寫死在套件的設定檔裡:
DEFAULT_BOOT_LAYERS = ("core", "resources")
(jedi-integrity/jedi_integrity/config.py:49,jedi monorepo)
第二步:cryptography 有沒有被歸進不核對的那一層? 落地版產品用 Nuitka 把大部分程式碼編譯成機器碼,但少數幾個「公開的大套件」為了省編譯時間,選擇不編譯、原樣照抄一份放進產物——這份「不編譯的名單」就決定了誰會被歸進 thirdparty 層。cryptography 本身不在這份名單上,但清單裡的 pdfminer(PDF 解析用的套件)依賴 cryptography,而系統會把「被排除套件的所有相依套件」一起連坐排除,所以 cryptography 也被一起帶進了不核對的那一層。
實際查證:
$ .venv/bin/python -c "import importlib.metadata as m; print(m.requires('pdfminer.six'))"
['charset-normalizer>=2.0.0', 'cryptography>=36.0.0', 'Pillow; extra == "image"']確認 pdfminer.six 的相依清單裡明確寫了 cryptography>=36.0.0。
再用 BE repo scripts/build/build_release.sh 裡「求排除套件完整相依閉包」的那段邏輯(resolve_excluded_closure(),:370-406),在本機用同一套演算法實際跑一次(不是憑空猜測,是把腳本裡那段 Python 邏輯抽出來、餵進本機已安裝的套件清單去跑),輸出的閉包清單裡確實包含 cryptography:
Crypto、PIL、_cffi_backend、apiclient、certifi、cffi、charset_normalizer、
cryptography、dateutil、fontTools、google、...(共 35 項)
這一步屬於「分層邏輯推導 + metadata 佐證」,不是實測——沒有真的跑一次完整的 15~20 分鐘正式 build 去產出落地版的成品清單再 grep 確認(卡片明說沒有現成產物就不必為此重編)。但邏輯鏈本身是可重現的、每一步都貼了實際指令輸出,可信度高。
會,完全放行。這一步是實測,不是推論。
怎麼測的:套件自己附了一個「實驗台」(jedi-integrity/harness/),可以在不碰任何真實產品的情況下,在暫存目錄裡造一份假的「產品檔案」+一份真的數位簽章,然後真的跑一次「開機檢查」的程式碼。這個實驗台本來就是套件作者留給開發者驗證用的,我們用它,沒有另外碰觸真實產品的簽章或憑證。
基準線(確認機制本來是有效的):先跑「改一支核心檔案、不動驗章工具」的情境,確認開機檢查真的會擋下來:
[FATAL] 啟動失敗:產物完整性驗證未通過,服務拒絕啟動並已鎖定(視同無有效授權)。
原因:檔案內容與簽章 manifest 不符
不符檔案(共 1 項):
- [core] common/integrity/startup_gate.so(預期 d32aad21d655… 實際 51689e1aaa53…)
→ 機制本身沒有壞,正常情況下確實能擋下竄改。
關鍵實驗:接著寫了一支「惡意驗章器」——這不是改套件的程式碼,是另外寫一支獨立的小工具,去冒充套件原本要用的那個驗章元件(這正是攻擊者真正會做的事:把系統呼叫的驗章工具整支換掉,而不是去改讀不懂的簽章演算法本身)。這支假驗章器做兩件事:
再重跑一次「改一支核心檔案 + 用這支假驗章器」的情境,結果:
[poc] 已竄改核心檔:common/integrity/startup_gate.so
[poc] 情境 A:掉包驗章器 + 未重簽 manifest → 跑 run_startup_gate()
[INTEGRITY] 產物完整性驗證通過(core+resources 共 4 檔,version=harness-0.0.1)
[poc] 結果:閘門【通過】,儘管檔案已被竄改、manifest 未重簽。
開機檢查印出「驗證通過」,正常放行,跟檔案完全沒被動過的情況看起來一模一樣,沒有任何警告或異常訊號。
是,一起失效。也是實測。
抽查程式碼(run_spot_check)跟開機檢查共用同一段「讀取並確認簽章清單」的邏輯(jedi_integrity/manifest.py 的 load_verified_manifest),也就是同一個驗章器。接續上面的實驗,開機檢查通過後緊接著跑一次抽查:
[poc] 情境 B:閘門通過後接線 runtime checker,跑一次抽查(spot check)
[poc] 抽查回傳值:True(True=通過/視為完整)
抽查回傳 True(代表「檢查完整,沒發現問題」),跟正常情況一模一樣。代表這套機制唯一的「事後補救」——四小時抽查一次——同樣會被同一次掉包一併打穿,不會因為抽查多跑一次就抓到破綻。
run_startup_gate() 與 run_spot_check() 函式本身,沒有走任何捷徑或模擬殼。T1-2 確認成立,建議跨 arc 總表 §3.1 第 13~18 項裡對應這一條的嚴重度維持 HIGH,不降級。理由:這不只是「理論上可能」,是實測證實「掉包一個工具,整套保護(開機檢查+定期抽查)同時、完全失效,且系統自己毫無察覺、不會留下任何錯誤或警示」。是否要開修正卡、修法方向為何,交由決策者在後續裁定。
feature/FR-075,兩者皆未切換。poetry 虛擬環境內操作,PoC 腳本與所有產出檔案只存在於系統暫存目錄(/private/tmp/...),未寫入任何 repo 目錄,實驗結束後已由套件既有的 cleanup() 機制清除暫存目錄。jedi-integrity 套件任何一行原始碼,harness/dev_app.py 原檔未動。惡意驗章器是另開的一支獨立 PoC 腳本(只在暫存目錄存在,未 commit 進任何 repo),透過 import 套件既有的公開介面(ISignatureVerifier port)注入,屬正常的「換一顆符合介面的元件」用法,不是修改套件內部。/opt/guidant/pki,未部署或重啟任何服務。