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

C19 api → 規則包網址

url 型檢測基準,第一次抽取

客戶建立「檢測基準」時不上傳壓縮檔、改填一個網址,系統去那個網址把規則包下載回來解析走的那條線。它是系統替使用者打任意網址的原語——下載回來的東西會被交給外部程式(cinc-auditor)解析,解析出的清單給人看,網址之後還會隨派工交給客戶機房的代理程式自己再下載一次(C24)。這一頁回答:這條線怎麼連、哪些功能走它、它帶來什麼威脅、駭客怎麼打、我們要怎麼防、目前做到哪。

url 型 profile 替人打任意網址 威脅 3 種 掃描命中 3 條

這一頁怎麼來的:DFD Level 0 把產品運作時的連線編成 C01~C25;STRIDE 六頁每一條問題都標了發生在哪幾條線。這裡以連線為單位整理。3 條命中裡 M03-1 是「下載回來的內容交給外部程式時被當程式碼執行」——它對上傳型與網址型兩種來源都成立,STRIDE 頁同時標 C02 與 C19;M03-5 同時標 C19 與 C24(代理程式那條)。個別問題修了沒不在這頁講。

§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
    ADM(["👤 客戶管理員<br/>(建檢測基準)"])
    subgraph MAIN["🏠 主系統 api"]
        direction TB
        REG["登記 url 型基準<br/>只收 https"]
        FETCH["safe_http_fetch<br/>五道防線・60 秒・50MB"]
        SHA["算 sha256<br/>隨派工下發給 agent"]
        LIM["共用解析層<br/>檔數/解開總量/膨脹倍數<br/>inspec.yml 含 <% 拒收"]
        CINC["cinc-auditor<br/>(外部程式,讀 inspec.yml 當 ERB 樣板)"]
        REG --> FETCH --> SHA --> LIM --> CINC
    end
    URL(["🌐 規則包網址<br/>(客戶填的任何 https 位址)"])
    INTRA(["🏢 客戶內網/雲端 metadata"])
    AGENT(["🤖 客戶機房 agent<br/>C24 自己再下載一次"])
    ADM -- "C02 · 填網址" --> REG
    FETCH == "C19 · HTTPS GET(IP 釘住)" ==> URL
    FETCH -. "被擋:私有段/http/file://" .-> INTRA
    SHA -. "派工帶 url+sha256" .-> AGENT
    classDef ext fill:#FBFCFC,stroke:#9AA8AA,stroke-dasharray:3 2
    classDef open fill:#FDECEC,stroke:#C0392B
    class ADM,URL,INTRA,AGENT ext
    class REG,CINC open
C19 — api 到規則包網址。客戶管理員填網址建基準 → 背景抽取:safe_http_fetch 五道防線(只准 https、解析後每顆 IP 不得落私有段、用檢查過的 IP 連線、邊收邊算 50MB 上限、重導向 ≤3 跳每跳重驗)→ 算 sha256 → 共用解析層(三道防炸彈上限、擋 ERB 樣板)→ cinc-auditor。紅色是這條線的兩個特性入口:網址由人填、下載回來的內容會被當程式讀。
§2

二、這條線怎麼連

項目 現況
誰 → 誰 guidant-api(抽取在背景執行緒,不握 DB 連線)→ 客戶管理員填的網址。沒有網域白名單(決策者裁示:新增來源就要改設定,維護成本大於收益)
協定/埠 只准 https(ALLOWED_SCHEMES = ("https",),登記與下載兩處都用同一份白名單);埠預設 443、可自填。http:// 登記時回 DETECTION_TOOLS_400013;升級前既有的 http 基準不強制失效,抽取時寫醒目警告
五道防線 ① scheme 白名單;② 解析後的每一顆 IP 都不得落私有/保留段(ipaddress 內建旗標:RFC1918、loopback、link-local 含雲端 metadata 169.254.169.254、multicast、保留、IPv4-mapped IPv6 先攤回 v4);③ IP 釘住——用檢查過的那顆 IP 連線、Host 與 SNI 另帶原 hostname,憑證驗證對原 hostname 生效(防 DNS rebinding);④ 邊收邊算、超過 50MB 即斷(DETECTION_PROFILE_MAX_MB);⑤ 重導向 ≤3 跳、每跳重跑前四道
逾時 下載 60 秒;清單頁的「內容變了沒」探測(HEAD,零 body)5 秒,走同一套防線
下載後 算 sha256 落庫,隨派工 build_payload() 下發給代理程式對帳;交共用解析層 _read_entries()——三道防炸彈上限(檔數 10,000/解開 500MB/膨脹 200 倍,archive_limits.py);_repack_flat() 交 cinc-auditor 前讀 inspec.yml,含 <% 拒收
錯誤回顯 所有失敗收斂成 SafeFetchError +自己寫的中文訊息,httpx 的原始錯誤(含內部位址)只進 log——因為抽取失敗訊息會回顯到前端,是一條資訊回傳通道
誰能建 能力點 detection-profile.*(軸④,is_platform=false,租戶管理員能建自己的基準);公版要平台管理員(service 層 _guard_system_writable);另有軸⑥授權守門 require_license("detection-profile")
何時存在 url 型 profile 建立/改版/手動重新解析時;清單頁開啟時另發一次 HEAD

依據:套件 jedi_detection/common/safe_http_fetch.py(模組 docstring 五道防線、_validate_and_pin()、_is_blocked_ip()、_read_with_limit()、DEFAULT_TIMEOUT_SEC/DEFAULT_MAX_REDIRECTS)、app/service/detection_profile_extraction_service.py(_download()、source_sha256、session 之外執行)、app/service/detection_profile_service.py:960(登記只收 https)、common/archive_limits.py、common/profile_extractor/inspec.py(_ERB_MARKER)、common/detection_profile_ref.py(build_payload 帶 sha256)、api/routes/detection_profile_route.py(守門三層);主專案 config/config.py:200-206(四個上限)。

DFD 對照:DFD 寫「HTTPS(只收 https,CM-2057);第一次抽取、算 sha256」與實況相符。補充 DFD 沒寫的:清單頁開啟時另有一次 HEAD 探測(也走這條線、同一套防線);私有段 IP 與 DNS rebinding 的防護在這一支。

§3

三、哪些功能會走這條線

群 功能 對威脅的意義
① 建立/改版 url 型基準 POST /detection-tool-profiles(網址型走 JSON)、/{uid}/new-version、PATCH /detection-tool-profile-versions/{uid}/source → 背景下載+解析 觸發者是客戶管理員;填的網址決定系統去連誰。下載回來的東西會進外部解析程式
② 手動重新解析 POST /detection-tool-profile-versions/{uid}/extraction 同一條下載路徑再跑一次;併行上限 4(DETECTION_PROFILE_EXTRACTION_MAX_CONCURRENT,M03-10 修後)
③ 清單頁探測 list_controls() 對 url 型來源發 HEAD 比對 ETag,一樣就直接顯示現有清單 零 body 但同樣是「替人打網址」;SSRF 風險與 GET 相同,少一道防線就是換動詞繞過
④ 派工下發 build_payload() 把 url+sha256 交給代理程式,代理程式走 C24 自己下載執行 這條線算的 sha256 是代理程式對帳的唯一依據;代理端實際比對尚未做(CM-2402)
§4

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

掃描在這條線上抓到 3 條問題(M03-3、M03-1、M03-5),歸納起來是三種攻擊手法。前提:攻擊者有一個能建檢測基準的帳號(客戶管理員);T3 的第二階段攻擊者站在客戶機房的網路路徑上。

# 風險 威脅 駭客怎麼打 得手什麼 STRIDE 實例
T1 🟡 中 網址那條路繞過上傳那條路的上限 ① 有建立檢測基準權限的帳號;② 在自己的網站放一個 45MB、解開數十 GB 的壓縮檔;③ 新增檢測基準時選「網址」、填這個網址;④ 系統下載回來直接解開,上傳那條路的三道檢查完全沒跑(修前);⑤ 伺服器記憶體被吃光,所有客戶一起停擺 讓整個產品對所有客戶停擺 D 讓服務停擺 M03-3 網址型規則包完全繞過壓縮檔的三道上限 🟡
T2 🟠 高 下載回來的設定檔被外部程式當程式碼執行 ① 任一客戶的管理員;② 做一包檢測規則,在 inspec.yml 寫一段 Ruby 樣板(<% … %>);③ 放在網址上讓系統下載(或直接上傳),結構全部正常;④ 系統排解析,cinc-auditor 一讀設定檔就以後端程序身分把那段程式跑起來;⑤ 讀出系統密鑰、把資料庫身分提權成超級管理員,改寫全平台客戶的資料 整台後端主機的權限;改寫全平台客戶資料 E 權限提升、T 竄改資料 M03-1 客戶上傳的檢測規則包會被外部工具當成程式碼執行 🟠
T3 🟡 中 網址不加密、沒指紋,代理程式拿到什麼就跑什麼 ① 客戶管理員用一個 http 網址建了檢測基準(修前收 http);② 攻擊者站在代理程式下載的網路路徑上;③ 代理程式領到掃描工作去下載時,回一包換過的規則;④ 代理程式手上有客戶主機帳密,直接執行那包規則,掃描報告也一起造假;⑤ 因為不加密,連破解都不用 在客戶內網的代理程式上執行任意程式碼;偽造掃描結果 T 竄改資料、E 權限提升 M03-5 網址型規則包放行不加密連線、也不記指紋 🟡

三種手法的共同點:這條線把「外部網址的內容」變成「系統信任的規則」——T1 是內容太大、T2 是內容有程式、T3 是內容被換。三者都是「內容不可信」的不同面向,而「網址由人填」只是讓攻擊者更容易把內容送進來。有一種 SSRF 手法(填內網或雲端 metadata 位址讓系統替你打)在掃描範圍內沒有實例,因為五道防線在掃描前就已到位——列在第五段 D4。

§5

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

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

# 防線 擋哪種威脅 目前做到哪
D1 資源上限放在兩種來源唯一共用的那一層,不在各入口各補一次。檔數、解開總量、膨脹倍數三道;下載本身另有 50MB 邊收邊算 T1 ✅ archive_limits.py 接進 _read_entries(),上傳與網址兩路、未來第三路都天然涵蓋;刻意不在網址分支另補(補第二條路下次加來源照樣漏)。驗證:隨包規則包重打包前後一致;2MB 宣告 500MB 被擋。另:併行解析上限 4(M03-10)
D2 交給外部程式之前看內容,不只驗結構。inspec.yml 含 ERB 樣板記號就拒收;擋點在兩路共用層 T2 ✅ _repack_flat() 讀 inspec.yml,含 <% 拒收並回明確訊息;開工前實查隨包 13 筆基準與 8 支內建規則包都沒用樣板。⚠️ 已知打折:只看 inspec.yml 一個檔,若 cinc-auditor 對 controls/*.rb 也做樣板渲染則擋不住;長期正解「把外部程式關進沙箱」未做——cinc-auditor 仍以後端程序身分跑
D3 網址只收 https,第一次抽取記指紋,派工帶給代理程式對帳 T3 ✅ 登記白名單取 safe_http_fetch.ALLOWED_SCHEMES 不另立一份,http 回 DETECTION_TOOLS_400013;既有 http 基準不強制失效但抽取時寫警告;source_sha256 落庫、build_payload() 下發。⚠️ 代理程式端實際比對指紋尚未做(CM-2402 另開小卡)——目前指紋只是「記了」,沒有人在對
D4 ◇ SSRF 五道防線:scheme 白名單、每顆 IP 查禁區、IP 釘住、大小上限、重導向每跳重驗;HEAD 探測走同一套 填內網或雲端 metadata 位址讓系統替你打(掃描範圍內無實例) ✅ safe_http_fetch.py 全部到位,模組 docstring 逐條說明「少了會怎樣」;DNS rebinding 以 IP 釘住擋、SNI 帶原 hostname 憑證驗證仍生效(已實測改 SNI 會 Hostname mismatch);多顆 A record 全部都要過;錯誤收斂成中文訊息不回顯 httpx 原文。這是全 codebase 唯一一支完整的 SSRF 防護,C13 D6、C15 的網址欄都還沒用它
D5 ◇ 外部程式關進沙箱。cinc-auditor 以獨立容器、無網路、唯讀檔案系統、最小權限跑,不以後端程序身分 T2 的根治(D2 是治標) ⚠️ 沒有。證據拆檔那條已有 guidant-sandbox 容器(C08),cinc-auditor 解析沒套同一個模式。M03-1 修正時明寫「長期正解未做」
D6 ◇ url 型基準的內容會漂,清單頁要說得出「這是哪一刻的快照」 使用者看到的清單與代理程式實際跑的不一致 ✅ 清單頁 HEAD 探測 ETag,變了就提示重新解析;sha256 記的是「平台解析的是哪一份」。⚠️ 探測只比 ETag/Last-Modified,對方伺服器不給這兩個標頭就比不出來

最便宜的一步:D3 的代理端比對——sha256 已經隨派工送到代理程式手上,差的是代理程式下載後算一次、不同就拒跑;這是 CM-2402 的範圍。D5 最有價值但是一個獨立的基礎建設工作。

§6

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

  • controls/*.rb 的樣板渲染(D2):M03-1 修正自承打折——只擋 inspec.yml。該實際做一包只有 controls/x.rb 含 <% 的規則包餵 cinc-auditor,看它跑不跑。
  • 代理程式端的指紋比對(D3):CM-2402 的範圍,掃描時尚未做;做完前 T3 的「換內容」面仍開(只是 https 讓換起來難)。
  • cinc-auditor 沙箱化(D5):T2 的根治;目前解析以後端程序身分跑、能讀 .env。
  • 其他吃網址的欄位是否共用這支防線(D4):safe_http_fetch 是全 codebase 唯一完整的 SSRF 防護;AI 服務位址(C13)、GitLab 網址(C15)、Drive webhook 公開網址(GOOGLE_DRIVE_APP_CONFIG.public_base_url)都是人填的網址、都沒走它。
  • 既有 http 基準的實際數量:升級前存的 http 基準不強制失效,STG/POC 有幾筆沒盤。

依據:STRIDE 六頁信任邊界連線標記(CM-2403/2404 驗收後版本)、套件 jedi-detection(common/safe_http_fetch.py、common/archive_limits.py、common/profile_extractor/inspec.py、common/detection_profile_ref.py、app/service/detection_profile_extraction_service.py、app/service/detection_profile_service.py、api/routes/detection_profile_route.py)、主專案 config/config.py、DFD Level 0。M03-1 同時標 C02(上傳型),M03-5 同時標 C24(代理程式下載),本頁各歸一種手法。