# 第 5 批材料（子集 C：M02/M04/M14/M15/M17/M18/M23/M19＋掃描總表，20 件）


## SUMMARY #13｜出自 M02-4＋M03-13｜⬜ 未修

任何有帳號的人上傳一份網頁檔，別人在系統裡點一下預覽，攻擊者的程式就在那個人的瀏覽器裡用他的身分跑起來；掃描報告「檔名叫什麼就存什麼」也走同一條預覽路徑


### 模組頁列
M02 第 4 條（M02-file-upload.md:92）
| **4** | 上傳的網頁檔，別人點預覽時會在我們的網域裡被當成程式執行 | 🟠 高 | 外部送什麼就收什麼 | **受害者只要點一下預覽，攻擊者的程式就在受害者的瀏覽器裡、用受害者的身分跑起來**——可以偷走他的登入狀態、冒用他的身分操作。上傳者只需要一個有效帳號 | 預覽端建可預覽類型清單、清單外一律當附件下載；清單內的加上瀏覽器層的隔離指令；並由伺服器自己確認檔案內容真的是那個格式 | ⬜ **未修** |


### 模組頁列
M03 第 13 條（M03-detection.md:130）
| **13** | **代理程式回報報告時「檔名叫什麼就存什麼」** | 🟡 中 | 外部送什麼就收什麼 | 不去掉路徑、也不驗副檔名。稽核人員在畫面上點開**預覽**時，**網頁格式的檔案會被瀏覽器當成網頁執行**——那段程式是用**稽核人員本人的身分**在跑，可以冒用他做任何事。**而網頁格式正是掃描報告的正式格式，這是日常路徑不是罕見情境**。⚠️ **要走到危險的那個動作得多轉一手**：在「執行歷史」那一區點掃描報告是**下載**（強制存檔、不會在頁面裡開，那條是安全的）；危險的**預覽**要從**證據清單**那邊點。掃描報告收回來時**會同時寫一筆證據**，所以這條路是通的 | 存檔前先去掉路徑、再比對副檔名白名單；更保險的做法是一律自己命名 | ⬜ **未修** |


## SUMMARY #14｜出自 M02-5＋M22-2｜⬜ 未修

任何登入帳號打一支查系統設定的功能，就拿到雲端檔案儲存空間的帳號密碼明文，之後可以完全繞過我們的系統直連儲存空間，把所有客戶的檔案列出、下載、覆蓋、刪除


### 模組頁列
M02 第 5 條（M02-file-upload.md:93）
| **5** | 任何登入者打一支查設定的功能，就拿到雲端儲存的帳號密碼明文 | 🟠 高 | 密碼外流 | **拿到那組帳密後可以完全繞過整個系統、直接連進儲存空間**——之後在程式裡補再多檢查都沒用。拿到的是自己那家公司的那一組，但**落地版是一客戶一套系統，那一組就是那家的全部** | 設定類的密碼欄位一律預設不回傳；補權限檢查；並清掉寫死在程式與腳本裡的那組帳密。**五個步驟有先後順序**，見下方 | ⬜ **未修** |


### 模組頁列
M22 第 2 條（M22-system-core.md:82）
| **2** | **任何登入帳號打一支查設定的功能，就拿到檔案儲存的帳號密碼明文** | 🟠 高 | 只驗登入、不檢查歸屬 | **不必是管理員、不必有任何權限、不必知道任何編號，一個請求就拿到可讀寫所有客戶上傳檔案的鑰匙。** 拿到之後可以完全繞過我們的系統直連儲存空間——那時候程式裡補再多檢查都沒用。同一條路還會吐出寄信與員工帳號目錄的位址、埠號、登入帳號 | 讀取端比照同一支檔案裡的寫入端補上權限檢查，**三個入口都要補、補一個沒有用** | ⬜ **未修** |


## SUMMARY #21｜出自 M15-1｜⬜ 未修

使用者在 AI 儀表板打字，任何基層員工都能誘導 AI 選中 26 支查詢裡的任何一支，而且能無限次改寫問法重試直到成功


### 模組頁列
M15 第 1 條（M15-ai-dashboard.md:93）
| **1** | 打字就能誘導 AI 選中 26 支查詢功能裡的任何一支，而且可以無限次重試 | 🟠 高 | 外部送什麼就收什麼 | **公司裡任何一個有帳號的人**，都能拿到他本來看不到的東西——整份組織架構、誰有什麼權限、公司有哪些客戶、所有專案。而且**每試一次不用付代價**，可以反覆改寫問法直到成功 | 補上呼叫外部 AI 的等待逾時、每人每分鐘次數上限（與 AI 聊天助手那塊合併一起做）；能拿到什麼則由第 2 條的權限檢查收口 | ⬜ **未修**（已裁定修法，待統一安排） |


## SUMMARY #82｜出自 M04-3｜⬜ 未修

使用者的密碼裡只要有雙引號（強密碼常見），寫進紀錄時只遮掉前半截，後半截原文留在資料庫九十天


### 模組頁列
M04 第 3 條（M04-common.md:75）
| **3** | 全站唯一那道密碼遮蔽，遇到密碼裡有雙引號就只遮一半 | 🟡 中 | 敏感內容寫進日誌 | **密碼越強越容易中招**——強密碼本來就會帶特殊字元。遮不乾淨的半截密碼會留在資料庫裡九十天，受害最深的是系統整合用的密碼與金鑰密語 | **改一行**：讓比對認得被跳脫的雙引號（見[下方](#mask-quote)） | ⬜ **未修** |


## SUMMARY #83｜出自 M04-7｜⬜ 未修

任何登入帳號在任何清單畫面把「一頁幾筆」填成超大數字，就能叫資料庫整張表一次撈完，重複幾次整個產品對所有客戶停止回應


### 模組頁列
M04 第 7 條（M04-common.md:79）
| **7** | 一次可以要求回傳幾筆資料，沒有上限 | 🟡 中 | 資源耗盡 | 任何登入的人送一個很大的數字，就能叫資料庫把整張表一次全撈，重複幾次**整個產品對所有客戶停止回應**。既有的「請求大小上限」擋不住，因為送出去的請求很小、要回來的資料很大 | **分兩步**：先盤點誰在要求很大的筆數，處理完再加上限（見[下方](#page-size)） | ⬜ **未修**（**這頁投報率最高的一條**） |


## SUMMARY #84｜出自 M04-8｜⚠️ 部分修

客戶的環境設定值打錯字時程式認不得，就悄悄退回開發用的紀錄設定（出貨設定與安裝程式都補了，但「認不得就退回開發設定」的行為還在）


### 模組頁列
M04 第 8 條（M04-common.md:80）
| **8** | 環境設定值打錯字時，程式會悄悄退回開發用的紀錄設定 | 🟡 中 | 要先做產品決策 | 客戶那邊已有兩層保底（安裝程式會設、部署設定也有預設），剩下的縫是**值打錯字**——例如寫成 `production` 而不是 `prod`，程式認不得就悄悄退回開發設定 | 認不得時改成套用正式設定。**不要報錯、不要擋住啟動**（見[下方](#run-env)） | ⚠️ **部分修**——出貨設定與安裝程式都補上了，**但「認不得就退回開發設定」這個行為還在** |


## SUMMARY #94｜出自 M14-1｜⬜ 未修

產品的聊天框對訊息長度與呼叫次數完全不管，最基層員工用四到八個連線送超大訊息就能讓整個產品對所有人停止回應


### 模組頁列
M14 第 1 條（M14-ai-bot.md:67）
| **1** | 聊天沒有長度上限，也沒有次數限制 | 🟡 中 | 資源耗盡 | **整個產品對所有人停止回應**——不只聊天功能，連登入都會逾時。做得到的人是**任何一個最基層的員工**，四到八個連線就夠，不需要特殊權限。另一種後果是**把公司共用的 AI 額度燒光**，直接變成帳單 | 加訊息長度上限、加等待逾時、開一個「每人每分鐘最多問幾次」的插槽給主系統接 | ⬜ **未修**——**已裁定要修**（照左欄三步做），並裁定**與 AI 儀表板那塊的同款問題合併處理**（工單 CM-1638 已開） |


## SUMMARY #95｜出自 M15-4｜⬜ 未修

儀表板把查到的資料整包原樣送回畫面，夾帶每個帳號的密碼加密鹽值與「是不是超級管理員」旗標，送給外部 AI 的樣本也沒遮蔽


### 模組頁列
M15 第 4 條（M15-ai-dashboard.md:96）
| **4** | 查到的資料整包原樣送回畫面，夾帶密碼加密用的鹽值與「是不是超級管理員」旗標 | 🟡 中 | 回應夾帶不該送的欄位 | 畫面上只顯示三欄，**但系統那一邊其實已經把整包送出去了**——包含每個帳號的密碼加密材料。而且送給外部 AI 的樣本資料同樣沒有遮蔽 | 兩步：先把鹽值與超管旗標加進既有的「不要送」清單止血，再改成在資料本身打記號、看到記號就跳過 | ⬜ **未修**（已裁定修法，待統一安排） |


## SUMMARY #98｜出自 M17-7｜⬜ 未修

安裝程式每裝一套都種下同一組最高權限帳號密碼且不強制首次登入就改，破解一次就等於拿到每一套裝出去的最高權限帳號（已裁定維持記錄、暫不調整）


### 模組頁列
M17 第 7 條（M17-bulletin.md:82）
| **7** | 安裝程式**每裝一套**都種下同一組最高權限帳號密碼，而且不強制首次登入就改 | 🟡 中 | 密碼外流 | **把那組密碼破解一次，就等於拿到每一套裝出去的最高權限帳號**。這個帳號還被特別保護、刪不掉也停不掉。**這條會跟著出貨，屬於出貨前必須處理的一條** | 改成安裝時隨機產生，或強制首次登入改密碼 | ⬜ **未修**（**決策者裁定：維持記錄、暫不調整**，之後一併處理安裝期的憑證策略）。<br>**這條性質與其他五條不同**——不是「某一把外流了、換掉就好」，是每套安裝都種同一組，換發解決不了 |


## SUMMARY #99｜出自 M18-7｜⬜ 未修

開發環境的私鑰簽出來的授權檔，正式環境照樣認得——三個環境的公鑰全部並列在產品裡的同一份清單（已裁定：等正式簽發站建好一併處理）


### 模組頁列
M18 第 7 條（M18-license.md:93）
| **7** | 三個環境共用同一份寫死的公鑰——開發環境簽的授權檔，正式環境也會認 | 🟡 中 | 要先做產品決策 | **拿開發環境的私鑰就能簽出正式環境認得的授權檔**。目前私鑰都在自己機器上，實際風險有限；但正式簽發站建好、開始對外簽發之後，這就是一條真正的繞過路徑 | 出貨時按環境只編進該環境的公鑰 | ⬜ **未修**（決策者裁定：**等正式簽發站建好一併處理**，工單 CM-1582 暫緩。**不是遺漏，是判斷過的取捨**） |


## SUMMARY #100｜出自 M23-4｜⬜ 未修

系統帶著公司的 GitHub 通行證去開問題單時，不確認對方是不是真的 GitHub，網路上的中間人可以假冒 GitHub 把通行證攔走


### 模組頁列
M23 第 4 條（M23-issue.md:68）
| **4** | **連線到 GitHub 時把憑證驗證關掉**（兩處） | 🟡 中 | 密碼外流 | 帶著公司的 GitHub 通行證走一條**不確認對方是不是真的 GitHub** 的連線——網路上的中間人可以假冒 GitHub，**把公司的通行證攔走**。這個模組的通行證**外流過一次**（已處置），現在同一個模組還在用這種方式傳它 | 把那兩處的關閉開關拿掉 | ⬜ **未修**（工單 CM-1632） |


## SUMMARY #101｜出自 M23-5｜⬜ 未修

公司內部網路裡有心人，可以在我們打包機安裝套件的當下偷偷掉包內容，等於在打包機上執行他的程式碼——而打包機產出的就是客戶實際拿到的安裝檔


### 模組頁列
M23 第 5 條（M23-issue.md:69）
| **5** | **連公司內部套件倉庫走的是沒有加密的連線**，而且這是安裝套件的主要來源（28 支程式庫全部一樣） | 🟡 中 | 密碼外流 | **內部網路裡有心人可以在安裝套件的當下偷偷掉包內容**，等於在打包機上執行他的程式碼——**而打包機產出的就是客戶實際拿到的安裝檔**。這是整條供應鏈最根本的一個破口 | 內部套件倉庫改走加密連線；順便把鎖定套件版本的那份檔案納入版本控制（第二道防線目前也沒跟著出貨） | ⬜ **未修**（工單 CM-1634） |


## SUMMARY #102｜出自 掃描總表 122｜⬜ 未修

匯出 Word 時使用者填的文字沒有做跳脫處理就塞進範本，可以讓匯出的稽核文件夾帶偽造內容


### 掃描總表 §3.1 第 122 項
| 122 | **匯出 Word 時，使用者填的文字沒有做「跳脫處理」就塞進 Word 範本，可以讓匯出的稽核文件夾帶偽造內容**——`app/oscal/service/export/ssp_docx_generator.py:60` 的 `tpl.render(context)` 少了 `autoescape=True`，用的套件（docxtpl）**預設是關閉跳脫的、要自己明確打開**。Word 檔內部是一種標記語言寫的文字檔，使用者在「系統名稱」「系統描述」「網路架構」「資料流」這些欄位填入**看起來像 Word 內部指令的文字**，它就會被當成真的指令、而不是顯示成文字。**出事會怎樣**：①在匯出的稽核文件裡插入稽核員根本沒寫過的段落或實作說明；②藏一段 Word 的「欄位指令」，叫**閱讀者的 Word** 去抓外部內容，對方一開檔就觸發。**匯出的系統安全計畫就是要交給稽核方的正式文件，內容能被動手腳等於證據本身不可信**。**要先有什麼才打得到**：①能編輯該計畫文字欄位的人（專案負責人，或有範本編輯權的人）②另一個人去匯出並打開那份檔案。**為什麼資料庫沒擋下來**：這條跟隔離無關——資料是合法使用者合法填進自己專案的，問題出在輸出那一刻沒有把它當「資料」處理 | 中等（3:0，24 票全投零漏投） | FR-113 O2 第 4 條（`app/oscal/service/export/ssp_docx_generator.py:60`） | ✅ 一行就能修（`tpl.render(context, autoescape=True)`），或把每個從資料來的字串先包成套件提供的安全型別。⚠️ **開卡時務必寫明**：**要修在這個出口、不要去每個輸入端擋**——Word／PDF／ODT 三條路都走這同一支產生器，修出口一次到位；修完要實際匯出一份含特殊符號的內容確認排版沒被打壞。🔴 **2026-09-21 補（FR-113 O4）**：**第 126 項是同一個病的另一個出口**（匯出控制項現況的 Excel 把使用者寫的字當公式執行），**兩條同一組修法、建議同一張卡做完**；而且 §4 🅶 組早就為第 78／82 項抽過一支「開頭是公式字元就前置單引號」的中和函式，**這三個出口沿用同一支即可、不要各寫一份** |


## SUMMARY #103｜出自 掃描總表 §3.2 第 11｜⬜ 未修

把系統日誌寫進資料庫如果失敗，錯誤會往上竄、把使用者原本那個請求一起弄壞——現在能跑純屬運氣（靠處理器排序），而正式環境現在也會走到這條路了


### 掃描總表 §3.2 第 11 項
| 11 | ⚠️ **2026-09-20 複查：風險被放大了**——commit `4bf7906`（CM-1920）標題是「只放行稽核事件與 ERROR，**prod／stg 補掛 db handler**」，改的是「寫多少」不是「寫失敗怎麼辦」。`db_handler.py:30-51` 的 `emit()` 全函式仍無 try／except，等於**這條沒有錯誤處理的路現在正式環境也會走**。原文：**把日誌寫進資料庫如果失敗，會連帶把原本使用者的請求一起弄壞，而且它現在能正常運作純粹是運氣好、靠日誌處理器剛好排在後面**。負責寫資料庫日誌的這段程式**完全沒有做任何錯誤處理**，一旦寫入失敗，錯誤會直接往上竄回業務程式，**變成使用者看到的系統錯誤**——本來只是「記錄失敗」這種小事，結果連累了整個請求失敗。而且這段程式用到的一個時間欄位，理論上要先經過另一段「格式化」邏輯才會有值，但負責資料庫日誌的這個格式化邏輯沒有提供對應設定，本來應該也會出錯——**它現在之所以能正常運作，純粹是因為這個日誌處理器剛好排在第三個順位**，前面兩個處理器順便把這個欄位填好了，屬於巧合而非設計。**沒有「無限迴圈寫日誌」這種更嚴重的組合**（錯誤是往呼叫端傳遞，不是在日誌處理器內部又觸發一次寫日誌）。三位檢查員一致認定這不是資安問題，首腦也同意（找不到攻擊者能主動觸發的路徑）。**修法**：這段程式補上完整的錯誤處理，失敗時呼叫標準的錯誤處理方法；資料庫日誌的格式化邏輯補上時間格式設定，或改用另一個一定會有值的時間欄位 | `db_handler.py:13-30` | ✅ 檔名行號與修法齊全，直接抄 |


## SUMMARY #123｜出自 M02-9｜⬜ 未修

使用者在本機儲存模式下刪掉一個檔，轉檔產生的 PDF 備份沒被一起清掉，還留在硬碟上


### 模組頁列
M02 第 9 條（M02-file-upload.md:97）
| **9** | 本機儲存刪檔時，漏清了轉檔產生的備份 | 🟡 中 | 資料清理不完整 | **使用者以為刪掉了，但轉出來的 PDF 還留在硬碟上**——「刪了沒刪乾淨」對稽核產品是實質缺陷 | 補上那一步；並把兩種儲存方式的共通邏輯抽到同一處 | ⬜ **未修** |


## SUMMARY #124｜出自 M02-11｜⬜ 未修

系統連到自己裝在客戶機房的檔案儲存空間時走的是不加密連線，檔案內容與帳密在客戶內網上明文跑


### 模組頁列
M02 第 11 條（M02-file-upload.md:98）
| **11** | 連雲端儲存時不使用加密連線 | ⚪ 低 | 密碼外流 | 檔案內容與帳密在公司內部網路上以明文傳輸。**目前七筆設定全部都明確填成不加密**，不是漏填 | 出貨裝機時預設開啟加密；現有環境另外評估切換時機 | ⬜ **未修** |


## SUMMARY #125｜出自 M04-9｜⚠️ 部分修

兩種運作紀錄的詳細程度被寫死成最詳細，紀錄量暴增吃掉磁碟（三處已改一處，還剩兩處）


### 模組頁列
M04 第 9 條（M04-common.md:81）
| **9** | 兩種紀錄的詳細程度被寫死成最詳細 | ⚪ 低 | 資源耗盡 | **沒有安全問題**（這兩種紀錄只寫檔案、不進資料庫），但紀錄量會暴增、吃掉磁碟 | 兩處調回一般等級 | ⚠️ **部分修**——正式設定裡有八種紀錄分類，曾有三處被設成最詳細，已改掉一處，**還剩兩處** |


## SUMMARY #135｜出自 M23-7｜⬜ 未修

程式在刪除附件時算出了「該過濾掉哪些檔案」，接下來卻用回沒過濾的那一份清單；現在一次只傳一個檔案編號所以打不到，未來做了批次刪除會刪錯檔案而且不會報錯


### 模組頁列
M23 第 7 條（M23-issue.md:71）
| **7** | 刪除附件時的安全檢查只做了一半（算出了該過濾掉哪些，卻沒有真的用那個結果） | ⚪ 低 | 外部送什麼就收什麼 | **目前打不到**——現在的操作流程一次只會傳一個檔案編號。但**未來如果做批次刪除功能，會在使用者不知情的情況下刪錯檔案，而且不會報錯** | 把算出來的過濾結果真的拿去用（一行） | ⬜ **未修**（工單 CM-1633） |


## SUMMARY #136｜出自 M19-3｜⬜ 未修

任何登入帳號在清單頁把「一頁顯示幾筆」送一個超大的數字，就叫資料庫一次把整張表撈出來（根源在全站共用底層，與 M04-7 同一件事）


### 模組頁列
M19 第 3 條（M19-asset.md:66）
| **3** | 清單一頁要顯示幾筆，沒有上限 | ⚪ 低 | 資源耗盡 | 任何登入帳號送一個很大的數字，就能叫資料庫一次把整張表撈出來 | **這一條的根源不在這一塊**，在所有模組共用的底層——全站清單都吃到。修一處全站生效 | ⬜ **未修**（歸在共用基礎那一塊，這裡不另計） |


## SUMMARY #137｜出自 掃描總表 §3.2 第 12｜⬜ 未修

系統自動產生的密碼比自家要求的最短長度少一個字元（要 12 給 11），可能通不過自家的規定


### 掃描總表 §3.2 第 12 項
| 12 | **系統自動產生的密碼，長度比要求的少一個字元**。產生密碼的邏輯會先放進 3 個固定類型的種子字元（小寫字母、大寫字母、數字；原本設計裡第 4 種「標點符號」那一行已經被註解掉不用了），再補足其餘的隨機字元——**但補足隨機字元的數量計算公式，還是照著「有 4 種種子字元」時的算法，導致實際產出的密碼長度比設定值少一個字元**。這是當初把標點符號那一行拿掉時，忘記同步調整計算公式所留下的殘留問題。**後果**：系統自動產生的密碼，可能通不過自家系統要求的最短密碼長度規定（要求 12 個字元，結果只給了 11 個）。**修法**：把計算公式裡的數字調整回正確值（只是改一個數字） | `common_util.py:64` | ✅ 檔名行號與修法齊全，直接抄 |
