提交 #1035490: Chat2DB Chat2DB community server (chat2db-community) audited at commit `2905d33a2` (git describe `v5.3.6-109-g2905d33a2`); official source build; the service passes the matched table name into the pinned `com.baomidou:mybatis-plus-generator:3.5.3.1` path-信息

标题Chat2DB Chat2DB community server (chat2db-community) audited at commit `2905d33a2` (git describe `v5.3.6-109-g2905d33a2`); official source build; the service passes the matched table name into the pinned `com.baomidou:mybatis-plus-generator:3.5.3.1` path-
描述Class-generator output files escape the declared exportPath through a traversal-named table in Chat2DB Vendor: Chat2DB Affected versions: audited at commit `2905d33a2` (git describe `v5.3.6-109-g2905d33a2`); official source build; the service passes the matched table name into the pinned `com.baomidou:mybatis-plus-generator:x.x.x.x` path-info/name-strategy flow at this commit Vulnerability class: Path traversal leading to file write outside a declared boundary (CWE-22) Preconditions: Anonymous in a headless/server deployment (`/api/**` unauthenticated; every Chat2DB request in the proof succeeded with no credentials). The caller must control a datasource of a type that accepts `../` inside quoted identifiers (PostgreSQL used in the proof), creatable anonymously via `POST /api/connection/datasource/create`; that datasource must contain a table literally named with the traversal segment (the generator emits files only for metadata-matched tables); and, for full file emission, the escape-side package directories (`com/my/entity`, `com/my/mapper`) must exist in the writable escape location — an attacker-controlled location in the practical attack. Without them the first unattended request still creates the escape directory tree outside `exportPath` but the file writes fail; with them present the request succeeds and all files land outside `exportPath`. An anonymous caller of a headless Chat2DB deployment registers their own PostgreSQL datasource containing a quoted table name with `../` segments, and the class generator writes its entity/mapper/XML files outside the `exportPath` declared in the request — the traversal string survives into the output file names and the generated mapper namespace unescaped. Data flow: JSON `tableName` at `POST /api/rdb/table/generate/class` → `DbMybatisGenerateServiceImpl.generateClass` (`:48` escaped path into `pathInfo[OutputFile.xml]`, `:119` `globalConfig.outputDir`, `:125` `addInclude` of the matched table) → `mybatis-plus-generator` `PathInfoHandler` / `NamingStrategy.underlineToCamel` (splits only on `_`; dots and slashes survive) → file writes outside `exportPath`. ### Affected interfaces | Interface | |---| | POST /api/rdb/table/generate/class — JSON body `{"dataSourceId":<id>,"tableName":"../<name>","exportPath":"/tmp/out/jail"}`; anonymous in headless deployments | | POST /api/connection/datasource/create — enabling interface (attacker-supplied PostgreSQL); anonymous in headless deployments | Primary route detail: ``` POST /api/rdb/table/generate/class HTTP/1.1 Content-Type: application/json {"dataSourceId":1791449467217999,"tableName":"../c2db-pwn-f2","exportPath":"/tmp/c2db-f2-out/jail"} ``` Anonymous (headless deployment): no cookie, token or `Authorization` header is required. The datasource-existence guard is satisfied by the datasource the same caller just created. ### Root cause 1. `DbTableController.java:317-318` passes `tableName` verbatim to `DbMybatisGenerateServiceImpl.generateClass`; the service builds the output directory from it (`new File(exportPath, tableName)`) and, after the escape, hands the matched table name to the generator (`addInclude`/`table2Class`, `DbMybatisGenerateServiceImpl.java:48-59,113-133`). 2. `:48` reassigns `exportPath` to the escaped directory path; `:52` puts it as `pathInfo[OutputFile.xml]`; `:119` uses it as `globalConfig.outputDir`; `:120-124` set the package config (`parent=com.my`, `entity`, `mapper`, `xml(outputDir)`); `:125` `addInclude`; `:132` Freemarker engine. 3. Library internals on the pinned `mybatis-plus-generator:x.x.x.x` jar: `PathInfoHandler` computes output-file defaults as `joinPath(outputDir, packageInfo)` then overlays the application map; `NamingStrategy.underlineToCamel` splits only on `_` and upper-cases the next character — dots and slashes survive, so a matched table named `../c2db-pwn-f2` yields file names still containing the `../` prefix. Missing guard: a database table identifier is data that the generator turns into class names and paths; nothing validates it as a plain identifier, and nothing canonicalizes the computed output file paths and asserts containment inside the declared `exportPath`. Baseline: official source build at commit `2905d33a2`, headless server self-hosted on `127.0.0.1:18762`; attacker-side PostgreSQL 16 container on `127.0.0.1:18962` started by the reporter on the same host. No credentials were sent to Chat2DB anywhere in this proof. **1. Plant the traversal-named table (quoted identifier, legal PostgreSQL):** ```sql CREATE TABLE "../c2db-pwn-f2" (id int); CREATE TABLE orders (id int, name text); ``` **2. Register the attacker datasource anonymously** (`POST /api/connection/datasource/create`, `url:"jdbc:postgresql://127.0.0.1:18962/postgres"`) → `{"success":true,...,"data":{"id":1791449467217999,...}}`. **3. Positive — generate with the traversal-named table:** ```sh $ curl -X POST 'http://127.0.0.1:18762/api/rdb/table/generate/class' -H 'Content-Type: application/json' \ -d '{"dataSourceId":1791449467217999,"tableName":"../c2db-pwn-f2","exportPath":"/tmp/c2db-f2-out/jail"}' {"success":true,"errorCode":null,"errorMessage":null,...} # HTTP 200 ``` The declared `exportPath` stayed empty and every generated artifact landed outside it: ``` /tmp/c2db-f2-out/jail <- declared exportPath, empty /tmp/c2db-f2-out/c2db-pwn-f2/com/my/c2db-pwn-f2DO.java (404B, entity) /tmp/c2db-f2-out/c2db-pwn-f2/com/my/c2db-pwn-f2Mapper.java (284B, mapper) /tmp/c2db-f2-out/c2db-pwn-f2Mapper.xml (214B, xml; one level further out) ``` The mapper XML contains `<mapper namespace="com.my.mapper.../c2db-pwn-f2Mapper">` — the original `../` segments unescaped. **4. Negative control — ordinary table name keeps the boundary:** same datasource, `tableName:"orders"`, `exportPath:"/tmp/c2db-f2-neg/jail2"` → `success:true`, all artifacts strictly inside the declared root (`.../jail2/orders/com/my/entity/OrdersDO.java` etc.); no escape anywhere under `/tmp`. The single variable between positive and negative is the table name. **5. Stated conditions, measured at runtime:** with a two-level `../../` name and a bare `/tmp` `exportPath`, the escape resolves to a non-writable root location for a non-root server user (generator reports `Unable to create directory`, no files land) — the depth must target a writable location; when the escape-side package directories do not exist, the first unattended request fails at `FileNotFoundException` on the entity write but has already created the escape directory tree outside `exportPath`; pre-creating `com/my/entity|mapper` in the attacker-writable escape location turns the repeat into the full `success:true` file-level escape. All testing was performed against a self-hosted instance on the reporter's own host. No third-party system, production instance or customer data was involved. - **Vendor contact**: GitHub Private Vulnerability Reporting - chat2db/Chat2DB - **Status**: vendor notified 2026-10-09 through GitHub Private Vulnerability Reporting; private report <<GHSA_LINK>>; not publicly disclosed; no CVE assigned - **Duplicate check**: public advisories (NVD/OSV/GitHub) and the project issue tracker were searched before reporting; please tell us if this duplicates something already tracked internally.
来源⚠️ https://github.com/chat2db/Chat2DB
用户
 lucasg2g (UID 84737)
提交2026-10-15 07時34分 (24 日前)
管理2026-11-08 15時18分 (24 days later)
状态已接受
VulDB条目432889 [OtterMind Chat2DB 直到 5.3.8 Class Generator DbMybatisGenerateServiceImpl.java DbMybatisGenerateServiceImpl.generateClass tableName 目录遍历]
积分20

Do you need the next level of professionalism?

Upgrade your account now!