GHSA-H4PH-CRVJ-9H92

Vulnerability from github – Published: 2026-05-27 00:35 – Updated: 2026-07-10 19:07
VLAI
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.

Show details on source website

{
  "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"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

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.

Loading…

Detection rules are retrieved from Rulezet.

Loading…

Loading…

Loading…