GHSA-h4ph-crvj-9h92

Suggest an improvement
Source
https://github.com/advisories/GHSA-h4ph-crvj-9h92
Import Source
https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/05/GHSA-h4ph-crvj-9h92/GHSA-h4ph-crvj-9h92.json
JSON Data
https://api.osv.dev/v1/vulns/GHSA-h4ph-crvj-9h92
Aliases
Published
2026-05-27T00:35:56Z
Modified
2026-07-10T19:15:11Z
Severity
  • 8.8 (High) CVSS_V3 - CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H CVSS Calculator
Summary
Pimcore Admin Classic Bundle Vulnerable to SQL Injection in Translation Grid Date Filter via Unsanitized Property Parameter
Details

GitHub 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.

Database specific
{
    "cwe_ids": [
        "CWE-89"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-05-27T00:35:56Z",
    "nvd_published_at": null,
    "severity": "HIGH"
}
References

Affected packages

Packagist / pimcore/admin-ui-classic-bundle

Package

Name
pimcore/admin-ui-classic-bundle
Purl
pkg:composer/pimcore/admin-ui-classic-bundle

Affected ranges

Type
ECOSYSTEM
Events
Introduced
2.0.0-RC1
Fixed
2.3.6

Affected versions

v2.*
v2.0.0-RC1
v2.0.0-RC3
v2.0.0-RC4
v2.0.0
v2.0.1
v2.0.2
v2.1.0
v2.1.1
v2.1.2
v2.1.3
v2.1.4
v2.2.0
v2.2.1
v2.2.2
v2.2.3
v2.3.0
v2.3.1
v2.3.2
v2.3.3
v2.3.4
2.*
2.0.0-RC2

Database specific

last_known_affected_version_range
"<= 2.3.5"
source
"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/05/GHSA-h4ph-crvj-9h92/GHSA-h4ph-crvj-9h92.json"

Packagist / pimcore/admin-ui-classic-bundle

Package

Name
pimcore/admin-ui-classic-bundle
Purl
pkg:composer/pimcore/admin-ui-classic-bundle

Affected ranges

Type
ECOSYSTEM
Events
Introduced
0 Unknown introduced version / All previous versions are affected
Fixed
1.7.18

Affected versions

v1.*
v1.0.0-BETA1
v1.0.0-RC1
v1.0.0-RC2
v1.0.0
v1.0.1
v1.0.2
v1.0.3
v1.0.4
v1.0.5
v1.0.6
v1.1.0-RC1
v1.1.0
v1.1.1
v1.1.2
v1.1.3
v1.1.4
v1.2
v1.2.0-RC1
v1.2.1
v1.2.2
v1.2.3
v1.3.0-RC1
v1.3.0
v1.3.1
v1.3.2
v1.3.3
v1.3.4
v1.3.5
v1.4.1
v1.4.2
v1.4.3
v1.4.4
v1.4.5
v1.5.0-RC1
v1.5.0-RC2
v1.5.0
v1.5.1
v1.5.2
v1.5.3
v1.5.4
v1.5.5
v1.6.0-RC1
v1.6.0-RC2
v1.6.0
v1.6.1
v1.6.2
v1.6.3
v1.6.4
v1.6.5
v1.6.6
v1.7.0
v1.7.1
v1.7.2
v1.7.3
v1.7.4
v1.7.5
v1.7.6
v1.7.7
v1.7.8
v1.7.9
v1.7.10
v1.7.12
v1.7.13
v1.7.14
v1.7.15
v1.7.16
1.*
1.4.0

Database specific

source
"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/05/GHSA-h4ph-crvj-9h92/GHSA-h4ph-crvj-9h92.json"