Guidant AI 資安檢視總報告 · 連線清單
所有業務 API 走的那條線——使用者在畫面上做的每一件事,最後都變成這條線上的一個請求。它承載全產品的功能,也承載 168 條掃描問題,佔六頁 STRIDE 不重複條目的三分之二。這一頁回答:這條線怎麼連、哪些功能走它、它帶來什麼威脅、駭客怎麼打、我們要怎麼防、目前做到哪。
這一頁怎麼來的:DFD Level 0 把產品運作時的連線編成 C01~C25;STRIDE 六頁每一條問題都標了發生在哪幾條線。這裡以連線為單位整理。C02 命中的 168 條太多,不逐條表,改成十二種攻擊手法,每種寫駭客怎麼打、得手什麼,底下列全部實例連回 STRIDE 頁。個別問題修了沒不在這頁講。
%%{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
U(["👤 已登入使用者<br/>(任何角色、任何客戶)"])
FE["guidant-fe<br/>nginx 前門"]
subgraph API["guidant-api :8000"]
direction TB
MW["入口攔截器<br/>記日誌・遮密碼"]
JWT["驗 JWT<br/>(你是誰)"]
subgraph EP["400+ 支端點,各自守門"]
direction LR
E1["有守門:<br/>專案成員?角色?能力點?"]
E2["沒守門:<br/>只問有沒有登入"]
end
MW --> JWT --> EP
end
DB[("guidant-db<br/>RLS 以租戶隔離")]
U == "C01" ==> FE
FE == "C02 · /api/1.0 · JWT Bearer" ==> API
E1 -- "C05" --> DB
E2 -- "C05 · 同一個租戶內<br/>RLS 擋不住" --> DB
classDef ext fill:#FBFCFC,stroke:#9AA8AA,stroke-dasharray:3 2
classDef bad fill:#FDECEC,stroke:#C0392B
class U ext
class E2 bad
| 項目 | 現況 |
|---|---|
| 誰 → 誰 | guidant-fe(nginx)→ guidant-api(Flask/gunicorn)。走 docker 內部網路,api 的 :8000 不對外開,只有前門連得到 |
| 協定/埠 | HTTP :8000(內網明文;加密在前門 C01 終止)。路徑固定 /api/1.0/…,所有業務路由都掛在這個前綴下 |
| 傳什麼 | 全部業務 API:讀寫專案、問卷、範本、稽核紀錄、系統設定、檔案上傳與下載、匯入匯出、AI 功能。前門轉發時帶 X-Real-IP/X-Forwarded-For,api 靠這個知道真正來源 |
| 怎麼驗身分 | JWT(Bearer)。api 入口驗 token 有效、解出使用者與租戶,寫進 session 變數給資料庫 RLS 用。但入口只驗「你是誰」,不驗「你能不能做這件事」——後者由每一支端點自己呼叫守門(common/authz/ 七個軸:平台管理員、超級管理員、專案角色、能力點、簽章憑證、授權、總部層級) |
| 驗身分之前做的事 | 入口攔截器先記請求日誌、遮密碼(見 C01 T2),再驗 JWT |
| 租戶隔離 | 資料庫層 RLS 以 session 變數擋跨客戶讀寫。同一客戶內的隔離(你是不是這個專案的人)RLS 不管,全靠端點守門 |
| 何時存在 | 必有 |
依據:FE nginx.onprem.conf、common/middleware/app_mw.py、common/authz/__init__.py 決策表、DFD Level 0。
全部業務功能。對這條線的威脅分析,功能依「守門的軸」分四群——每群出的問題形狀不同:
| 群 | 功能 | 守門靠什麼 | 威脅的來源 |
|---|---|---|---|
| ① 專案內的讀寫 | 稽核任務、證據、問卷填答、稽核判定、改善計畫、摘要報告、流程 | 「你是不是這個專案的人、什麼角色」——每支端點要自己先查成員資格 | 端點忘了查,或查了 A 動的是 B |
| ② 公司級共用資料 | 合規範本(SSP)、框架、公告、設備清冊、意見回饋、人員名冊 | 能力點(RBAC)或「你是不是這家公司的」 | 只問登入不問能力;查詢條件沒填就回整張表 |
| ③ 系統設定與帳號 | SMTP/LDAP/儲存/日誌轉送設定、個人資料、綁定外部帳號、授權檔 | 總部層級、平台管理員、本人確認 | 層級算錯、把設定連密碼一起回給瀏覽器、改個人資料順手改角色 |
| ④ 匯入匯出與解析 | Excel/Word/PDF/YAML/規則包上傳解析,Excel/Word/PDF 匯出 | 大小上限、格式驗證、輸出跳脫 | 特製檔讓伺服器卡死;使用者的字進了公式或範本語法 |
168 條歸納成十二種手法。前提都一樣:攻擊者有一個有效帳號(任何客戶的任何員工都算;T6 與 T10 的部分條目連帳號都不用)。每種寫駭客實際的步驟與得手什麼;實例欄是該手法的全部條目,滑鼠移到編號上顯示標題,點了連回 STRIDE 頁。
| # | 風險 | 威脅 | 駭客怎麼打 | 得手什麼 | STRIDE | 實例 |
|---|---|---|---|---|---|---|
| T1 | 🟠 高 | 換一個編號,讀到別人專案的東西(24 條) | ① 用自己的帳號登入,打開任何一個自己的專案,從網址或請求裡看到專案/任務/輪次/報告的編號;② 把編號換成別人的(編號多半是連號或可猜);③ 端點只檢查「有沒有登入」,不檢查「這個編號是不是你的」,照樣回資料。 | 同一家客戶內其他專案的稽核判定、改善計畫、證據清單、人員配置、報告——嚴重時跨客戶(RLS 沒擋到的查法)。 | I 資料外洩 | M02-1 🟠、M06-1 🟠、M06-5 🟡、M06-6 🟡、M10-2 🟡、M10-3 🟡、M10-4 🟠、M10-22 ⚪、M11-1 🟠、M11-4 🟡、M11-12 🟡、M12-1 🟠、M12-4 ⚪、M12-5 ⚪、M20-3 🟡、M05-2 🟠、M05-6 🟡、M10-21 🟡、M03-12 🟡、M06-17 🟡、M06-22 ⚪、M07-2 🟡、M07-3 🟡、M24-2 🟠 |
| T2 | 🟠 高 | 送空白查詢,整張表一次撈走(14 條) | ① 找一支列表查詢端點(專案成員、任務指派、填答歷史、問卷討論);② 把查詢條件全部留空、或把每頁筆數填極大;③ 底層「條件沒填就不過濾」,一次回整個客戶(或全站)的資料。 | 全公司成員名冊含 email、一萬多筆指派紀錄、所有討論內容。 | I 資料外洩 | M05-1 🟠、M05-3 🟡、M05-4 🟡、M05-5 🟡、M10-1 🟡、M10-5 🟡、M10-9 🟡、M10-11 🟡、M21-2 ⚪、M11-31 ⚪、M11-40 ⚪、M04-7 🟡、M19-3 ⚪、M12-3 🟡 |
| T3 | 🟠 高 | 只問登入不問能力,沒被授權的員工直接打網址(18 條) | ① 畫面上看不到某功能(選單沒給你);② 從別人的操作或前端程式碼找到那支端點的網址;③ 直接打,端點只驗 JWT 不驗能力點或角色,照樣給。 | 合規範本全文、範本掛的程序書(附檔案編號可下載)、設備清冊、流程範本、AI 儀表板查到的任何資料。 | I 資料外洩、E 權限提升 | M11-14 🟡、M11-15 🟡、M11-16 🟡、M11-17 🟡、M11-28 ⚪、M19-1 🟡、M17-3 ⚪、M17-4 ⚪、M06-11 ⚪、M05-11 ⚪、M03-8 ⚪、M11-13 🟡、M15-2 🟡、M15-3 🟡、M15-7 ⚪、M17-2 🟡、M14-1 🟡、M06-2 🟠 |
| T4 | 🟠 高 | 檢查 A、動的是 B——兩個編號不核對(14 條) | ① 請求帶兩個編號(例如「專案」與「任務」、「範本」與「子項」);② 端點只檢查第一個是不是你的,第二個不核對是否屬於第一個;③ 第一個填自己的、第二個填別人的,通過檢查後動到別人的資料。 | 改、刪別人專案的任務與文件,把別人的程序書掛到自己控制項再下載。 | T 竄改資料、E 權限提升 | M06-18 🟡、M06-19 🟡、M06-20 ⚪、M06-24 ⚪、M10-6 🟡、M10-10 ⚪、M10-12 ⚪、M11-7 🟡、M11-11 🟡、M23-7 ⚪、M05-10 🟡、M06-21 ⚪、M12-9 ⚪、M07-1 🟠 |
| T5 | 🟠 高 | 角色算寬了:唯讀能改、建立能蓋、子公司管母公司(22 條) | ① 拿一個低權限角色(唯讀、只有建立權、子公司管理員、專案經理);② 找到寫入端點,它檢查的角色集合比該有的寬、或檢查的是另一個條件(範本存不存在、歸屬只在改分享時才查);③ 做本來不該做的事。 | 唯讀角色完成或退回別人的稽核任務、對客戶機器發動掃描;只有建立權的人用整批匯入蓋掉既有範本;子公司改母公司範本。 | E 權限提升、T 竄改資料 | M06-7 🟡、M03-14 🟡、M12-6 ⚪、M17-1 🟡、M23-2 🟡、M05-8 🟡、M11-5 🟡、M11-6 🟡、M11-8 🟡、M11-9 🟡、M11-10 🟡、M11-2 🟠、M11-3 🟠、M11-27 🟡、M11-30 ⚪、M11-33 ⚪、M06-9 🟡、M06-10 🟡、M19-2 ⚪、M02-12 🟡、M24-12 ⚪、M10-14 ⚪ |
| T6 | 🔴 最嚴重 | 帳號與登入本身的洞(9 條) | 三種路——(a) 自助入口沒確認本人:存個人資料時夾帶 role=admin、綁定外部帳號不驗是本人、雙因子驗證碼無限次猜;(b) 登入的第三方驗證沒接好:LDAP 匿名能過、Google 登入是空殼;(c) 出廠就帶的後門:每套安裝同一組最高權限帳號、靜態目錄不用登入就能下載上傳檔、首次開通沒上鎖。 |
把自己升成管理員、接管任何帳號、不用帳號就拿到檔案。 | S 冒充身分、E 權限提升 | M13-3 🟠、M13-4 🟠、M13-6 🟠、M13-2 🟠、M13-5 🟠、M13-1 🔴、M17-7 🟡、M24-19 ⚪、M24-3 🟡 |
| T7 | 🟠 高 | 層級算錯:子公司改全站、他家總部管我家(4 條) | ① 以子公司管理員、或另一家客戶的總部管理員登入;② 打全公司共用設定的寫入端點(登入規則、帳號目錄、日誌轉送去向);③ 「誰算總部」的判斷只擋自家子公司、擋不住別家總部,或根本沒分層級。 | 改掉全站登入規則與 LDAP 目錄、把所有客戶的操作日誌轉到自己的伺服器、改暱稱在全公司每份範本埋公式。 | E 權限提升、T 竄改資料 | M22-1 🟠、M22-4 🟠、M09-1 🟠、M11-21 🟡 |
| T8 | 🟠 高 | 回應帶太多:設定連密碼一起回、錯誤訊息吐內部細節(17 條) | ① 任何登入帳號打「查設定」端點,回應裡儲存空間、郵件伺服器的帳號密碼是明文;② 或打「測試連線」「寄測試信」把目的地改成自己的主機,系統把存著的密碼送過來;③ 或故意送壞資料,錯誤訊息把資料庫原始錯誤、伺服器暫存路徑、密碼鹽值整段回給畫面。 | 儲存與郵件系統的憑證、客戶主機的維運帳密、原廠 AI 金鑰、內部路徑與結構。 | I 資料外洩 | M02-5 🟠、M22-2 🟠、M22-3 ⚪、M16-1 🟡、M07-13 🟡、M03-2 🟠、M03-6 🟡、M15-4 🟡、M04-12 ⚪、M05-12 ⚪、M11-22 🟡、M11-32 ⚪、M24-16 ⚪、M24-17 ⚪、M04-1 🟠、M04-3 🟡、總表#145 ⚪ |
| T9 | 🟠 高 | 使用者的字變成程式:公式、範本語法、網頁、AI 指令(14 條) | ① 在任何會被匯出或顯示的欄位(暱稱、描述、意見回饋、規則包)填特製內容:Excel 公式 `=cmd | …、Word 範本語法 {{…}}`、網頁程式、換行夾偽造日誌、對 AI 的指令;② 等別人匯出 Excel/Word、點預覽、或 AI 儀表板處理那段字;③ 內容在對方的電腦、我們的伺服器、或 AI 的判斷裡被當成指令執行。 |
在稽核人員電腦執行程式、讓伺服器替人連內網、往客戶監控系統塞假日誌、誘導 AI 選任何查詢。 | T 竄改資料、E 權限提升 |
| T10 | 🟠 高 | 特製檔或請求讓伺服器卡死(20 條) | ① 做一份幾 KB 的特製檔(壓縮炸彈、幾萬個空白的 Word、一百萬個數字的 Excel 說明頁、特製 YAML、頁數極多的 PDF、永遠算不完的流程圖);② 從匯入端點上傳,或打「檢查流程圖」「重新解析」這類任何登入者都能打、不限大小的端點;③ 解析時記憶體撐爆或比對卡住,處理程序全被占住。 | 讓整個產品對所有客戶停止回應(其中 M04-15 連帳號都不用)。 | D 讓服務停擺 | M04-15 🟠、M06-3 🟠、M06-4 🟡、M06-8 🟡、M06-23 🟡、M03-3 🟡、M03-4 🟡、M03-10 🟡、M03-11 🟡、M11-23 🟡、M11-24 🟡、M11-25 🟡、M11-26 🟡、M11-34 ⚪、M11-35 ⚪、M11-36 ⚪、M11-37 ⚪、M12-2 🟡、M12-8 ⚪、M18-2 🟠 |
| T11 | 🟠 高 | 繞過商務授權(6 條) | ① 客戶自己上傳舊授權檔、或拿開發環境簽的授權;② 或等過期唯讀的縫(排程一天一次、代理程式網址整段放行、問卷即時填答不經檢查);③ 或選單反灰但直接打網址。 | 停權復權、過期照用、沒買的模組照用。受損的是原廠收入。 | E 權限提升 | M18-1 🟠、M18-7 🟡、M18-9 ⚪、M18-11 ⚪、M18-13 ⚪、M05-14 ⚪ |
| T12 | 🟠 高 | 身分與紀錄對不上:沒身分時隔離關掉、日誌記錯人(6 條) | ① 讓請求在「沒有登入身分」的狀態下進到業務邏輯(背景工作、某些例外路徑),客戶隔離整個關掉;② 或把網址加長讓這次操作不進日誌、把來源 IP 欄位自填、操作者名稱由呼叫端自填。 | 跨客戶讀寫、事後查不到是誰做的。 | R 事後無法追查、E 權限提升 | M04-2 🟠、M09-6 🟡、M09-7 ⚪、M08-7 ⚪、M06-12 ⚪、M21-3 🟡 |
十二種手法的共同點:T1~T7、T12 共 111 條都是同一件事——入口只驗「你是誰」,「你能不能做這件事」寫在 400 多支端點裡各自為政,漏一支就是一個洞。T8~T10 是另一件事——端點把使用者給的東西(檔案、字串、查詢條件)當成可信的,沒有統一的上限與跳脫。這兩個特性是這條線的本質,不是某支程式的 bug。
對應十二種威脅,防線分九條。D1~D7 直接對應掃描抓到的手法;標 ◇ 的是依這條線的特性補的標準防線,掃描範圍沒涵蓋。「目前」欄寫程式裡實際有的機制;⚠️ 表示只靠慣例或只守到局部。
| # | 防線 | 擋哪種威脅 | 目前做到哪 |
|---|---|---|---|
| D1 | 資源域守門統一走 common/authz/,不自己寫。任何帶資源編號的端點,先 resolve 資源、再查呼叫者與資源的關係(專案成員/角色/歸屬),用決策表選對的軸 |
T1、T4、T5 | ✅ FR-048 已建七軸決策表與 canonical 實作,舊四套路徑改 deprecated shim,守衛測試擋新引用。⚠️ 是「新 code 一律用」的規範,既有 400 多支端點沒有全面回掃——168 條裡 T1/T4/T5 的 60 條就是回掃抓到的,掃描範圍外的端點不保證 |
| D2 | 兩個編號必核對歸屬。請求帶父子兩個編號時,守門要驗子屬於父,不能只驗父 | T4 | ⚠️ 逐支修,沒有框架層強制。assert_project_role 系列有做 control→group→project 的 fallback,但「任務屬於專案」這類核對仍靠端點自己寫 |
| D3 | 列表查詢條件沒填就拒絕,每頁筆數有上限。底層 repo 不接受空條件回整張表 | T2 | ⚠️ 逐支修(M10-9 是底層通則,修了那一支)。沒有 repo 層的全域守則,新加的列表端點可能再犯 |
| D4 | 能力點(RBAC)掛在 route decorator,看不到的功能網址也打不到。選單反灰與端點守門用同一張能力點表 | T3、T11 | ✅ @require_capability 軸④已有,super_admin 旁路。⚠️ 能力點 seed 不全(決策表註明「未 seed 前過渡用 require_super_admin」),哪些端點已掛、哪些還沒,沒有清單 |
| D5 | 總部層級與租戶隔離有單一判斷點。「誰算總部」只有一個函式;「沒有登入身分」時 RLS 必須是最嚴而不是關掉 | T7、T12 | ✅ 軸⑦ require_tenant_hq() 已建(CM-2401 補了三組共用設定)。✅ M04-2 無身分時隔離已改成拒絕。⚠️ 哪些端點該掛軸⑦,靠人判斷 |
| D6 | 回應 schema 白名單,不回不該回的欄位。設定類回應一律遮密鑰;錯誤訊息只回代碼,原始例外只進日誌 | T8 | ✅ 遮密碼名單有(mark_password),設定讀取三組已補守門。⚠️ 遮蓋靠「名單」,新欄位沒加進名單就漏(M22-3 就是);錯誤訊息回畫面的習慣沒有框架層攔截 |
| D7 | 所有使用者輸入在出口跳脫:匯出 Excel 時公式前綴加 '、Word 範本語法轉義、日誌欄位去換行、對 AI 的輸入與工具選擇分離 |
T9 | ✅ 逐條修了(Excel 公式、Word 語法、日誌換行、AI 工具選擇)。⚠️ 跳脫寫在各匯出點,沒有共用的「輸出工具」統一處理,新匯出功能要記得 |
| D8 ◇ | 解析前先量大小、解析時限資源。所有匯入與解析端點:上傳檔大小、解開後大小、檔案數、解析逾時與記憶體上限,用同一套常數 | T10 | ✅ 逐條修了 20 條,各自加了上限。⚠️ 上限散在各模組,沒有一處「所有解析端點必過」的守門;前門不限大小(C01 D7) |
| D9 ◇ | 端點守門覆蓋率用程式產清單、不靠記憶:每支路由掛了哪個軸、哪些只驗 JWT,定期產表對照 | 全部 | ⚠️ 沒有。scripts/deliverables/collect_routes.py 能列路由,但沒有「掛了哪個守門」這一欄。這是把 D1~D5 從「規範」變「機制」的那一步 |
最該補的一步:D9。168 條裡 T1~T7 共 100 多條,全是「某支端點漏了守門」——這種病不會因為修了 168 支就好,會在下一支新端點再長出來。產一份「路由 × 守門軸」的清單,讓漏掛的端點在 review 時就看得見,比再掃一輪便宜得多。
<resource>.manage 已 seed、哪些端點還在用 require_super_admin() 過渡。query 方法遇到空條件怎麼處理,沒有盤過。依據:STRIDE 六頁信任邊界連線標記(CM-2403/2404 驗收後版本)、common/authz/__init__.py 決策表、common/middleware/app_mw.py、FE nginx.onprem.conf、DFD Level 0。分組是首腦依標題與「駭客怎麼打」歸納的,同一條若兩種手法都沾,歸主要那種。