Guidant AI 資安檢視總報告 · 連線清單

C18 api → 授權簽發站(License Center,落地版啟用時)

落地版產品向原廠的授權簽發站換取、展延授權檔走的那條線。它有兩個方向的不信任:我們不信對面(簽發站回來的授權檔照樣本地驗章),對面也不信我們(開通序號本身就是密碼)。流的是開通序號、機器指紋、簽好的授權檔,以及後台請照用的 API token。這一頁回答:這條線怎麼連、哪些功能走它、它帶來什麼威脅、駭客怎麼打、我們要怎麼防、目前做到哪。

落地版啟用時 序號即密碼 威脅 3 種 掃描命中 3 條

這一頁怎麼來的:DFD Level 0 把產品運作時的連線編成 C01~C25;STRIDE 六頁每一條問題都標了發生在哪幾條線。這裡以連線為單位整理。3 條命中裡 M18-3 的洞在簽發站那端(獨立系統、獨立 repo),本頁列為實例是因為攻擊者打的就是這條線的另一頭;M18-1、M18-7 的洞在產品這端的驗章與停權邏輯。個別問題修了沒不在這頁講。

§1

一、連線圖

%%{init: {'theme':'base','themeVariables':{'primaryColor':'#E2F0F1','primaryTextColor':'#14201F','primaryBorderColor':'#0E7C86','secondaryColor':'#EEF2F3','secondaryTextColor':'#14201F','tertiaryColor':'#FBFCFC','tertiaryTextColor':'#14201F','lineColor':'#4A5A5C','textColor':'#14201F','mainBkg':'#E2F0F1','nodeBorder':'#0E7C86','nodeTextColor':'#14201F','edgeLabelBackground':'#FBFCFC','titleColor':'#14201F','clusterBkg':'#FBFCFC','clusterBorder':'#E4EAEB'}}}%%
flowchart LR
    CUST(["👤 客戶任一登入帳號<br/>(開通頁)"])
    OPS(["👤 原廠後台管理員"])
    ANY(["🕵 網路上任何人"])
    subgraph MAIN["🏠 主系統 api"]
        direction TB
        ACT["線上開通<br/>序號+機器指紋"]
        ADMIN["後台請照/展延<br/>X-API-Token"]
        VER["本地驗章<br/>Ed25519・查 kid・長度與解壓上限<br/>host 版核對指紋"]
        PK[("內建公鑰表<br/>DEV・STG・POC 三把並列")]
        SUS[("停權表<br/>tenant_license_suspensions<br/>記在客戶身上,不在授權檔上")]
        ACT --> VER
        ADMIN --> VER
        PK --> VER
        VER --> SUS
    end
    LC(["🏛 License Center<br/>LICENSE_ACTIVATION_SERVER_URL"])
    CUST -- "C02 · 填序號" --> ACT
    OPS -- "C02 · 請照/展延" --> ADMIN
    ACT == "C18 · POST /api/activation/activate" ==> LC
    ADMIN == "C18 · POST /api/internal/issue・extend<br/>X-API-Token" ==> LC
    LC -. "簽好的授權檔" .-> VER
    ANY -. "直接打開通 API 猜序號" .-> LC
    classDef ext fill:#FBFCFC,stroke:#9AA8AA,stroke-dasharray:3 2
    classDef open fill:#FDECEC,stroke:#C0392B
    class CUST,OPS,ANY,LC ext
    class PK open
C18 — api 到授權簽發站。兩種入口:客戶在線上開通頁填序號(api 代打簽發站換照);原廠後台用 API token 請照/展延。不論哪條路,拿回的授權檔都走同一支本地驗章(Ed25519,按 kid 查內建公鑰表),host 版再核對機器指紋。紅色是三個被打過的點:序號曾只有 8 碼、公鑰表三個環境並列、停權曾記在可被換掉的授權檔上。
§2

二、這條線怎麼連

項目 現況
誰 → 誰 guidant-api → License Center(獨立系統、獨立 repo、獨立 DB,與主產品只靠「簽好的授權檔」交互,零程式依賴)。位址 LICENSE_ACTIVATION_SERVER_URL(環境變數,預設 http://127.0.0.1:5062)
協定/埠 HTTP(S),httpx,逾時 10 秒(兩條路各自 _ACTIVATION_SERVER_TIMEOUT/_LC_REQUEST_TIMEOUT)。scheme 由環境變數決定,預設是 http;憑證驗證是 httpx 預設開
兩條路 ① 客戶線上開通:POST /license/activation/online(登入即可)→ api 帶開通序號+本機機器指紋打簽發站 /api/activation/activate → 收回簽好的授權檔;② 原廠後台請照/展延:POST /license/tenants/{uid}/lc-issue/lc-extend(平台管理員)→ api 帶 X-API-Token(LICENSE_CENTER_API_TOKEN)打 /api/internal/issue/extend/plans
收回來的東西怎麼驗 不因來源是簽發站就信:兩條路收到的授權檔都走 activate_license()/upload_license() 完整驗章——v2 信封 {kid, payload, signature},Ed25519,按 kid 查內建 PUBLIC_KEYS;解碼前先擋長度(32 MiB)、解壓有界(16 MiB);host 版再比對機器指紋;時鐘回撥檢查(clock_watermark)
停權怎麼記 新表 config.tenant_license_suspensions,一列=該客戶目前被停權;與授權檔脫鉤(M18-1 修後)。客戶換照不會解除停權
公鑰表 jedi_license_runtime/common/public_keys.py 的 PUBLIC_KEYS:DEV、STG、POC 三把並列、都編進產品;任一環境簽的照其他環境都認(M18-7,裁定不修,交付首位客戶前必做)
何時存在 落地版啟用/展延時。授權檔本身也可離線上傳(/license/activation/upload、/license/upload),那條不走這線

依據:套件 jedi_license_runtime/app/service/license_verification_service.py(activate_license_online()、_verify_signed_license())、tenant_license_admin_service.py(_call_lc()、_call_lc_get())、common/engine.py(verify_payload()、_unpack_payload() 兩道上限)、common/public_keys.py、api/routing.py(路由與守門等級);主專案 config/config.py:320-327(LICENSE_ACTIVATION_SERVER_URL、LICENSE_CENTER_API_TOKEN)。

DFD 對照:DFD 寫「HTTPS;簽章授權檔」。實查 scheme 由環境變數決定、預設值是 http://127.0.0.1:5062(本機開發用),正式部署要靠部署文件把它設成 https——程式沒有強制。已回寫本卡。另補 DFD 沒寫的:後台請照那條路帶 X-API-Token,是第二種憑證。

§3

三、哪些功能會走這條線

群 功能 對威脅的意義
① 客戶線上開通 開通頁填序號 → 換回綁本機的授權檔 觸發者是客戶任一登入帳號;序號是唯一憑證——簽發站那端「持有序號=持有那張授權」
② 原廠後台請照/展延 原廠管理員在產品後台替某客戶請一張新照、或展延現行照 帶 X-API-Token 出門;token 在環境變數。展延=換一張新照取代舊照(replace 制)
③ 方案清單 後台請照表單的下拉選單透傳簽發站的方案清單 唯讀,無落地

不在這條線上但共用驗章:離線上傳授權檔(客戶自己拿檔案上傳)走 C02,驗章邏輯與 ① ② 同一支。

§4

四、這條線會帶來什麼威脅、駭客怎麼打

掃描在這條線上抓到 3 條問題(M18-1、M18-3、M18-7),歸納起來是三種攻擊手法。受損的都是原廠的收入與商務控制,不是客戶資料。

# 風險 威脅 駭客怎麼打 得手什麼 STRIDE 實例
T1 🟠 高 大量猜序號,領走別人付費的授權 ① 不需要任何帳號,直接打簽發站的開通 API(這條線的另一頭對網路開放);② 用程式大量送隨機序號配上自己機器的指紋;③ 簽發站的鎖定按「這個序號錯幾次」計,每次換一個序號就永遠不鎖(修前序號只有 8 碼、約 43 億組);④ 撞中一張別人尚未兌換的序號,那張授權就綁到攻擊者的機器 領走別人付費的授權 S 冒充身分 M18-3 開通序號只有 8 個字,猜得到別人的授權 🟠
T2 🟠 高 被停權後自己換一張照復權 ① 平台把某客戶停權(欠費或違約),產品轉唯讀;② 該客戶任一登入帳號把手上的舊授權檔重新上傳、或走線上開通換一張;③ 系統建一筆新的授權紀錄取代現行照,停權欄位記在舊照上、新照是空的;④ 唯讀當場解除,不需要平台同意 被停權的客戶自行改寫授權狀態;平台的最後商務手段形同無效 E 權限提升、T 竄改資料 M18-1 被停權的客戶自己上傳舊授權檔就能復權 🟠
T3 🟡 中 用開發環境的私鑰簽出正式環境認的照 ① 取得開發環境簽發站的私鑰(例如某台開發機外流);② 自己簽一張全功能、永不到期的授權檔;③ 上傳到正式環境;④ 產品照 kid 在內建公鑰表找到開發環境那把,驗章通過 改寫自己的授權內容(功能、到期日、客戶數) S 冒充身分、T 竄改資料 M18-7 三個環境的公鑰並列,開發環境簽的照正式也認 🟡

三種手法的共同點:這條線的信任全靠密碼學——序號是密碼(T1)、簽章是信任根(T3)——而密碼學的強度取決於「夠不夠長」與「哪些鑰匙算數」。T2 不一樣:它是「商務狀態記在客戶能換掉的東西上」,與加密無關,是資料模型的問題。

§5

五、我們要怎麼防、目前做到哪

對應三種威脅與這條線的本質,防線分六條。D1~D3 直接對應掃描抓到的手法;標 ◇ 的(D4~D6)是依這條線的特性補的標準防線,掃描範圍沒涵蓋、沒有實例。「目前」欄寫程式與設定裡實際有的機制;⚠️ 表示只靠慣例或只守到局部。

# 防線 擋哪種威脅 目前做到哪
D1 序號要夠長、有效期、失敗鎖定按來源計。約 128 位元隨機值;未兌換的序號有效期;鎖定按來源 IP 存資料庫,另有全站失敗預算擋分散式猜測 T1 ✅ 簽發站 CM-1583:secrets.token_urlsafe(16)、30 天效期、按 IP 鎖定+全站預算、序號與指紋欄位加長度上限、紀錄不帶明文前綴、補發作廢舊序號。驗證:連猜 6 個不同序號第 6 次 429。這一條在簽發站 repo,不在產品 repo
D2 停權記在客戶身上,不記在授權檔上。換照不影響停權;解除停權是刪那一列 T2 ✅ 新表 tenant_license_suspensions,force_lock_tenant()/unlock_tenant() 寫新表,LicenseGuard.snapshot_for() 讀新表。⚠️ 新表一出生帶著寫反的 RLS,子單位可刪母公司的停權紀錄——另案 M18-8
D3 出貨版只認正式簽發站的公鑰。開發、測試環境的公鑰不進客戶拿到的 image T3 🚫 裁定不修(已記錄):三把公鑰對應三台內部簽發站、私鑰都在內部,目前沒有正式簽發站也沒有外部客戶。失效條件:交付首位客戶前,出貨版改成只認正式公鑰。打包流程的正式公鑰守門路徑已修正(CM-1581)但目前守的是三把並列的表
D4 ◇ 收回來的授權檔不因來源是簽發站就信,一律本地驗章+有界解析 簽發站被假扮、或簽發站本身被攻破後回假照 ✅ activate_license_online() 明寫「收到照後仍走完整驗章+指紋核對,BE 是唯一信任邊界」;_unpack_payload() 解碼前擋 32 MiB、解壓有界 16 MiB(M18-2,CM-1572);時鐘回撥檢查
D5 ◇ 這條線只走 https,位址不可被改成攻擊者的簽發站 假扮簽發站拿 X-API-Token、或回假照(D4 擋得住假照,擋不住 token 外流) ⚠️ LICENSE_ACTIVATION_SERVER_URL 預設 http://127.0.0.1:5062,程式不強制 https;正式部署靠部署文件設對。token 外流的損害:能用原廠身分向簽發站請照(簽發站那端的 /api/internal/* 守門是 token)
D6 ◇ X-API-Token 的保管與輪替。它是原廠對簽發站的身分,與 DB 密碼同級 後台請照那條路被冒用 ⚠️ 環境變數(.env);沒有輪替機制、沒有到期;簽發站端有沒有按 token 記來源與限速,屬 LC repo 範圍,本頁沒查

最便宜的一步:D5——config.py 讀 LICENSE_ACTIVATION_SERVER_URL 時,非 DEV 環境且 scheme 不是 https 就拒絕啟動(與 SANDBOX_MODE=inprocess 只准 DEV 同一個模式)。D3 是最重要的一步,但已裁定綁交付首位客戶的時點。

§6

六、依這些防線,掃描還沒看過的地方

  • 簽發站 /api/internal/* 的守門(D6):產品這端帶 token 出門,簽發站那端怎麼驗、有沒有限速與來源記錄,在 LC repo,本輪掃描範圍外。
  • LICENSE_ACTIVATION_SERVER_URL 的 http 預設(D5):本頁開檔新發現,不在 168 條裡;正式環境若忘了改,序號、指紋、token 全明文。
  • 機器指紋的可偽造性:compute_machine_fingerprint() 算什麼、能不能在另一台機器上複製出同一個值,決定「一張照只能一台機器用」守不守得住(M18-9 同帳號兩家並用是另一面)。
  • 離線上傳與線上開通共用驗章:D4 的驗章是兩條路共用的單一支,好處是守一處;壞處是那一支若有洞(如 M18-2 修前的解壓炸彈),兩條路都開。

依據:STRIDE 六頁信任邊界連線標記(CM-2403/2404 驗收後版本)、套件 jedi-license-runtime(app/service/license_verification_service.py、tenant_license_admin_service.py、common/engine.py、common/public_keys.py、api/routing.py)、主專案 config/config.py、DFD Level 0。M18-3 的修正在 License Center repo(CM-1583),本頁依 STRIDE 頁記載引用。