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

C02 前門 → api(guidant-fe → guidant-api,HTTP :8000)

所有業務 API 走的那條線——使用者在畫面上做的每一件事,最後都變成這條線上的一個請求。它承載全產品的功能,也承載 168 條掃描問題,佔六頁 STRIDE 不重複條目的三分之二。這一頁回答:這條線怎麼連、哪些功能走它、它帶來什麼威脅、駭客怎麼打、我們要怎麼防、目前做到哪。

必有 驗 JWT・不驗權限 威脅 12 種 掃描命中 168 條

這一頁怎麼來的:DFD Level 0 把產品運作時的連線編成 C01~C25;STRIDE 六頁每一條問題都標了發生在哪幾條線。這裡以連線為單位整理。C02 命中的 168 條太多,不逐條表,改成十二種攻擊手法,每種寫駭客怎麼打、得手什麼,底下列全部實例連回 STRIDE 頁。個別問題修了沒不在這頁講。

§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
    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
C02 — 前門到 api。前門把 /api/1.0 的請求原樣轉給 api;api 入口驗 JWT(你是誰),之後「你能不能做這件事」由每一支端點自己檢查——這是 168 條問題的來源:檢查寫在 400 多支端點裡,漏一支就是一個洞。
§2

二、這條線怎麼連

項目 現況
誰 → 誰 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。

§3

三、哪些功能會走這條線

全部業務功能。對這條線的威脅分析,功能依「守門的軸」分四群——每群出的問題形狀不同:

群 功能 守門靠什麼 威脅的來源
① 專案內的讀寫 稽核任務、證據、問卷填答、稽核判定、改善計畫、摘要報告、流程 「你是不是這個專案的人、什麼角色」——每支端點要自己先查成員資格 端點忘了查,或查了 A 動的是 B
② 公司級共用資料 合規範本(SSP)、框架、公告、設備清冊、意見回饋、人員名冊 能力點(RBAC)或「你是不是這家公司的」 只問登入不問能力;查詢條件沒填就回整張表
③ 系統設定與帳號 SMTP/LDAP/儲存/日誌轉送設定、個人資料、綁定外部帳號、授權檔 總部層級、平台管理員、本人確認 層級算錯、把設定連密碼一起回給瀏覽器、改個人資料順手改角色
④ 匯入匯出與解析 Excel/Word/PDF/YAML/規則包上傳解析,Excel/Word/PDF 匯出 大小上限、格式驗證、輸出跳脫 特製檔讓伺服器卡死;使用者的字進了公式或範本語法
§4

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

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。

§5

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

對應十二種威脅,防線分九條。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 時就看得見,比再掃一輪便宜得多。

§6

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

  • 路由守門覆蓋表(D9):掃描是人讀程式抓的,168 條之外有沒有漏網不知道。用程式列出全部路由與各自掛的守門軸,「只驗 JWT」的那些逐一判定該不該。
  • 能力點 seed 清單(D4):哪些 <resource>.manage 已 seed、哪些端點還在用 require_super_admin() 過渡。
  • repo 層空條件行為(D3):M10-9 修了一支底層,其他 repo 的 query 方法遇到空條件怎麼處理,沒有盤過。

依據:STRIDE 六頁信任邊界連線標記(CM-2403/2404 驗收後版本)、common/authz/__init__.py 決策表、common/middleware/app_mw.py、FE nginx.onprem.conf、DFD Level 0。分組是首腦依標題與「駭客怎麼打」歸納的,同一條若兩種手法都沾,歸主要那種。