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

C01 瀏覽器 → 前門

guidant-fe,HTTPS :443

使用者的瀏覽器連到系統唯一對外入口的那條線。這一頁回答:這條線怎麼連、哪些功能走它、它會給系統帶來什麼威脅、駭客怎麼打、我們要怎麼防、目前做到哪。

必有 不驗身分 威脅 4 種 掃描命中 5 條

這一頁怎麼來的:DFD Level 0 把產品運作時的連線編成 C01~C25;STRIDE 六頁每一條問題都標了發生在哪幾條線。這裡以連線為單位,把這條線的特性、掃描在這條線上抓到的攻擊手法、以及由此該有的防線整理成一頁。個別問題修了沒不在這頁講,請點條目連回 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/>(網路上任何人)"])
    ATK(["🕵 攻擊者<br/>(不需帳號)"])
    subgraph MAIN["🏠 主系統 stack(只有 :443 對外)"]
        direction TB
        FE["guidant-fe<br/>nginx 前門 :443<br/>TLS 終止・分流・不驗身分"]
        API["guidant-api :8000<br/>驗 JWT"]
        SIO["guidant-socketio :8002<br/>驗 JWT"]
        STATIC[("前端靜態頁")]
        FE -- "/api/1.0 · C02" --> API
        FE -- "/socket.io · C03" --> SIO
        FE -- "其他路徑" --> STATIC
    end
    subgraph OPEN["不需登入就打得到的入口(在 api 內、驗身分之前或之外)"]
        direction TB
        O1["登入/忘記密碼"]
        O2["Drive 授權回呼頁"]
        O3["每個請求的前置處理<br/>(記日誌、遮密碼)"]
    end
    U == "C01 · HTTPS :443<br/>頁面+API+WebSocket" ==> FE
    ATK -. "C01 · 同一條線<br/>不需帳號" .-> FE
    API --- OPEN
    classDef ext fill:#FBFCFC,stroke:#9AA8AA,stroke-dasharray:3 2
    classDef open fill:#FDECEC,stroke:#C0392B
    class U,ATK ext
    class O1,O2,O3 open
C01 — 瀏覽器到前門。前門是整套系統唯一對外開的埠,自己不驗身分,收到的流量分三路:靜態頁自己回、/api/1.0 轉給 api(C02)、/socket.io 轉給 socketio(C03)。紅色是不需登入就打得到的入口。
§2

二、這條線怎麼連

項目 現況
誰 → 誰 使用者的瀏覽器 → guidant-fe 容器(nginx)。這是整套系統唯一對外開的埠,後端 api(:8000)與 socketio(:8002)都不對外,全由這裡分流
協定/埠 HTTPS :443(TLS 1.2/1.3)。:80 只做 301 轉到 https,不供站
傳什麼 三種東西共用同一條線:① 前端靜態頁(Vue 打包檔);② 業務 API 請求(路徑 /api/1.0,轉給 api,即 C02);③ 即時推播的 WebSocket(路徑 /socket.io,轉給 socketio,即 C03)
怎麼驗身分 這條線本身不驗。身分(JWT)夾在請求裡往後送給 api 驗;前門只負責加密通道與分流。所以「不需要登入就打得到的東西」全在這條線上暴露給網路上任何人
憑證 客戶自備(正式 CA 或自簽),由 compose 唯讀掛進容器,不進 image。憑證檔缺失時 nginx 直接啟動失敗,不退回 http——刻意的,http 站台會「看得到畫面但登不進去」(瀏覽器在非 https 下不給 Web Crypto)
反向代理帶的資訊 轉給後端時加 X-Real-IP/X-Forwarded-For/X-Forwarded-Proto,後端靠這些知道真正來源與 https
大小與時間限制 上傳大小不限(client_max_body_size 0,交由後端各模組自己把關);API 逾時 600 秒、WebSocket 3600 秒
何時存在 必有。沒有這條線就沒有產品

依據:FE repo nginx.onprem.conf(落地版正式出貨設定)、DFD Level 0 的 STG 盤點。

§3

三、哪些功能會走這條線

全部——使用者在畫面上做的每件事都從這裡進來。但對這條線的威脅分析來說要分兩群,因為前門不驗身分:

群 功能 對威脅的意義
① 不需登入就打得到 登入頁、忘記密碼、Google Drive 授權回呼頁、所有靜態頁;以及每一個請求在驗身分之前系統先做的事(記操作日誌、遮密碼) 網路上任何人都構成攻擊面,不需要帳號。這一群出的問題嚴重度天生高一級
② 登入後的回程 所有 API 的回應、檔案預覽(伺服器把儲存裡的檔交回瀏覽器畫出來)、匯出下載 回程把伺服器上的內容送進使用者瀏覽器、在本站網域裡執行。內容若是別人上傳的,就是把不可信的東西放進可信的網域
§4

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

掃描在這條線上抓到 5 條問題(M13-1、M04-15、M24-1、M02-4、M03-13),歸納起來是四種攻擊手法。每種寫駭客實際的步驟與得手什麼,條目是實例。

# 風險 威脅 駭客怎麼打 得手什麼 STRIDE 實例
T1 🔴 最嚴重 不需登入的入口把祕密從回程交出去 ① 不需帳號,打開登入頁的「忘記密碼」;② 輸入目標的信箱(例如原廠管理員)送出;③ 系統把重設鑰匙直接寫在回應裡,不必收信;④ 拿鑰匙改掉目標密碼 接管任何知道信箱的帳號,含原廠最高權限管理員 S 冒充身分、E 權限提升 M13-1 忘記密碼把重設鑰匙直接回傳給任何人 🔴
T2 🟠 高 驗身分之前的處理被餵特製內容,把全站卡死 ① 不需帳號,對任何網址(連不存在的都行)送一個請求,內容放 128KB 特製文字;② 系統在知道對方是誰之前就把整份內容拿去比對規則遮密碼;③ 比對卡住數秒到數分鐘;④ 同時送幾個,所有處理程序全卡住 不必任何帳號,讓整個產品對所有客戶停止回應 D 讓服務停擺 M04-15 不必登入,送一個特製內容的請求就讓整個產品停止回應 🟠
T3 🟠 高 不需登入的頁面把網址參數放進頁面程式,在本站網域執行 ① 不需帳號,做一個指向回呼頁的連結,參數裡塞 </script><script>…;② 寄給一個已登入的使用者;③ 對方一點,頁面被切開,攻擊者的程式在本站網域、以受害者的登入身分跑;④ 讀走登入權杖送出去 受害者的登入身分;受害者是管理員就是整家公司 S 冒充身分、E 權限提升 M24-1 雲端硬碟授權回呼頁,點一個連結就被偷走登入身分 🟠
T4 🟠 高 回程把別人上傳的內容當網頁交給瀏覽器執行 ① 有帳號的人(或控制得了代理程式回報的人)上傳含程式的網頁檔,或把它改名成 .png;② 當證據或掃描報告存進系統;③ 稽核人員在證據清單點「預覽」;④ 伺服器照上傳者填的副檔名把它當網頁送回,瀏覽器在本站網域裡執行;⑤ 程式用受害者身分讀改資料 受害者(通常是稽核人員)的登入身分 S 冒充身分、E 權限提升 M02-4 上傳的網頁檔,別人點預覽時被當成程式執行 🟠
M03-13 掃描報告「檔名叫什麼就存什麼」,走同一條預覽路徑 🟠

四種手法的共同點:T1~T3 都利用「前門不驗身分,驗身分之前與之外的東西對全世界開放」;T4 利用「回程落在本站網域,瀏覽器分不出內容是我們寫的還是別人上傳的」。這兩個特性就是這條線的本質風險,不是某支程式的 bug——程式修好了,特性還在,下次加一個不需登入的端點或一種新的預覽格式,同樣的路就再開一次。

§5

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

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

# 防線 擋哪種威脅 目前做到哪
D1 不需登入的入口,回應不得帶任何可直接使用的祕密(重設鑰匙、權杖、一次性碼)。祕密只能走第二條路(信箱)送到本人手上;成功與失敗的回應要長得一樣,不讓人探信箱存不存在 T1 ✅ 忘記密碼回應已改固定空白、成功失敗逐位元組相同。⚠️ 沒有統一的「不需登入端點回應檢查」,靠逐支 review
D2 驗身分之前只做便宜、有上限的事。身分檢查之前執行的每一步(記日誌、遮密碼、解析內容)都要對輸入長度設上限、耗時隨長度線性 T2 ✅ 入口先把內容截到 64KB、遮密碼規則改線性、一個請求只遮一次,有效能測試守著。⚠️ 沒有規範要求新加的前置處理先過這條;before_request 裡其他步驟沒逐一盤過
D3 不需登入的頁面,網址參數不得進頁面程式。回呼頁、錯誤頁只認白名單代碼,顯示文字由前端查翻譯,不回顯外部字串;頁面帶 CSP 只准執行帶隨機碼的程式 T3 ✅ Drive 回呼頁已白名單+跳脫+CSP nonce。⚠️ 目前只有這一個不需登入的伺服器渲染頁;日後加其他 OAuth 回呼要照同一套
D4 回程交回瀏覽器的檔案,由伺服器決定格式,不信上傳者說的。可在瀏覽器裡打開的格式用白名單;伺服器讀檔頭驗真實格式;網頁類一律帶 CSP sandbox;所有檔案回應帶 X-Content-Type-Options: nosniff;檔名在進儲存前先消毒 T4 ✅ 預覽路徑全部做到(套件 preview_policy.py);掃描報告檔名先過 sanitize_report_filename()
D5 ◇ 全站 CSP 兜底:前門對所有回應加 Content-Security-Policy,限制程式只能來自本站、禁 inline script,讓 T3、T4 這類「別人的程式在本站網域跑」即使繞過 D3/D4 也被瀏覽器擋 T3、T4 ⚠️ 沒有。前門 nginx 沒設任何安全回應標頭;CSP 只在 Drive 回呼頁與預覽回應兩處局部存在
D6 ◇ 只走 HTTPS,憑證缺失寧可不啟動;加 HSTS 讓瀏覽器記住永遠走 https,堵第一次打 http 那一下被攔的空窗;加 X-Frame-Options/frame-ancestors 不讓本站被嵌進別人的頁面(點擊劫持) 這條線的前提——沒有加密通道,上面所有身分與資料都是明文 ✅ :80 只 301、憑證缺失啟動失敗。⚠️ 沒有 HSTS、沒有 frame 限制;客戶用自簽憑證時瀏覽器警告會讓使用者習慣按「仍要前往」
D7 ◇ 請求大小與速率在前門就有上限,不讓單一來源用大量或大體積請求耗盡後端 T2 的延伸 ⚠️ 前門 client_max_body_size 0 不限大小(刻意交後端把關),沒有速率限制。後端各模組各自有大小上限,但若某個新端點忘了設,前門不會幫擋

最便宜的一步:D5、D6 的安全回應標頭,在前門 nginx 加五行就有,一次覆蓋所有頁面與回應,也是資安問卷必問項。這一輪掃描 24 個模組頁沒有任何一條提到它,因為掃描範圍是程式碼不是部署設定。

§6

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

  • 不需登入端點的完整清單(D1~D3):目前知道的是登入、忘記密碼、Drive 回呼。應該用程式產一份「哪些路由不掛身分檢查」的清單逐一對照,而不是靠記憶。
  • 驗身分之前的處理步驟清單(D2):攔截器裡目前做了哪幾件事、各自有沒有上限。這輪只修了遮密碼這一件。
  • 前門安全回應標頭(D5、D6):一個都沒設,整條線最大的空缺。

依據:STRIDE 六頁信任邊界連線標記(CM-2403/2404 驗收後版本)、FE repo nginx.onprem.conf、DFD Level 0、全 codebase grep 安全標頭(只有 google_drive_integration_route.py 與套件 preview_policy.py 兩處)。