---
title: C11 api → 客戶 LDAP／AD
eyebrow: Guidant AI 資安檢視總報告 · 連線清單
h1: C11 api → 客戶帳號目錄（LDAP／AD，依設定）
lede: 登入頁「用公司帳號登入」走的那條線——使用者輸入公司帳號密碼，系統拿去向客戶自己的員工帳號目錄（Active Directory 或 OpenLDAP）核對。這條線上流的是**每一位員工輸入的密碼**與客戶的目錄服務帳號。這一頁回答：這條線**怎麼連**、**哪些功能走它**、它帶來**什麼威脅、駭客怎麼打**、我們**要怎麼防、目前做到哪**。
chips:
  - { text: "依設定", kind: plain }
  - { text: "員工密碼經過", kind: warn }
  - { text: "威脅 3 種", kind: accent }
  - { text: "掃描命中 1 條", kind: plain }
---

> **這一頁怎麼來的**：[DFD Level 0](../DFD/dfd-level0.html#c11) 把產品運作時的連線編成 C01～C25；[STRIDE 六頁](../STRIDE/S-spoofing.html)每一條問題都標了發生在哪幾條線。這裡以**連線**為單位整理。命中 1 條，但那一條在掃描時是三個洞合併寫的（匿名能過、帳號字串能騙查詢、加密不認人），本頁依「駭客怎麼打」拆回三種手法。個別問題修了沒不在這頁講。

## 一、連線圖

```{.mermaid cap="C11 — api 到客戶帳號目錄。登入頁的帳號密碼經 C02 進 api，api 先用服務帳號（或匿名）查這個人是誰，再用「他的身分＋他輸入的密碼」真正登入目錄一次。紅色是三個被打過的點：目錄位址由客戶管理員填、查詢字串由使用者的帳號欄組成、對方憑證預設不驗。"}
%%{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
```

## 二、這條線怎麼連

| 項目 | 現況 |
|---|---|
| **誰 → 誰** | `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 是那台的設定不是產品預設。已回寫本卡。

## 三、哪些功能會走這條線

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

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

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

掃描在這條線上抓到 1 條問題（[M13-2](../STRIDE/I-information-disclosure.html#m13-2)），它在掃描時是三個洞合併寫的，依「駭客怎麼打」拆成**三種攻擊手法**。三種的實例都是同一條。

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

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

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

對應三種威脅與這條線的本質，防線分五條。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 已裁定過預設不勾，改回要重新拍板。

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

- **LDAP 路徑的失敗鎖定**（D5）：掃描只抓到驗證邏輯的洞，沒看「猜密碼有沒有上限」。本地帳號有、LDAP 沒有，這是本頁開檔新發現，不在 168 條裡。
- **「無加密」被選用的現場比例**：STG 設 636，但出貨預設是「無」。該在安裝文件或設定頁明寫建議值。
- **服務帳號密碼明文落庫**：`THIRD_PARTY_LOGIN` 的 `secret` 與 SMTP 一樣讀取有遮、落庫明文（見 [C10](C10-api-to-smtp.html) 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。*
