---
title: 授權管理
eyebrow: Guidant AI 資安檢視 · 模組報告
h1: 授權管理（jedi-license-runtime ＋ 簽發站）
lede: 決定「客戶買了哪些功能、用到什麼時候」的那套機制。**這是大部分問題已經修好的一塊**——十二條發現裡十條已完成修正；剩下兩條，一條決策者裁定等正式簽發站建好一併處理，一條歸 1.21.1 hotfix 與其他模組的同類問題一起改。
---

## 這塊在產品裡做什麼

產品是賣授權的：客戶買了哪些功能、可以用到哪一天，靠一張**授權檔**決定。這套機制由兩端組成：

| 這一端 | 在哪裡 | 做什麼 |
|---|---|---|
| **簽發站** | 我們公司內部 | 簽出授權檔（蓋章的私鑰在這裡） |
| **驗證端** | 裝在客戶的產品裡 | 收到授權檔，驗章是不是真的、有沒有過期、綁的是不是這台機器 |

**簽發站是一套獨立的系統**——它有自己的程式、自己的資料庫，跟客戶手上的產品完全分開，而且**簽授權用的那把私鑰就放在它手上**。它不是這塊的一個小角落，本頁九條問題裡有五條就出在它身上。

::: grid2
::: {.card .plain}
#### 它平常在做什麼
1. 業務賣出一份授權，我們在簽發站簽一張授權檔
2. 客戶把授權檔匯入自己的系統（或輸入一組開通序號線上領取）
3. 產品每次啟動時驗一遍，決定開放哪些功能
:::
::: {.card .warn}
#### 為什麼它是敏感目標
**攻擊者的動機直接而明確：繞過授權等於免費使用整套產品。**

其他模組的問題多半要先有帳號或有網路位置；這塊的誘因是金錢，而且**簽發站有一個免登入的對外入口**（客戶輸入開通序號線上領取授權的那個）。
:::
:::

---

## 檢視軌跡

**檢視期間**：2026-09-06（單日完成四輪）｜**範圍**：118 個檔案（驗證端 43、簽發站 75）｜**共 4 輪**

> **原始技術報告**放在需求中心的 [FR-076 站](https://guidantai-feature-doc.jedicotech.com/FR-076-2609-license-chain-security-scan/)，檔名見下表最後一欄。那裡有每一輪的完整技術細節、檔案清單與逐條推理，供需要深究或稽核抽查時查閱。

| 輪次 | 查什麼 | 檔數 | 覆核投票 | 結果 | 原始報告檔名 |
|---|---|---:|---|---|---|
| **L1** | 驗章核心——怎麼確認這張授權檔是真的（客戶端） | 10 | 12 票全投完，零中斷 | 通過 1 條（屬這塊），另 2 條是讀到別的模組撈到的 | `scan-L1-verification-core` |
| **L2** | 授權狀態怎麼記、怎麼被查詢（客戶端） | 33 | 12 票全投完，零中斷 | 通過 1 條；**人工另外查出 1 條，比工具報的任何一條都嚴重** | `scan-L2-license-state-api` |
| **L3** | **簽發站**的簽章核心 | 32 | 24 票全投完（中途有一次程序卡住，自動重試成功） | 通過 2 條（屬這塊），另 3 條與下一輪重複 | `scan-L3-issuance-core` |
| **L4** | **簽發站**的網頁後台 | 43 | 21 票全投完，零中斷 | 通過 4 條，全部集中在兩個地方 | `scan-L4-web-backoffice` |

**四輪裡有兩輪、共 75 個檔查的是簽發站**——它被查的份量比客戶端還重，本頁的發現也大半出自那裡（開通序號太短、序號遮罩失效、防猜機制失靈、登入跳轉沒檢查，以及公鑰清單那條連帶查出的打包守門壞掉）。

::: {.callout .ok}
**四輪全部覆核完整跑完——這是整批檢視裡執行品質最穩的一組**

四輪都是一次跑完、沒有任何一輪撞到用量上限。**這不是運氣，是把每一輪控制在 43 個檔案以內換來的。**

在此之前的那一組（登入與權限模組）曾經用錯設定跑四輪大範圍，**全部失敗、共燒 29.6 小時、零產出**。這一組改用小範圍之後，最大的一輪 43 個檔案 110 分鐘一次跑完。
:::

::: {.callout .warn}
**兩件事必須說清楚**

**① 這四輪都沒查「主系統這一端怎麼把授權機制接上來」**——那一端由[主系統自己的程式](M24-host.html)那組掃描（U1、U2 兩輪）查過，查出本頁第 11～13 條。

**② 有一條問題是工具讀過了但沒報，由人工發現的**——L2 那條「被停權的客戶換一張新授權檔就能解除停權」。工具的四輪都沒提，是統籌者人工核對時追出來的，而且**它比工具報出來的任何一條都嚴重**。

這說明一件事：**「工具沒報」不等於「沒有問題」**，只代表工具那一輪沒往那個方向想。
:::

::: {.callout .warn}
**還有一件事四輪都沒查：那把私鑰本身怎麼保管**

四輪查的是「**用到私鑰的那些程式寫得對不對**」——簽章怎麼做、金鑰怎麼被讀進來、驗章怎麼驗。**但「私鑰檔案本身放在哪、誰拿得到、那台機器怎麼保護」，四輪一次都沒進過範圍。**

工作規範裡確實有一條「不可以把私鑰內容寫進任何檔案」，但那是**作業規定，不是檢視結論**——它管的是我們自己別寫出去，不代表有人去查過它現在放得安不安全。

**為什麼這件事要特別講**：私鑰是整套授權機制的根。**拿到它就能偽造任何一張授權檔**，前面那些修好的防線全部繞過。這裡誠實交代：**這件事我們還沒查過**——而「還沒查」跟「查過沒事」意義完全不同。
:::

---

## 問題一覽

**排序按風險，同風險的同一類排在一起。**「出事會怎樣」那欄講的是**業務影響**——誰受影響、損失什麼，不是技術現象。

| # | 問題 | 風險 | 分類 | 出事會怎樣（誰受影響） | 怎麼修 | 狀態 |
|---|---|---|---|---|---|---|
| **1** | 被停權的客戶，自己上傳一張舊授權檔就能解除停權 | 🟠 高 | 只驗登入、不檢查歸屬 | **我們把欠費或違約的客戶停權之後，他自己點兩下就恢復了**，完全不需要我們同意。停權這件事在商務上是最後手段，**形同無效** | 把「停權」從授權檔搬到客戶身上——換照就不會把它洗掉 | ✅ **已修**：新增一張獨立的停權紀錄表，與授權檔完全脫鉤；換照不會動到它。**統籌者已開檔確認新表與讀取邏輯都在**。⚠️ 但資料庫那一層還留著另一條解除停權的路，見第 8 條 |
| **2** | 驗章之前就先解壓縮，而且沒有上限 | 🟠 高 | 資源耗盡 | **任何一個登入帳號用一個 1.4MB 的請求，就能讓伺服器吃掉 1GB 記憶體、把整個產品打掛**。落地版是單一容器，等於全產品停擺 | 解壓縮前先擋長度、解壓縮時設上限 | ✅ **已修**：兩道上限都在（解碼前先擋長度、解壓時有界），程式旁邊還寫明了「兩道都不可省、順序不可對調」的理由 |
| **3** | 開通序號只有 8 個字，43 億組——猜得到別人的授權 | 🟠 高 | 身分驗證缺失 | **猜中別人的序號就能領走那張授權**。而這是簽發站唯一一個**不用登入**的對外入口 | 序號長度大幅拉長 | ✅ **已修**：序號改用約 128 位元的隨機值產生 |
| **4** | 專門「把序號遮起來再寫紀錄」的那段程式，遮罩取前 8 碼——而序號剛好 8 碼，等於整串印出去 | 🟡 中 | 敏感內容寫進日誌 | **未兌換的開通序號等同一張可以領走授權的票**，整串落在系統紀錄裡，看得到紀錄的人就撿得到 | 改成只印無法回推的摘要，不留明文前綴 | ✅ **已修**：改成純摘要、**不帶明文前綴**。程式旁邊寫明了理由——「遮罩這段程式不該依賴『呼叫的人剛好序號夠長』才安全」 |
| **5** | 線上開通入口的防猜機制等於沒鎖：**用被猜的那組序號當鎖定對象** | 🟡 中 | 身分驗證缺失 | 猜錯一次就換下一組序號，**永遠不會被鎖**。連同「計數用的那份暫存沒有上限、而且查詢時也會寫入」——**不用登入就能把伺服器記憶體撐爆** | 改成按來源位址鎖定、計數改存資料庫 | ✅ **已修**：改成按**來源位址**鎖定、計數存進資料庫，而且是**先查鎖定再查序號**（被鎖時連查都不查，猜的人拿不到「序號存不存在」的線索） |
| **6** | 登入後的跳轉網址沒有檢查，可以被導到外站 | 🟡 中 | 外部送什麼就收什麼 | 誘騙簽發站的管理員點一個連結，登入後被導到偽造的頁面**把管理密碼交出去**——而簽發站的管理員手上握著簽發私鑰 | 限制只能跳回站內 | ✅ **已修**：加上一段檢查程式，把跳轉目標收斂成站內相對路徑 |
| **7** | 三個環境共用同一份寫死的公鑰——開發環境簽的授權檔，正式環境也會認 | 🟡 中 | 要先做產品決策 | **拿開發環境的私鑰就能簽出正式環境認得的授權檔**。目前私鑰都在自己機器上，實際風險有限；但正式簽發站建好、開始對外簽發之後，這就是一條真正的繞過路徑 | 出貨時按環境只編進該環境的公鑰 | 📋 **已裁記錄不修（1.21.0）**——決策者裁定等正式簽發站建好一併處理，工單 CM-1582 暫緩。**不是遺漏，是判斷過的取捨** |
| **12** | 落地版的商務授權總開關只是一個設定值，客戶自己的主機管理員改一行就能整個關掉 | 🟡 中 | 要先做產品決策 | **客戶可以不付錢就用沒買的模組、授權過期照常寫資料、子公司數量上限失效**——受損的是原廠的商務收入，不是客戶資料。防竄改只核對程式檔、不核對設定值，改了不會觸發任何警示 | 決策者裁定：落地版拿掉這兩個開關；真的要暫時放行，改由原廠補發測試用授權 | ✅ **已修**（CM-2205，commit `c8aa7d026`，1.21.0 出貨） |
| **8** | 這塊的三張資料表，**資料庫那道門禁規則方向寫反了**——而且同一條規則連「改」「刪」一起管 | 🟠 高 | 客戶資料沒隔開 | **被停權的客戶只要有一個子單位帳號，就能直接把母公司頭上那筆停權紀錄刪掉，整棵樹解除停權**；也能改掉母公司授權的到期日與模組清單、刪掉追查用的異動紀錄湮滅痕跡。**第 1 條在程式那一層修好了，這條是同樣效果的另一條路** | 「看」保持現狀（子單位本來就必須看得到母公司那張照），把「改」「刪」限制成只有最頂層的客戶能做 | ⬜ **未修，歸 1.21.1 hotfix**（CM-2289） |
| **9** | 同一張授權檔可能被兩家客戶同時用上 | ⚪ 低 | 客戶資料沒隔開 | 在極短的時間窗內，兩邊同時匯入同一張授權檔都會成功。實際發生的機率很低 | 資料庫加一道「同一時刻只能有一份生效」的限制 | ✅ **已修**：資料庫那道限制已經建好，同一份授權不可能再出現兩份同時生效 |
| **11** | 授權過期後轉唯讀，只靠每天一次的排程；讀授權時不當場重算，排程沒跑就一直算「正常」 | ⚪ 低 | 要先做產品決策 | **過期的客戶可以繼續新增、修改資料**，最長到排程下一次跑完（每天台灣時間早上 10 點）；若部署方式讓排程沒在跑，就一直可寫 | 決策者裁定「過期即唯讀」：讀授權時當場算一次，取較嚴的那個 | ✅ **已修，1.21.0 出貨**（8-G，CM-2217，commit `3ed47c7a9`／套件 `8ccfac73`） |
| **13** | 授權過期的唯讀限制有兩個縫：代理程式那一段網址被整段放行、問卷的即時填答通道完全不經過唯讀檢查 | ⚪ 低 | 要先做產品決策 | **過期的客戶還能產生新的代理程式註冊碼、還能填問卷寫入資料**——唯讀名存實亡的兩個角落 | 放行範圍收窄成代理程式回報結果那幾支（機器對機器保留）；問卷即時通道補上唯讀檢查 | ✅ **已修，1.21.0 出貨**（8-G，CM-2217，commit `3ed47c7a9`／套件 `8ccfac73`） |

### 另外一條：不是資安問題，但要一起處理

| # | 問題 | 影響 | 怎麼處理 |
|---|---|---|---|
| **10** | 簽發站的「方案」表單，可以把**基礎包**裡的模組（設備與資訊系統清冊、意見回饋）逐項取消勾選 | 基礎包設計上每張授權一定有、不拆賣，所以產品那一端刻意不檢查「有沒有買」這兩個模組。**哪天簽出一張少了基礎包模組的授權，客戶畫面上的選單會不見、但直接打網址照樣能用**——授權與實際能用的功能對不上。這是在資產與意見回饋的主系統接線那一輪查證時帶出來的 | **已裁定：表單上的基礎包鎖死勾選**，不讓人取消。在簽發站 `license_center` 的方案表單 `plan_form.html:58`；模組目錄 `license_center/src/license_center/core/module_catalog.py:8-14` |

---

## 這塊的三張資料表，門禁規則方向寫反了（第 8 條）

::: {.callout .warn}
**這一條要整段讀完再下判斷——它有一半是「本來就該這樣、不能改」，另一半才是真正的問題。**
:::

### 先講背景：資料庫自己也有一道門禁

除了程式裡的權限檢查，**資料庫自己還有一道門禁**，規定「哪個客戶碰得到哪幾筆資料」。這是最後一道防線：就算程式那層漏了，這一道還擋得住。

這塊一共只有三張資料表——**授權檔、授權異動紀錄、停權紀錄**，就是全部了。**這三張的門禁規則寫法一模一樣，而且方向是反的。**

| | 正常應該是 | 這三張實際是 |
|---|---|---|
| 一個子單位碰得到誰的資料 | 它**自己**，和它**底下**的子單位 | 它**自己**，和它**頭上**的母公司 |

方向整個倒過來了。

### 但有一半是設計要的，不能改

**「子單位看得到母公司的授權」這件事本身是對的，一定要保留。**

因為授權的設計就是：**照綁在最頂層的那個客戶身上，底下所有子單位共用這一張**——子單位不另外發照。系統判斷「你買了沒」的第一步，就是把任何一個子單位換算成它頭上那個持照的客戶，再去讀那張照。

所以**如果把方向改成跟其他表一樣**（只看得到自己和底下），會發生什麼？**每個子單位都查不到頭上那張照，被系統判成「沒買」，打任何功能都被擋掉。** 功能當場壞掉。

### 真正的問題在「改」那一面

這三條規則**不是只管「看得到什麼」，它同一條規則連「改得到什麼」「刪得到什麼」一起管**，而且**沒有另外加任何寫入限制**——所以結論是：**看得到，就改得到、刪得到。**

實際會發生這三件事：

| 子單位帳號可以做什麼 | 後果 |
|---|---|
| 🔴 **刪掉母公司頭上的停權紀錄** | **停權紀錄表的設計是「有一列就代表停權中」，刪掉那一列就等於解除停權**——刪除本來就是解除停權的正規做法。所以**被停權的客戶只要手上有一個子單位帳號，就能讓整棵樹當場復權**，不需要換照，也不需要走任何功能入口 |
| **改掉母公司授權的到期日與模組清單** | 系統判斷「買了什麼、用到哪天」讀的就是那一列——改它等於自己改自己的合約內容 |
| **刪改授權異動紀錄** | 那是用來追查「誰換了照、誰被停權、什麼時候」的時間線。**能刪它，就能把上面兩件事的痕跡一併抹掉** |

### 和第 1 條的關係：同一個洞被複製到新表上

本頁第 1 條寫的是「被停權的客戶換一張照就能解除停權」，狀態是**已修**——**程式那一層確實修好了**，換照不會再洗掉停權。

**但資料庫這一層還開著一扇門，效果一模一樣：一樣是解除停權。**

而且更要記住的是：**那張停權紀錄表就是修第 1 條時新建的，一出生就帶著這條寫反的門禁規則**（三張表的寫法一模一樣）——**等於修一個洞的時候，順手把同一個洞複製到新表上了。**

第 1 條的狀態仍然是「已修」（程式那層的修正確實完成且有效），但必須並列記住：**資料庫那條路還在，已經併進跨模組那一批集中處理。**

**這也是這一條同樣評為高風險的原因**——效果一樣，而且這條走的路更短、更難追：

| | 第 1 條（已修） | 這一條 |
|---|---|---|
| 結果 | 被停權的客戶自行復權 | **一樣**——被停權的客戶自行復權，而且是整棵樹 |
| 要做什麼 | 得換上一張舊的授權檔 | **只要刪掉一列資料** |
| 走哪條路 | 走正常的功能入口 | **繞過所有功能入口** |
| 留得下痕跡嗎 | 換照這件事本身有紀錄 | **連追查用的異動紀錄都能一起刪掉** |

**同樣的結果評不同等級會讓人誤判**，所以這一條與第 1 條同列高風險。

### 怎麼修（決策者已裁定）

**「看」保持現狀，「改」和「刪」擋掉。**

| | 怎麼做 |
|---|---|
| **看** | 照舊——子單位必須看得到母公司那張照，這是授權繼承的基礎 |
| **改／刪** | 限制成**只有最頂層的那個客戶**才能做，子單位一律擋掉 |

::: {.callout .crit}
🔴 **施工紅線：這三張絕對不可以照另外八張的做法改**

弱點檢測那塊查出同樣「方向寫反」的表一共 11 張，其中 8 張要改成正確方向。**但這三張授權表不行**——改了會讓每個子單位查不到頭上那張照、被系統判成沒買，**打任何功能都被擋掉**。

這三張只能做「讀保持現狀、寫擋掉」這一種改法。
:::

::: {.callout .decided}
**決策者裁定：那 11 張表要分兩組改**

先前在弱點檢測那塊的裁定是「11 張門禁規則寫反的表集中處理、出貨前完成、改用正確寫法」——**那是在「11 張都照同一套改」的前提下做的**。

現在知道其中三張不能照那套改，**裁定已修正為分兩組**：

| 組別 | 幾張 | 怎麼改 |
|---|---|---|
| 一般資料表 | **8 張** | 改成正確方向（自己＋底下的子單位） |
| **這塊的授權三張** | **3 張** | **讀保持現狀、寫擋掉**（只有最頂層客戶能改能刪） |

其餘條件不變：集中一次處理、出貨前必須完成、**施工時逐條比對不要批次取代**（會把已經寫對的一起改壞）。
:::

> 📎 **跨頁對照**：11 張表這件事由[弱點檢測那塊](M03-detection.html)起頭（那頁第 7 條）。**那頁講的是「有 11 張寫反、要一起改」；本頁是唯一講清楚「方向寫反對授權會造成什麼實際後果、以及為什麼其中三張不能照那套改」的地方。** 兩頁要一起看。

---

## 第 1 條：被停權的客戶自己換照就能復權 {#cm1579}

::: {.callout .crit}
**這一條是工具沒報、人工查出來的，而且它比工具報的任何一條都嚴重。**

它也是唯一一條**會直接影響商務關係**的問題——其他條講的是技術風險，這一條講的是「我們的最後手段形同無效」。
:::

### 問題是什麼

我們把某個客戶停權（欠費、違約），系統就會把他的產品轉成**唯讀**——看得到但改不了。

停權這個狀態，**原本是記在「那一張授權檔」上的**。

而換授權檔的流程是「新增一張、取代舊的」——**新的那一張上面，停權標記是空的**。

所以整件事變成：

```
我們停權某個客戶
        ↓
客戶把自己手上那張舊授權檔重新上傳一次
        ↓
系統：「這是一張新的紀錄，停權標記空的」
        ↓
唯讀解除，客戶照常使用
```

**不需要我們同意、不需要管理員、不需要任何特殊權限。** 而且有四條路都會走到同一段程式，其中三條是客戶自己就能打的——**那三條只要登入、不需要管理員身分，這是刻意的設計**，因為開通授權的當下客戶通常還沒有管理員在場，要求管理員會把自己鎖在門外。

### 為什麼這是一個「設計層」的問題，不是寫錯

程式沒有任何一行寫錯。**錯的是「停權這件事該記在哪裡」這個決定。**

| 停權記在哪 | 結果 |
|---|---|
| 記在**授權檔**上（原本） | 停權跟著「那一張檔」走，換檔就洗掉 |
| 記在**客戶**身上（修好後） | 停權跟著「那個客戶」走，換幾次檔都在 |

### 怎麼修好的

新增一張獨立的停權紀錄表：**一列等於一個客戶目前處於停權狀態，解除就刪掉那一列**。與授權檔完全脫鉤，換照動不到它。

舊的兩個欄位保留不刪——歷史資料上的值是「當初那一份被停權過」的稽核紀錄，執法只讀新表。

**統籌者已開檔確認**：新表與讀取邏輯都在，授權狀態查詢與管理後台兩條路都改成讀新表。

::: {.callout .warn}
**但這條要連著第 8 條一起看**

程式那一層確實修好了，換照不會再洗掉停權。**可是資料庫那一層還開著另一條路——子單位帳號可以直接把停權紀錄刪掉，效果一模一樣。** 而那張新表正是修這一條時建的，一出生就帶著寫反的門禁規則。詳見上面第 8 條。
:::

---

## 第 2～6 條：其餘五條已修的

### 第 2 條：驗章之前的無上限解壓縮（高，已修）

**問題**：授權檔是壓縮過的，系統要先解開才能驗章。但**解壓縮之前沒有任何上限**——送一個「壓得極小、解開極大」的檔案（壓縮比可達千倍），程式會在驗章之前就把記憶體吃光。

**關鍵在於順序**：簽章簽的是解開後的原文，所以**不能先驗章再解壓**。唯一的解法是在解壓的過程中就設上限。

**怎麼修好的**：兩道上限——解碼之前先擋字串長度（連解碼都不做），解壓縮時設定上限並檢查有沒有超過。程式旁邊還寫明了「兩道都不可省、順序不可對調」的理由，**這一點做得很好**——下一個接手的人看到就知道不能省。

簽發站那一側有同源的一段解壓程式，但**它沒有對外入口會收外部送進來的檔案**，判定不構成問題。

### 第 3、4 條：開通序號太短、遮罩等於沒遮（高＋中，已修）

這兩條**是同一件事的兩面**：

- 序號只有 8 個字（約 43 億組），是簽發站**唯一免登入入口**的唯一憑證
- 而專門「把序號遮起來再寫紀錄」的那段程式，遮罩取的是**前 8 碼**——序號剛好 8 碼，**等於整串印進紀錄**

**兩邊各自的假設都合理**：寫紀錄那邊假設「前綴太短猜不出剩下的」，簽發那邊假設「夠亂就不必更長」。**但沒有任何一處把兩個假設對起來看。**

更麻煩的是——**守門測試一直是綠燈**，因為測試餵進去的假序號長度是 18 個字，剛好蓋不到這個問題。

**怎麼修好的**：序號改用約 128 位元的隨機值；遮罩那段程式改成純摘要、**不留明文前綴**。程式旁邊寫明了理由：「做摘要的那段程式本身不該依賴『呼叫的人剛好序號夠長』才安全」——**這正確地把責任放回那段程式自己身上**。

### 第 5 條：防猜機制用被猜的序號當鎖定對象（中，已修）

**問題**：線上開通入口原本有「同一組序號 15 分鐘內失敗 5 次就鎖定」的防猜機制。

**但鎖定的對象是「被猜的那組序號」**——猜錯一次就換下一組序號繼續猜，**永遠不會被鎖**。

連同另外兩個問題：計數用的那份暫存**沒有上限**，而且**查詢的時候也會寫入**——所以不用登入就能一直打、把伺服器記憶體撐爆。

**最能說明問題的是這個對照**：同一個專案的**後台登入**早就做對了（計數存資料庫、按來源位址計、程式旁邊還寫明了為什麼不能存在記憶體裡）。**線上開通入口沒有套用那份現成的知識——是遺漏，不是不懂。**

**怎麼修好的**：改成按**來源位址**鎖定、計數存進資料庫，而且順序改成**先查鎖定再查序號**（被鎖時連查都不查，猜的人也拿不到「這組序號存不存在」的線索）。取不到來源位址時一律歸到同一個計數槽——否則「取不到位址」就變成免費的無限次嘗試。

### 第 6 條：登入後的跳轉網址沒有檢查（中，已修）

**問題**：簽發站登入頁的網址後面可以掛一段「登入完把我送回哪裡」的指示，程式拿到之後**完全不檢查就直接跳轉**。

**為什麼這在簽發站特別危險**：誘騙簽發站管理員點一個連結、登入後被導到偽造的頁面，就可能把管理密碼交出去——**而簽發站的管理員手上握著簽發私鑰**。

**怎麼修好的**：加一段檢查程式，把跳轉目標收斂成站內相對路徑，擋掉外站與各種繞過寫法。

---

## 第 7 條：三個環境共用同一份公鑰（中，未修）

### 問題是什麼

驗章時用的公鑰是**直接編進產品程式裡**的，而且**三個環境（開發／測試／展示）的公鑰全部並列在同一份清單裡**。

驗章的方式是「照上面帶著一個代號，按代號去清單裡查對應的公鑰」——所以**任何一個環境簽出來的授權檔，其他環境都會承認**。

**開發環境的私鑰簽出來的授權檔，正式環境照樣認。**

### 為什麼現在還沒修

程式旁邊的說明寫明了這是**刻意設計**——為了支援日後換發新公鑰時不中斷服務（新版同時帶新舊兩把過渡）。設計本身沒錯，**問題在於「過渡期間並列」與「三個不同環境並列」是兩件事，被同一個機制混在一起了**。

::: {.callout .decided}
**決策者已裁定：等正式簽發站建好一併處理**

**這不是遺漏，是判斷過的取捨**：目前根本還沒有正式站，等架上去的時候一併處理就好。現階段是展示階段、沒有外部客戶、三把私鑰都在自己機器上；要成立這條攻擊，得先拿到我們某一台機器上的私鑰——那時候問題早就不只是授權了。

**但這個裁定有明確的失效條件**：**正式簽發站建好、開始對外簽發之後，這就變成一條真正的繞過路徑**。屆時必須改成「出貨時按環境只編進該環境的公鑰」。

**附帶發現並且當場修掉的一件事**：檢視時發現打包流程裡本來有一道「檢查公鑰有沒有放對」的守門，但**它檢查的那個檔案位置在先前一次搬遷後就不存在了**——所以每次打包都用「跳過這道檢查」的方式繞過，**沒有人發現守門早就壞了**。這是小修正，已經修好。
:::

---

## 第 9 條：同一張授權檔被兩家客戶同時用（低，已修）

**問題**：兩家客戶在極短的時間窗內同時匯入同一張授權檔，兩邊都會成功——因為檢查與寫入之間有一個空檔。

**實際發生機率很低**，所以評為低風險。

**怎麼修好的**：資料庫加一道「同一時刻每份授權只能有一份生效」的限制，由資料庫自己保證，不靠程式搶快。

::: {.callout .warn}
**這條有一個「照抄建議會做壞功能」的陷阱，值得記下來**

工具原本建議的修法是「加一道整張表都不准重複的限制」，**那會擋掉正當的流程**——同一個客戶想換回舊的授權檔時，系統會為同一份授權再新增一列（新列生效、舊列留作歷史）。整張表不准重複會讓這條路直接失敗，而「換回舊照」是程式旁邊明文承認的正當用法。

**照抄工具的建議會做壞功能。** 最後採用的是「同一時刻只能有一份**生效**」，既擋住撞車、也留得住歷史。
:::

---

## 這塊的結論

::: {.callout .ok}
**一句話**：這是整輪檢視裡**大部分問題已經修好的一塊**——十二條發現裡十條完成修正（1.21.0 出貨），剩下兩條各有明確歸屬：第 7 條決策者裁定等正式簽發站建好一併處理，第 8 條歸 1.21.1 hotfix（CM-2289），與弱點檢測那塊的資料庫隔離規則同一張卡。

**建議**：
- **第 8 條要跟著跨模組那批一起做完，而且出貨前一定要完成**——它是第 1 條的另一條路，第 1 條修得再好，這條開著就等於沒關門。施工時記住那條紅線：**這三張不能照另外八張的做法改**。
- **第 7 條有明確的失效條件**：正式簽發站一旦開始對外簽發，這條就從「不急」變成「必須先修」。**建議把它綁進簽發站上線的檢查清單**，不要靠記憶。
- **最需要補的是還沒查過的那一塊**：**那把私鑰本身怎麼保管**——它是整套機制的根，拿到它前面所有防線都白做，而四輪查的都是「用私鑰的程式」，沒有查過私鑰本身。

**兩個方法上的教訓值得記**：
1. 這塊最嚴重的那一條（停權可被換照解除）**工具四輪都沒報**，是人工核對時追出來的。**「工具沒報」永遠不等於「沒有問題」。**
2. 修第 1 條時新建的那張表，**一出生就帶著寫反的門禁規則**——**修洞的時候很容易把同一個洞複製到新東西上**。新建資料表時，門禁規則要當成必檢項目，不能照抄隔壁。
:::

| 這塊的數字 | |
|---|---|
| 查了幾輪、幾個檔 | 4 輪、118 個檔案（客戶端 43、簽發站 75） |
| 一共查出幾條問題 | **12 條**（本模組四輪 9 條，主系統掃描另查出 3 條：第 11～13 條；另有 1 條非資安的表單問題，第 10 條） |
| 本模組四輪那 9 條怎麼來的 | 工具四輪報出 7 條；人工核對另外查出 2 條（第 1 條與第 8 條）——**而且這兩條都比工具報的任何一條更值得注意** |
| 最嚴重的有幾條 | 沒有「最嚴重」等級的；最高是 **4 條高風險**——其中 3 條已修好，剩第 8 條（資料庫那道門禁規則）未修 |
| 已經修好 | **10 條**（1.21.0 出貨） |
| 已裁記錄不修 | **1 條**——第 7 條（決策者裁定等正式簽發站建好一併處理，CM-1582 暫緩） |
| 還沒修 | **1 條**——第 8 條，歸 1.21.1 hotfix（CM-2289） |
| 查過但這頁不能替它背書的 | 那把私鑰本身怎麼保管——**四輪都沒查過** |

---

| 你想知道 | 看哪裡 |
|---|---|
| 我們用什麼方法查、結果有多可信 | [檢視方法與工具](GUIDE-01-method-and-tools.html) |
| 同樣「門禁規則寫反」的其他 8 張表 | [弱點檢測](M03-detection.html)（那頁第 7 條） |
| 其他模組的檢視結果 | [回總報告首頁](index.html) |
