Pimcore: SQL Injection in Custom Reports via Malicious Report Configuration
The advisory, in context
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
Affected packages
Start with the recorded fix for your release branch. Check your installed version and test compatibility before upgrading. Fixing this advisory does not rule out other vulnerabilities in the release.
From 2026.1.0 before 2026.1.6
Fixed in 2026.1.6
6 explicitly affected versions
v2026.1.0, v2026.1.1, v2026.1.2, v2026.1.3, v2026.1.4, v2026.1.5
View affected-version evidence
{
"ranges": [
{
"events": [
{
"introduced": "2026.1.0"
},
{
"fixed": "2026.1.6"
}
],
"type": "ECOSYSTEM"
}
],
"versions": [
"v2026.1.0",
"v2026.1.1",
"v2026.1.2",
"v2026.1.3",
"v2026.1.4",
"v2026.1.5"
]
}From 12.0.0-RC1 before 12.3.10
Fixed in 12.3.10
29 explicitly affected versions
v12.0.0, v12.0.0-RC1, v12.0.0-RC2, v12.0.1, v12.0.2, v12.0.3, v12.0.4, v12.1.0, v12.1.1, v12.1.2, v12.1.3, v12.1.4, v12.1.5, v12.2.0, v12.2.1, v12.2.2, v12.2.3, v12.2.4, v12.3.0, v12.3.1, v12.3.1.1, v12.3.2, v12.3.3, v12.3.4, v12.3.5, v12.3.6, v12.3.7, v12.3.8, v12.3.9
View affected-version evidence
{
"ranges": [
{
"events": [
{
"introduced": "12.0.0-RC1"
},
{
"fixed": "12.3.10"
}
],
"type": "ECOSYSTEM"
}
],
"versions": [
"v12.0.0",
"v12.0.0-RC1",
"v12.0.0-RC2",
"v12.0.1",
"v12.0.2",
"v12.0.3",
"v12.0.4",
"v12.1.0",
"v12.1.1",
"v12.1.2",
"v12.1.3",
"v12.1.4",
"v12.1.5",
"v12.2.0",
"v12.2.1",
"v12.2.2",
"v12.2.3",
"v12.2.4",
"v12.3.0",
"v12.3.1",
"v12.3.1.1",
"v12.3.2",
"v12.3.3",
"v12.3.4",
"v12.3.5",
"v12.3.6",
"v12.3.7",
"v12.3.8",
"v12.3.9"
]
}Earlier releases before 11.5.19
Fixed in 11.5.19
296 explicitly affected versions
10.0.8, 2.2.0, 2.2.1, 2.2.2, 2.3.0, 3.0.0, 3.0.1, 3.0.2, 3.0.3, 3.0.4, 3.0.5, 3.0.6, 3.1.0, 3.1.1, 4.0.0, 4.0.1, 4.1.0, 4.1.1, 4.1.2, 4.1.3, 4.2.0, 4.3.0, 4.3.1, 4.4.0, 4.4.1, 4.4.2, 4.4.3, 4.5.0, 4.6.0, 4.6.1, 4.6.2, 4.6.3, 4.6.4, 4.6.5, v10.0.0, v10.0.0-BETA1, v10.0.0-BETA2, v10.0.0-BETA3, v10.0.0-BETA4, v10.0.1, v10.0.2, v10.0.3, v10.0.4, v10.0.5, v10.0.6, v10.0.7, v10.0.9, v10.1.0, v10.1.1, v10.1.2, v10.1.3, v10.1.4, v10.1.5, v10.2.0, v10.2.1, v10.2.10, v10.2.2, v10.2.3, v10.2.4, v10.2.5, v10.2.6, v10.2.7, v10.2.8, v10.2.9, v10.3.0, v10.3.1, v10.3.2, v10.3.3, v10.3.4, v10.3.5, v10.3.6, v10.3.7, v10.4.0, v10.4.1, v10.4.2, v10.4.3, v10.4.4, v10.4.5, v10.4.6, v10.5.0, v10.5.1, v10.5.10, v10.5.11, v10.5.12, v10.5.13, v10.5.14, v10.5.15, v10.5.16, v10.5.17, v10.5.18, v10.5.19, v10.5.2, v10.5.20, v10.5.21, v10.5.22, v10.5.23, v10.5.24, v10.5.25, v10.5.3, v10.5.4, v10.5.5, v10.5.6, v10.5.7, v10.5.8, v10.5.9, v10.6.0, v10.6.1, v10.6.2, v10.6.3, v10.6.4, v10.6.5, v10.6.6, v10.6.7, v10.6.8, v10.6.9, v11.0.0, v11.0.0-ALPHA1, v11.0.0-ALPHA2, v11.0.0-ALPHA3, v11.0.0-ALPHA4, v11.0.0-ALPHA5, v11.0.0-ALPHA6, v11.0.0-ALPHA7, v11.0.0-ALPHA8, v11.0.0-BETA1, v11.0.0-RC1, v11.0.0-RC2, v11.0.1, v11.0.10, v11.0.11, v11.0.12, v11.0.2, v11.0.3, v11.0.4, v11.0.5, v11.0.6, v11.0.7, v11.0.8, v11.0.9, v11.1.0, v11.1.0-RC1, v11.1.1, v11.1.2, v11.1.3, v11.1.4, v11.1.5, v11.1.6, v11.2.0, v11.2.1, v11.2.2, v11.2.3, v11.2.4, v11.2.5, v11.2.6, v11.2.7, v11.3.0, v11.3.0-RC1, v11.3.0-RC2, v11.3.1, v11.3.2, v11.3.3, v11.4.0, v11.4.0-RC1, v11.4.1, v11.4.2, v11.4.3, v11.4.4, v11.5.0, v11.5.0-RC1, v11.5.0-RC2, v11.5.1, v11.5.10, v11.5.11, v11.5.12, v11.5.13, v11.5.14, v11.5.14.1, v11.5.2, v11.5.3, v11.5.4, v11.5.5, v11.5.6, v11.5.7, v11.5.8, v11.5.9, v5.0.0, v5.0.0-RC, v5.0.1, v5.0.2, v5.0.3, v5.0.4, v5.1.0, v5.1.0-alpha, v5.1.1, v5.1.2, v5.1.3, v5.2.0, v5.2.1, v5.2.2, v5.2.3, v5.3.0, v5.3.1, v5.4.0, v5.4.1, v5.4.2, v5.4.3, v5.4.4, v5.5.0, v5.5.1, v5.5.2, v5.5.3, v5.5.4, v5.6.0, v5.6.1, v5.6.2, v5.6.3, v5.6.4, v5.6.5, v5.6.6, v5.7.0, v5.7.1, v5.7.2, v5.7.3, v5.8.0, v5.8.1, v5.8.2, v5.8.3, v5.8.4, v5.8.5, v5.8.6, v5.8.7, v5.8.8, v5.8.9, v6.0.0, v6.0.1, v6.0.2, v6.0.3, v6.0.4, v6.0.5, v6.1.0, v6.1.1, v6.1.2, v6.2.0, v6.2.1, v6.2.2, v6.2.3, v6.3.0, v6.3.1, v6.3.2, v6.3.3, v6.3.4, v6.3.5, v6.3.6, v6.4.0, v6.4.1, v6.4.2, v6.5.0, v6.5.1, v6.5.2, v6.5.3, v6.6.0, v6.6.1, v6.6.10, v6.6.11, v6.6.2, v6.6.3, v6.6.4, v6.6.5, v6.6.6, v6.6.7, v6.6.8, v6.6.9, v6.7.0, v6.7.1, v6.7.2, v6.7.3, v6.8.0, v6.8.1, v6.8.10, v6.8.11, v6.8.12, v6.8.2, v6.8.3, v6.8.4, v6.8.5, v6.8.6, v6.8.7, v6.8.8, v6.8.9, v6.9.0, v6.9.1, v6.9.2, v6.9.3, v6.9.4, v6.9.5, v6.9.6
View affected-version evidence
{
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "11.5.19"
}
],
"type": "ECOSYSTEM"
}
],
"versions": [
"10.0.8",
"2.2.0",
"2.2.1",
"2.2.2",
"2.3.0",
"3.0.0",
"3.0.1",
"3.0.2",
"3.0.3",
"3.0.4",
"3.0.5",
"3.0.6",
"3.1.0",
"3.1.1",
"4.0.0",
"4.0.1",
"4.1.0",
"4.1.1",
"4.1.2",
"4.1.3",
"4.2.0",
"4.3.0",
"4.3.1",
"4.4.0",
"4.4.1",
"4.4.2",
"4.4.3",
"4.5.0",
"4.6.0",
"4.6.1",
"4.6.2",
"4.6.3",
"4.6.4",
"4.6.5",
"v10.0.0",
"v10.0.0-BETA1",
"v10.0.0-BETA2",
"v10.0.0-BETA3",
"v10.0.0-BETA4",
"v10.0.1",
"v10.0.2",
"v10.0.3",
"v10.0.4",
"v10.0.5",
"v10.0.6",
"v10.0.7",
"v10.0.9",
"v10.1.0",
"v10.1.1",
"v10.1.2",
"v10.1.3",
"v10.1.4",
"v10.1.5",
"v10.2.0",
"v10.2.1",
"v10.2.10",
"v10.2.2",
"v10.2.3",
"v10.2.4",
"v10.2.5",
"v10.2.6",
"v10.2.7",
"v10.2.8",
"v10.2.9",
"v10.3.0",
"v10.3.1",
"v10.3.2",
"v10.3.3",
"v10.3.4",
"v10.3.5",
"v10.3.6",
"v10.3.7",
"v10.4.0",
"v10.4.1",
"v10.4.2",
"v10.4.3",
"v10.4.4",
"v10.4.5",
"v10.4.6",
"v10.5.0",
"v10.5.1",
"v10.5.10",
"v10.5.11",
"v10.5.12",
"v10.5.13",
"v10.5.14",
"v10.5.15",
"v10.5.16",
"v10.5.17",
"v10.5.18",
"v10.5.19",
"v10.5.2",
"v10.5.20",
"v10.5.21",
"v10.5.22",
"v10.5.23",
"v10.5.24",
"v10.5.25",
"v10.5.3",
"v10.5.4",
"v10.5.5",
"v10.5.6",
"v10.5.7",
"v10.5.8",
"v10.5.9",
"v10.6.0",
"v10.6.1",
"v10.6.2",
"v10.6.3",
"v10.6.4",
"v10.6.5",
"v10.6.6",
"v10.6.7",
"v10.6.8",
"v10.6.9",
"v11.0.0",
"v11.0.0-ALPHA1",
"v11.0.0-ALPHA2",
"v11.0.0-ALPHA3",
"v11.0.0-ALPHA4",
"v11.0.0-ALPHA5",
"v11.0.0-ALPHA6",
"v11.0.0-ALPHA7",
"v11.0.0-ALPHA8",
"v11.0.0-BETA1",
"v11.0.0-RC1",
"v11.0.0-RC2",
"v11.0.1",
"v11.0.10",
"v11.0.11",
"v11.0.12",
"v11.0.2",
"v11.0.3",
"v11.0.4",
"v11.0.5",
"v11.0.6",
"v11.0.7",
"v11.0.8",
"v11.0.9",
"v11.1.0",
"v11.1.0-RC1",
"v11.1.1",
"v11.1.2",
"v11.1.3",
"v11.1.4",
"v11.1.5",
"v11.1.6",
"v11.2.0",
"v11.2.1",
"v11.2.2",
"v11.2.3",
"v11.2.4",
"v11.2.5",
"v11.2.6",
"v11.2.7",
"v11.3.0",
"v11.3.0-RC1",
"v11.3.0-RC2",
"v11.3.1",
"v11.3.2",
"v11.3.3",
"v11.4.0",
"v11.4.0-RC1",
"v11.4.1",
"v11.4.2",
"v11.4.3",
"v11.4.4",
"v11.5.0",
"v11.5.0-RC1",
"v11.5.0-RC2",
"v11.5.1",
"v11.5.10",
"v11.5.11",
"v11.5.12",
"v11.5.13",
"v11.5.14",
"v11.5.14.1",
"v11.5.2",
"v11.5.3",
"v11.5.4",
"v11.5.5",
"v11.5.6",
"v11.5.7",
"v11.5.8",
"v11.5.9",
"v5.0.0",
"v5.0.0-RC",
"v5.0.1",
"v5.0.2",
"v5.0.3",
"v5.0.4",
"v5.1.0",
"v5.1.0-alpha",
"v5.1.1",
"v5.1.2",
"v5.1.3",
"v5.2.0",
"v5.2.1",
"v5.2.2",
"v5.2.3",
"v5.3.0",
"v5.3.1",
"v5.4.0",
"v5.4.1",
"v5.4.2",
"v5.4.3",
"v5.4.4",
"v5.5.0",
"v5.5.1",
"v5.5.2",
"v5.5.3",
"v5.5.4",
"v5.6.0",
"v5.6.1",
"v5.6.2",
"v5.6.3",
"v5.6.4",
"v5.6.5",
"v5.6.6",
"v5.7.0",
"v5.7.1",
"v5.7.2",
"v5.7.3",
"v5.8.0",
"v5.8.1",
"v5.8.2",
"v5.8.3",
"v5.8.4",
"v5.8.5",
"v5.8.6",
"v5.8.7",
"v5.8.8",
"v5.8.9",
"v6.0.0",
"v6.0.1",
"v6.0.2",
"v6.0.3",
"v6.0.4",
"v6.0.5",
"v6.1.0",
"v6.1.1",
"v6.1.2",
"v6.2.0",
"v6.2.1",
"v6.2.2",
"v6.2.3",
"v6.3.0",
"v6.3.1",
"v6.3.2",
"v6.3.3",
"v6.3.4",
"v6.3.5",
"v6.3.6",
"v6.4.0",
"v6.4.1",
"v6.4.2",
"v6.5.0",
"v6.5.1",
"v6.5.2",
"v6.5.3",
"v6.6.0",
"v6.6.1",
"v6.6.10",
"v6.6.11",
"v6.6.2",
"v6.6.3",
"v6.6.4",
"v6.6.5",
"v6.6.6",
"v6.6.7",
"v6.6.8",
"v6.6.9",
"v6.7.0",
"v6.7.1",
"v6.7.2",
"v6.7.3",
"v6.8.0",
"v6.8.1",
"v6.8.10",
"v6.8.11",
"v6.8.12",
"v6.8.2",
"v6.8.3",
"v6.8.4",
"v6.8.5",
"v6.8.6",
"v6.8.7",
"v6.8.8",
"v6.8.9",
"v6.9.0",
"v6.9.1",
"v6.9.2",
"v6.9.3",
"v6.9.4",
"v6.9.5",
"v6.9.6"
]
}