GHSA-H4PH-CRVJ-9H92
Vulnerability from github – Published: 2026-05-27 00:35 – Updated: 2026-07-10 19:07GitHub Security Advisory Draft — GM-369
Summary
SQL injection in Pimcore's translation grid date filter — the user-supplied property field from the filter JSON is interpolated directly into a UNIX_TIMESTAMP(DATE(FROM_UNIXTIME(...))) SQL expression without parameterization or allowlist validation.
Severity
CVSS 3.1: 8.8 (High) — AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
Affected Component
- Package:
pimcore/admin-ui-classic-bundle - File:
src/Controller/Admin/TranslationController.php - Lines: 565 (input), 569 (inadequate sanitization), 593 (injection point)
- Endpoint:
POST /admin/translation/translations
Description
The translation grid endpoint processes JSON filter parameters. When a filter has type: "date", the property field is extracted and used to construct a SQL expression:
$fieldname = $filter[$propertyField]; // Line 565 — user input
$fieldname = str_replace('--', '', $fieldname); // Line 569 — trivially bypassable
$fieldname = $tableName . '.' . $fieldname; // Line 577
$fieldname = "UNIX_TIMESTAMP(DATE(FROM_UNIXTIME({$fieldname})))"; // Line 593 — injection
The str_replace('--', '') sanitization is trivially bypassable (use /**/ comments or ----). In non-language mode, $fieldname is concatenated directly into the SQL condition without quoting or parameterization.
Impact
Authenticated user with translations view permission can extract arbitrary database data via UNION-based or error-based SQL injection. Combined with GM-249 (unsafe unserialize), this enables an SQLi → deserialization → RCE chain.
Proof of Concept
POST /admin/translation/translations
filter=[{"property":"1))) UNION SELECT password FROM users WHERE ((1","type":"date","operator":"eq","value":"2026-01-01"}]
Suggested Fix
Validate $fieldname against an allowlist of valid column names before SQL interpolation:
$allowedDateColumns = ['creationDate', 'modificationDate'];
if (!in_array($fieldname, $allowedDateColumns, true)) {
continue;
}
References
- CWE-89: SQL Injection
- Related: CVE-2026-27461 (RLIKE injection in Dependency/Dao.php — different code path)
Suggested Fix
In TranslationController.php: (1) Add allowlist check for non-language fieldnames before processing. (2) Replace raw string interpolation UNIX_TIMESTAMP(DATE(FROM_UNIXTIME({$fieldname}))) with $db->quoteIdentifier($fieldname) to prevent SQL injection in date filter expressions.
--- a/src/Controller/Admin/TranslationController.php
+++ b/src/Controller/Admin/TranslationController.php
@@ -569,7 +569,15 @@ class TranslationController extends AdminAbstractController
$fieldname = str_replace('--', '', $fieldname);
if (!$languageMode && in_array($fieldname, $validLanguages)
|| $languageMode && !in_array($fieldname, $validLanguages)) {
continue;
}
+ // Allowlist non-language fieldnames to prevent SQL injection
+ $allowedNonLanguageFields = ['key', 'type', 'creationDate', 'modificationDate'];
+ if (!$languageMode && !in_array($fieldname, $allowedNonLanguageFields) && !in_array($fieldname, $validLanguages)) {
+ continue;
+ }
+
if (!$languageMode) {
$fieldname = $tableName . '.' . $fieldname;
}
@@ -582,7 +590,7 @@ class TranslationController extends AdminAbstractController
} elseif ($filter[$operatorField] == 'eq') {
$operator = '=';
- $fieldname = "UNIX_TIMESTAMP(DATE(FROM_UNIXTIME({$fieldname})))";
+ // Use validated fieldname only — never interpolate raw user input into SQL functions
+ $fieldname = sprintf('UNIX_TIMESTAMP(DATE(FROM_UNIXTIME(%s)))', $db->quoteIdentifier($fieldname));
}
Proposed Fix
--- a/src/Controller/Admin/TranslationController.php
+++ b/src/Controller/Admin/TranslationController.php
@@ -569,7 +569,15 @@ class TranslationController extends AdminAbstractController
$fieldname = str_replace('--', '', $fieldname);
if (!$languageMode && in_array($fieldname, $validLanguages)
|| $languageMode && !in_array($fieldname, $validLanguages)) {
continue;
}
+ // Allowlist non-language fieldnames to prevent SQL injection
+ $allowedNonLanguageFields = ['key', 'type', 'creationDate', 'modificationDate'];
+ if (!$languageMode && !in_array($fieldname, $allowedNonLanguageFields) && !in_array($fieldname, $validLanguages)) {
+ continue;
+ }
+
if (!$languageMode) {
$fieldname = $tableName . '.' . $fieldname;
}
@@ -582,7 +590,7 @@ class TranslationController extends AdminAbstractController
} elseif ($filter[$operatorField] == 'eq') {
$operator = '=';
- $fieldname = "UNIX_TIMESTAMP(DATE(FROM_UNIXTIME({$fieldname})))";
+ // Use validated fieldname only — never interpolate raw user input into SQL functions
+ $fieldname = sprintf('UNIX_TIMESTAMP(DATE(FROM_UNIXTIME(%s)))', $db->quoteIdentifier($fieldname));
}
Happy to submit this as a PR against a private fork if that is the preferred workflow.
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 2.3.5"
},
"package": {
"ecosystem": "Packagist",
"name": "pimcore/admin-ui-classic-bundle"
},
"ranges": [
{
"events": [
{
"introduced": "2.0.0-RC1"
},
{
"fixed": "2.3.6"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "Packagist",
"name": "pimcore/admin-ui-classic-bundle"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "1.7.18"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-44741"
],
"database_specific": {
"cwe_ids": [
"CWE-89"
],
"github_reviewed": true,
"github_reviewed_at": "2026-05-27T00:35:56Z",
"nvd_published_at": null,
"severity": "HIGH"
},
"details": "# GitHub Security Advisory Draft \u2014 GM-369\n\n## Summary\nSQL injection in Pimcore\u0027s translation grid date filter \u2014 the user-supplied `property` field from the filter JSON is interpolated directly into a `UNIX_TIMESTAMP(DATE(FROM_UNIXTIME(...)))` SQL expression without parameterization or allowlist validation.\n\n## Severity\nCVSS 3.1: 8.8 (High) \u2014 AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H\n\n## Affected Component\n- **Package:** `pimcore/admin-ui-classic-bundle`\n- **File:** `src/Controller/Admin/TranslationController.php`\n- **Lines:** 565 (input), 569 (inadequate sanitization), 593 (injection point)\n- **Endpoint:** `POST /admin/translation/translations`\n\n## Description\nThe translation grid endpoint processes JSON filter parameters. When a filter has `type: \"date\"`, the `property` field is extracted and used to construct a SQL expression:\n\n```php\n$fieldname = $filter[$propertyField]; // Line 565 \u2014 user input\n$fieldname = str_replace(\u0027--\u0027, \u0027\u0027, $fieldname); // Line 569 \u2014 trivially bypassable\n$fieldname = $tableName . \u0027.\u0027 . $fieldname; // Line 577\n$fieldname = \"UNIX_TIMESTAMP(DATE(FROM_UNIXTIME({$fieldname})))\"; // Line 593 \u2014 injection\n```\n\nThe `str_replace(\u0027--\u0027, \u0027\u0027)` sanitization is trivially bypassable (use `/**/` comments or `----`). In non-language mode, `$fieldname` is concatenated directly into the SQL condition without quoting or parameterization.\n\n## Impact\nAuthenticated user with translations view permission can extract arbitrary database data via UNION-based or error-based SQL injection. Combined with GM-249 (unsafe unserialize), this enables an SQLi \u2192 deserialization \u2192 RCE chain.\n\n## Proof of Concept\n```\nPOST /admin/translation/translations\nfilter=[{\"property\":\"1))) UNION SELECT password FROM users WHERE ((1\",\"type\":\"date\",\"operator\":\"eq\",\"value\":\"2026-01-01\"}]\n```\n\n## Suggested Fix\nValidate `$fieldname` against an allowlist of valid column names before SQL interpolation:\n```php\n$allowedDateColumns = [\u0027creationDate\u0027, \u0027modificationDate\u0027];\nif (!in_array($fieldname, $allowedDateColumns, true)) {\n continue;\n}\n```\n\n## References\n- CWE-89: SQL Injection\n- Related: CVE-2026-27461 (RLIKE injection in Dependency/Dao.php \u2014 different code path)\n\n\n---\n\n## Suggested Fix\n\nIn `TranslationController.php`: (1) Add allowlist check for non-language fieldnames before processing. (2) Replace raw string interpolation `UNIX_TIMESTAMP(DATE(FROM_UNIXTIME({$fieldname})))` with `$db-\u003equoteIdentifier($fieldname)` to prevent SQL injection in date filter expressions.\n\n```diff\n--- a/src/Controller/Admin/TranslationController.php\n+++ b/src/Controller/Admin/TranslationController.php\n@@ -569,7 +569,15 @@ class TranslationController extends AdminAbstractController\n $fieldname = str_replace(\u0027--\u0027, \u0027\u0027, $fieldname);\n \n if (!$languageMode \u0026\u0026 in_array($fieldname, $validLanguages)\n || $languageMode \u0026\u0026 !in_array($fieldname, $validLanguages)) {\n continue;\n }\n \n+ // Allowlist non-language fieldnames to prevent SQL injection\n+ $allowedNonLanguageFields = [\u0027key\u0027, \u0027type\u0027, \u0027creationDate\u0027, \u0027modificationDate\u0027];\n+ if (!$languageMode \u0026\u0026 !in_array($fieldname, $allowedNonLanguageFields) \u0026\u0026 !in_array($fieldname, $validLanguages)) {\n+ continue;\n+ }\n+\n if (!$languageMode) {\n $fieldname = $tableName . \u0027.\u0027 . $fieldname;\n }\n@@ -582,7 +590,7 @@ class TranslationController extends AdminAbstractController\n } elseif ($filter[$operatorField] == \u0027eq\u0027) {\n $operator = \u0027=\u0027;\n- $fieldname = \"UNIX_TIMESTAMP(DATE(FROM_UNIXTIME({$fieldname})))\";\n+ // Use validated fieldname only \u2014 never interpolate raw user input into SQL functions\n+ $fieldname = sprintf(\u0027UNIX_TIMESTAMP(DATE(FROM_UNIXTIME(%s)))\u0027, $db-\u003equoteIdentifier($fieldname));\n }\n\n```\n\n---\n\n## Proposed Fix\n\n```diff\n--- a/src/Controller/Admin/TranslationController.php\n+++ b/src/Controller/Admin/TranslationController.php\n@@ -569,7 +569,15 @@ class TranslationController extends AdminAbstractController\n $fieldname = str_replace(\u0027--\u0027, \u0027\u0027, $fieldname);\n \n if (!$languageMode \u0026\u0026 in_array($fieldname, $validLanguages)\n || $languageMode \u0026\u0026 !in_array($fieldname, $validLanguages)) {\n continue;\n }\n \n+ // Allowlist non-language fieldnames to prevent SQL injection\n+ $allowedNonLanguageFields = [\u0027key\u0027, \u0027type\u0027, \u0027creationDate\u0027, \u0027modificationDate\u0027];\n+ if (!$languageMode \u0026\u0026 !in_array($fieldname, $allowedNonLanguageFields) \u0026\u0026 !in_array($fieldname, $validLanguages)) {\n+ continue;\n+ }\n+\n if (!$languageMode) {\n $fieldname = $tableName . \u0027.\u0027 . $fieldname;\n }\n@@ -582,7 +590,7 @@ class TranslationController extends AdminAbstractController\n } elseif ($filter[$operatorField] == \u0027eq\u0027) {\n $operator = \u0027=\u0027;\n- $fieldname = \"UNIX_TIMESTAMP(DATE(FROM_UNIXTIME({$fieldname})))\";\n+ // Use validated fieldname only \u2014 never interpolate raw user input into SQL functions\n+ $fieldname = sprintf(\u0027UNIX_TIMESTAMP(DATE(FROM_UNIXTIME(%s)))\u0027, $db-\u003equoteIdentifier($fieldname));\n }\n```\n\nHappy to submit this as a PR against a private fork if that is the preferred workflow.",
"id": "GHSA-h4ph-crvj-9h92",
"modified": "2026-07-10T19:07:21Z",
"published": "2026-05-27T00:35:56Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/pimcore/pimcore/security/advisories/GHSA-h4ph-crvj-9h92"
},
{
"type": "WEB",
"url": "https://github.com/pimcore/admin-ui-classic-bundle/pull/1111"
},
{
"type": "WEB",
"url": "https://github.com/pimcore/admin-ui-classic-bundle/commit/80e57a23d9e19574eddfe9b08e8f26785b2b0d90"
},
{
"type": "WEB",
"url": "https://github.com/pimcore/admin-ui-classic-bundle/releases/tag/v2.3.6"
},
{
"type": "PACKAGE",
"url": "https://github.com/pimcore/pimcore"
}
],
"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 Admin Classic Bundle Vulnerable to SQL Injection in Translation Grid Date Filter via Unsanitized Property Parameter"
}
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.