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

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

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

피드문의개인정보© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
CVE-2026-64638 — CVE-2026-64638에 대한 개념 증명(PoC) 익스플로잇: WordPress 로그인에서의 반사형 XSS를 DOM clobbering과 연계하여 관리자 계정 탈취 및 원격 코드 실행을 달성합니다. | Kitploit
도구/GitHubGitHub/dungsocool/cve-2026-64638
Phishing ToolsVulnerability AnalysisCode AnalysisExploitationWeb Application ExploitationWeb SecurityPayload Development
GitHubdungsocool/cve-2026-64638

CVE-2026-64638

CVE-2026-64638에 대한 개념 증명(PoC) 익스플로잇: WordPress 로그인에서의 반사형 XSS를 DOM clobbering과 연계하여 관리자 계정 탈취 및 원격 코드 실행을 달성합니다.

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

인기

모두 보기 →

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

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

CVE-2026-64638

로그인 화면의 반사형 XSS로 이어지는 PHP 코드 실행 — WordPress Core

소프트웨어: WordPress Core ≤ 7.0.2 (7.0.3 이전의 모든 버전)

CVSS: 8.9 (높음)

CWE: CWE-79 — 웹 페이지 생성 중 입력의 부적절한 중화

인증 필요: 없음 (Pre-Auth)

사용자 상호작용: 활성 (관리자가 링크 1회 클릭 필요)

영향: XSS → 계정 탈취 → 원격 코드 실행


1. 이 취약점이란?

WordPress는 전 세계 웹사이트의 40% 이상을 차지하는 세계에서 가장 인기 있는 콘텐츠 관리 시스템입니다. 모든 WordPress 사이트에는 /wp-login.php에 로그인 페이지가 있으며, 이는 인증 없이 누구나 접근할 수 있는 공개 엔드포인트입니다.

사용자가 잘못된 사용자 이름을 입력하면 WordPress는 사용자가 방금 입력한 사용자 이름이 포함된 오류 메시지를 표시합니다: “사용자 이름 X은(는) 이 사이트에 등록되어 있지 않습니다.” 문제는 사용자 이름 값이 어떤 이스케이프 함수도 거치지 않고 HTML 응답에 직접 배치된다는 점입니다. 공격자는 실제 사용자 이름 대신 HTML/JavaScript를 입력하기만 하면 코드가 브라우저에서 실행됩니다.

이것은 반사형 XSS(reflected XSS) 결함입니다 — 페이로드가 요청에 포함되어 서버가 HTML에서 동일하게 반사합니다. 위험한 이유는 이 결함이 로그인 페이지에 있기 때문입니다. 로그인 페이지는 관리자가 자주 접근하는 곳이며, 여기서 관리자 세션 쿠키를 탈취할 수 있습니다.

연구팀은 또한 이 XSS가 WordPress의 emoji-loader의 DOM 클로버링 취약점과 연결될 수 있어 외부 서버에서 JavaScript를 로드할 수 있음을 발견했습니다. 이를 통해 공격자는 새 관리자 계정 생성 → 웹셸이 포함된 플러그인 설치 → 서버에서 PHP 코드 실행까지 진행할 수 있습니다. 이 공격 체인을 XSS2Shell이라고 합니다.

속성값
CVE IDCVE-2026-64638
CVSS 점수8.9 (높음)
소프트웨어WordPress Core ≤ 7.0.2
인증필요 없음 (Pre-Auth)
사용자 상호작용클릭 1회 필요 (관리자가 링크 클릭)
공격 복잡성높음
패치됨WordPress 7.0.3 (2026-06-08)
제보자pwn.ai 팀 (HackerOne 경유)
HackerOne 보고서#3877102

2. 용어 설명

DOM 클로버링과 emoji-loader

WordPress는 모든 페이지(로그인 페이지 포함)에서 emoji-loader.js 파일을 통해 이모지 지원을 로드합니다. 이 스크립트는 id="wp-emoji-settings" 요소에서 구성을 읽습니다:

// Before patch (vulnerable)
const settings = JSON.parse(
    document.getElementById('wp-emoji-settings').textContent
);

document.getElementById()는 DOM에서 일치하는 id를 가진 첫 번째 요소를 반환합니다. 공격자가 원래 스크립트 태그 앞에 <div id="wp-emoji-settings">를 주입하면 getElementById는 실제 구성 대신 공격자의 콘텐츠를 읽습니다. 이 기법을 DOM 클로버링이라고 합니다 — HTML 요소를 주입하여 JavaScript 동작을 덮어쓰는 것입니다.

이모지 구성에는 JavaScript 파일(concatemoji)을 로드하기 위한 URL이 포함되어 있습니다. 공격자가 이 URL을 제어하면 → 외부 서버에서 JS 파일을 로드하여 → 브라우저 컨텍스트에서 임의 코드를 실행할 수 있습니다.

XSS에서 WordPress RCE까지

관리자 컨텍스트에서 JavaScript 실행이 달성되면 공격자는 전체 WordPress 관리자 권한을 갖게 됩니다:

  1. 새 관리자 계정 생성 — 관리자 세션으로 /wp-admin/user-new.php 호출
  2. PHP 코드가 포함된 플러그인 설치 — /wp-admin/plugin-install.php를 통해 플러그인 업로드
  3. 테마 파일 수정 — 테마 편집기를 통해 PHP 백도어 삽입

위 3가지 방법 중 하나라도 서버에서 PHP 코드를 실행할 수 있게 합니다 — 즉 RCE입니다.

3. 소스 코드 분석 — 근본 원인

1단계: 싱크(Sink) 찾기 — 사용자 이름이 HTML에 배치되는 위치

wordpress-develop의 수정 커밋 0d6d42e에서 wp-includes/user.php 파일의 사용자 이름/이메일이 오류 메시지에 직접 배치되는 3개 위치를 확인했습니다:

# View diff between vulnerable and patched versions
git diff 7.0.2..7.0.3 -- src/wp-includes/user.php

위치 1 — 189행 (사용자 이름이 존재하지 않음):

수정 전:

// BEFORE (vulnerable):
sprintf(
    __( 'The username <strong>%s</strong> is not registered...' ),
    $username      // ← no escaping
)

image.png

수정 후:

// fixed:
sprintf(
    __( 'The username <strong>%s</strong> is not registered...' ),
    esc_html( $username )    // ← escaped
)

위치 2 — 216행 (잘못된 비밀번호):

수정 전:

image.png

// BEFORE:
'<strong>' . $username . '</strong>'    // ← no escaping

수정 후:

// AFTER:
'<strong>' . esc_html( $username ) . '</strong>'

위치 3 — 299행 (이메일의 잘못된 비밀번호):

수정 전:

image.png

// BEFORE:
'<strong>' . $email . '</strong>'    // ← no escaping

수정 후:

// AFTER:
'<strong>' . esc_html( $email ) . '</strong>'

2단계: 소스(Source) 추적 — 데이터는 어디에서 오는가?

POST 요청에서 오류 메시지까지의 데이터 흐름:

$_POST['log']                           ← user input from login form
    ↓
wp_signon() [user.php:51]
    $credentials['user_login'] = wp_unslash($_POST['log'])     ← only removes backslashes
    ↓
wp_authenticate($username, $password) [pluggable.php:689]
    $username = sanitize_user($username)     ← strips HTML tags, but has a bypass
    ↓
wp_authenticate_username_password() [user.php:153]
    get_user_by('login', $username)          ← user not found
    ↓
    sprintf('The username <strong>%s</strong>...', $username)   ← XSS!
    ↓
WP_Error → login_header() → wp_admin_notice()
    wp_kses_post(...)     ← filters HTML but allows <div>, <a>,  through
    ↓
HTML response → browser render → JavaScript execute

3단계: 두 방어 계층, 약점, 디버깅을 통한 확인

WordPress에는 사용자 이름이 HTML에 도달하기 전에 2개의 필터링 계층이 있습니다:

계층 1: sanitize_user() — strip_tags()를 호출하여 HTML 태그를 제거합니다. 그러나 PHP의 strip_tags()에는 알려진 한계가 있습니다: 비표준 태그 형식으로 필터를 우회할 수 있습니다.

계층 2: wp_kses_post() — <div>, <a>, `` 등 특정 속성을 가진 안전한 HTML 하위 집합을 통과시킵니다 (단, onerror, onload와 같은 이벤트 핸들러는 제거). 핵심: wp_kses_post는 DOM 클로버링에 정확히 필요한 요소인 <div id="wp-emoji-settings">를 허용합니다.

pwn.ai 팀은 두 계층을 모두 우회하여 실용적인 페이로드를 주입할 방법을 찾았습니다. 구체적인 기술적 세부 사항은 공개되지 않았습니다.

Xdebug를 이용한 디버깅 — 데이터 흐름 확인

사용자 이름이 이스케이프 없이 HTML로 직접 들어가는지 시각적으로 확인하기 위해 Xdebug + VS Code를 사용하여 실행 체인의 주요 지점에 중단점을 설정했습니다.

1단계 — 로그인 폼에 XSS 페이로드 입력:

http://localhost:8282/wp-login.php에 접속하여 사용자 이름으로 ``를 입력한 후 Log In을 클릭합니다. 알림 팝업이 나타나면 — XSS가 동작하는 것입니다.

image.png

image.png

2단계 — user.php:184에 중단점 설정 — XSS가 발생하는 위치:

wp_authenticate_username_password() 함수 내부의 return new WP_Error(...)에 중단점을 설정합니다. 디버거가 멈추면 다음을 관찰합니다:

  • Variables → Locals 패널: $username = "" — 그대로인 HTML 페이로드, 이스케이프되지 않음
  • Superglobals → $_POST 패널: log = "" — 페이로드가 폼 입력에서 비롯되었음을 확인
  • Call Stack 패널: wp_authenticate_username_password() → WP_Hook->apply_filters → apply_filters → wp_authenticate → wp_signon → {main} wp-login.php

image.png

$username 값은 $_POST['log'] → wp_unslash() → (sanitize_user 우회) → 186-189행의 오류 메시지로 sprintf()되는 경로를 거치며, 그 사이에 esc_html()이 없습니다. 실습 환경에서는 pwn.ai가 발견한 우회를 시뮬레이션하기 위해 sanitize_user()를 주석 처리했습니다.

3단계 — functions.php:9200에 중단점 설정 — 최종 출력:

echo wp_get_admin_notice( $message, $args )에 중단점을 설정합니다 — 이는 HTML이 브라우저로 출력되기 직전의 마지막 줄입니다:

  • Variables → Locals 패널: $message = "<p><strong>Error:</strong> The username <strong></strong> is not registered...</p>" — 페이로드가 HTML 오류 메시지 안에 온전히 존재
  • 실습 환경의 9200행은 wp_kses_post()로 감싸져 있지 않으며(우회를 시뮬레이션하기 위해 패치됨), 페이로드가 브라우저로 직접 전달됩니다

image.png

원본 WordPress에서 이 줄은 echo wp_kses_post( wp_get_admin_notice(...) )입니다 — wp_kses_post()는 onerror 속성을 제거하지만, <div>는 허용 목록에 있으므로 <div id="wp-emoji-settings">는 통과시킵니다. 이것이 정확히 DOM 클로버링 공격의 벡터입니다.

4단계: 두 번째 수정 커밋 — emoji-loader 강화

커밋 a12c8f5는 DOM 클로버링을 차단하도록 emoji-loader.js를 수정합니다:

// BEFORE (vulnerable): accepts any element with a matching id
const settings = JSON.parse(
    document.getElementById('wp-emoji-settings').textContent
);

// AFTER (fixed): only accepts <script> element
const selector = 'script#wp-emoji-settings';
const script = document.querySelector(selector);
if (!(script instanceof HTMLScriptElement)) {
    throw new Error(`Element missing:${selector}`);
}
const settings = JSON.parse(script.text);

수정은 3가지를 변경합니다:

도구 다운로드