⚠️ 歷史紀錄:本報告描述的問題已於 2026-11-07 全部修正並合回 Agent 主線(修正卡 CM-3559~3564、3573~3576、3584,驗收紀錄見母卡 CM-3507)。文中的檔名與行號是掃描當下(commit
bab2aa3)的版本,修正後已變動,請勿拿來定位現行程式。
沒有急件,最嚴重是中風險。 工具找到 3 個問題,2 中 1 低,都經三個獨立檢查員投票確認。其中 F2(DNS 查兩次)就是已登記的 #233,從 connector 這端再確認一次,不當新問題重報。真正新的是 F1(中):Agent 連 ZAP 預設走明文 http,就算改成 https,ZAP 官方套件也寫死「不驗憑證」。所以 ZAP 的 API key 和登入後掃描用的「目標網站帳密」,在網路上都能被偷聽。另外 F3(低):限制爬蟲範圍的規則沒有在主機名結尾封口,爬蟲可能爬到名字很像的別台主機。人工補查另外發現兩件值得記的事(見「重點看什麼」逐條查證):使用者可以把逾時改到沒有上限;ZAP 報告原樣上傳當證據,沒有遮蔽。
guidant_agent/scan/adapters/connectors/zap.py(1 檔,642 行)。這支程式叫客戶自備的 ZAP daemon(常駐在背景、等 API 呼叫的 ZAP 服務),對一個網址做被動或主動掃描。主動掃描會送真的攻擊測試流量(payload)。它也可以帶表單登入帳密,做「登入後掃描」。
為了查證,翻了範圍外的 scan/domain/local_run.py、scan/app/local_runs.py、sync/app/allowed_targets.py,以及已安裝的 zapv2 套件。這些檔只為查證而讀,本身不當作發現。
| # | 這是什麼問題 | 出事會怎樣 | 要先有什麼才打得到 | 在哪裡 | 怎麼修 | 嚴重度 |
|---|---|---|---|---|---|---|
| F1 | Agent→ZAP 預設明文 http;https 也不驗憑證(zapv2 寫死 verify=False) |
偷聽者拿到 ZAP API key(等於整台 ZAP 的遙控器)與目標網站登入帳密 | 能在 Agent 與 ZAP 之間的網路偷聽或做中間人 | zap.py:146、:160、:370-372 |
非本機(loopback)就拒收 http;https 自己帶驗證 CA 的連線,不靠 zapv2 預設 | 中 |
| F2 | 清冊在 Agent 查 DNS,ZAP 自己再查一次(DNS 換綁)——已登記 #233,不重報 | 主動掃描打到清冊外的內網主機 | 能派 ZAP 任務+掌控一個 DNS 網域 | zap.py:460、:482 |
同 #233 | 中(已登記) |
| F3 | 爬蟲範圍規則 re.escape(target_url)+".*" 沒有在主機名結尾封口 |
http://10.0.0.5 的規則也會比中 10.0.0.50、10.0.0.5.attacker.tld、別的埠 |
目標網站頁面上有一條這種長相的連結 | zap.py:320 |
規則在 scheme://host[:port] 後面接 ([/?#].*)?$ |
低 |
白話: ZAP 的位址(base_url)如果沒寫 http:// 或 https://,程式會自己補上 http://(:146)。每一次 API 呼叫都帶著 X-ZAP-API-Key 這個標頭(header)。登入後掃描時,目標網站的帳密是放在網址的查詢參數裡傳給 ZAP 的(:370-372,users/action/setAuthenticationCredentials)。就算位址寫成 https,ZAP 官方 Python 套件 zapv2 內部也寫死 verify=False,也就是不檢查對方憑證真假。這直接違反本 repo「不得關閉 TLS 驗證」的規則(SR-03);只是違規藏在第三方套件裡,守衛 test_no_cert_none 抓不到。
出事會怎樣: 同網段的人用 ARP 偽造(冒充別台機器的網路位址)攔下流量,就拿到 API key。拿到 key 可以叫 ZAP 掃任何主機、載入腳本,可能等於在 ZAP 主機上執行程式。他也拿到目標網站的帳號密碼。
要先有什麼: 能看到或攔下 Agent 與 ZAP 之間的流量;要偷帳密的話,還要剛好有人跑登入後掃描。
在哪裡: guidant_agent/scan/adapters/connectors/zap.py:146(補 http)、:160(ZAPv2(apikey=..., proxies=...))、:370-372(帳密送出)。
怎麼修: 位址不是本機時,就拒收 http(或至少大聲警告)。https 的情況,改成自己建一個會驗證憑證的連線(可以設定 ZAP 的根憑證 CA),不靠 zapv2 預設。
已登記於主系統總表 #233(B6a F2)。這次從 connector 這端確認:target_url 和 login_url 都是用主機名原樣交給 ZAP(:460 ajaxSpider、:482 ascan、form 登入的 loginUrl),沒有先釘住查到的 IP。沒有新面向,不重報。
白話: _context_regex 把目標網址轉義後,在後面接 .*,代表「後面接什麼都算」。目標是 http://10.0.0.5 時,這條規則也會比中 http://10.0.0.51/、http://10.0.0.5:8080/、http://10.0.0.5.attacker.tld/。
出事會怎樣: 目標網站上如果有一條這種連結(例如使用者能自己貼內容的頁面),ZAP 的爬蟲會把它當成範圍內的網址:爬進去、點按鈕、送表單,流量就打到清冊外的主機。主動掃描的 payload 是從目標網站的節點開始打,所以影響有限。
要先有什麼: 目標網站頁面上出現一條開頭跟 target_url 一樣的連結。
在哪裡: guidant_agent/scan/adapters/connectors/zap.py:313-320。
怎麼修: 從解析出來的 scheme/host/port 組規則,主機名後面只准接 /、?、# 或網址結尾。
| 重點 | 人工查過 |
|---|---|
:340-343 登入帳密從 params 讀,會不會落地或進 log |
落地:不成立。 local_runs._insert 存 params 前,會先用 public_params 濾掉欄名含 pass/username/token 等字的欄位(scan/domain/local_run.py:11-17),所以 login_password、login_username 不會寫進 local_runs.params。log:不成立。 :379-381 只記錄「帳號已提供=True/False」。傳輸:成立,即 F1。 主系統→Agent 這段是 8443 mTLS 的派工 payload;本棒沒有深追,屬 B 系列已掃的範圍。 |
:153-160 Agent→ZAP 是 http 還是 https、api_key 會不會進 access log |
成立,即 F1。 預設是 http;https 也不驗憑證。zapv2 0.6.0 的 key 放在標頭 X-ZAP-API-Key,不放查詢參數,所以一般 access log 不會記到 key。但登入帳密是放在查詢參數(:371-372),會進 ZAP 或中間 proxy 的 access log(記錄每筆請求網址的檔),這一點補記進 F1。base_url 改位址沿用舊 key(#232):本檔照收 credentials,沒有另外處理,同 #232,不重報。 |
:179/:340 只擋起點,爬蟲會不會爬出去 |
部分成立,即 F3。 程式有建 context(ZAP 用來界定掃描範圍的設定),爬蟲受它限制,但規則沒封口,相似的主機名或 IP 會被當成範圍內。另外,login_url 會過清冊,但它沒有被要求跟 target_url 同一台主機;這一點沒有深追。 |
:170 active 誰能選、:187 timeout 上限 |
active: 本檔不檢權限,只要是合法值就照跑;權限在呼叫端的 scope 控制(B5 已看過)。timeout:成立(新,低風險)。 :188 只做 int(override),沒有上限也沒有下限,使用者可以設成很多天,主動掃描就會長時間對目標送攻擊流量;設成負數會立刻逾時。工具沒報,建議在修正卡補一個上限。 |
logged_in_indicator 長度、帳密含 &/= 會不會拆欄位 |
拆欄位:不成立。 帳密和 loginUrl 都經過 quote(..., safe="") 編碼(:359、:371),&、= 會被轉成 %26、%3D。indicator 長度:成立但影響小。 沒有長度上限,正規表達式交給 ZAP 執行;ReDoS(惡意正規表達式拖垮 CPU)也只會發生在 ZAP 那邊。 |
| 報告抓回、上傳前有沒有遮、檔名拼法 | 沒遮:成立(設計面,記錄不升級)。 :597 拿到 core.htmlreport() 的原文,直接當證據上傳。ZAP 報告裡可能有目標網站回應的片段(cookie、表單值),Agent 端沒有遮蔽。這是證據保真與遮蔽之間的取捨,交給決策者判斷。檔名:不成立。 _build_filename(:633-642)只取 netloc,非安全字元一律換成 _,沒有路徑穿越。_task_uid 只用在 context 名稱的前 8 碼(:197-198),不進檔名。 |
| ZAP 錯誤原文回給使用者前有沒有過濾 | 部分成立(低)。 _probe_failure_message(:233-246)會把 base_url 和例外原文前 200 字回給使用者,可能露出 ZAP 的內網位址。不過看得到這段訊息的人本來就能在工具設定頁看到這個位址,所以影響很小。 |
failed)。3 個候選問題都由三個檢查員投票:F1、F2 是 3 比 0,F3 是 2 比 1。stamp 是 verified。全部都是讀程式碼得出的結論,沒有實際執行任何東西。public_params 的過濾規則。「重點」7 條逐條看過,另外補出 timeout 沒有上限、報告未遮兩件事。zapv2 內部的 verify=False 是採信檢查員讀套件原始碼的結果,我自己沒有再去打開 site-packages 核對。| 項目 | 值 |
|---|---|
| run ID | wf_c71a3350-ff3 |
| 掃描版本 | 3d5aa2afda96a6d7fe52b3c04db27f88346ed059 |
| 強度與範圍 | low,1 檔 |
| verification.status | verified(reason_kind 為 null) |
| candidates(候選問題) | 3(去重後 3) |
| panel_votes(檢查員投票) | 9,全部投出 |
| researchers_dispatched / returned(派出/回來的研究員) | 1 / 1 |
| 耗時 | 約 23 分鐘,10 個 agent,約 55 萬 token |
| 原始產物 | docs/security-scan/FR-177/runs/CLAUDE-SECURITY-20261106-032314/ |