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

C11 api → 客戶帳號目錄

LDAP/AD,依設定

登入頁「用公司帳號登入」走的那條線——使用者輸入公司帳號密碼,系統拿去向客戶自己的員工帳號目錄(Active Directory 或 OpenLDAP)核對。這條線上流的是每一位員工輸入的密碼與客戶的目錄服務帳號。這一頁回答:這條線怎麼連、哪些功能走它、它帶來什麼威脅、駭客怎麼打、我們要怎麼防、目前做到哪。

依設定 員工密碼經過 威脅 3 種 掃描命中 1 條

這一頁怎麼來的:DFD Level 0 把產品運作時的連線編成 C01~C25;STRIDE 六頁每一條問題都標了發生在哪幾條線。這裡以連線為單位整理。命中 1 條,但那一條在掃描時是三個洞合併寫的(匿名能過、帳號字串能騙查詢、加密不認人),本頁依「駭客怎麼打」拆回三種手法。個別問題修了沒不在這頁講。

§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/>(任何人,不需帳號)"])
    ADM(["👤 客戶管理員<br/>(總部層級)"])
    subgraph MAIN["🏠 主系統"]
        direction TB
        CFG[("LDAP 設定<br/>全系統一份<br/>位址・埠・加密・驗憑證<br/>服務帳號密碼")]
        API["guidant-api<br/>① 用服務帳號查人<br/>② 用他的身分+密碼再登入一次"]
        CFG --> API
    end
    LDAP(["🗂 客戶帳號目錄<br/>AD/OpenLDAP<br/>(位址由設定頁填)"])
    MITM(["🕵 站在路上的人"])
    ADM -- "C02 · 改設定/測試連線" --> CFG
    U -- "C02 · 帳號+密碼" --> API
    API == "C11 · LDAP/LDAPS/STARTTLS<br/>服務帳密+員工帳密+查詢" ==> LDAP
    MITM -. "不驗憑證時可假扮" .-> LDAP
    classDef ext fill:#FBFCFC,stroke:#9AA8AA,stroke-dasharray:3 2
    classDef open fill:#FDECEC,stroke:#C0392B
    class U,ADM,LDAP,MITM ext
    class CFG open
C11 — api 到客戶帳號目錄。登入頁的帳號密碼經 C02 進 api,api 先用服務帳號(或匿名)查這個人是誰,再用「他的身分+他輸入的密碼」真正登入目錄一次。紅色是三個被打過的點:目錄位址由客戶管理員填、查詢字串由使用者的帳號欄組成、對方憑證預設不驗。
§2

二、這條線怎麼連

項目 現況
誰 → 誰 guidant-api → 客戶自己的帳號目錄伺服器(AD 或 OpenLDAP)。位址、埠、Base DN、服務帳號密碼全由客戶在「LDAP 設定」頁填
協定/埠 三選一,由設定頁「加密」下拉決定:無(ldap://,明文,預設埠 389)/SSL(ldaps://,一連上就加密)/STARTTLS(先明文連上再升級)。下拉預設是「無」(encryption: "")
傳什麼 兩段:① 用設定的服務帳號(沒設就匿名)向目錄查這個人的完整身分(DN、信箱、顯示名);② 用查到的 DN +使用者輸入的密碼真正向目錄登入一次——這次成功才算密碼對。AD 模式走 NTLM,一步完成
怎麼驗對方身分 選了 SSL 或 STARTTLS 時看「驗證伺服器憑證」開關:勾了走 CERT_REQUIRED +可貼信任憑證;沒勾走 CERT_NONE(程式會記一行警告)。後端 DTO 預設是驗(LDAP_VERIFY_CERT = True),但前端新建設定預設不勾(verify_cert: false,CM-2239 決策者裁定),升級前舊設定缺鍵則維持後端預設
查詢字串 使用者輸入的帳號先經 escape_filter_chars 跳脫再組成 (cn=…)/(sAMAccountName=…);AD 模式先拆網域再跳脫
誰能改目的地 寫入要總部層級(COMPANY_WIDE_CONFIG_GROUPS 含 THIRD_PARTY_LOGIN);設定全系統一份存 ROOT。「測試連線」(POST /ldap/connect-test)用的是畫面上那份設定
何時存在 依設定。沒設位址時登入頁選「公司帳號登入」會回 LOGIN_LDAP_SERVER_NOT_CONFIGURED

依據:套件 jedi_iam/infra/adapter/authenticate_adapter/ldap/ldap_adapter.py(_get_ldap_server()、connect()、_verify_open_ldap_user()、兩處 escape_filter_chars)、jedi_iam/app/dto/login_config.py(LDAP_VERIFY_CERT 預設 True、LDAP_PORT 預設 389)、jedi_iam/app/service/login_config_assembler.py(缺鍵不覆寫)、主專案 common/constant/company_wide_config.py、前端 src/views/ldap-config/LdapConfigForm.vue(加密下拉預設「無」、verify_cert: false)。

DFD 對照:DFD 寫「LDAPS :636」。實查是三選一且預設「無」(明文 :389),LDAPS 只是其中一個選項;STG 設成 636 是那台的設定不是產品預設。已回寫本卡。

§3

三、哪些功能會走這條線

群 功能 對威脅的意義
① 登入 登入頁「用公司帳號登入」(POST /login,authenticate_type 為 LDAP)→ 查人+驗密碼 觸發者是網路上任何人(登入頁不需帳號);每一次登入都把一位員工的密碼送上這條線
② 測試連線 LDAP 設定頁「測試連線」(POST /ldap/connect-test)→ 用畫面上的設定連一次 帶的是服務帳號密碼;目的地由操作者當場指定

沒有第三群:系統不做目錄同步、不定期拉人員清單,只在登入當下查一個人。

§4

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

掃描在這條線上抓到 1 條問題(M13-2),它在掃描時是三個洞合併寫的,依「駭客怎麼打」拆成三種攻擊手法。三種的實例都是同一條。

# 風險 威脅 駭客怎麼打 得手什麼 STRIDE 實例
T1 🟠 高 只查人、不驗密碼就放行 ① 任何人打開登入頁,選「公司帳號登入」;② 帳號填某位員工的名字、密碼隨便填;③ 系統用服務帳號(或匿名)向目錄查「這個人存不存在」,查到就當登入成功——使用者輸入的密碼從頭到尾沒被目錄比對過;④ 以該員工身分進系統 不用密碼就以任意員工身分登入 S 冒充身分、E 權限提升 M13-2 公司帳號登入:匿名能過、帳號字串能騙查詢、加密連線不認人 🟠
T2 🟠 高 帳號欄塞萬用字元改寫查詢 ① 任何人打開登入頁;② 帳號欄填 * 或 *)(… 這類目錄查詢的特殊字元;③ 查詢條件 (cn=<帳號>) 被改寫成「隨便一個人」或「所有人」;④ 系統拿第一筆回來的人當登入者 以查詢結果裡的任何人身分登入;或探出目錄裡有哪些帳號 S 冒充身分、T 竄改資料 同上 🟠
T3 🟠 高 站在路上假扮目錄伺服器 ① 攻擊者站在我們與目錄伺服器之間;② 假扮成目錄伺服器,拿一張自己簽的憑證;③ 系統沒勾「驗證憑證」(或根本選了「無加密」),把服務帳號密碼與每位登入員工輸入的密碼送過來;④ 他也可以回一份假的查詢結果,讓系統以為任何人登入成功 客戶的目錄服務帳號密碼、每位員工的密碼;或冒充任何人登入 I 資料外洩、S 冒充身分 同上 🟠

三種手法的共同點:這條線由不需帳號的人觸發(登入頁),而且每一次都載著一位員工的密碼——所以它的錯誤直接等於「帳號接管」,不是資料外洩那種等級。T1、T2 是「把查詢當驗證」與「把輸入當查詢語法」,T3 是「目的地由人填、對方身分不驗」(與 C10 郵件那條同一個病)。

§5

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

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

# 防線 擋哪種威脅 目前做到哪
D1 兩段式驗證:查人與驗密碼分開,密碼必須真的向目錄登入一次。查人用服務帳號;驗密碼用「查到的 DN + 使用者輸入的密碼」再 bind 一次,失敗才算密碼錯。兩段缺一不可 T1 ✅ _verify_open_ldap_user() 兩段式;connect() 有帶 username 就一定拿它 bind,程式註解明寫「只做①不做②等同從不驗密碼」。AD 模式走 NTLM 一步到位。驗證:DEV 真實 AD 伺服器手測三態
D2 使用者輸入進查詢字串前一律跳脫。*、括號、反斜線等目錄查詢的特殊字元不得原樣進 filter T2 ✅ 兩處組 filter 都用 ldap3 內建 escape_filter_chars;AD 模式先拆網域再跳脫(順序不能反)。⚠️ 跳脫寫在 adapter 兩個方法裡,新加一種查詢(例如日後做目錄同步)要記得
D3 加密連線必須驗對方身分。選了 SSL/STARTTLS 就要 CERT_REQUIRED;自簽憑證貼信任憑證,不是關掉驗證 T3 ✅ 機制有:verify_cert + ca_cert_pem 兩個欄位,勾了走 Tls(validate=CERT_REQUIRED);測試連線遇憑證錯回專屬錯誤碼 LOGIN_LDAP_CERT_VERIFY_FAILED。⚠️ 前端新建預設不勾(CM-2239),後端 DTO 預設雖是驗、但前端送來的是明確的 false,所以新客戶接上去就是不驗的狀態;設定頁沒有提示
D4 ◇ 不給「無加密」選項,或至少預設加密。員工密碼走明文 LDAP :389 等於同網段任何人都能側錄 T3 的更壞版本(連假扮都不用,直接看) ⚠️ 沒有。加密下拉三選一且預設「無」;選「無」時整條線明文,服務帳密與每位員工密碼都是明文封包。程式只在 CERT_NONE 時記警告,選「無」時什麼都不記
D5 ◇ 登入失敗次數與鎖定涵蓋 LDAP 路徑。本地帳號有鎖定(LOGIN_MAX_LOCK_COUNT/LOGIN_USER_LOCK_TIME),LDAP 登入的失敗也要計入,否則拿登入頁對客戶目錄猜密碼沒上限 用登入頁對客戶目錄做密碼猜測 ⚠️ 沒有。失敗計數寫在 login_domain_service.is_password_correct(),只有本地密碼 adapter 走到;LDAP adapter 第二段 bind 失敗直接拋 401,不碰 login_failed_count。即使客戶目錄自己有鎖定政策,攻擊者也是借我們的登入頁去觸發它(可能反過來把員工帳號鎖死)

最便宜的一步:D4——加密下拉預設改成 STARTTLS、「無」移到最後並加一行「僅限測試環境」;D3 的預設改回勾。這兩個都是前端一行預設值,但決策者 CM-2239 已裁定過預設不勾,改回要重新拍板。

§6

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

  • LDAP 路徑的失敗鎖定(D5):掃描只抓到驗證邏輯的洞,沒看「猜密碼有沒有上限」。本地帳號有、LDAP 沒有,這是本頁開檔新發現,不在 168 條裡。
  • 「無加密」被選用的現場比例:STG 設 636,但出貨預設是「無」。該在安裝文件或設定頁明寫建議值。
  • 服務帳號密碼明文落庫:THIRD_PARTY_LOGIN 的 secret 與 SMTP 一樣讀取有遮、落庫明文(見 C10 D5)。
  • 測試連線的目的地:POST /ldap/connect-test 帶畫面上的設定連任何主機,與郵件的 M16-1 同形;守門是總部層級,沒有「主機改了要重填密碼」那道。

依據:STRIDE 六頁信任邊界連線標記(CM-2403/2404 驗收後版本)、套件 jedi_iam/infra/adapter/authenticate_adapter/ldap/ldap_adapter.py、jedi_iam/app/dto/login_config.py、jedi_iam/app/service/login_config_assembler.py、jedi_iam/api/routing.py(/ldap/connect-test)、主專案 common/constant/company_wide_config.py、前端 LdapConfigForm.vue、DFD Level 0。